Skip to content

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

Merged
dackclup merged 6 commits into
mainfrom
claude/form4-neg-guard
May 29, 2026
Merged

feat(scoring): Phase 4.5e PR 6 — Form-4 10b5-1 negation guard (residual footgun #1)#303
dackclup merged 6 commits into
mainfrom
claude/form4-neg-guard

Conversation

@dackclup
Copy link
Copy Markdown
Owner

Summary

  • Phase 4.5e PR 6 — closes the long-standing residual of footgun feat(phase-0): project scaffolding #1 from compute/scoring/form4_signals.py module docstring + 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 = pure engineering of the approved mitigation.
  • Architecture — post-detector wrapper on edgar.ownership.core.detect_10b5_1_plan: when detector returns True, re-scan the resolved footnote text for negation phrases (terminated / cancelled / no / previously / former / etc.) within ±5 word tokens of the 10b5-1 mention. On match, downgrade True → False and bump a thread-safe module-level counter.
  • Schema 0.10.10 → 0.10.11-phase4.6 PATCH bump for the new Metadata.form4_negation_guard_downgrade_count: int | None field (Rule 18 observability). Triple lockstep — Pydantic ↔ TypeScript ↔ snapshot all touched + regenerated; schema_check verified clean.

What changed

File Change
compute/scoring/form4_insider.py +11-token _NEGATION_PATTERNS frozenset + compiled bidirectional _NEGATION_REGEX (±5 word-token window, case-insensitive, accepts both 10b5-1 and 10b-5-1 spellings) + _has_negation() helper + thread-safe reset/get/_bump_negation_downgrade_count() lifecycle + modified _detect_10b5_1_on_transaction to apply guard; module docstring §"FP caveat" updated
compute/scoring/form4_signals.py Footguns §1 docstring refreshed to note PR 6 mitigation landed; bias-direction reversal explained
compute/main.py Reset counter immediately before form4 ThreadPoolExecutor block; read on success path; alias to form4_negation_guard_downgrade_count local; pass to Metadata(...) constructor
compute/output/schemas.py New Metadata.form4_negation_guard_downgrade_count: int | None = None with full provenance docstring
frontend/lib/types.ts Mirror field declaration
frontend/lib/schema-snapshot.json Regenerated via --update-snapshot
compute/config.py SCHEMA_VERSION = "0.10.11-phase4.6"
tests/test_config.py Schema pin bumped with PR 6 rationale docstring
tests/test_scoring/test_form4_negation_guard.py NEW — 33 tests written by test-engineer (sonnet)
CLAUDE.md + AGENTS.md + PHASE_STATUS_INFLIGHT.md + SKILL.md + CONTEXT.md §Phase status / §Phase + version state / INFLIGHT entry / schema-version table row / live-snapshot pointer all updated

Rule 18 observability (new field)

Metadata.form4_negation_guard_downgrade_count — universe-wide count of True → False downgrades applied by the post-detector negation guard during Form-4 cache build. Gates the Q3 2026-08-19 cohort-acceptance check (issue #130) for the INSIDER_SELL_CLUSTER_WEIGHT 5.0 → 7.0 promotion alongside form4_rule10b5_one_excluded_count.

None semantics mirrors form4_wall_clock_seconds:

  • None when FORM4_FETCH_SKIP=1 (loop didn't run) OR outer try/except fired before end marker
  • int (zero is valid) on the happy path — warm-cache cron reports 0 (no detector ran this cycle); cold-cache cron populates the real cohort number

Methodology (skipped per pre-approval)

PR 4-eq Mode B verdict (2026-05-23) documented in form4_signals.py footgun §1 already pre-approved this hardening. PR 6 = pure engineering implementation. The Q3 audit reads the new metric from the surfaced metadata field — no fresh methodology 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 delta << 1% (most 10b5-1 disclosures are affirmative, not negated)
  • Bias direction REVERSED: pre-PR-6 was conservative (over-excluded legit opportunistic trades from cluster cohort); PR 6 returns terminated/former-plan footnotes to the cohort

Test plan

  • ruff check . — clean
  • python -m compute.output.schema_check — in sync (snapshot regenerated)
  • python -m pytest tests/ -m "not network"1366 → 1399 (+33) · zero regressions · 103s
  • python -m pytest tests/test_scoring/test_form4_negation_guard.py -v — 33/33 pass · 1.91s
  • Sanity matrix probe (14 cases) — all expected True/False match before test-engineer write
  • Pre-Mark-Ready gate: quantrank-reviewer (opus) + schema-sentinel (sonnet) + phase-coordinator Mode B (sonnet) parallel spawn
  • Vercel preview deploys cleanly (UI surface unchanged; schema-snapshot snapshot only)
  • simulate workflow green under 195m ceiling
  • Post-merge: cron Run feat(ui): Loss Chance % heuristic chip (PR 4e) #72+ populates form4_negation_guard_downgrade_count (cold-cache run; warm-cache will report 0)

Footgun caveats preserved

  • ~10-15% routine-but-not-10b5-1 contamination per Jagolinzer 2009 (insiders without 10b5-1 plans but with 5y calendar-fixed trade timing) remains as the deferred follow-up. Cohen 2008 full routine-vs-opportunistic classifier needs the 5y per-insider lookback that the current 180d cache cannot satisfy without a structural change.
  • Post-earnings-window seasonal clustering (footgun chore(phase-0): mark complete #2) and joint-filer + stale-title (footgun feat(phase-1): universe + prices + momentum stub #3) — separate PR 7 + PR 8 ladder slots if/when scope authorized.

Hard constraints honored

  • No composite score change · No Rule 16 / Top-5 violation
  • Schema triple lockstep verified clean
  • Annotate-only contract (PR 6 hardens an existing input filter; no new flag)
  • Defense layer 33 declared boolean flags UNCHANGED (no new flag added)
  • Cluster + C-suite weights UNCHANGED (5.0 / 3.0; promotion still gated on Q3 2026-08-19 cohort audit)
  • manipulation_index weights UNCHANGED

🤖 Generated with Claude Code


Generated by Claude Code

…al 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>
@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 4:14pm

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>
@dackclup dackclup marked this pull request as ready for review May 29, 2026 00:21
@dackclup dackclup merged commit 847c21b into main May 29, 2026
4 of 5 checks passed
@dackclup dackclup deleted the claude/form4-neg-guard branch May 29, 2026 00:21
dackclup pushed a commit that referenced this pull request May 29, 2026
Full .md consistency sweep (docs-reviewer audit, all facts re-verified
against compute/config.py + filesystem before applying). 13 MUST-FIX + 6
SHOULD-FIX text reconciles — no contradictions remain across the canonical
docs:

- Schema 0.10.10 -> 0.10.11-phase4.6 current-state in CLAUDE.md / PHASE_STATUS.md
  / CONTEXT.md / WORKFLOW.md / SKILL.md (PR #303 merged; was still showing the
  pre-bump value + a stale "in flight" framing).
- Agent count 18 -> 19 (+ 4-opus/14-sonnet -> 15-sonnet, 6 -> 7 coordination
  flows) in PHASE_STATUS.md subagent row + CONTEXT.md roster/mapping rows
  (PR #304 added expert-user-explorer; PR #307 added Flow 7).
- Skill count 42/38 -> 45 in .claude/agents/README.md + .claude/skills/README.md.
- Cleared stale "in flight (this PR)" blocks for PRs now merged (CLAUDE.md
  + AGENTS.md + PHASE_STATUS.md); moved #303-#310 into the Recently-merged
  lists; flipped 5 PHASE_STATUS_INFLIGHT.md headers (in flight -> merged + SHA).
- METHODOLOGY.md annotate line: fixed the math (was "7+5+23"=35; the proposed
  "7+5+26"=38 was also wrong) to the authoritative "33 declared = 7 vetoes +
  26 annotates; 5 numerical guards separate".

LIVE current-state facts only; dated/PR-attributed HISTORICAL narrative left
intact (append-only record). ruff check . clean (whole-repo, per the lesson).
Docs-only; no code/schema change.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
dackclup added a commit that referenced this pull request May 29, 2026
)

Full .md cross-doc consistency sweep after the 6-PR session (#303-#310): schema 0.10.10→0.10.11 current-state, agent roster 18→19 (4-opus/15-sonnet, 7 flows), skill count →45, cleared stale "in flight" blocks + flipped 5 INFLIGHT headers to merged, fixed METHODOLOGY annotate math (7 vetoes + 26 annotates = 33 declared). All facts re-verified against config.py + filesystem; LIVE current-state only, historical narrative untouched. Docs-only.

https://claude.ai/code/session_0144kHrCYNaamMPH57b7xdM7
dackclup pushed a commit that referenced this pull request Jun 2, 2026
…In-flight entries

- CLAUDE.md §Phase status: new "Recently merged" block (PR #331#373,
  2026-05-31→2026-06-02, 43 PRs covering $impeccable full-frontend pass +
  financial-engineer subagent + PR-B country/exchange chips + Commit A
  MUST-FIX sweep); old #303#330 block demoted to new "Earlier" section;
  "In flight" entries (PR-B + financial-engineer) replaced with "(none)"
- PHASE_STATUS.md: date 2026-05-31 → 2026-06-02; same recently-merged /
  earlier restructure with compact one-liners for all 43 PRs
- AGENTS.md §Phase + version state: "In flight — PR-B" bullet replaced
  with "all PRs through #373 merged" pointer to CLAUDE.md
- PHASE_STATUS_INFLIGHT.md: Commit B entry appended

No compute / schema / scoring / valuation / frontend code change.

https://claude.ai/code/session_01ELWfJoJp5kMje2j4zoUCQh
dackclup added a commit that referenced this pull request Jun 2, 2026
…ntries (#380)

Drains 43 PRs (#331#373) into CLAUDE.md + PHASE_STATUS.md Recently-merged blocks. Old #303#330 block demoted to Earlier. In-flight entries for PR-B and financial-engineer replaced with (none). AGENTS.md In-flight bullet updated. PHASE_STATUS_INFLIGHT.md Commit B entry appended. No compute/schema/frontend change.
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