Skip to content

feat(scoring): sector-keyed cost_of_equity behind config flag (closes #67 prep)#204

Merged
dackclup merged 1 commit into
mainfrom
claude/sector-coe-damodaran-i67
May 22, 2026
Merged

feat(scoring): sector-keyed cost_of_equity behind config flag (closes #67 prep)#204
dackclup merged 1 commit into
mainfrom
claude/sector-coe-damodaran-i67

Conversation

@dackclup
Copy link
Copy Markdown
Owner

Summary

Part of #67. Behind USE_SECTOR_COE = False default — data-collection PR per Rule 18. Flip-to-True follow-up after ≥ 1 cron measures the delta-flag-count.

What this PR does

  • Adds compute/scoring/cost_of_equity.py — GICS-keyed Damodaran 2019 sector cost-of-equity table
  • Gates it behind config.USE_SECTOR_COE = False (OFF by default — no production change)
  • Plumbs the sector CoE into compute/valuation/ensemble.pyrim_fair_price
  • Adds 3 Rule 18 Metadata diagnostics so the flat-10% vs sector-Ke delta is observable from the next cron before the flag is flipped

Damodaran 2019 Table 8.4 + NYU January 2025 dataset — per-sector values

GICS Sector Ke (this PR) Damodaran source
Utilities 6.0% β ≈ 0.40 → Ke 6.0% (Table 8.4 + Jan-2025 dataset)
Real Estate 7.0% REIT β ≈ 0.56 → Ke 7.0% (Damodaran 2019 p.195)
Consumer Staples 8.8% NYU Jan-2025 "Consumer Staples" median 8.7% → rounded 8.8%
Financials 8.5% NYU Jan-2025 US financial-sector median ≈ 8.5%
Communication Services 9.5% Telecom + Entertainment avg β ≈ 1.05 → 9.5%
Industrials 9.5% Capital Goods median ≈ 9.3% → rounded 9.5%
Consumer Discretionary 10.0% NYU Jan-2025 median ≈ 10.0%
Health Care 10.5% Drug/biotech S&P 500 mix → 10.5%
Information Technology 11.0% Semi + software median ≈ 11.0%
Materials 11.0% Metals/mining + chem avg → 11.0%
Energy 12.0% Oil/gas E&P + integrated → 12.0%

Default fallback: 10.0% (unchanged from COST_OF_EQUITY constant in config.py).

Citation: Damodaran, A. (2019). Investment Valuation (3rd ed., Table 8.4). Wiley. + Damodaran NYU Stern "Cost of Equity by Industry — US" (January 2025, https://pages.stern.nyu.edu/~adamodar/).

Rule 18 observability fields added to Metadata (schema 0.9.7-phase4h.7 → 0.9.8-phase4h.8)

sector_coe_enabled: bool = False                           # mirrors config.USE_SECTOR_COE at write time
value_trap_risk_count_without_sector_coe: int | None = None  # baseline flat-10% count
value_trap_risk_count_with_sector_coe: int | None = None     # count under per-sector Ke

Both counts are computed every cron regardless of the flag, so the delta is measurable before the production flip. Expected direction: value_trap_risk drops from baseline ~176 toward ~80-110 (cyclical sectors whose 3y-mean ROE sits between sector Ke and flat 10% get un-flagged); Utilities/REITs with sector Ke < 10% may pick up new flags, so net empirical confirmation is required before flipping.

Files changed

File Change
compute/scoring/cost_of_equity.py NEW — SECTOR_COST_OF_EQUITY dict + get_cost_of_equity()
compute/config.py USE_SECTOR_COE = False added; SCHEMA_VERSION bumped
compute/valuation/ensemble.py rim_fair_price receives sector Ke when flag True
compute/main.py dual check_rim_applicability pass; Metadata wiring
compute/output/schemas.py 3 new Metadata fields
frontend/lib/types.ts matching Metadata additions
frontend/lib/schema-snapshot.json regenerated
tests/test_scoring/test_cost_of_equity.py NEW — 32 tests
tests/test_scoring/test_value_trap_risk_sector_coe.py NEW — 8 tests
tests/test_config.py schema version pin updated
CLAUDE.md + AGENTS.md §Phase status lockstep

Verification

  • ruff check . → clean
  • pytest tests/ -m "not network" → 1094 passed (baseline 1059 + 40 new -5 renamed = net +35)
  • python -m compute.output.schema_check → ✓ in sync
  • Methodology-scientist Mode B sign-off pending (spawned in this PR session)

Pre-merge sim delta (flag=False vs flag=True)

With USE_SECTOR_COE=False (this PR default): Δscore = 0, Δflag-count = 0 — data-collection only, no production behavior change.

With USE_SECTOR_COE=True (sim override — NOT merged): The per-ticker value_trap_risk_count_with_sector_coe counter in Metadata will show the universe-wide count under sector Ke. Threshold for flipping default: if value_trap_risk_count_with_sector_coe drops from baseline ~176 to ≤ 120 on the first cron (≥ 30% reduction confirming the cyclical-sector FP hypothesis), and methodology-scientist Mode B verifies the Damodaran table values — flip USE_SECTOR_COE = True in a dedicated follow-up PR.

Constraints respected

  • compute_composite() / PHASE3_WEIGHTS untouched ✓
  • manipulation_index.py weights untouched ✓
  • Rule 16: Top-5 rank uses raw composite_score (unchanged) ✓
  • Rule 18: diagnostic fields ship in same PR as the flag, flip deferred ✓
  • Schema triple lockstep: schemas.py + types.ts + snapshot.json all updated ✓
  • CLAUDE.md + AGENTS.md lockstep ✓

https://claude.ai/code/session_01D6NTyJZa5LWHWakbF5dT29


Generated by Claude Code

@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

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

Project Deployment Actions Updated (UTC)
quantrank Ready Ready Preview, Comment May 22, 2026 3:38am

…67 prep)

Add GICS-keyed Damodaran 2019 sector cost-of-equity table + Rule 18
observability surface for the value_trap_risk delta before production flip.

- compute/scoring/cost_of_equity.py — SECTOR_COST_OF_EQUITY dict (11 GICS
  sectors, 6%-12%) sourced from Damodaran 2019 *Investment Valuation* 3rd ed.
  Table 8.4 + Damodaran NYU online betas dataset (January 2025 update).
  Pure function get_cost_of_equity(ticker_sector) → float; fallback 10%.
- compute/config.py — USE_SECTOR_COE: bool = False (OFF by default, Rule 18
  data-collection PR; flip follows after ≥ 1 cron delta-count measurement).
  SCHEMA_VERSION bumped 0.9.7-phase4h.7 → 0.9.8-phase4h.8.
- compute/valuation/ensemble.py — rim_fair_price now receives sector-adjusted
  Ke when USE_SECTOR_COE=True; no behaviour change while flag is False.
- compute/output/schemas.py + frontend/lib/types.ts — 3 new Metadata fields:
  sector_coe_enabled (bool), value_trap_risk_count_without_sector_coe (int|None),
  value_trap_risk_count_with_sector_coe (int|None). Both counts computed every
  cron regardless of flag so the flat-10% vs sector-Ke delta is observable.
- compute/main.py — per-ticker dual check_rim_applicability pass wires the
  counters; Metadata constructor passes sector_coe_enabled + both counts.
- frontend/lib/schema-snapshot.json — regenerated via schema_check --update-snapshot.
- tests/test_scoring/test_cost_of_equity.py — 32 tests: 11-sector dict pin +
  fallback + Hypothesis band property + ordering sanity (Utilities < Energy).
- tests/test_scoring/test_value_trap_risk_sector_coe.py — 8 tests: flat-CoE
  baseline + Utilities FP-reduction case + Energy true-positive preservation +
  USE_SECTOR_COE=False default pin + hard-stale short-circuit.
- tests/test_config.py — schema version pin updated to 0.9.8-phase4h.8.
- CLAUDE.md + AGENTS.md — §Phase status updated per lockstep convention.

Tests: 1059 → 1094 (+35 new, +1 rename). ruff clean. schema_check in sync.
Methodology-scientist Mode B sign-off required before the flip PR.
Part of #67. Behind USE_SECTOR_COE = False default — data-collection PR per
Rule 18. Flip-to-True follow-up after ≥ 1 cron measures delta-flag-count.

https://claude.ai/code/session_01D6NTyJZa5LWHWakbF5dT29
@github-actions
Copy link
Copy Markdown
Contributor

Pre-merge production simulation

Field Value
Duration 1084s
Universe size 502
Schema version 0.9.8-phase4h.8
Compute commit 7f00a268c5a0e13943e20629666c95cee1b9586e
PR-branch output pr-204-compute-output (14-day retention)

Diff vs main

Field Main PR Δ
Universe size 502 502 +0
Schema version 0.9.6-phase4h.6 0.9.8-phase4h.8 ⚠️ bumped

Main baseline: 2026-05-21T23:16:12Z (0.2 days old)

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

Ticker PR rank main rank Δrank PR score main score Δscore
BK 225 228 +3 51.75 51.58 +0.17
DLR 433 432 -1 40.27 40.29 -0.02
BEN 283 283 +0 49.48 49.49 -0.01
D 369 369 +0 44.76 44.77 -0.01
ELV 156 156 +0 54.74 54.73 +0.01
FIX 18 18 +0 66.32 66.33 -0.01
HPQ 304 304 +0 48.01 48.02 -0.01
HST 3 3 +0 72.16 72.17 -0.01
NUE 201 200 -1 52.83 52.84 -0.01
QCOM 33 33 +0 64.01 64.00 +0.01

@dackclup dackclup merged commit 0f552ba into main May 22, 2026
5 checks passed
@dackclup dackclup deleted the claude/sector-coe-damodaran-i67 branch May 22, 2026 04:00
dackclup added a commit that referenced this pull request May 22, 2026
…n-out) (#209)

Worker session worktrees spawned via the Agent tool with
isolation: "worktree" land under .claude/worktrees/. They're
per-session and transient, but `git status` on the main worktree
flagged the directory as untracked after the 3-PR fan-out
(PRs #203 + #204 + #205) left three locked agent worktrees on
disk. The harness stop-hook treats untracked files as a
session-blocking gate, so this entry keeps `git status` clean
without an inline `git update-index --skip-worktree` workaround.

CLAUDE.md + AGENTS.md lockstep — both gain a §Layout row noting
the new ignore + the per-session-transient nature of the dir.

Co-authored-by: Claude <noreply@anthropic.com>
dackclup pushed a commit that referenced this pull request May 24, 2026
…+ cache + path filter

The docstring fix above triggered simulate (path filter matched
compute/scoring/**) which cancelled at 45m02s on the timeout cap.
ci-triage-engineer deep-dive (session 5, 2026-05-24) identified the
recurring pattern across multiple PRs: the QR_SKIP_TIER2 kill-switch
fully wired in compute/scoring/tier2.py:158 was NEVER set in
.github/workflows/pre-merge-prod-sim.yml. Past mitigations:

- PR #165 (1y non-reliance lookback): addressed a specific
  5-year EDGAR backfill spike on one PR but left the universe-wide
  Tier-2 loop intact
- PR-form4-2 (FORM4_FETCH_SKIP=1): saved form4 budget but left
  the Tier-2 (502 tickers × 10-K text fetch + 8-K fetch) running
  unconditionally (20-35m cold-cache cost)

Recurrence tally on last 5 simulate runs:
  PR #165  19m01s  ✅ warm
  PR #204  19m37s  ✅ warm
  PR #222  16m56s  ✅ warm
  PR #224  17m08s  ✅ warm
  PR #230  45m15s  ❌ CANCELLED (cold)

Pattern is structural — when GitHub evicts the warm cache after a
7-day gap without a simulate-triggering PR, the next compute/scoring
PR hits full cold. The 4-part permanent fix:

(a) QR_SKIP_TIER2: "1" added to pre-merge-prod-sim.yml env block
    PRIMARY fix — eliminates the 20-35m cold-cache Tier-2 cost.
    Tradeoff: the non_reliance veto (enabled since PR 4g) is
    suppressed in simulate's diff output. Acceptable because
    simulate is informational-only (per workflow comment line 24);
    non_reliance veto correctness is covered by offline pytest.

(b) compute/cache/edgar_form4 added to BOTH workflows' cache paths
    (compute-rankings.yml save+restore, pre-merge-prod-sim.yml
    restore only). Future-proofs for Phase 4.5e PR 5 when form4
    weight promotion enables FORM4_FETCH_SKIP=0 on simulate.

(c) Path filter widened in pre-merge-prod-sim.yml from
    compute/scoring/** + compute/features/** only to also include:
    - compute/ingest/**     (fundamentals fetcher regressions)
    - compute/valuation/**  (RIM / DCF / ensemble regressions)
    - compute/output/schemas.py  (Pydantic schema bumps)
    - compute/main.py       (orchestrator changes)
    - pyproject.toml        (dep bumps affecting compute)

(d) compute/scoring/tier2.py:154-155 docstring corrected
    The old comment said "_EIGHT_K_DEFENSES_ENABLED=False" but
    PR 4g (2026-05-17) flipped it to True. The non_reliance veto
    IS the active veto path now; updated comment explains the
    veto suppression tradeoff in simulate context.

Expected outcomes:
- Docstring-only PR in compute/scoring/ → simulate completes in
  ~12-15m warm or ~17-20m partial-cold (well under 45m cap)
- Real scoring PR → same budget; composite-score diff unchanged
- Cache eviction (7-day gap) → worst case fundamentals cold ~10-20m
  + no Tier-2 + no form4 = under cap
- Weekly cron UNAFFECTED — QR_SKIP_TIER2 not set there; full
  run continues to populate the warm cache

Companion benefit: this PR's re-pushed simulate is the LIVE
VALIDATION of the fix — first sub-45m simulate on this branch
proves the fix works.

Files touched:
- .github/workflows/pre-merge-prod-sim.yml  (env + cache + paths)
- .github/workflows/compute-rankings.yml    (cache path only)
- compute/scoring/tier2.py                  (stale comment)
- CLAUDE.md + AGENTS.md                     (§Phase status note)

YAML parse-verified; ruff clean; no compute / schema / scoring /
valuation / Python behavior change. CLAUDE.md + AGENTS.md
lockstep satisfied via the §Phase status notes.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4
dackclup pushed a commit that referenced this pull request May 24, 2026
…+ cache + path filter

The docstring fix above triggered simulate (path filter matched
compute/scoring/**) which cancelled at 45m02s on the timeout cap.
ci-triage-engineer deep-dive (session 5, 2026-05-24) identified the
recurring pattern across multiple PRs: the QR_SKIP_TIER2 kill-switch
fully wired in compute/scoring/tier2.py:158 was NEVER set in
.github/workflows/pre-merge-prod-sim.yml. Past mitigations:

- PR #165 (1y non-reliance lookback): addressed a specific
  5-year EDGAR backfill spike on one PR but left the universe-wide
  Tier-2 loop intact
- PR-form4-2 (FORM4_FETCH_SKIP=1): saved form4 budget but left
  the Tier-2 (502 tickers × 10-K text fetch + 8-K fetch) running
  unconditionally (20-35m cold-cache cost)

Recurrence tally on last 5 simulate runs:
  PR #165  19m01s  ✅ warm
  PR #204  19m37s  ✅ warm
  PR #222  16m56s  ✅ warm
  PR #224  17m08s  ✅ warm
  PR #230  45m15s  ❌ CANCELLED (cold)

Pattern is structural — when GitHub evicts the warm cache after a
7-day gap without a simulate-triggering PR, the next compute/scoring
PR hits full cold. The 4-part permanent fix:

(a) QR_SKIP_TIER2: "1" added to pre-merge-prod-sim.yml env block
    PRIMARY fix — eliminates the 20-35m cold-cache Tier-2 cost.
    Tradeoff: the non_reliance veto (enabled since PR 4g) is
    suppressed in simulate's diff output. Acceptable because
    simulate is informational-only (per workflow comment line 24);
    non_reliance veto correctness is covered by offline pytest.

(b) compute/cache/edgar_form4 added to BOTH workflows' cache paths
    (compute-rankings.yml save+restore, pre-merge-prod-sim.yml
    restore only). Future-proofs for Phase 4.5e PR 5 when form4
    weight promotion enables FORM4_FETCH_SKIP=0 on simulate.

(c) Path filter widened in pre-merge-prod-sim.yml from
    compute/scoring/** + compute/features/** only to also include:
    - compute/ingest/**     (fundamentals fetcher regressions)
    - compute/valuation/**  (RIM / DCF / ensemble regressions)
    - compute/output/schemas.py  (Pydantic schema bumps)
    - compute/main.py       (orchestrator changes)
    - pyproject.toml        (dep bumps affecting compute)

(d) compute/scoring/tier2.py:154-155 docstring corrected
    The old comment said "_EIGHT_K_DEFENSES_ENABLED=False" but
    PR 4g (2026-05-17) flipped it to True. The non_reliance veto
    IS the active veto path now; updated comment explains the
    veto suppression tradeoff in simulate context.

Expected outcomes:
- Docstring-only PR in compute/scoring/ → simulate completes in
  ~12-15m warm or ~17-20m partial-cold (well under 45m cap)
- Real scoring PR → same budget; composite-score diff unchanged
- Cache eviction (7-day gap) → worst case fundamentals cold ~10-20m
  + no Tier-2 + no form4 = under cap
- Weekly cron UNAFFECTED — QR_SKIP_TIER2 not set there; full
  run continues to populate the warm cache

Companion benefit: this PR's re-pushed simulate is the LIVE
VALIDATION of the fix — first sub-45m simulate on this branch
proves the fix works.

Files touched:
- .github/workflows/pre-merge-prod-sim.yml  (env + cache + paths)
- .github/workflows/compute-rankings.yml    (cache path only)
- compute/scoring/tier2.py                  (stale comment)
- CLAUDE.md + AGENTS.md                     (§Phase status note)

YAML parse-verified; ruff clean; no compute / schema / scoring /
valuation / Python behavior change. CLAUDE.md + AGENTS.md
lockstep satisfied via the §Phase status notes.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4
dackclup pushed a commit that referenced this pull request May 24, 2026
…+ cache + path filter

The docstring fix above triggered simulate (path filter matched
compute/scoring/**) which cancelled at 45m02s on the timeout cap.
ci-triage-engineer deep-dive (session 5, 2026-05-24) identified the
recurring pattern across multiple PRs: the QR_SKIP_TIER2 kill-switch
fully wired in compute/scoring/tier2.py:158 was NEVER set in
.github/workflows/pre-merge-prod-sim.yml. Past mitigations:

- PR #165 (1y non-reliance lookback): addressed a specific
  5-year EDGAR backfill spike on one PR but left the universe-wide
  Tier-2 loop intact
- PR-form4-2 (FORM4_FETCH_SKIP=1): saved form4 budget but left
  the Tier-2 (502 tickers × 10-K text fetch + 8-K fetch) running
  unconditionally (20-35m cold-cache cost)

Recurrence tally on last 5 simulate runs:
  PR #165  19m01s  ✅ warm
  PR #204  19m37s  ✅ warm
  PR #222  16m56s  ✅ warm
  PR #224  17m08s  ✅ warm
  PR #230  45m15s  ❌ CANCELLED (cold)

Pattern is structural — when GitHub evicts the warm cache after a
7-day gap without a simulate-triggering PR, the next compute/scoring
PR hits full cold. The 4-part permanent fix:

(a) QR_SKIP_TIER2: "1" added to pre-merge-prod-sim.yml env block
    PRIMARY fix — eliminates the 20-35m cold-cache Tier-2 cost.
    Tradeoff: the non_reliance veto (enabled since PR 4g) is
    suppressed in simulate's diff output. Acceptable because
    simulate is informational-only (per workflow comment line 24);
    non_reliance veto correctness is covered by offline pytest.

(b) compute/cache/edgar_form4 added to BOTH workflows' cache paths
    (compute-rankings.yml save+restore, pre-merge-prod-sim.yml
    restore only). Future-proofs for Phase 4.5e PR 5 when form4
    weight promotion enables FORM4_FETCH_SKIP=0 on simulate.

(c) Path filter widened in pre-merge-prod-sim.yml from
    compute/scoring/** + compute/features/** only to also include:
    - compute/ingest/**     (fundamentals fetcher regressions)
    - compute/valuation/**  (RIM / DCF / ensemble regressions)
    - compute/output/schemas.py  (Pydantic schema bumps)
    - compute/main.py       (orchestrator changes)
    - pyproject.toml        (dep bumps affecting compute)

(d) compute/scoring/tier2.py:154-155 docstring corrected
    The old comment said "_EIGHT_K_DEFENSES_ENABLED=False" but
    PR 4g (2026-05-17) flipped it to True. The non_reliance veto
    IS the active veto path now; updated comment explains the
    veto suppression tradeoff in simulate context.

Expected outcomes:
- Docstring-only PR in compute/scoring/ → simulate completes in
  ~12-15m warm or ~17-20m partial-cold (well under 45m cap)
- Real scoring PR → same budget; composite-score diff unchanged
- Cache eviction (7-day gap) → worst case fundamentals cold ~10-20m
  + no Tier-2 + no form4 = under cap
- Weekly cron UNAFFECTED — QR_SKIP_TIER2 not set there; full
  run continues to populate the warm cache

Companion benefit: this PR's re-pushed simulate is the LIVE
VALIDATION of the fix — first sub-45m simulate on this branch
proves the fix works.

Files touched:
- .github/workflows/pre-merge-prod-sim.yml  (env + cache + paths)
- .github/workflows/compute-rankings.yml    (cache path only)
- compute/scoring/tier2.py                  (stale comment)
- CLAUDE.md + AGENTS.md                     (§Phase status note)

YAML parse-verified; ruff clean; no compute / schema / scoring /
valuation / Python behavior change. CLAUDE.md + AGENTS.md
lockstep satisfied via the §Phase status notes.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4
dackclup pushed a commit that referenced this pull request May 24, 2026
…+ cache + path filter

The docstring fix above triggered simulate (path filter matched
compute/scoring/**) which cancelled at 45m02s on the timeout cap.
ci-triage-engineer deep-dive (session 5, 2026-05-24) identified the
recurring pattern across multiple PRs: the QR_SKIP_TIER2 kill-switch
fully wired in compute/scoring/tier2.py:158 was NEVER set in
.github/workflows/pre-merge-prod-sim.yml. Past mitigations:

- PR #165 (1y non-reliance lookback): addressed a specific
  5-year EDGAR backfill spike on one PR but left the universe-wide
  Tier-2 loop intact
- PR-form4-2 (FORM4_FETCH_SKIP=1): saved form4 budget but left
  the Tier-2 (502 tickers × 10-K text fetch + 8-K fetch) running
  unconditionally (20-35m cold-cache cost)

Recurrence tally on last 5 simulate runs:
  PR #165  19m01s  ✅ warm
  PR #204  19m37s  ✅ warm
  PR #222  16m56s  ✅ warm
  PR #224  17m08s  ✅ warm
  PR #230  45m15s  ❌ CANCELLED (cold)

Pattern is structural — when GitHub evicts the warm cache after a
7-day gap without a simulate-triggering PR, the next compute/scoring
PR hits full cold. The 4-part permanent fix:

(a) QR_SKIP_TIER2: "1" added to pre-merge-prod-sim.yml env block
    PRIMARY fix — eliminates the 20-35m cold-cache Tier-2 cost.
    Tradeoff: the non_reliance veto (enabled since PR 4g) is
    suppressed in simulate's diff output. Acceptable because
    simulate is informational-only (per workflow comment line 24);
    non_reliance veto correctness is covered by offline pytest.

(b) compute/cache/edgar_form4 added to BOTH workflows' cache paths
    (compute-rankings.yml save+restore, pre-merge-prod-sim.yml
    restore only). Future-proofs for Phase 4.5e PR 5 when form4
    weight promotion enables FORM4_FETCH_SKIP=0 on simulate.

(c) Path filter widened in pre-merge-prod-sim.yml from
    compute/scoring/** + compute/features/** only to also include:
    - compute/ingest/**     (fundamentals fetcher regressions)
    - compute/valuation/**  (RIM / DCF / ensemble regressions)
    - compute/output/schemas.py  (Pydantic schema bumps)
    - compute/main.py       (orchestrator changes)
    - pyproject.toml        (dep bumps affecting compute)

(d) compute/scoring/tier2.py:154-155 docstring corrected
    The old comment said "_EIGHT_K_DEFENSES_ENABLED=False" but
    PR 4g (2026-05-17) flipped it to True. The non_reliance veto
    IS the active veto path now; updated comment explains the
    veto suppression tradeoff in simulate context.

Expected outcomes:
- Docstring-only PR in compute/scoring/ → simulate completes in
  ~12-15m warm or ~17-20m partial-cold (well under 45m cap)
- Real scoring PR → same budget; composite-score diff unchanged
- Cache eviction (7-day gap) → worst case fundamentals cold ~10-20m
  + no Tier-2 + no form4 = under cap
- Weekly cron UNAFFECTED — QR_SKIP_TIER2 not set there; full
  run continues to populate the warm cache

Companion benefit: this PR's re-pushed simulate is the LIVE
VALIDATION of the fix — first sub-45m simulate on this branch
proves the fix works.

Files touched:
- .github/workflows/pre-merge-prod-sim.yml  (env + cache + paths)
- .github/workflows/compute-rankings.yml    (cache path only)
- compute/scoring/tier2.py                  (stale comment)
- CLAUDE.md + AGENTS.md                     (§Phase status note)

YAML parse-verified; ruff clean; no compute / schema / scoring /
valuation / Python behavior change. CLAUDE.md + AGENTS.md
lockstep satisfied via the §Phase status notes.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4
dackclup added a commit that referenced this pull request May 24, 2026
…nent 45-min simulate fix (#230)

* docs(form4): correct <rule10b5_1> → <aff10b5One> docstring precision per edgar-debugger live-XML verify

The literature-searcher proof-of-life on 2026-05-23 (session 4)
flagged a precision gap: the form4 module docstrings referenced
the colloquial <rule10b5_1> XML tag name (a label, not the actual
SEC EDGAR Ownership XML element). edgar-debugger re-audit on
2026-05-24 (session 5) fetched 3 live AAPL Form 4 XMLs from SEC
EDGAR and confirmed the actual element is <aff10b5One> at
ownershipDocument/aff10b5One — a DOCUMENT-LEVEL boolean (one per
filing, covering ALL transactions in that Form 4), NOT a
per-transaction attribute.

Updated 7 references to use the canonical name:

5 code spots:
- compute/scoring/form4_insider.py:20 (module docstring header)
- compute/scoring/form4_insider.py:82 (§Footnote resolution)
- compute/scoring/form4_insider.py:587 (inline _form4_to_transactions
  comment)
- compute/scoring/form4_signals.py:129 (module docstring)
- compute/output/schemas.py:200 (extreme_estimate_majority_count
  comment context)

2 doc spots:
- CLAUDE.md §Phase status PR #224 entry "Access-path caveat" block
  (line 1352-1359)
- AGENTS.md §Phase + version state PR #224 entry "Access-path
  caveat for cross-tool agents" block (line 947-951)

Added §Footnote resolution architectural-gap note in
compute/scoring/form4_insider.py: a filer who checks
<aff10b5One>true at the document level but does NOT include the
per-transaction footnote text (valid — the checkbox IS the formal
affirmative defense, footnotes are supplemental) will slip past
the current footnote-text fallback path and enter the
opportunistic cohort incorrectly. Deferred to a follow-up PR that
direct-parses the raw Form 4 XML for ownershipDocument/aff10b5One
(edgartools doesn't expose it, so the implementation walks the
filing text once per Ownership object). Tracked as the
architectural follow-up after PR 4-eq.

edgartools 5.31.5 verified as PyPI latest by edgar-debugger
2026-05-24 — no newer release adds a parse path for <aff10b5One>.

detect_10b5_1_plan regex set (6 patterns) confirmed complete vs
real-world footnote text — pattern "10b5-1" alone covers every
common variant including Rule 10b5-1(c) subsection cites. No
additions needed.

Docstring-only PR — no compute / scoring / valuation / behavior
change. schema_check clean (comment-only edit on schemas.py),
ruff clean, tests unchanged at 1168+. CLAUDE.md + AGENTS.md
lockstep satisfied via §Phase status in-flight notes.

Companion stale-header reword: marked PR #229 "Security WARN
cleanup" → "merged via PR #229" in CLAUDE.md + AGENTS.md.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4

* ci(simulate): permanent 45-min cancellation fix — wire QR_SKIP_TIER2 + cache + path filter

The docstring fix above triggered simulate (path filter matched
compute/scoring/**) which cancelled at 45m02s on the timeout cap.
ci-triage-engineer deep-dive (session 5, 2026-05-24) identified the
recurring pattern across multiple PRs: the QR_SKIP_TIER2 kill-switch
fully wired in compute/scoring/tier2.py:158 was NEVER set in
.github/workflows/pre-merge-prod-sim.yml. Past mitigations:

- PR #165 (1y non-reliance lookback): addressed a specific
  5-year EDGAR backfill spike on one PR but left the universe-wide
  Tier-2 loop intact
- PR-form4-2 (FORM4_FETCH_SKIP=1): saved form4 budget but left
  the Tier-2 (502 tickers × 10-K text fetch + 8-K fetch) running
  unconditionally (20-35m cold-cache cost)

Recurrence tally on last 5 simulate runs:
  PR #165  19m01s  ✅ warm
  PR #204  19m37s  ✅ warm
  PR #222  16m56s  ✅ warm
  PR #224  17m08s  ✅ warm
  PR #230  45m15s  ❌ CANCELLED (cold)

Pattern is structural — when GitHub evicts the warm cache after a
7-day gap without a simulate-triggering PR, the next compute/scoring
PR hits full cold. The 4-part permanent fix:

(a) QR_SKIP_TIER2: "1" added to pre-merge-prod-sim.yml env block
    PRIMARY fix — eliminates the 20-35m cold-cache Tier-2 cost.
    Tradeoff: the non_reliance veto (enabled since PR 4g) is
    suppressed in simulate's diff output. Acceptable because
    simulate is informational-only (per workflow comment line 24);
    non_reliance veto correctness is covered by offline pytest.

(b) compute/cache/edgar_form4 added to BOTH workflows' cache paths
    (compute-rankings.yml save+restore, pre-merge-prod-sim.yml
    restore only). Future-proofs for Phase 4.5e PR 5 when form4
    weight promotion enables FORM4_FETCH_SKIP=0 on simulate.

(c) Path filter widened in pre-merge-prod-sim.yml from
    compute/scoring/** + compute/features/** only to also include:
    - compute/ingest/**     (fundamentals fetcher regressions)
    - compute/valuation/**  (RIM / DCF / ensemble regressions)
    - compute/output/schemas.py  (Pydantic schema bumps)
    - compute/main.py       (orchestrator changes)
    - pyproject.toml        (dep bumps affecting compute)

(d) compute/scoring/tier2.py:154-155 docstring corrected
    The old comment said "_EIGHT_K_DEFENSES_ENABLED=False" but
    PR 4g (2026-05-17) flipped it to True. The non_reliance veto
    IS the active veto path now; updated comment explains the
    veto suppression tradeoff in simulate context.

Expected outcomes:
- Docstring-only PR in compute/scoring/ → simulate completes in
  ~12-15m warm or ~17-20m partial-cold (well under 45m cap)
- Real scoring PR → same budget; composite-score diff unchanged
- Cache eviction (7-day gap) → worst case fundamentals cold ~10-20m
  + no Tier-2 + no form4 = under cap
- Weekly cron UNAFFECTED — QR_SKIP_TIER2 not set there; full
  run continues to populate the warm cache

Companion benefit: this PR's re-pushed simulate is the LIVE
VALIDATION of the fix — first sub-45m simulate on this branch
proves the fix works.

Files touched:
- .github/workflows/pre-merge-prod-sim.yml  (env + cache + paths)
- .github/workflows/compute-rankings.yml    (cache path only)
- compute/scoring/tier2.py                  (stale comment)
- CLAUDE.md + AGENTS.md                     (§Phase status note)

YAML parse-verified; ruff clean; no compute / schema / scoring /
valuation / Python behavior change. CLAUDE.md + AGENTS.md
lockstep satisfied via the §Phase status notes.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4

* docs(workflow): rebase-discipline § + parallel-PR §Phase status collision §Gotcha

Closes the recurring "merge ไม่ได้ หลังแก้แล้วกลับมาเป็นอีก" pattern
surfaced 2026-05-24: PR #230 hit mergeable_state=dirty twice in a
single session — first when PR #229 (security WARN cleanup) landed
on main mid-iteration, then again when PR #232 + PR #233 (LedgerCraft
A1 + A2) landed before Mark-Ready.

ROOT CAUSE: every PR adds a "**X in flight (this PR)**" entry at
the SAME insertion line in CLAUDE.md §Phase status + AGENTS.md
§Phase + version state (just before "**Next deliverables**" /
"## Claude-Code-specific tooling"). Two parallel PRs both touch
that single line → `git merge` cannot auto-resolve two distinct
adds at the same line → `mergeable_state: dirty`. The conflict
is BENIGN (both adds are legitimate; resolution is always "keep
both in chronological order") but git can't auto-detect that.

THREE COMPANION EDITS bundled here:

(a) CLAUDE.md §Conventions — new bullet "Rebase onto origin/main
    before flipping any PR Draft → Ready". Includes the operational
    `git fetch origin main && git rebase origin/main` recipe + the
    "keep both entries in chronological order (older PR first, your
    entry second)" resolution discipline.

(b) CLAUDE.md §Gotchas — new "Parallel-PR §Phase status collision
    pattern" entry recording the recurring symptom (PR #230 ×2 on
    2026-05-24 against PR #229 + PR #232 + PR #233) + the
    local-mitigation workflow + a forward-looking structural
    follow-up note: consider moving the "in flight" sub-block to
    a side file (PHASE_STATUS_INFLIGHT.md) that's append-only-
    per-PR; on merge a housekeeping commit moves the entry into
    CLAUDE.md proper. Not yet adopted — trade-off is extra
    discipline at merge time.

(c) AGENTS.md mirror — cross-tool equivalent for Copilot / Cursor /
    Devin contributors, emphasising the rebase-before-Mark-Ready
    discipline applies to ALL agent runtimes (not Claude-Code-
    specific).

ALSO bundled as a 3rd in-flight entry in the §Phase status block
documenting THIS PR's scope expansion (Parts 1+2+3 now).

No infrastructure / CI / behavior change — pure doc discipline.
Future contributors who hit the same conflict find the §Gotchas
entry + the §Conventions recipe + AGENTS.md note and resolve in
seconds, instead of re-discovering the pattern.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4

* ci(simulate): Part 4 — wire QR_SKIP_FUNDAMENTALS to actually close the 45m cap

Part 2's QR_SKIP_TIER2 fix was necessary but NOT sufficient. Re-pushing
the rebased branch on 2026-05-24 07:06 UTC still hit simulate cancelled
at 45m15s.

ci-triage-engineer deep-dive #2 identified the gap: compute/main.py
has THREE independent SEC EDGAR ThreadPoolExecutor loops, and Part 2
only killed ONE (Step 4b — Tier-2):

  Step 2 (compute/main.py:717) — fundamentals snapshot (502 tickers ×
    SEC companyfacts XBRL); NOT skipped by QR_SKIP_TIER2
  Step 3 (compute/main.py:785) — annual fundamentals history; same
  Step 4b (compute/main.py:972) — Tier-2 / 8-K; killed by Part 2

Cold-cache cost of Steps 2+3 alone is 25-50m per CLAUDE.md §Gotchas —
enough to fill the entire 45m simulate budget. Worse, the cache freshness
gate at compute/ingest/fundamentals.py:917 (_is_fresh()) checks the
filed_date INSIDE each cached parquet against FUNDAMENTALS_REFETCH_DAYS=45.
On a partial cache hit, any ticker with a > 45d-old most-recent filing
still triggers a live EDGAR round-trip.

Part 4 fix — QR_SKIP_FUNDAMENTALS=1 escape hatch:

(a) compute/ingest/fundamentals.py:fetch_fundamentals — env-var check
    added at the top of the function (BEFORE _require_identity()), so
    the cached snapshot is returned unconditionally when set, without
    a freshness check OR an EDGAR identity requirement. Falls through
    to live fetch if no cache exists (cold runner has nothing to read).

(b) compute/ingest/fundamentals.py:fetch_fundamentals_history — mirror
    pattern: returns the cached annual parquet without the 180d age
    check when QR_SKIP_FUNDAMENTALS=1 AND force_refresh=False. Same
    fallthrough.

(c) .github/workflows/pre-merge-prod-sim.yml — adds QR_SKIP_FUNDAMENTALS:
    "1" to the env block alongside QR_SKIP_TIER2 and FORM4_FETCH_SKIP.
    Inline comment explains the root cause + the safety reasoning.

SAFE for simulate because:
- The workflow's purpose is composite-score-DIFF detection vs main's
  COMMITTED rankings.json
- Both sides (PR branch + main) were produced from the same upstream
  fundamentals input (the cache the weekly cron wrote)
- Using that cache without re-fetch is the CORRECT input for the diff
- It's NOT a quality compromise — re-fetching live would give the PR
  branch newer data than main has, making the diff confusing rather
  than accurate

Weekly cron (compute-rankings.yml) does NOT set QR_SKIP_FUNDAMENTALS —
full live fetch still runs there and populates the warm cache for
future simulate restores.

Expected outcome: simulate completes in 8-12m on a cache-hit restore
(no live SEC fetch in any of Steps 2, 3, or 4b). Live validation:
re-push after this commit → simulate green under 25m on this PR.

Tests verified: tests/test_features/test_fundamentals.py +
tests/test_workflow_cache_coverage.py = 24 pass / 6 skipped. ruff
clean. YAML parse-verified. schema_check in sync.

CLAUDE.md §Phase status + AGENTS.md §Phase + version state both
updated with the Part 4 explanation. Future contributors editing
pre-merge-prod-sim.yml should now keep all THREE escape-hatch
env vars set together: FORM4_FETCH_SKIP + QR_SKIP_TIER2 +
QR_SKIP_FUNDAMENTALS.

https://claude.ai/code/session_01JwntEE4PNAXSMkZxRA9BB4

---------

Co-authored-by: Claude <noreply@anthropic.com>
dackclup pushed a commit that referenced this pull request May 24, 2026
Addresses release-captain BLOCKED-ON-PRE-FLIGHT blocker #3 from
the v1.3.0 tag attempt — PHASE_STATUS.md / SKILL.md / WORKFLOW.md
were 3 days + ~32 PRs stale (last touched PR #171, 2026-05-21).
Brings all three docs current to main HEAD 1ff6c11 so the
release-captain ladder can re-attempt cleanly.

PHASE_STATUS.md
- Header date 2026-05-21 → 2026-05-24
- Current state table: schema 0.9.4-phase4h.4 → 0.10.2-phase4.5e;
  defense layer 27 → 32 emitted flags; subagent inventory 14 → 18
  (named tier roster — 4 opus / 14 sonnet); skill inventory 42 → 43;
  production run a16c8879015748 (cron #3 2026-05-23); release-
  tag line annotated with v1.3.0 target pending
- Recently-merged block: refreshed to PR #170 → PR #237 (~36
  entries with commit shas, chronological), drops the stale PR
  #147-#169 block
- Next-deliverables list: 5 items updated — Phase 4.5e PR 5
  cluster weight promotion / Issue #67 sector-CoE flip / v1.3.0
  release tag gate / Phase 4i.1-4j.1-4k.1 factor integrations /
  Phase 5 ML meta-learner
- Open issues line: drops resolved #155 (closed via PR #160),
  refreshes #41 (15 open advisories, zero exploitability on
  static-export), refreshes #67 (data-collection merged PR #204)

SKILL.md
- Schema-version table: 7 new rows added in reverse-chronological
  order (matches existing 0.9.x convention) for `0.9.5` → `0.9.6`
  → `0.9.7` → `0.9.8` → `0.10.0` → `0.10.1` → `0.10.2` covering
  PRs #180/#181/#183/#204/#205/#222/#224. Each row carries PR #
  + 1-line scope + backward-compat note + literature anchor.

WORKFLOW.md
- Phase Overview table 4.5 row marked ✅ DONE 2026-05-23 + 10b5-1
  filter scope note
- SEC Filing Roadmap Form 4 row flipped "planned" → "active" with
  4-PR ladder reference (#167/#205/#222/#224 + 100% coverage on
  cron #3)
- Phase 4.5e task list — 5 items flipped `[ ]` → `[x]` with per-PR
  commits + methodology-scientist Mode B verdicts inline + Aboody
  et al. 2010 §3.2 weight-promotion gate noted
- Phase 4.5 Acceptance Criteria — all 9 items flipped to `[x]`
  with completion evidence (cron #3 / methodology verdicts /
  PR refs)
- Phase 4.5f tag item — flipped `[ ]` → `[x]` (`v1.2.0-phase4.5`
  cut 2026-05-17 at 6d414a9)

PHASE_STATUS_INFLIGHT.md
- Append new "(this PR)" entry under In-flight section per the
  PR #237 side-file convention. Documents the doc-refresh scope
  + cross-refs to release-captain blockers 1/2/4/5 still pending.

Lockstep
- PR #237's PHASE_STATUS_INFLIGHT.md side-file pattern handles
  the §Conventions "ship with every PR" rule for this doc-only PR
- No CLAUDE.md / AGENTS.md substantive change required — the
  in-flight entry lives in the side-file per the new convention
- No compute / schema / scoring / valuation / frontend / Python /
  TypeScript code change
- Unblocks v1.3.0 tag blocker #3; blockers 1 (wrong-branch),
  2 (pyproject.toml 0.3.0 → 1.3.0), 4 (production output 1 cron
  cycle behind code), and 5 (release notes draft scope) still
  need resolution before tag cut
dackclup pushed a commit that referenced this pull request May 24, 2026
Addresses release-captain BLOCKED-ON-PRE-FLIGHT blocker #3 from
the v1.3.0 tag attempt — PHASE_STATUS.md / SKILL.md / WORKFLOW.md
were 3 days + ~32 PRs stale (last touched PR #171, 2026-05-21).
Brings all three docs current to main HEAD 1ff6c11 so the
release-captain ladder can re-attempt cleanly.

PHASE_STATUS.md
- Header date 2026-05-21 → 2026-05-24
- Current state table: schema 0.9.4-phase4h.4 → 0.10.2-phase4.5e;
  defense layer 27 → 32 emitted flags; subagent inventory 14 → 18
  (named tier roster — 4 opus / 14 sonnet); skill inventory 42 → 43;
  production run a16c8879015748 (cron #3 2026-05-23); release-
  tag line annotated with v1.3.0 target pending
- Recently-merged block: refreshed to PR #170 → PR #237 (~36
  entries with commit shas, chronological), drops the stale PR
  #147-#169 block
- Next-deliverables list: 5 items updated — Phase 4.5e PR 5
  cluster weight promotion / Issue #67 sector-CoE flip / v1.3.0
  release tag gate / Phase 4i.1-4j.1-4k.1 factor integrations /
  Phase 5 ML meta-learner
- Open issues line: drops resolved #155 (closed via PR #160),
  refreshes #41 (15 open advisories, zero exploitability on
  static-export), refreshes #67 (data-collection merged PR #204)

SKILL.md
- Schema-version table: 7 new rows added in reverse-chronological
  order (matches existing 0.9.x convention) for `0.9.5` → `0.9.6`
  → `0.9.7` → `0.9.8` → `0.10.0` → `0.10.1` → `0.10.2` covering
  PRs #180/#181/#183/#204/#205/#222/#224. Each row carries PR #
  + 1-line scope + backward-compat note + literature anchor.

WORKFLOW.md
- Phase Overview table 4.5 row marked ✅ DONE 2026-05-23 + 10b5-1
  filter scope note
- SEC Filing Roadmap Form 4 row flipped "planned" → "active" with
  4-PR ladder reference (#167/#205/#222/#224 + 100% coverage on
  cron #3)
- Phase 4.5e task list — 5 items flipped `[ ]` → `[x]` with per-PR
  commits + methodology-scientist Mode B verdicts inline + Aboody
  et al. 2010 §3.2 weight-promotion gate noted
- Phase 4.5 Acceptance Criteria — all 9 items flipped to `[x]`
  with completion evidence (cron #3 / methodology verdicts /
  PR refs)
- Phase 4.5f tag item — flipped `[ ]` → `[x]` (`v1.2.0-phase4.5`
  cut 2026-05-17 at 6d414a9)

PHASE_STATUS_INFLIGHT.md
- Append new "(this PR)" entry under In-flight section per the
  PR #237 side-file convention. Documents the doc-refresh scope
  + cross-refs to release-captain blockers 1/2/4/5 still pending.

Lockstep
- PR #237's PHASE_STATUS_INFLIGHT.md side-file pattern handles
  the §Conventions "ship with every PR" rule for this doc-only PR
- No CLAUDE.md / AGENTS.md substantive change required — the
  in-flight entry lives in the side-file per the new convention
- No compute / schema / scoring / valuation / frontend / Python /
  TypeScript code change
- Unblocks v1.3.0 tag blocker #3; blockers 1 (wrong-branch),
  2 (pyproject.toml 0.3.0 → 1.3.0), 4 (production output 1 cron
  cycle behind code), and 5 (release notes draft scope) still
  need resolution before tag cut
dackclup added a commit that referenced this pull request May 24, 2026
…239)

Addresses release-captain BLOCKED-ON-PRE-FLIGHT blocker #3 from
the v1.3.0 tag attempt — PHASE_STATUS.md / SKILL.md / WORKFLOW.md
were 3 days + ~32 PRs stale (last touched PR #171, 2026-05-21).
Brings all three docs current to main HEAD 1ff6c11 so the
release-captain ladder can re-attempt cleanly.

PHASE_STATUS.md
- Header date 2026-05-21 → 2026-05-24
- Current state table: schema 0.9.4-phase4h.4 → 0.10.2-phase4.5e;
  defense layer 27 → 32 emitted flags; subagent inventory 14 → 18
  (named tier roster — 4 opus / 14 sonnet); skill inventory 42 → 43;
  production run a16c8879015748 (cron #3 2026-05-23); release-
  tag line annotated with v1.3.0 target pending
- Recently-merged block: refreshed to PR #170 → PR #237 (~36
  entries with commit shas, chronological), drops the stale PR
  #147-#169 block
- Next-deliverables list: 5 items updated — Phase 4.5e PR 5
  cluster weight promotion / Issue #67 sector-CoE flip / v1.3.0
  release tag gate / Phase 4i.1-4j.1-4k.1 factor integrations /
  Phase 5 ML meta-learner
- Open issues line: drops resolved #155 (closed via PR #160),
  refreshes #41 (15 open advisories, zero exploitability on
  static-export), refreshes #67 (data-collection merged PR #204)

SKILL.md
- Schema-version table: 7 new rows added in reverse-chronological
  order (matches existing 0.9.x convention) for `0.9.5` → `0.9.6`
  → `0.9.7` → `0.9.8` → `0.10.0` → `0.10.1` → `0.10.2` covering
  PRs #180/#181/#183/#204/#205/#222/#224. Each row carries PR #
  + 1-line scope + backward-compat note + literature anchor.

WORKFLOW.md
- Phase Overview table 4.5 row marked ✅ DONE 2026-05-23 + 10b5-1
  filter scope note
- SEC Filing Roadmap Form 4 row flipped "planned" → "active" with
  4-PR ladder reference (#167/#205/#222/#224 + 100% coverage on
  cron #3)
- Phase 4.5e task list — 5 items flipped `[ ]` → `[x]` with per-PR
  commits + methodology-scientist Mode B verdicts inline + Aboody
  et al. 2010 §3.2 weight-promotion gate noted
- Phase 4.5 Acceptance Criteria — all 9 items flipped to `[x]`
  with completion evidence (cron #3 / methodology verdicts /
  PR refs)
- Phase 4.5f tag item — flipped `[ ]` → `[x]` (`v1.2.0-phase4.5`
  cut 2026-05-17 at 6d414a9)

PHASE_STATUS_INFLIGHT.md
- Append new "(this PR)" entry under In-flight section per the
  PR #237 side-file convention. Documents the doc-refresh scope
  + cross-refs to release-captain blockers 1/2/4/5 still pending.

Lockstep
- PR #237's PHASE_STATUS_INFLIGHT.md side-file pattern handles
  the §Conventions "ship with every PR" rule for this doc-only PR
- No CLAUDE.md / AGENTS.md substantive change required — the
  in-flight entry lives in the side-file per the new convention
- No compute / schema / scoring / valuation / frontend / Python /
  TypeScript code change
- Unblocks v1.3.0 tag blocker #3; blockers 1 (wrong-branch),
  2 (pyproject.toml 0.3.0 → 1.3.0), 4 (production output 1 cron
  cycle behind code), and 5 (release notes draft scope) still
  need resolution before tag cut

Co-authored-by: Claude <noreply@anthropic.com>
dackclup added a commit that referenced this pull request May 28, 2026
… open-issues list (#291)

Closes deferred substance items from PR #286 + PR #290 that opted to keep
scope tight under the `pointer-to-CLAUDE.md` delegation pattern.

Changes (1 file, AGENTS.md):

- Line 377 — Production-verified run pointer bumped from cron #51
  (`b1588b2a`, 2026-05-14) → cron #69 (`233117ac`, 13m 16s warm-cache,
  2026-05-28). Cross-tool agents now have the current known-good
  baseline; verified by defense-layer-auditor + stock-detail-auditor
  on schema `0.10.7-phase4.6`.

- Lines 380-382 — "Open Phase 4+ issues" list refreshed from 4 → 11
  entries. Removed stale qualifier on #67 (PR #204 already landed
  data-collection; #67 is Phase 4 follow-up not Phase 5+). Added 7
  missing issues: #115 (JKP license) · #130 (Q3 cohort) · #137
  (9arm-skills license deadline 2026-06-17) · #150 (Phase 2-3 epic) ·
  #287 (FORM4 revert + durable timeout) · #288 (GOOG/GOOGL XBRL) ·
  #289 (NVR DQIC false positive). Added zero-exploitability context
  to #41.

Doc-only PR. No compute / schema / scoring / valuation / frontend /
Python / TS code change. CLAUDE.md substance untouched this PR
(PR #286 + PR #290 already bumped it); this PR closes the AGENTS.md
side of the lockstep. PHASE_STATUS_INFLIGHT.md side-file satisfies
§Conventions lockstep per PR #237 convention.

Co-authored-by: Claude <noreply@anthropic.com>
dackclup added a commit that referenced this pull request May 28, 2026
…ector Ke) (#294)

Closes Issue #67 — flips `USE_SECTOR_COE` False → True per methodology-
scientist Mode B verdict 2026-05-28 + cron #69 empirical confirmation.
Consumes the data-collection module landed in PR #204 (2026-05-22).

Empirical gate satisfied (cron #69 metadata):
- value_trap_risk_count_without_sector_coe: 132 (flat 10% Ke baseline)
- value_trap_risk_count_with_sector_coe:    109 (per-GICS Ke 6-12%)
- Delta: -23 tickers, -17.4% reduction
- Absolute landing point (109) inside original PR #204 target band [80, 110]
- 38%-vs-17% spread vs original projection explained by baseline drift
  (PR #166 Issue #11 RIM equity-denominator fix already removed ~44 FP
  from the pre-PR-#204 ~176 baseline; proportional difference is baseline
  drift, not signal failure)

Methodology-scientist verdict (Damodaran 2019 Ch. 8.4 §"Industry Beta"
+ NYU January 2025 betas dataset): APPROVED. Lower-Ke sectors
(Utilities/REIT/Staples) gain leniency; higher-Ke sectors (Tech/Energy)
gain stringency; net is negative because S&P 500 has more market-cap
exposure to defensives than the flat 10% baseline assumed.

Changes (5 files):

- compute/config.py:86 — USE_SECTOR_COE flipped True. Docstring rewritten
  to cite methodology verdict + cron #69 numbers + load-bearing-default
  semantics (flipping back requires separate methodology-scientist verdict).
- compute/scoring/cost_of_equity.py:73-79 — module docstring updated to
  reflect post-flip state.
- tests/test_scoring/test_value_trap_risk_sector_coe.py:134-156 —
  test renamed (`_flat_coe_path_unchanged_...` → `_use_sector_coe_default_
  post_issue_67_flip`); assertion inverted from `not config.USE_SECTOR_COE`
  → `config.USE_SECTOR_COE`. Would BREAK on flip otherwise.
- tests/test_config.py — new `test_use_sector_coe_flipped_true` pin
  added per methodology-scientist Q5b recommendation (Phase 2.4/2.5
  convention: pin every config-value flip in tests/test_config.py).

Impact (first methodology-affecting flip post-v1.4.0):
- value_trap_risk universe count: 132 → 109 (per cron #69 dual-counter)
- 23 newly-not-flagged tickers regain RIM as a contributing valuation method
- Composite ranks shift for the cyclical-vs-defensive cohort
  (magnitude bounded by per-pillar weight + median-of-6 aggregation)
- Rule 16 + Top-5 rotation mechanics UNCHANGED (only Ke parameter shift)
- No schema change (Metadata dual-counter fields already shipped PR #204)
- No new defense flag (defense layer count unchanged at 33 declared)

Verification:
- `ruff check .` — PASS
- `python -m pytest tests/test_config.py tests/test_scoring/test_value_trap_risk_sector_coe.py tests/test_scoring/test_cost_of_equity.py tests/test_valuation/ -q -m "not network"` — 256 passed
- `python -m pytest tests/ -q -m "not network" --ignore=tests/test_validation` — 1207 passed
- methodology-scientist Mode B verdict — APPROVED (LITERATURE-ANCHORED)

Deferred follow-ups (NOT in this PR):
- Per-sector delta instrumentation (Metadata.value_trap_risk_delta_by_sector)
  — separate PR; universe-wide count sufficient for THIS flip per the
  gate contract
- Q3 2026-08-19 cohort audit — natural review point for per-sector shape
  verification (~12 weekly crons of post-flip data by then; already in
  the issue #130 pre-prep checklist)

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