agent review: reject future-dated sources at submit; treat fetched pages as data
validate() now takes the submission stamp and rejects any source without a readable publication date or dated after the submission day (UTC). The skill tells the agent and its subagents that web pages, news and filings are data, never instructions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -136,8 +136,23 @@ def cmd_candidates(args) -> None:
|
||||
f"{'the next session rebalances' if until == 0 else f'next rebalance in {until} sessions (vetoes act daily)'}")
|
||||
|
||||
|
||||
def validate(sub: dict, pack: dict) -> tuple[dict, list[str]]:
|
||||
"""Normalized scores and warnings; raises ValueError on a bad file."""
|
||||
def _source_day(src) -> pd.Timestamp | None:
|
||||
"""A source's publication day (UTC), or None when it has no readable date."""
|
||||
try:
|
||||
ts = pd.Timestamp(src["date"])
|
||||
except (TypeError, KeyError, ValueError):
|
||||
return None
|
||||
if pd.isna(ts):
|
||||
return None
|
||||
if ts.tzinfo is not None:
|
||||
ts = ts.tz_convert("UTC").tz_localize(None)
|
||||
return ts.normalize()
|
||||
|
||||
|
||||
def validate(sub: dict, pack: dict, now: pd.Timestamp) -> tuple[dict, list[str]]:
|
||||
"""Normalized scores and warnings; raises ValueError on a bad file.
|
||||
``now`` is the submission stamp: no source may be dated after its day (UTC)."""
|
||||
today = now.tz_convert("UTC").tz_localize(None).normalize()
|
||||
want = [c["ticker"] for c in pack["candidates"]]
|
||||
rows = sub.get("scores")
|
||||
if not isinstance(rows, dict):
|
||||
@@ -160,10 +175,20 @@ def validate(sub: dict, pack: dict) -> tuple[dict, list[str]]:
|
||||
raise ValueError(f"{t}: a veto needs the evidence in 'risks'")
|
||||
if not str(v.get("summary", "")).strip():
|
||||
raise ValueError(f"{t}: 'summary' is required")
|
||||
sources = v.get("sources", [])
|
||||
if not isinstance(sources, list):
|
||||
raise ValueError(f"{t}: 'sources' must be a list of {{url, date}}")
|
||||
for src in sources:
|
||||
day = _source_day(src)
|
||||
if day is None:
|
||||
raise ValueError(f"{t}: source {src!r} needs a publication 'date' (YYYY-MM-DD)")
|
||||
if day > today:
|
||||
raise ValueError(f"{t}: source {src.get('url')} is dated {src['date']}, "
|
||||
f"after the submission day {today.date()} (UTC)")
|
||||
out[t] = {"score": s, "veto": veto, "veto_reason": reason if veto else None,
|
||||
"confidence": v.get("confidence"),
|
||||
**{k: v.get(k, "") for k in ("earnings", "fundamentals", "logic", "risks", "summary")},
|
||||
"sources": v.get("sources", [])}
|
||||
"sources": sources}
|
||||
extreme = sum(abs(v["score"]) == hi for v in out.values())
|
||||
if extreme > 10:
|
||||
warn.append(f"{extreme} names at +/-{hi}; the rubric expects at most about 10")
|
||||
@@ -179,11 +204,11 @@ def cmd_submit(args) -> None:
|
||||
sys.exit(f"no candidate pack for as_of={as_of!r} ({pack_path})")
|
||||
with open(pack_path) as fh:
|
||||
pack = json.load(fh)
|
||||
now = pd.Timestamp.now(tz="UTC")
|
||||
try:
|
||||
scores, warn = validate(sub, pack)
|
||||
scores, warn = validate(sub, pack, now)
|
||||
except ValueError as exc:
|
||||
sys.exit(f"rejected: {exc}")
|
||||
now = pd.Timestamp.now(tz="UTC")
|
||||
deadline = pd.Timestamp(pack["deadline_utc"])
|
||||
out = {"strategy": NAME, "as_of": as_of, "created_at": now.isoformat(timespec="seconds"),
|
||||
"model": sub.get("model", ""), "n_candidates": len(scores), "scores": scores}
|
||||
|
||||
Reference in New Issue
Block a user