Skip to content

feat(perf): Issue #287 PR A — durable timeout + per-loop wall-clocks#297

Merged
dackclup merged 1 commit into
mainfrom
claude/issue-287-edgar-timeout-hardening
May 28, 2026
Merged

feat(perf): Issue #287 PR A — durable timeout + per-loop wall-clocks#297
dackclup merged 1 commit into
mainfrom
claude/issue-287-edgar-timeout-hardening

Conversation

@dackclup
Copy link
Copy Markdown
Owner

Summary

Three-part durable fix for the 2026-05-25 weekly cron cancellation at the 150m timeout ceiling (incident-commander session 8: PR #205 added Form-4 as the 5th SEC EDGAR loop without bumping the budget). Does NOT revert FORM4_FETCH_SKIP=1 — that is PR B, gated on ≥ 1 cron completing under the new 195m ceiling with all 4 wall-clock fields populated.

Part 1 — compute-rankings.yml timeout-minutes: 150 → 195

Plus inline 5-loop cold-cache budget docstring (prices 5m + fundamentals 25m + history 15m + form-4 10m + tier-2 35m + osap 5m + scoring 3m ≈ 98m warm-realistic + 20% SEC-throttle headroom ≈ 118m, + commit step + runner spin-up = 195m target).

Part 2 — Cache-restore canary step

Inserted between Restore compute caches and Run weekly compute. Bash one-liner (du -sm + GNU find -printf '%T@' + bc) emits size+age for 10 cache directories to the workflow log in ~15-30s. Surfaces cache eviction before any SEC fetch begins instead of after 150-195m of polling. Standard tools on ubuntu-latest; fail-open on missing utilities.

Part 3 — 4 new Metadata.*_wall_clock_seconds fields

  • tier2_wall_clock_seconds: float | None = None
  • form4_wall_clock_seconds: float | None = None
  • osap_wall_clock_seconds: float | None = None
  • cross_source_wall_clock_seconds: float | None = None

Schema PATCH bump 0.10.8-phase4.6 → 0.10.9-phase4.6 (additive Metadata-only). compute/main.py wraps each of the 4 loops with time.monotonic() start/end markers + 4 distinct defensive patterns:

Loop Pattern None semantic
Tier-2 outer try/except wraps ThreadPoolExecutor interpreter-level failure
Form-4 start INSIDE else: branch FORM4_FETCH_SKIP=1
OSAP start before try, end at try success except (full pipeline failure); QR_SKIP_OSAP still populates a small float
Step 8 cross_source always populated (no skip path)

Wall-clock fields are semantically distinct from fundamentals_latency_p95_seconds: the latter is per-ticker fetch p95 (tenacity-cascade detector); these are total loop wall-clock (budget-overrun + cache-eviction detector). Both are needed — see CLAUDE.md §Gotchas new bullet.

Lockstep + verification

Pre-push 3-reviewer gate

Reviewer Verdict
schema-sentinel (sonnet) ✅ PASS — Metadata 47 → 51 fields, triple aligned, PATCH bump correct
security-reviewer (sonnet) ✅ SAFE-TO-PUSH — 0 CRITICAL/FAIL/WARN, 8 sections PASS
quantrank-reviewer (opus) ✅ PASS post-fix (2 FAIL + 2 WARN resolved: ruff I001 auto-fixed, QR_SKIP_OSAP docstring corrected, CLAUDE.md gotcha added, stale line-comment ride-along fix)

PR B follow-up (separate, gated)

Single-line revert of FORM4_FETCH_SKIP=1 from compute-rankings.yml env block. Gates: ≥ 1 weekly cron green at < 195m with form4_wall_clock_seconds populated. Unblocks Phase 4.5e PR 5 (cluster weight promotion 5.0 → 7.0) gate-data accumulation for Q3 2026-08-19 quarterly cohort audit.

Closes parts 1-3 of #287.

Test plan

  • ruff check . — All checks passed
  • python -m compute.output.schema_check — in sync
  • pytest tests/test_config.py — 11/11 PASS (schema pin held at 0.10.9-phase4.6)
  • pytest tests/ (full suite via test-engineer) — 1365 → 1367 PASS (+2)
  • Vercel preview build — gated on this PR's CI
  • Cron-ui(badge): unify recommendation badge with sector/MoS outlined-light pattern #70 (Mon 2026-06-01 22:00 UTC) — empirical validation: verify all 4 wall-clock fields populate + 4-loop totals < 195m + canary log shows cache size+age before SEC fetch

Generated by Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quantrank Ready Ready Preview, Comment May 28, 2026 8:34am

@dackclup dackclup marked this pull request as ready for review May 28, 2026 08:28
Three-part durable fix for the 2026-05-25 weekly cron cancellation at
the 150m timeout ceiling (incident-commander session 8 verdict — PR
budget). Does NOT revert FORM4_FETCH_SKIP=1 — that is PR B, gated on
>= 1 cron completing under the new 195m ceiling with all 4 wall-clock
fields populated.

Part 1 — `compute-rankings.yml` `timeout-minutes: 150 -> 195` plus
inline 5-loop cold-cache budget docstring (prices 5m + fundamentals
25m + history 15m + form-4 10m + tier-2 35m + osap 5m + scoring 3m =
~98m warm-realistic + 20% SEC-throttle headroom = ~118m, +commit step
+ runner spin-up = 195m).

Part 2 — Cache-restore canary step inserted between `Restore compute
caches` and `Run weekly compute`. Bash one-liner (`du -sm` + GNU
`find -printf '%T@'` + `bc`) emits size+age for 10 cache directories
to the workflow log in ~15-30s. Surfaces cache eviction before any
SEC fetch begins instead of after 150-195m of polling. Standard tools
on ubuntu-latest; fail-open on missing utilities.

Part 3 — 4 new `Metadata.*_wall_clock_seconds` fields
(`tier2_wall_clock_seconds`, `form4_wall_clock_seconds`,
`osap_wall_clock_seconds`, `cross_source_wall_clock_seconds`). Schema
PATCH bump `0.10.8-phase4.6 -> 0.10.9-phase4.6` (additive Metadata
only). `compute/main.py` wraps each of the 4 loops with `time.monotonic()`
start/end markers and 4 distinct defensive patterns:
- Tier-2: outer try/except, `None` on interpreter-level failure
- Form-4: start INSIDE the `else:` branch so FORM4_FETCH_SKIP=1 leaves
  `form4_wall_clock_seconds = None` (skip semantic)
- OSAP: start before try, end at try success, `None` in except. Note:
  QR_SKIP_OSAP only bypasses the freshness gate so wall-clock still
  populates with a small float (~0.5-2s) on cache-hit fast return —
  documented in CLAUDE.md §Gotchas + schema docstring.
- Step 8 cross_source umbrella: always populated; measures the ENTIRE
  Step 8 per-ticker loop (fair-price + manipulation + StockDetail
  write), not just cross_source sub-calls — documented limitation.

Schema triple lockstep: `compute/output/schemas.py` (Pydantic) +
`frontend/lib/types.ts` (TS mirror) + `frontend/lib/schema-snapshot.json`
(regenerated via `python -m compute.output.schema_check --update-snapshot`).
All 4 fields shaped identically across the triple as
`float | None = None` / `number | null` optional.

Test surface: tests/test_config.py schema pin bumped + docstring
rewrite; new tests/test_output/test_wall_clock_schema.py with 2 active
GREEN tests (schema round-trip + None defaults) + 3 skipped stubs with
TODO docstrings for orchestrator-level behavior (harness gap honestly
acknowledged — the `run_weekly_compute()` orchestrator harness build-
out is orthogonal scope; PR B's cron data validates behavior
empirically).

Docs lockstep: CLAUDE.md §Phase status schema pointer bumped +
§Gotchas new bullet explaining wall-clock-vs-per-ticker-p95 semantic +
per-loop None semantics. AGENTS.md Issue #287 status updated to "PR A
in flight this PR". PHASE_STATUS_INFLIGHT.md full in-flight entry
appended per PR #237 side-file convention.

Verification ladder:
- ruff check .                              PASS
- python -m compute.output.schema_check     PASS (in sync)
- pytest tests/test_config.py               11/11 PASS
- pytest tests/ (full, sub-agent reported)  1365 -> 1367 PASS (+2)

Pre-push 3-reviewer gate:
- schema-sentinel (sonnet)                  PASS (Metadata 47 -> 51 fields)
- security-reviewer (sonnet)                SAFE-TO-PUSH (0 CRIT/FAIL/WARN)
- quantrank-reviewer (opus)                 PASS post-fix (2 FAIL + 2 WARN
                                            resolved: ruff I001 auto-fixed,
                                            QR_SKIP_OSAP docstring corrected,
                                            CLAUDE.md gotcha added, stale
                                            line-comment ride-along fix)

PR B = single-line revert of FORM4_FETCH_SKIP=1 from
compute-rankings.yml env block. Gates: >= 1 cron green at < 195m with
`form4_wall_clock_seconds` populated. Unblocks Phase 4.5e PR 5
(cluster weight promotion 5.0 -> 7.0) gate-data accumulation for
Q3 2026-08-19 quarterly cohort audit.

Closes part 1-3 of Issue #287.

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa
@dackclup dackclup force-pushed the claude/issue-287-edgar-timeout-hardening branch from 4b83f31 to 5879b79 Compare May 28, 2026 08:33
@dackclup dackclup merged commit ecb60e6 into main May 28, 2026
4 of 5 checks passed
@dackclup dackclup deleted the claude/issue-287-edgar-timeout-hardening branch May 28, 2026 08:33
@github-actions
Copy link
Copy Markdown
Contributor

Pre-merge production simulation

Field Value
Duration 444s
Universe size 502
Schema version 0.10.9-phase4.6
Compute commit a7d26a251ee517978a989b636b1b07021d8b1b88
PR-branch output pr-297-compute-output (14-day retention)

Diff vs main

Field Main PR Δ
Universe size 502 502 +0
Schema version 0.10.7-phase4.6 0.10.9-phase4.6 ⚠️ bumped

Main baseline: 2026-05-28T03:14:33Z (0.2 days old)

Top-10 movers (sorted by |Δcomposite_score|)

Ticker PR rank main rank Δrank PR score main score Δscore
HD 226 271 +45 51.75 50.18 +1.57
LVS 122 108 -14 56.79 57.70 -0.91
SNPS 493 495 +2 31.80 31.55 +0.25
DDOG 426 423 -3 40.51 40.75 -0.24
MCHP 492 491 -1 32.21 32.41 -0.20
PCG 285 289 +4 49.38 49.24 +0.14
ROST 222 218 -4 51.99 52.13 -0.14
NKE 442 438 -4 39.59 39.73 -0.14
PNW 224 223 -1 51.81 51.93 -0.12
LITE 496 497 +1 30.89 30.79 +0.10

dackclup added a commit that referenced this pull request May 28, 2026
Closes the silent-failure gap surfaced by Issue #287 PR A's Rule 18
instrumentation on cron Run #71 (368dccd, 2026-05-28 08:44 UTC). The
PR #292 GOOG/GOOGL per-class XBRL share-override fix did not fire in
production despite the code being correct on the runner.

Root cause (edgar-debugger 2026-05-28 verdict):

  - PR #292 (e9aaab3, 04:22 UTC) landed the per-class XBRL override
    at compute/ingest/fundamentals.py:1043-1067 (Branch 3 of
    _build_snapshot).
  - Branch 3 only executes on live EDGAR fetch — fetch_fundamentals
    short-circuits at _is_fresh() (line 1292-1294) when cached parquet
    age by latest_filed_date < FUNDAMENTALS_REFETCH_DAYS = 45.
  - Earlier same-day cron 0ad1d57 (03:22 UTC, pre-PR-#292) wrote a
    stale aggregate parquet (GOOG shares_outstanding = 12.116B).
  - Cron Run #71 restored that parquet from the GitHub Actions cache;
    _is_fresh() returned True on latest_filed_date=2026-04-30 (28d
    < 45d), and Branch 3 never ran.
  - metadata.multi_class_per_class_attempt_count = 0 (PR #292 Rule 18
    disambiguator working as designed — the smoking gun).
  - fundamentals_latency_p50_seconds = 0.0 (warm-cache replay confirmed
    universe-wide).

Fix scope (6 files, YAML + paired test bump):

  - .github/workflows/compute-rankings.yml — 3 instances cache-v4- →
    cache-v5- (key + 2 restore-keys) + comment block expanded to cite
    Issue #288 follow-up + PR #292 + PR #269 + introduce a 2-trigger
    bump taxonomy (schema change OR value-correctness fix in live-
    fetch-only path).
  - .github/workflows/pre-merge-prod-sim.yml — mirror 3-string flip
    per the file's own "bump together if either changes" comment.
    Without this the simulate workflow would lose all 11 warm caches
    on every PR.
  - tests/test_workflow_cache_coverage.py — paired-test bump per the
    PR 4c.1 v3→v4 precedent. Function renamed
    test_workflow_cache_key_is_v4 → _v5; docstring rewritten to cite
    Issue #288 + PR #292 + the 3-trigger bump taxonomy.
  - CLAUDE.md §Phase status — drain stale "in flight" wording for
    PR #297 (now merged) + empirical-validation note for cron Run #71
    + "in flight this PR" entry for the cache-v5 bump.
  - AGENTS.md open-issues list — update #287 (PR A merged via #297),
    #288 (fix in flight this PR), #289 (closed by PR #293).
  - PHASE_STATUS_INFLIGHT.md — full in-flight entry appended per
    PR #237 side-file convention.

Why Option A (cache-key bump) over alternatives (per edgar-debugger):

  - Option B (targeted per-ticker invalidation): introduces cache-layer-
    knows-multi-class semantics + chicken-and-egg "detect stale
    aggregate from cached parquet" condition.
  - Option C (refactor override out of fetch path): cache hit triggers
    live SEC call (violates cache semantics) + FundamentalsSnapshot is
    frozen.
  - Option A: matches PR 4c.1 v3→v4 precedent exactly + zero compute/
    change + guaranteed correctness on next cron.

One-time cost: ~25-50 min cold cron on the immediately-following
weekly run (full S&P 500 universe live re-fetch). Subsequent crons
return to warm-cache ~5-10 min budget. No timeout-minutes impact —
PR #297 just bumped to 195m which absorbs cold-cache reality.

Verification on next cron Run #72:

  - metadata.multi_class_per_class_attempt_count = 2 (was 0)
  - metadata.multi_class_per_class_override_count = 2
  - stocks/GOOG.json shares_outstanding ≈ 5.429B (Class C, was 12.116B)
  - stocks/GOOGL.json shares_outstanding ≈ 5.822B (Class A, was 12.116B)
  - stocks/GOOG.json market_cap ≈ $2.09T (was $4.66T)
  - stocks/GOOGL.json market_cap ≈ $2.59T (was $4.71T)
  - metadata.fundamentals_latency_p50_seconds > 0.0 (live fetch active)

Adjacent findings deferred (NOT in this PR):

  - FOX / FOXA / NWS / NWSA: same multi_class_aggregate_shares_suspected
    annotate firing but they are on MULTI_CLASS_SHARE_ALLOWLIST
    (UNDERCOUNT path, PR #257). Decision on whether to add to overcount
    allowlist deferred to Q3 2026-08-19 quarterly cohort audit per
    methodology-scientist precedent (needs live XBRL probe).
  - OSAP wall-clock 347.1s on Run #71: cold OSAP download (cache > 31d
    mtime or evicted). Single observation; not a regression. Watch on
    next 2-3 crons.

Hard constraints honored:

  - No compute / scoring / schema / valuation / Rule 16 / Top-5
    invariant touched
  - No new defense flag · No new dep · No new env-var
  - YAML + paired-test diff (per quantrank-reviewer feedback on
    PR-title framing — original "YAML-only" was misleading)
  - Schema version UNCHANGED at 0.10.9-phase4.6 (no Pydantic / TS /
    snapshot change)

Pre-push 3-reviewer gate:

  - phase-coordinator Mode B (sonnet): LOCKSTEP-SATISFIED — both
    CLAUDE.md + AGENTS.md substance touched, INFLIGHT entry well-
    shaped, branch in-sync with origin/main (no rebase needed)
  - quantrank-reviewer (opus): FIX-AND-RE-REVIEW → 2 FAIL + 4 WARN.
    Both FAILs fixed in this commit (tests/test_workflow_cache_coverage.py
    test pin + pre-merge-prod-sim.yml cache-v4 stragglers). WARN 1
    (Issue #288 lifecycle) addressed via Reopens/Closes directives
    below. WARN 2 (AGENTS.md cron #69 cross-ref), WARN 3 (comment
    density) deferred — minor.

Reopens #288
Closes #288

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa

Co-authored-by: Claude <noreply@anthropic.com>
dackclup added a commit that referenced this pull request May 28, 2026
…rkers + bump pointers (#299)

Closes today's 10-PR cycle (#286 / #290 / #291 / #292 / #293 / #294 /
#295 / #296 / #297 / #298). Mirror of PR #286 (post-v1.4.0 cycle
drain) for the post-cron-#71 cycle.

Three stale `(in flight, 2026-05-28)` markers in
PHASE_STATUS_INFLIGHT.md drained to `(merged 2026-05-28, <SHA>)`:

  - PR #295 (`2d2ec83e`) — Post-session housekeeping drain 6 INFLIGHT
    + bump pointers
  - PR #297 (`ecb60e64`) — Issue #287 PR A: durable timeout + cache
    canary + per-loop wall-clock Metadata (schema 0.10.8 → 0.10.9-phase4.6)
  - PR #298 (`030675e9`) — Issue #288 follow-up: cache-key bump v4 → v5

Bodies preserved (historical record).

CLAUDE.md §Phase status — drained the "(In flight this PR — cache-v5)"
qualifier (PR #298 merged) + added post-PR-#298 confirmation note +
cron Run #71 production-verified pointer.

AGENTS.md open-issues list — #288 status flipped "(fix in flight this
PR)" → "(closed by PR #298 cache-v5 bump)" + clarified the silent-
failure root-cause + Run #72 verification gate.

Why this PR exists: without end-of-day drain, session N+1 reading
CLAUDE.md / PHASE_STATUS_INFLIGHT.md would see 3 PRs still marked
"in flight" despite them merging hours earlier — the same friction
pattern PR #286 closed for the post-v1.4.0 cycle. Three same-day
drains in one PR keeps the side-file disciplined.

Scope (3 files, doc-only):

  - PHASE_STATUS_INFLIGHT.md — 3 header substitutions + this PR's
    own in-flight entry appended per PR #237 side-file convention
  - CLAUDE.md §Phase status pointer refresh
  - AGENTS.md open-issues list #288 status update

Hard constraints honored:

  - No code / scoring / schema / valuation / Rule 16 / Top-5
    invariant touched
  - No new defense flag · No new dep · No new env-var
  - Doc-only diff (Markdown only)
  - Schema version UNCHANGED at 0.10.9-phase4.6 (no Pydantic / TS /
    snapshot change)

PHASE_STATUS_INFLIGHT.md side-file satisfies §Conventions "ship with
every PR" lockstep per PR #237 convention. Same drain template as
PR #286 (post-v1.4.0 cycle).

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa

Co-authored-by: Claude <noreply@anthropic.com>
dackclup added a commit that referenced this pull request May 28, 2026
…ULD-FIX cross-doc drifts (#301)

Comprehensive .md housekeeping closing today's 11-PR session day.
Output of docs-reviewer (sonnet) full Tier 1 + Tier 2 audit on main
(post-PR-#299) — verdict NEEDS-CROSS-REF-FIX with 14 prioritized
findings; this PR applies all 8 MUST-FIX + 6 SHOULD-FIX. 3 NICE-TO-FIX
deferred to follow-up.

Scope (7 files, doc-only):

  - SKILL.md schema-version history table (line 240) — prepend 2 new
    rows: 0.10.9-phase4.6 (PR #297, 4 *_wall_clock_seconds fields +
    195m timeout + cache canary, empirically validated cron Run #71)
    + 0.10.10-phase4.6 (PR #300 in flight — Issue #67 follow-up per-
    sector delta). Closes the canonical-history gap where PR #297 +
    PR #300 were absent.

  - PHASE_STATUS.md §Current state — schema row 0.10.8 → 0.10.9 +
    PR #300 in-flight note; Post-tag production patches row extended
    with PR #295/#296/#297/#298/#299 SHAs + one-liners; Production
    run pointer 0ad1d57 cron #69368dccd cron Run #71 with the
    PR #297 wall-clock empirical numbers + Issue #288 cache-replay
    smoking gun (multi_class_per_class_attempt_count=0 +
    fundamentals_latency_p50_seconds=0.0). Recently merged block
    extended 6 → 11 PRs. Issue closure status updated. Next
    deliverables refreshed: Issue #67 flip removed (PR #294 already
    executed); item 2 now = Issue #287 PR B FORM4 revert; PR #300
    per-sector delta added as item 3. Open issues list refreshed
    (#288 + #289 marked closed; #287 PR A vs PR B split).

  - CLAUDE.md §Phase status Recently merged block — extended 6 → 11
    PRs with full SHA + one-liner per PR. New "In flight" sub-section
    added for PR #300.

  - AGENTS.md §Phase + version state — Production-verified run
    cron #69 (233117a, 13m 16s) → cron Run #71 (368dccd, 14m 32s,
    2026-05-28 08:44 UTC, schema 0.10.7 → 0.10.9-phase4.6); 4 new
    wall-clock field values cited; Issue #288 cache-replay smoking
    gun captured; closed-issue note for #288 + #289 + #287 PR A.

  - CONTEXT.md §Live snapshot — schema 0.10.8 → 0.10.9 + PR #300
    in-flight note; new "Post-tag patches" row listing PRs #292-#299
    + PR #300 in flight; cron status cron #69 2026-05-27 → Run #71
    2026-05-28; Sector-CoE row updated with empirical 132 → 109;
    §Roadmap Stage 0 description refreshed.

  - WORKFLOW.md §Agentic 6-Phase Cadence session-start protocol —
    inline schema 0.10.7-phase4.6 replaced with current 0.10.9-phase4.6
    + pointer guidance to PHASE_STATUS.md §Current state as the
    canonical bump-per-schema-PR target. Closes the recurring inline-
    schema drift pattern.

  - PHASE_STATUS_INFLIGHT.md — this PR's in-flight entry appended per
    PR #237 side-file convention.

docs-reviewer lockstep cross-check after this PR:

  - SCHEMA_VERSION: ALIGNED across all 6 canonical docs at
    0.10.9-phase4.6 with PR #300 in-flight note where applicable
  - Defense layer 33 declared: ALIGNED (was already)
  - USE_SECTOR_COE = True post-PR #294: ALIGNED (was stale in
    AGENTS.md issue #67 framing + PHASE_STATUS.md Next deliverables;
    both fixed)
  - Subagent count 18: ALIGNED (was already)
  - Skill count 45: ALIGNED (was already)
  - Latest cron Run #71 368dccd: ALIGNED (was stale in AGENTS.md +
    PHASE_STATUS.md + CONTEXT.md; all fixed)
  - Issue #288 + #289 closure status: ALIGNED (was stale as open in
    AGENTS.md + PHASE_STATUS.md; both fixed)

3 NICE-TO-FIX deferred:

  - README.md Honest Limitations does not reference Phase 4.6 honest
    re-validation harness (PR #283). Coverage gap, not break.
  - WORKFLOW.md Phase 4.5 row cites v1.2.0; technically closed at
    v1.3.0-phase4.5e. Historical-context only.
  - METHODOLOGY.md USE_SECTOR_COE framing needs verification before
    edit.

Hard constraints honored:

  - No code / scoring / schema / valuation / Rule 16 / Top-5
    invariant touched
  - No new defense flag · No new dep · No new env-var
  - Markdown-only diff (no JSON / YAML / Python / TS change)
  - Schema version UNCHANGED on main at 0.10.9-phase4.6 (PR #300 will
    bump 0.10.10 on its merge)
  - AGENTS.md substance lockstep with CLAUDE.md per the established
    delegation pattern

Verification:

  - ruff check .                          PASS (no Python touched)
  - python -m compute.output.schema_check PASS (no schema touched)
  - pytest tests/ -m "not network"        N/A (no test surface)
  - Cross-reference grep — all 7 anchor strings consistent across
    all 6 docs after fix

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa

Co-authored-by: Claude <noreply@anthropic.com>
dackclup pushed a commit that referenced this pull request May 28, 2026
… instrumentation

Methodology-scientist Mode B Q2 follow-up deferred from PR #294 (sector-
CoE flip, 2026-05-28 05:39 UTC). Adds
`Metadata.value_trap_risk_delta_by_sector: dict[str, int] | None` so
Q3 2026-08-19 quarterly cohort audit has visible per-sector shape
evidence — not just the aggregate `value_trap_risk_count_*_sector_coe`
scalars that landed in PR #204.

Schema PATCH bump 0.10.9 → 0.10.10-phase4.6 (additive Metadata-only).

Methodology context (Damodaran 2019 Ch. 8.4 §"Industry Beta"):

  After `USE_SECTOR_COE = True` per-sector Ke replaces the flat 10%
  baseline at SECTOR_COST_OF_EQUITY (11 GICS sectors, Ke 6%-12%).
  Directional predictions:

  - Lower-Ke sectors (Utilities ~6-7% / Real Estate ~7-8% / Consumer
    Staples ~7-8%): ROE ≥ Ke threshold relaxed → fewer RIM-skipped →
    POSITIVE delta (sector DROPPED flags)
  - Higher-Ke sectors (Information Technology ~11-12% / Energy
    ~10-12%): ROE ≥ Ke tightened → more RIM-skipped → NEGATIVE delta
  - Neutral sectors (6 GICS sectors at ~9-11%): small delta near zero

Cron #69 + Run #71 universe-wide already confirmed the aggregate:
132 → 109 (−23 tickers, −17.4%). This PR breaks the −23 down by sector.

Scope (10 files, additive only):

  - compute/output/schemas.py — new value_trap_risk_delta_by_sector
    field with full docstring (methodology-scientist verdict +
    Damodaran 2019 anchor + direction semantics)
  - frontend/lib/types.ts — mirror TS field as Record<string, number> | null
  - frontend/lib/schema-snapshot.json — regenerated via --update-snapshot
  - compute/config.py — SCHEMA_VERSION = "0.10.10-phase4.6"
  - compute/main.py — 3 surgical edits mirroring existing scalar
    dual-counter pattern (init two dict[str, int] counters / per-sector
    increment co-located with the existing scalar bump in both branches
    / delta computation in Metadata constructor)
  - tests/test_config.py — schema version pin bump + docstring rewrite
  - tests/test_output/test_value_trap_delta_by_sector_schema.py (NEW) —
    2 active GREEN schema-contract tests (mirror test_wall_clock_schema.py
    pattern from PR #297)
  - CLAUDE.md — §Phase status pointer block refresh
  - AGENTS.md — open-issues #67 status: flip landed + per-sector
    follow-up in flight this PR
  - PHASE_STATUS_INFLIGHT.md — full in-flight entry per PR #237
    side-file convention

Implementation note:

  Per-sector dict construction uses
  `sorted(set(without) | set(with))` for stable key ordering;
  `.get(sec, 0)` fallback handles sectors appearing in only one path;
  `{} or None` falls back to None when both dicts are empty (test-mode
  universe). Co-located with the existing scalar bump in both
  `_rim_flat` (flat-Ke) and `_rim_sector` (sector-Ke) branches at the
  same `value_trap_risk_roe_below_cost_of_equity` reason guard — scalar
  and dict always stay in lockstep.

Verification ladder:

  - ruff check .                              PASS
  - python -m compute.output.schema_check     PASS (triple in sync 0.10.10)
  - pytest tests/test_config.py -v            11/11 PASS (pin held)
  - python -m pytest tests/test_output/       2/2 NEW PASS
  - Full offline suite via test-engineer      1367 → 1369 (+2 NEW)

Pre-push 3-reviewer gate:

  - schema-sentinel (sonnet)        PASS (52 fields, triple aligned,
                                    PATCH bump correct, snapshot
                                    alphabetical ordering held)
  - test-engineer (sonnet)          GREEN (2/2 new tests pass,
                                    1367 → 1369, 0 regressions,
                                    0 skipped stubs)
  - quantrank-reviewer (opus)       READY-TO-PUSH (0 FAIL, 4 WARN
                                    all pre-existing PR-#297-era
                                    drift, defer to next housekeeping
                                    PR — incl. SKILL.md/PHASE_STATUS.md
                                    schema-table tops still on 0.10.8)

Empirical validation gate (post-merge, next cron Run #72):

  - metadata.value_trap_risk_delta_by_sector populates as non-null dict
  - Damodaran shape directionally correct: Util/Real Estate/Staples
    POSITIVE, Information Technology/Energy NEGATIVE
  - sum(delta.values()) == without_sector_coe_count - with_sector_coe_count
    (= 23 per Run #71 universe-wide; matches within rounding)

Note: per-sector accumulation runs in the Step 8 per-ticker loop,
INDEPENDENT of cache-v5 cache busting (PR #298). Field populates on
next cron regardless of warm/cold fetch path.

Hard constraints honored:

  - No new defense flag · No scoring formula change · No Rule 16 /
    Top-5 violation
  - Additive-only schema change (PATCH bump)
  - Field nullable per Rule 18 graceful-degradation
  - Phase 4.5e PR 5 (cluster weight promotion) gate-data UNCHANGED —
    independent track

Methodology decision: methodology-scientist verdict NOT re-requested —
this is the EXACT field shape Mode B Q2 verdict from PR #294 explicitly
authorized. Future re-trigger only if post-merge cron shows sector
breakdown contradicting Damodaran prediction OR Q3 2026-08-19 audit
reads ≥ 6 crons of data and per-sector decay pattern needs interpretation.

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa
dackclup added a commit that referenced this pull request May 28, 2026
…ta instrumentation (#300)

Methodology-scientist Mode B Q2 follow-up deferred from PR #294 (sector-
CoE flip, 2026-05-28 05:39 UTC). Adds
`Metadata.value_trap_risk_delta_by_sector: dict[str, int] | None` so
Q3 2026-08-19 quarterly cohort audit has visible per-sector shape
evidence — not just the aggregate `value_trap_risk_count_*_sector_coe`
scalars that landed in PR #204.

Schema PATCH bump 0.10.9 → 0.10.10-phase4.6 (additive Metadata-only).

Methodology context (Damodaran 2019 Ch. 8.4 §"Industry Beta"):

  After `USE_SECTOR_COE = True` per-sector Ke replaces the flat 10%
  baseline at SECTOR_COST_OF_EQUITY (11 GICS sectors, Ke 6%-12%).
  Directional predictions:

  - Lower-Ke sectors (Utilities ~6-7% / Real Estate ~7-8% / Consumer
    Staples ~7-8%): ROE ≥ Ke threshold relaxed → fewer RIM-skipped →
    POSITIVE delta (sector DROPPED flags)
  - Higher-Ke sectors (Information Technology ~11-12% / Energy
    ~10-12%): ROE ≥ Ke tightened → more RIM-skipped → NEGATIVE delta
  - Neutral sectors (6 GICS sectors at ~9-11%): small delta near zero

Cron #69 + Run #71 universe-wide already confirmed the aggregate:
132 → 109 (−23 tickers, −17.4%). This PR breaks the −23 down by sector.

Scope (10 files, additive only):

  - compute/output/schemas.py — new value_trap_risk_delta_by_sector
    field with full docstring (methodology-scientist verdict +
    Damodaran 2019 anchor + direction semantics)
  - frontend/lib/types.ts — mirror TS field as Record<string, number> | null
  - frontend/lib/schema-snapshot.json — regenerated via --update-snapshot
  - compute/config.py — SCHEMA_VERSION = "0.10.10-phase4.6"
  - compute/main.py — 3 surgical edits mirroring existing scalar
    dual-counter pattern (init two dict[str, int] counters / per-sector
    increment co-located with the existing scalar bump in both branches
    / delta computation in Metadata constructor)
  - tests/test_config.py — schema version pin bump + docstring rewrite
  - tests/test_output/test_value_trap_delta_by_sector_schema.py (NEW) —
    2 active GREEN schema-contract tests (mirror test_wall_clock_schema.py
    pattern from PR #297)
  - CLAUDE.md — §Phase status pointer block refresh
  - AGENTS.md — open-issues #67 status: flip landed + per-sector
    follow-up in flight this PR
  - PHASE_STATUS_INFLIGHT.md — full in-flight entry per PR #237
    side-file convention

Implementation note:

  Per-sector dict construction uses
  `sorted(set(without) | set(with))` for stable key ordering;
  `.get(sec, 0)` fallback handles sectors appearing in only one path;
  `{} or None` falls back to None when both dicts are empty (test-mode
  universe). Co-located with the existing scalar bump in both
  `_rim_flat` (flat-Ke) and `_rim_sector` (sector-Ke) branches at the
  same `value_trap_risk_roe_below_cost_of_equity` reason guard — scalar
  and dict always stay in lockstep.

Verification ladder:

  - ruff check .                              PASS
  - python -m compute.output.schema_check     PASS (triple in sync 0.10.10)
  - pytest tests/test_config.py -v            11/11 PASS (pin held)
  - python -m pytest tests/test_output/       2/2 NEW PASS
  - Full offline suite via test-engineer      1367 → 1369 (+2 NEW)

Pre-push 3-reviewer gate:

  - schema-sentinel (sonnet)        PASS (52 fields, triple aligned,
                                    PATCH bump correct, snapshot
                                    alphabetical ordering held)
  - test-engineer (sonnet)          GREEN (2/2 new tests pass,
                                    1367 → 1369, 0 regressions,
                                    0 skipped stubs)
  - quantrank-reviewer (opus)       READY-TO-PUSH (0 FAIL, 4 WARN
                                    all pre-existing PR-#297-era
                                    drift, defer to next housekeeping
                                    PR — incl. SKILL.md/PHASE_STATUS.md
                                    schema-table tops still on 0.10.8)

Empirical validation gate (post-merge, next cron Run #72):

  - metadata.value_trap_risk_delta_by_sector populates as non-null dict
  - Damodaran shape directionally correct: Util/Real Estate/Staples
    POSITIVE, Information Technology/Energy NEGATIVE
  - sum(delta.values()) == without_sector_coe_count - with_sector_coe_count
    (= 23 per Run #71 universe-wide; matches within rounding)

Note: per-sector accumulation runs in the Step 8 per-ticker loop,
INDEPENDENT of cache-v5 cache busting (PR #298). Field populates on
next cron regardless of warm/cold fetch path.

Hard constraints honored:

  - No new defense flag · No scoring formula change · No Rule 16 /
    Top-5 violation
  - Additive-only schema change (PATCH bump)
  - Field nullable per Rule 18 graceful-degradation
  - Phase 4.5e PR 5 (cluster weight promotion) gate-data UNCHANGED —
    independent track

Methodology decision: methodology-scientist verdict NOT re-requested —
this is the EXACT field shape Mode B Q2 verdict from PR #294 explicitly
authorized. Future re-trigger only if post-merge cron shows sector
breakdown contradicting Damodaran prediction OR Q3 2026-08-19 audit
reads ≥ 6 crons of data and per-sector decay pattern needs interpretation.

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants