Skip to content

feat(scoring): Issue #67 follow-up — per-sector value_trap_risk delta instrumentation#300

Merged
dackclup merged 1 commit into
mainfrom
claude/per-sector-value-trap-delta
May 28, 2026
Merged

feat(scoring): Issue #67 follow-up — per-sector value_trap_risk delta instrumentation#300
dackclup merged 1 commit into
mainfrom
claude/per-sector-value-trap-delta

Conversation

@dackclup
Copy link
Copy Markdown
Owner

Summary

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 Investment Valuation 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:

Sector group Ke band Expected delta Tickers
Lower-Ke ~6-8% POSITIVE (sector dropped flags after flip) Utilities, Real Estate, Consumer Staples
Higher-Ke ~10-12% NEGATIVE (sector gained flags) Information Technology, Energy
Neutral ~9-11% near zero The other 6 GICS sectors

Cron #69 + Run #71 confirmed the aggregate: 132 → 109 (−23 tickers, −17.4%). This PR breaks the −23 down by sector so the next cron's metadata.json shows whether the cohort shift matches Damodaran prediction shape OR has unexpected outliers (methodology-scientist re-review trigger).

Scope (10 files, additive only)

File Change
compute/output/schemas.py New value_trap_risk_delta_by_sector field with full docstring (verdict + Damodaran 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 / per-sector increment co-located with scalar bump / delta computation in Metadata constructor)
tests/test_config.py Schema pin bump + docstring rewrite
tests/test_output/test_value_trap_delta_by_sector_schema.py (NEW) 2 active GREEN schema-contract tests mirroring 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.

Pre-push 3-reviewer gate

Reviewer Verdict
schema-sentinel (sonnet) ✅ PASS — Metadata 51 → 52 fields, triple aligned, PATCH bump correct, snapshot alphabetical ordering held
test-engineer (sonnet) ✅ GREEN — 2/2 new tests pass, suite 1367 → 1369 (+2), 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 — SKILL.md / PHASE_STATUS.md schema-table tops still at 0.10.8)

Opus reviewer explicitly ran grep -rn "0.10.9" tests/ to catch the PR #298-class test-pin mistake — found 4 stale refs in test_wall_clock_schema.py but ALL are non-runtime (fixture strings + docstring mentions); Metadata.version is plain str, no validator. All tests still pass.

Empirical validation (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()) == value_trap_risk_count_without_sector_coe - value_trap_risk_count_with_sector_coe

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 (a) post-merge cron shows sector breakdown contradicting Damodaran prediction OR (b) Q3 audit reads ≥ 6 crons of data and per-sector decay pattern needs interpretation.

Test plan

  • ruff check . — All checks passed
  • python -m compute.output.schema_check — in sync at 0.10.10-phase4.6
  • pytest tests/test_config.py — 11/11 PASS (pin held)
  • python -m pytest tests/test_output/test_value_trap_delta_by_sector_schema.py — 2/2 PASS
  • Full offline suite via test-engineer: 1367 → 1369 (+2 NEW; 0 regressions)
  • Vercel preview build — gated on this PR's CI
  • Cron Run feat(ui): Loss Chance % heuristic chip (PR 4e) #72 — empirical Damodaran-shape validation

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 12:56pm

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>
… 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 dackclup force-pushed the claude/per-sector-value-trap-delta branch from 61daa0d to 44dcf9c Compare May 28, 2026 12:55
@dackclup dackclup marked this pull request as ready for review May 28, 2026 12:55
@dackclup dackclup merged commit 5fa9a44 into main May 28, 2026
4 of 5 checks passed
@dackclup dackclup deleted the claude/per-sector-value-trap-delta branch May 28, 2026 12:55
dackclup added a commit that referenced this pull request May 28, 2026
PR #293 (95e638b, merged 2026-05-28 05:20 UTC) retired the Site-2
output-level data-quality ceiling per methodology-scientist Mode B
Option C verdict (Penman 2013 §7.4 + Damodaran 2019 Ch. 18 + Huber
1981 §1.4). PR #293 deleted the call site at compute/valuation/
ensemble.py Step 4.5 but RETAINED the 2 helper functions as dead code
for "one cycle hold" — explicit retention guard pinned the deferred
state. This PR closes that contract.

