review: per-surface rider, loosen for portfolio commentary

Reviewer was rejecting legitimate IT portfolio analyses, citing
descriptive risk language as actionable advice:

  reason: "Allocation guidance throughout: 'concentrazione gestibile',
  'non eliminabile', 'bassa esposizione', 'va monitorato'. Treats
  portfolio construction as actionable."

These phrases describe portfolio state (manageable concentration,
non-eliminable risk, low exposure, warrants monitoring) without
directing the user to take action. They are exactly the kind of
prose a portfolio commentary surface is supposed to produce. The
reviewer's generic "no financial advice" rule is too broad here.

Add a `surface` parameter to review_read() with a per-surface rider
mechanism (_SURFACE_RIDERS). The "portfolio" rider:

- Lists DESCRIPTIVE phrasings that are EXPLICITLY permitted:
  attribute naming ("high concentration", "currency exposure"),
  thesis invalidation conditions, impersonal observations about a
  position's sensitivity.
- Tightens the reject list to EXPLICIT calls to action: imperative
  verbs aimed at the reader, "you should", "consider X-ing",
  specific allocation prescriptions, price-target predictions.

portfolio_analysis.analyse() now passes surface="portfolio". All
other reviewer call sites (indicator summary, log, chat, digest)
default to surface=None and keep the generic rules.

tests/conftest.py's autouse review_read stub picks up **_kw so
adding new keyword arguments to review_read doesn't keep breaking
the locale-integration tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-05-29 16:44:27 +02:00
parent d47b310898
commit 0060166d32
3 changed files with 57 additions and 5 deletions

View file

@ -365,8 +365,14 @@ async def analyse(
# buy/sell or allocation language is a regulatory hazard. Drop
# the response on a reject and surface a retry-able error to the
# caller; no analysis is ever persisted server-side anyway.
# surface="portfolio" applies a rider that loosens the generic
# "no advice" rule to permit descriptive risk language
# ("concentration is high", "currency exposure is unhedged",
# "the position warrants monitoring") which is the actual
# purpose of this surface, while keeping explicit
# buy/sell/allocation directives forbidden.
if llm is not None:
verdict = await review_read(client, llm.content)
verdict = await review_read(client, llm.content, surface="portfolio")
review_cost = verdict.cost_usd or 0.0
if not verdict.clean:
status = "leaked"