← Back to all articles

Engineering

Render a structured comparison report without another AI call

Validation, escaping, deterministic totals and immutable vote records.

AI-generated editorial illustration.

1. Define the report contract

Store scores and votes as structured data rather than asking a summarizer to recreate them from prose. A report contract should define product order, dimension order, score bounds, missing values and provider identity. Keep original votes immutable when user weights change. An aggregate should also state which seats are independent and which are simulated. A final rendering step should consume a validated snapshot; opening a saved URL should not silently commission a new evaluation with different model versions or product facts.

2. Escape text at the output boundary

Product names and explanations are untrusted strings even if they came from a model. Escape HTML text, quote attribute values and validate link protocols separately. The example renders a table without an extra model call, preserves unknown values, and prevents a product name from becoming markup. In a complete server, also escape embedded JSON safely, protect private inputs, and apply appropriate response headers. HTML escaping is one boundary control; it does not make arbitrary external URLs or executable JavaScript safe.

from html import escape
import math

def cell(value):
    if value is None:
        return 'Not enough data'
    if type(value) not in (int,float) or not math.isfinite(value) or not 0<=value<=100:
        raise ValueError('invalid score')
    return f'{value:.1f}'.removesuffix('.0')

def table(names, dimensions, scores):
    if len(scores)!=len(dimensions) or any(len(row)!=len(names) for row in scores):
        raise ValueError('invalid matrix')
    head='<tr><th>Dimension</th>'+''.join('<th>'+escape(n)+'</th>' for n in names)+'</tr>'
    rows=''.join('<tr><th>'+escape(d)+'</th>'+''.join('<td>'+cell(v)+'</td>' for v in row)+'</tr>'
                 for d,row in zip(dimensions,scores))
    return '<table><thead>'+head+'</thead><tbody>'+rows+'</tbody></table>'

html=table(['Phone <script>','Other phone'],['Battery'],[[88,None]])
assert '<script>' not in html and '&lt;script&gt;' in html
assert 'Not enough data' in html
print('structured table rendered with escaped names and explicit missing data')

3. Translate narrative fields only

Product identities, score matrices and winner indices should not pass through a translation model as mutable values. Translate commentary against a content-version key, validate the returned string count, and merge only the approved narrative fields. Cache the translation separately from scoring. If a translation fails, show a recoverable state rather than mixing languages or presenting unrelated cached prose. Public server-rendered content should have a clear language policy, and language changes should preserve the numeric result and user-selected importance values.

4. Treat public sharing as a separate boundary

Require explicit publication confirmation and reject reports containing private supplemental facts or unsupported custom scenarios. Store a public snapshot rather than exposing an internal report lookup endpoint. Its URL should identify the snapshot and its selected weights, while model votes remain original. A search-friendly page needs crawlable text and honest metadata, not fabricated review counts. Before launch, test access to private routes without credentials, inspect a public snapshot for leaked input, and verify that a cached view does not initiate any new model scoring.

Compare your options →Explore GEO ↗

All product names, logos and brands belong to their respective owners and are used for identification only. VersusSmart is an independent service and is not affiliated with, sponsored by or endorsed by these providers.