read.markets/app
Giorgio Gilestro 45fa31bb2b ai: structured-output + reviewer agent for indicator summaries
Replaces the regex-based clean_summary / looks_like_leakage pipeline
that produced the 2026-05-29 valuation-read leak. Two layers of defence
in depth:

1. JSON-mode generation. The per-group and aggregate summary system
   prompts now require the model to emit a single object
   {"read": "..."}; response_format={"type":"json_object"} is passed
   through to the provider so the API enforces well-formed JSON. Prose
   outside the field is physically impossible. The "read" field is the
   only schema slot, so the model has nowhere to spill scratchpad
   into the envelope.

2. Reviewer agent. services/output_review.review_read() makes a second
   small LLM call that judges whether the candidate "read" string is
   publishable. It catches the residual failure mode — scratchpad
   INSIDE the field ("Let's see…", multi-question parentheticals,
   meta-commentary) — and returns a JSON verdict {"clean": bool,
   "reason": str}. Any failure (provider error, parse error, missing
   field) returns clean=false (fail-safe). Cost ~$0.0001/check; latency
   ~1-2 s in the hourly job, no user-facing latency.

The old regex scaffolding (_LEAK_PATTERNS, clean_summary,
looks_like_leakage, _TRAILING_QUOTE) is deleted entirely. It produced
false positives (chopped legitimate "The indicators are…" leaders) and
false negatives (never matched the chain-of-thought patterns the model
actually emits). The reviewer agent is strictly better on both.

On reviewer/parse rejection: don't persist a new IndicatorSummary; the
API's existing fallback to the previous good row continues to serve
the panel. Failures are logged as ind_summary.json_invalid /
ind_summary.reviewer_rejected so we can measure the rejection rate.

Reviewer cost is added to the row's recorded cost_usd so the monthly
budget cap covers the full pipeline.

Adds tests/test_output_review.py: 11 cases covering _extract_read
(JSON envelope handling — invalid JSON, missing field, wrong types,
empty values) and review_read (clean / unclean verdicts plus three
fail-safe paths for malformed reviewer responses).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 13:10:52 +02:00
..
jobs ai: structured-output + reviewer agent for indicator summaries 2026-05-29 13:10:52 +02:00
routers ui: log page tone badge follows the toggle (novice / pro) 2026-05-29 12:17:49 +02:00
services ai: structured-output + reviewer agent for indicator summaries 2026-05-29 13:10:52 +02:00
static ui: drop log-content's fixed-viewport scroll cap 2026-05-29 12:58:06 +02:00
templates ui: log page tone badge follows the toggle (novice / pro) 2026-05-29 12:17:49 +02:00
__init__.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
auth.py public: landing + pricing + legal pages, apex-ready, lawyer-reviewed 2026-05-24 00:08:02 +02:00
branding.py css: split cassandra.css into per-section files 2026-05-28 12:31:29 +02:00
cli.py docs: drop Phase D.x markers now that the referral loop is closed 2026-05-26 23:09:39 +02:00
config.py cleanup: drop stale tombstones and dead config fields 2026-05-27 19:25:33 +02:00
db.py sync: encrypted cloud backup for portfolios + settings UX rework 2026-05-23 16:15:54 +02:00
logging.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
main.py routers: extract chat + ops from api.py 2026-05-27 21:43:17 +02:00
models.py models: align translation column naming + add token counts 2026-05-27 21:18:29 +02:00
redis_client.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
scheduler_main.py scheduler: register email_digest_job at 06:30 UTC 2026-05-25 23:20:06 +02:00
schemas.py news: auto-tag headlines + market-aware cadence + filter UI 2026-05-21 23:25:03 +01:00
templates_env.py mobile: cache-bust static assets so browser picks up CSS/JS edits 2026-05-28 19:20:49 +02:00