Sixteen system prompts lived as module-level string constants across chat.py,
board.py, synthesis.py, trip.py and llm_tools.py. They are the main thing a fork
for another parliament has to rewrite, and editing them meant editing Python.
They now live under prompts/sv/ as Markdown. Placeholders use string.Template
($name), not str.format: three of these prompts embed literal JSON braces that
str.format raises on, and safe_substitute leaves an unknown placeholder alone
rather than killing a live chat turn over a typo.
Technical configuration that had leaked into the prompt text is now templated:
websearch_to_tsquery('swedish', ...) became '$fts_config', "Answer in Swedish"
became "Answer in $answer_language", and the åäö preservation rule became
$preserve_characters. Domain vocabulary from parliament.yaml is available too, so a
prompt can say $speech_plural and read naturally in any language.
PROMPTS_RELOAD=1 re-reads the files per call, so prompt iteration no longer needs
a server restart.
The prompts themselves stay Swedish. That is the intended design: a fork writes
prompts/<lang>/ in its own language, and the loader falls back through
<lang>/ -> shared -> en/.
Verified by snapshotting all sixteen constants before the move and asserting
equality after: 16/16 render byte-identically, including after parameterization —
which is what proves the templating substitutes exactly what was there before.
tests/test_prompts_golden.py keeps that guarantee going forward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Everything that made this Sweden-only in code now lives in one configuration file:
party list and colours, chamber activity types, the Postgres text-search
dictionary, the party-code pattern used to verify attributions, identifier shapes,
source URLs, the person-photo URL template, embedding model and dimension, and the
site copy.
info.py is deleted. Its party_colors_lighten table is gone too — the tint is now
computed from the party colour, so there is no second palette to keep in sync. Its
dead select_columns and css strings go with it.
The stylesheet no longer carries per-party rules. It had --party-M through
--party-NYD plus twenty [data-party="..."] selectors, which no amount of
configuration could adapt: CSS cannot read a dict. App.tsx now publishes the
configured palette as custom properties from /api/meta, and one color-mix rule
covers every party in any country.
The author's email and Twitter handle are out of the source entirely. Explainer
copy moved to content/sv/*.md, and contact details come from `site.contact`, which
upstream ships empty for a deployment to fill in via its own PARLIAMENT_CONFIG.
Two startup assertions added, both for failures that are otherwise silent: a
text-search config mismatch returns near-zero rows with no error, and a vector
column that disagrees with embeddings.dimension fails deep inside pgvector with a
message that never mentions configuration.
Verified: five searches spanning phrase, exclusion and prefix syntax — exercising
all three tsquery builders that changed — return byte-identical payloads against
production, with differing hit counts and first hits proving the queries really
differ. Frontend builds, and tsc reports the same 9 pre-existing errors as before,
none new.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>