docs(phase4.5c): mark 4.5c wave ✅ DONE + bump next deliverable to 4.5d#96
Merged
Conversation
Phase 4.5c Roychowdhury REM shipped via PR #95. Production verified on run #49 (commit `65097703`, warm-cache 6m25s — all 9 cache layers populated). ## What shipped `rem_suspect` annotate via per-sector OLS regressions on 3 abnormal proxies (CFO, Production, Discretionary Expenses). Module `compute/scoring/rem.py` (~420 LOC, pure-numpy via `np.linalg.lstsq`, no sklearn/statsmodels dep). 14 offline tests including golden numerical test recovering known-DGP coefficients. ## Production verification | Metric | Value | |---|---| | Fire rate | **16 / 502 (3.2%)** — within H0-to-correlation expected 2.8-7% | | Tickers fired | SMCI · WAT · ADM · TSN · HRL · STLD · FSLR · JBL · COHR · LII · LDOS · POOL · OMC · WY · TECH · RVTY | | Orthogonality check | NVDA / PLTR (Beneish-veto fired) **NOT** in REM list — confirms 4.5c captures real-manipulation signal orthogonal to accrual targets | | Real-world coverage | ADM (2024 SEC investigation) · SMCI (2024 investigation) · TSN / HRL (periodic scrutiny) · FSLR (solar channel-stuffing history) | ## End-state defense layer - Active vetoes: **7** (unchanged — 4.5c is annotate-only) - Annotate flags: 7 → **8** (+ `rem_suspect`) - Reason taxonomy: 31 → **32** - **Defense layer 9 → 14 after 4.5a + 4.5b + 4.5c** No schema delta — `rem_suspect` is a string in existing `valuation_warnings: list[str]`. `SCHEMA_VERSION` stays `0.7.1-phase4g`. ## Triple-doc lockstep changes | File | Change | |---|---| | `CLAUDE.md` | "Next deliverable" 4.5c → 4.5d. Defense layer "9 → 13 after 4.5a+4.5b" → "9 → 14 after 4.5a+4.5b+4.5c". 4.5c results + ticker list + orthogonality note inserted between 4.5b and the post-completion roadmap. | | `PHASE_STATUS.md` | Phase 4.5 row updated with 4.5c production stats. §4.5c header flipped to ✅ DONE 2026-05-17 with results table + orthogonality note. Original plan text preserved below for audit. | | `WORKFLOW.md` | §4.5c checkboxes [ ] → [x] with PR-number / LOC / test-count / production-verification citations + golden-test reference. | ## Next deliverable **Phase 4.5d — earnings-quality time-series + Burgstahler-Dichev kink at zero** (~180 LOC, ~7 days): - `m_score_deteriorating` annotate — Δ(Beneish M-score) > +0.5 over trailing 3y (manipulation gathering steam) - `loss_avoidance_pattern` annotate — NI ∈ [0, $5M] OR EPS ∈ [0, $0.05] for 3+ consecutive years (Burgstahler-Dichev 1997 kink) ## Audit trail (post-v1.0 doc PRs) | PR | Purpose | |---|---| | #81 | 4g ✅ DONE | | #86 | Phase 4.5 roadmap added | | #87 | "PR 4b next" → "§3 polish next" (was wrong) | | #88 | "§3 polish next" → "Phase-5 blocked" (was wrong) | | #92 | 4.5a wave ✅ DONE | | #94 | 4.5b wave ✅ DONE | | **this PR** | 4.5c wave ✅ DONE | ## Verification - No code changes; docs only - `grep "Next deliverable.*4.5c"` returns 0 hits (all moved to 4.5d) - `grep "9 → 14"` appears in CLAUDE.md (new defense layer count) - `grep "rem_suspect"` appears in PHASE_STATUS.md + WORKFLOW.md active-flags references https://claude.ai/code/session_015649aRyi2bvciQYZVNACd2
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
5 tasks
dackclup
added a commit
that referenced
this pull request
May 17, 2026
…als_momentum_high + loss_avoidance_pattern) (#97) Phase 4.5 manipulation-defense cluster sub-PR 6 of 6 (the last purely-defense sub before 4.5f composite + UI bundling). Two annotate-only flags derived from the per-ticker fundamentals history (annual XBRL). ## What's new ### `accruals_momentum_high` — Δ(TATA) over 3y > +0.05 - TATA = (NetIncome − OperatingCashFlow) / TotalAssets, the Sloan 1996 / Beneish 1999 accruals backbone. - Threshold +0.05 ≈ Beneish 1999 ΔM > +0.5 via the β_TATA = 4.679 coefficient (ΔM ≈ 4.679 × ΔTATA → ΔM > 0.5 ⇔ ΔTATA > 0.107). We use 0.05 — more sensitive since TATA alone captures less than the full 8-ratio signal; standard practitioner adaptation when shortening to one ratio. - Catches manipulation **gathering steam** — the snapshot-only Sloan + Beneish flags miss the trajectory entirely. **Practical note on naming**: PR #86 plan §4.5d called this `m_score_deteriorating` (full Δ(Beneish M-score) > +0.5). We chose TATA momentum as a practical equivalent: building 3 historical 8-ratio Beneish snapshots from XBRL history would require expanding the annual-history coverage of 6+ supplementary ratios (DSRI / GMI / AQI / etc.) that often have gaps for prior years. TATA is the single Beneish component that's a level rather than a ratio-of-ratios, and Sloan 1996 established it as the standalone accruals signal — so this is a clean shortening, not a weakening. ### `loss_avoidance_pattern` — Burgstahler-Dichev 1997 kink at zero - Fires when **3+ consecutive fiscal years** of tiny-positive earnings: NI ∈ [\$0, \$5M] **OR** EPS ∈ [\$0.00, \$0.05]. - Per-share band catches the high-share-count case where NI alone is above the absolute floor but per-share is still tiny. - Empirical kink-at-zero signature of managers shading reported earnings just enough to clear the loss / loss-threshold. ## Architecture | File | Change | |---|---| | `compute/scoring/earnings_quality.py` | **NEW** ~250 LOC — `check_accruals_momentum` + `check_loss_avoidance` + history-walk helpers (`_annual_values`, `_value_at_year`). Pure pandas; no new deps. | | `compute/main.py` | + 2 import lines + 2 per-ticker annotate appends in the Step-8 loop, slotting after `rem_suspect`. | | `tests/test_scoring/test_earnings_quality.py` | **NEW** ~225 LOC — 14 offline tests covering both flags (fires / doesn't fire / improves / threshold pins / EPS-band fallback / negative-NI rejection / large-NI rejection / multi-year streak / streak break / constants sanity). | ## Defense-layer end-state (after this PR ships) - Active vetoes: **7** (unchanged — 4.5d is annotate-only) - Annotate flags: 8 → **10** (+ `accruals_momentum_high`, `loss_avoidance_pattern`) - Reason taxonomy: 32 → **34** stable identifiers - Total defense layers: **9 → 16** after 4.5a + 4.5b + 4.5c + 4.5d No schema delta — both flags are strings in existing `valuation_warnings: list[str]`. `SCHEMA_VERSION` stays `0.7.1-phase4g`. ## Backward compat - Both check functions take `(snap, history)` — no caller changes elsewhere. Missing inputs (snap=None, no history, insufficient years) cleanly return fired=False. - No new EDGAR fetches — both flags read from existing fundamentals + fundamentals_history caches. ## Verification ladder - ✅ `ruff check .` — clean - ✅ `pytest tests/test_scoring/test_earnings_quality.py` — **14 passed** - ✅ `pytest tests/ -m "not network"` — **831 passed** (was 817; +14 new) - ✅ schema_check — N/A (no schema delta) - ⏳ Production verification deferred. Expected fire rates on S&P 500: - `accruals_momentum_high` ~3-8% (~15-40 tickers) — H0 from Δ(TATA) > 0.05 base rate - `loss_avoidance_pattern` ~1-3% (~5-15 tickers) — S&P 500 firms rarely report tiny-positive earnings for 3+ years (mega-cap distribution); base rate higher on small-caps per Burgstahler- Dichev 1997 original sample ## Sibling sub-PRs (Phase 4.5 cluster) - ✅ **4.5a wave** (PRs #89 / #90 / #91 + #92 docs) - ✅ **4.5b** (PR #93 + #94 docs) - ✅ **4.5c** (PR #95 + #96 docs) - **4.5d (this PR)** — earnings-quality time-series - ⬜ **4.5e** — Form 4 insider clustering (~420 LOC, ~12 days — needs new SEC Form 4 parser) - ⬜ **4.5f** — `manipulation_index` composite + composite-score penalty + UI pillar card + README Honest Limitations + schema bump → **v1.2.0-phase4.5** https://claude.ai/code/session_015649aRyi2bvciQYZVNACd2 Co-authored-by: Claude <noreply@anthropic.com>
Merged
5 tasks
dackclup
added a commit
that referenced
this pull request
May 17, 2026
…e Google Fonts dependency (#98) PR #96 CI run #249 (docs(phase4.5c) merge to main) failed the Frontend (build) job with: NextFontError: Failed to fetch \`IBM Plex Sans\` from Google Fonts. at app/layout.tsx — IBM_Plex_Sans({subsets: ['latin'], weight: ['400','500','600','700'], display: 'swap', variable: '--font-ibm-plex-sans'}) A transient Google Fonts network blip during CI build broke `next/font/google`. The very next CI run (PR #97 4.5d) went green again — so the failure was a flaky external service, not a code regression. Production was unaffected (compute-rankings.yml runs independently of ci.yml). This commit closes the door on the failure mode for good by moving all 3 fonts from `next/font/google` to `@fontsource/*` npm packages (SIL Open Font License, OFL — free to redistribute): - `@fontsource/ibm-plex-sans` @ ^5.2.8 - `@fontsource/jetbrains-mono` @ ^5.2.8 - `@fontsource/instrument-serif` @ ^5.2.8 @fontsource bundles the woff2 files into node_modules, so the Next.js build never touches an external host. ## Changes | File | Change | |---|---| | `frontend/package.json` | + 3 `@fontsource/*` dependencies (and corresponding `package-lock.json` entries) | | `frontend/app/layout.tsx` | Drop `next/font/google` imports (`IBM_Plex_Sans`, `JetBrains_Mono`, `Instrument_Serif`). Drop the `className={...variable}` stack on `<html>`. CSS variables now declared in globals.css. | | `frontend/app/globals.css` | + 9 `@import '@fontsource/*'` lines (Plex 400/500/600/700, JetBrains 400/500/600, Instrument 400 normal + italic — exactly matches the prior next/font config). + 3 new CSS variable declarations (`--font-ibm-plex-sans`, `--font-jetbrains-mono`, `--font-instrument-serif`) so existing `--font-sans/mono/serif` chains keep resolving the same fallback stack. | ## Backward compat - All existing consumers (Tailwind utility classes, `StockLogo.tsx`'s inline `fontFamily: 'var(--font-mono)'`, globals.css base-style font-family rules) continue to read the same CSS variable names — no rename ripple-through. - Font-display behavior: @fontsource defaults to `font-display: swap` which matches the previous `display: 'swap'` config. - Bundle size impact: +~140KB woff2 files (vs the previous Google Fonts CDN fetch). Negligible on the static export; the resilience win outweighs the size cost. ## Verification - ✅ `next build` — 506 static pages built; no NextFontError - ✅ `tsc --noEmit` — clean - ✅ No external font fetch in build logs (verified via build output) - ✅ Build offline-capable from now on ## Why @fontsource not vendored woff2 files - @fontsource packages are the OFL-compliant official source - Updates ship via `npm update` if the IBM Plex / JetBrains / Instrument projects release new revisions - No license/attribution risk vs hand-vendoring binary files ## Not in this PR - npm audit warnings (5 vulns, 1 critical) are in the existing Next.js dep tree per issue #41 (Next.js 14 → 16 bump). Not introduced by this PR; tracked separately. https://claude.ai/code/session_015649aRyi2bvciQYZVNACd2 Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 4.5c Roychowdhury REM shipped via PR #95. Production verified on run #49 (commit
65097703, warm-cache 6m25s).Production stats
End-state defense layer
rem_suspect)No schema delta.
SCHEMA_VERSIONstays0.7.1-phase4g.Triple-doc lockstep
CLAUDE.mdPHASE_STATUS.mdWORKFLOW.mdNext deliverable
Phase 4.5d — earnings-quality time-series + Burgstahler-Dichev kink (~180 LOC, ~7 days):
m_score_deterioratingannotate — Δ(Beneish M-score) > +0.5 over trailing 3yloss_avoidance_patternannotate — NI ∈ [0, $5M] OR EPS ∈ [0, $0.05] for 3+ consecutive years (Burgstahler-Dichev 1997)Test plan
grep "Next deliverable.*4.5c"returns 0 hitsgrep "9 → 14"in CLAUDE.mdgrep "rem_suspect"in active referenceshttps://claude.ai/code/session_015649aRyi2bvciQYZVNACd2
Generated by Claude Code