Cron Run #71 (368dccd, 2026-05-28 08:44 UTC) confirmed clean
operation:

  - NVR fair-price section renders correctly (was empty pre-PR-#293)
  - valuation_output_anomalous cohort dropped 5 → 4 (NVR removed)
  - No regression on Site-1 veto cohort (MTB / CPT / MRNA / HBAN
    per PR #265 writer-parity emit still firing)

Scope (8 files, net −41 lines):

  - compute/valuation/ensemble.py — REMOVED 2 dead functions:
    - _has_corrupt_input(methods) -> bool (13 lines)
    - _data_quality_corrupt_result(methods) -> EnsembleResult (43 lines)
    Step 4.5 comment block at lines 449-479 refreshed: "kept as dead
    code for one cycle" → "removed in this PR after cron Run #71
    confirmed clean".

  - tests/test_valuation/test_ensemble.py — REMOVED 2 imports +
    3 active tests that exercised the removed functions:
    - test_data_quality_sanity_guard_triggers_on_extreme_method_value
    - test_data_quality_guard_boundary_exactly_at_ceiling
    - test_data_quality_guard_skipped_methods_dont_trigger
    The one-cycle retention guard test_L2_dead_code_functions_still_
    callable_after_site2_deletion REPLACED with new
    test_L2_dead_code_functions_removed_post_one_cycle that pins the
    removal via `not hasattr(_ensemble, "_has_corrupt_input")` +
    `not hasattr(_ensemble, "_data_quality_corrupt_result")` — so a
    future accidental re-introduction surfaces as a clear "Issue #289
    retirement reverted" failure. The 2 surviving tests
    (test_site2_data_quality_guard_retired_post_issue_289 +
    test_L3_site2_ceiling_not_invoked_for_high_share_price_ticker)
    keep verifying POST-RETIREMENT invariants on the NVR cohort.

  - compute/config.py:127-148 — FAIR_PRICE_DATA_QUALITY_CEILING
    STAYS ACTIVE (Site-1 in compute/scoring/risk_overlay.py shares
    the constant). Comment block updated to note "dead-code helpers
    REMOVED in the PR #293 follow-up after cron Run #71 confirmed
    clean".

  - compute/valuation/applicability.py:65-78 — reference to
    _data_quality_corrupt_result replaced with reference to the
    writer-parity emit at compute/main.py on the Site-1 veto cohort.

  - tests/test_scoring/test_risk_overlay.py:520-528 —
    test_D4_data_quality_corruption_fires_at_boundary_strict
    docstring updated: "Mirrors _has_corrupt_input's strict
    inequality" → "Site-1 (here) is the canonical input-corruption
    guard. Site-2 was retired per Issue #289 Option C; the strict `>`
    invariant lives ONLY here now." Test logic UNCHANGED.

  - CLAUDE.md §Phase status — drained "(in flight this PR — Issue
    #67 follow-up)" wording (PR #300 merged); added in-flight note
    for this PR's Site-2 dead-code removal.

  - AGENTS.md open-issues #289 entry — appended "dead-code helpers
    REMOVED in the PR #293 follow-up after cron Run #71 confirmed
    the retirement empirically".

  - 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 (no schema touched)
  - python -m pytest tests/test_valuation/test_ensemble.py
    tests/test_scoring/test_risk_overlay.py tests/test_config.py -q
                                          120 PASS (was 123 — net -3
                                          as planned: 3 deleted tests
                                          + 1 modified L2 guard)
  - grep -rn "_has_corrupt_input|_data_quality_corrupt_result"
    compute/ tests/                       only legitimate references
                                          remain (retirement comments
                                          + new removal-guard
                                          assertion strings)

Hard constraints honored:

  - No scoring formula change · No Rule 16 / Top-5 violation
  - Schema version UNCHANGED at 0.10.10-phase4.6
  - Site-1 input-corruption veto path UNCHANGED
  - FAIR_PRICE_DATA_QUALITY_CEILING constant retained (Site-1 shared)
  - Writer-parity valuation_output_anomalous emit at compute/main.py
    UNCHANGED — UI explanation chip continues for Site-1 cohort
  - test_L3 PR #293 end-to-end guard UNCHANGED — keeps verifying NVR
    cohort produces non-null median

Closes the PR #293 retirement contract.

https://claude.ai/code/session_01AGU8d6pm4u2fQQ5cebg9qa

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Pre-merge production simulation

Field Value
Duration 5038s
Universe size 501
Schema version 0.10.10-phase4.6
Compute commit 9a8b439a412bb9f9364d5f8037521d7f6ebd52d2
PR-branch output pr-300-compute-output (14-day retention)

Diff vs main

Field Main PR Δ
Universe size 502 501 -1
Schema version 0.10.9-phase4.6 0.10.10-phase4.6 ⚠️ bumped

Main baseline: 2026-05-28T08:44:52Z (0.2 days old)

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

Ticker PR rank main rank Δrank PR score main score Δscore
V 47 232 +185 62.26 51.62 +10.64
MO 272 127 -145 49.99 56.56 -6.57
CRM 201 307 +106 52.75 47.94 +4.81
BRK-B 206 309 +103 52.52 47.75 +4.77
ICE 421 405 -16 40.81 42.31 -1.50
SPGI 403 379 -24 42.32 43.81 -1.49
DLTR 39 45 +6 63.55 62.24 +1.31
CEG 395 376 -19 42.83 44.07 -1.24
CFG 336 310 -26 46.44 47.61 -1.17
HRL 456 463 +7 38.36 37.37 +0.99

Tickers in main only (1): PEG

dackclup pushed a commit that referenced this pull request May 28, 2026
dackclup added a commit that referenced this pull request May 29, 2026
…al footgun #1) (#303)

* feat(scoring): Phase 4.5e PR 6 — Form-4 10b5-1 negation guard (residual footgun #1)

Closes the residual of footgun #1 from `compute/scoring/form4_signals.py`
module docstring + the PR 4-eq Mode B verdict (2026-05-23, pre-approved
"harden detect_10b5_1_plan with a negation guard against FP matches on
phrases like '10b5-1 plan terminated'"). PR 6 implements the engineering
of the approved mitigation.

**Architecture** — post-detector wrapper:
- `compute/scoring/form4_insider.py` gains `_NEGATION_PATTERNS` (11 tokens)
  + `_NEGATION_REGEX` (compiled bidirectional regex; case-insensitive;
  accepts both `10b5-1` and `10b-5-1` spellings; ±5 word-token window)
  + `_has_negation()` helper + thread-safe module-level counter
- `_detect_10b5_1_on_transaction` modified: detector returns True →
  check `_has_negation(resolved_text)` → on match return False and bump
  the counter; detector returns False or None → pass through unchanged.
  Guard never fabricates a positive signal (only downgrades True → False).

**Schema** `0.10.10 → 0.10.11-phase4.6` (PATCH — additive Metadata field).
Triple touched: Pydantic + TypeScript + snapshot regenerated; verified
clean via `python -m compute.output.schema_check`.

**Rule 18 observability**: new
`Metadata.form4_negation_guard_downgrade_count: int | None`. Counter
is module-level + thread-safe (`threading.Lock` around an int) for the
`EDGAR_MAX_WORKERS=8` parallel Form-4 fetch loop. `compute/main.py`
resets before the `ThreadPoolExecutor` block and reads on the success
path. `None` semantics mirrors `form4_wall_clock_seconds`: None when
`FORM4_FETCH_SKIP=1` OR outer try/except fired.

**Methodology** — pre-approved (no fresh consultation needed). Expected
delta firing-rate per Cohen 2008 §III + Jagolinzer 2009 §3.2:
`insider_sell_cluster` +5% to +10% relative on a universe-baseline cron
(absolute << 1%; most 10b5-1 disclosures are affirmative). Bias direction
reversed: was conservative (over-excluded legit opportunistic trades);
now closer to ground truth (terminated + former plans no longer fire).

**Tests** — 33 new tests in `tests/test_scoring/test_form4_negation_guard.py`:
13 pattern coverage (parametrized) + 5 negative paths + 2 ±5-token window
boundary + 2 spelling variants + 5 `_detect_10b5_1_on_transaction`
integration with mocked footnotes_dict + 2 thread-safety (incl. 100
concurrent bumps / 8 workers) + 2 Hypothesis properties (idempotence +
monotonicity) + 2 manifest pins. Suite 1366 → 1399; zero regressions;
ruff clean. `tests/test_config.py` schema pin bumped with PR 6 rationale.

**Defense layer unchanged** at 33 declared boolean flags (PR hardens an
existing input filter; no new flag). Cluster + C-suite weights UNCHANGED
(5.0 / 3.0; promotion to 7.0 still gated on Q3 2026-08-19 audit per
PR 4-eq verdict).

**Closes**: residual of footgun #1 (Form-4 10b5-1 contamination) from
`compute/scoring/form4_signals.py` module docstring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(scoring): PR 6 review WARN-2 + WARN-3 cleanup

quantrank-reviewer (opus) flagged 3 non-blocking WARNs on the
initial PR 6 commit. 2 addressed inline; 1 deferred to follow-up.

- WARN-2 FIXED: `_NEGATION_REGEX` BEFORE branch gains an inline
  NOTE block explaining `no` is intentionally BEFORE-only (post-
  mention "10b5-1 plan, no shares sold" reads as affirmative
  disclosure + non-negation use of `no` → FP risk too high). AFTER
  branch carries a cross-reference comment so the asymmetry is
  self-documenting at the regex.
- WARN-3 FIXED: new
  `test_M3_negation_patterns_each_appear_in_compiled_regex`
  drift-detector — every token in `_NEGATION_PATTERNS` must appear
  somewhere in `_NEGATION_REGEX.pattern` source string. Catches
  drift in both directions (add to frozenset without updating
  regex; remove from regex without updating frozenset). Carve-outs
  handle the `cancelled` ↔ `canceled` collapse to `cancell?ed` and
  the multi-word `not in effect` `\s+` split. Tests 33 → 34;
  full suite 1399 → 1400.
- WARN-1 DEFERRED in `PHASE_STATUS_INFLIGHT.md` PR #303 entry:
  `_NEGATION_REGEX` anchor matches only 2 of upstream
  `detect_10b5_1_plan`'s 6 substrings. Bias direction remains safe
  (over-includes legit trades in cohort, never under-excludes).
  Fix path noted; gated on cron Run #72+ empirical data showing
  whether the gap is material.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(phase-status): bump for PR #303 — schema 0.10.10 → 0.10.11 + Recently merged refresh (PR #300/#301/#302)

* docs(workflow): bump session-start pointer to schema 0.10.10 on main + PR #303 in-flight (Phase 4.5e PR 6 negation guard 0.10.11)

* test(form4): PR 6 cross-reference smoke tests in test_form4_insider.py — quality-gate Section D

* ci: re-trigger simulate on 0a1fc86 (prior run cancelled by concurrency supersede)

---------

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