feat: name leftover-map unexplained share on leftover pairs (v2.12.27) - #537
feat: name leftover-map unexplained share on leftover pairs (v2.12.27)#537seonghobae wants to merge 9 commits into
Conversation
Persist unexplained leftover share s = U_c² / R̃² of centered leftover on leftover post–criterion pairs (ADR 0183). Two-axis reconstruction R̂_c and centered leftover U_c stay internal. A rank-1 cell stores share 0 even when mean(R) ≠ 0. Fallback omits the share. Do not persist leftover-map unexplained leftover U or reconstruction R̂.
|
Merge contract for exact head
Requested reviewers stay empty: the only org collaborator is the author, so review cannot be requested from this account. Independent of leftover stacks already in review, including #535 leftover-map unexplained leftover. Devin’s grand-mean-in-U note on #535 is addressed here by centering leftover before naming share, not by patching that head. |
|
Warning Review limit reachedNext included review available in 1 minute. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (24)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| filled_sq = float(filled * filled) | ||
| unexplained_sq = float(unexplained * unexplained) | ||
| if filled_sq > _LEFTOVER_SINGULAR_FLOOR: | ||
| return float(unexplained_sq / filled_sq) | ||
| if unexplained_sq <= _LEFTOVER_SINGULAR_FLOOR: | ||
| return 0.0 | ||
| return None |
There was a problem hiding this comment.
📝 Info: Per-cell share can exceed 1
_unexplained_leftover_share computes a per-cell ratio U_c²/R̃², not an aggregate. Two-axis reconstruction can overshoot one cell with opposite sign, so unexplained can exceed |filled| and s can exceed 1. The check constraint only rejects negatives and the frontend accepts any finite non-negative value, so a badge above 1.00 can render. Consistent with ADR 0183, which only forbids negative or non-finite shares.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Acknowledged — per-cell share can exceed 1 when two-axis reconstruction overshoots a cell. ADR 0183 only forbids a negative or non-finite share; the check constraint and badge follow that. Not clamping to 1.00 in this increment (that would hide honest overshoot). Author will not self-approve.
|
write-probe cycle 2026-08-24T08:04KST |
|
Cycle 2026-08-24T08:15KST write retry.
|
|
Independent next leftover gap is now open as #539 (explained share |
Drop the unused oidcReturnUrl import (pre-existing on main) and accept an optional AdminPanel accessToken so the login screen typechecks. Do not change leftover unexplained-share arithmetic.
|
Pushed tsc repair |
| def _unexplained_leftover_share(filled: float, reconstruction: float) -> float | None: | ||
| """Return ``s = U_c² / R̃²`` when both terms are finite; otherwise omit. | ||
|
|
||
| ``filled`` is centered leftover ``R̃ = R − center``. Gabriel | ||
| reconstruction recovers that centered matrix, not raw residual | ||
| ``R``. Using raw ``R`` in the denominator (or subtracting | ||
| reconstruction from ``R``) leaves the grand mean inside the named | ||
| leftover and makes a fully reconstructed rank-1 cell look | ||
| unexplained whenever ``center ≠ 0``. | ||
| """ | ||
| if not np.isfinite(filled) or not np.isfinite(reconstruction): | ||
| return None | ||
| unexplained = filled - reconstruction | ||
| if not np.isfinite(unexplained): | ||
| return None | ||
| filled_sq = float(filled * filled) | ||
| unexplained_sq = float(unexplained * unexplained) | ||
| if filled_sq > _LEFTOVER_SINGULAR_FLOOR: | ||
| return float(unexplained_sq / filled_sq) | ||
| if unexplained_sq <= _LEFTOVER_SINGULAR_FLOOR: | ||
| return 0.0 | ||
| return None |
There was a problem hiding this comment.
📝 Info: Centering-first share matches two-axis reconstruction
In leftover_pairs.py, reconstruction is the inner product of the padded two-axis coordinates, the rank-2 approximation of the same centered matrix the SVD factorized, and _unexplained_leftover_share uses centered leftover in both numerator and denominator. Rank-1/rank-2 maps reconstruct exactly, so share is 0 even when mean(R) != 0; only rank>=3 yields nonzero share. The floor guard returns 0.0 only when the unexplained part is also negligible, otherwise omits, avoiding unstable ratios. The result is always non-negative, satisfying the DB check.
Was this helpful? React with 👍 or 👎 to provide feedback.
# Conflicts: # AGENTS.md # ARCHITECTURE.md # CHANGELOG.md # CLAUDE.md # backend/tests/test_api.py # docs/adr/0003-fast-mlsirm-report-integration.md # frontend/package.json # lineageweave/leftover_pairs.py # pyproject.toml # tests/test_leftover_pairs.py # tests/test_period_report.py # tests/test_schema.py # uv.lock
…ve 2) # Conflicts: # AGENTS.md # ARCHITECTURE.md # CHANGELOG.md # CLAUDE.md # backend/app/report_ingestion.py # backend/tests/test_api.py # docs/adr/0003-fast-mlsirm-report-integration.md # docs/adr/0048-persist-lsirm-leftover-pairs.md # docs/adr/0049-leftover-pair-report-ui.md # frontend/package.json # frontend/src/App.test.tsx # frontend/src/api.ts # frontend/src/components/LeftoverPairList.tsx # frontend/src/i18n.test.ts # frontend/src/i18n.ts # lineageweave/leftover_pairs.py # pyproject.toml # scripts/seed_demo_data.py # tests/test_leftover_pairs.py # tests/test_period_report.py # tests/test_schema.py # uv.lock
| "Read leftover map rank {rank}, observed Y {observed}, expected E {expected}, and unexplained share {value} after IRT main effects, then open this post.": | ||
| "IRT 주효과 이후 잔여 맵 랭크 {rank}, 관측 Y {observed}, 기대 E {expected}, 설명되지 않은 몫 {value}를 읽은 다음, 이 글을 여세요.", | ||
| "Leftover map rank 0 means no leftover structure after IRT main effects. Read observed Y {observed}, expected E {expected}, and unexplained share {value}, then open this post.": | ||
| "IRT 주효과 이후 잔여 맵 랭크 0은 잔여 구조가 없음을 뜻합니다. 관측 Y {observed}, 기대 E {expected}, 설명되지 않은 몫 {value}를 읽은 다음, 이 글을 여세요.", |
There was a problem hiding this comment.
📝 Info: Unused combined rank + share i18n strings
Two combined next-action strings adding unexplained share {value} are defined and unit-tested across all five locales, but LeftoverPairList never uses them. Its branch chain selects LEFTOVER_MAP_UNEXPLAINED_SHARE_ACTION on its own and falls through to rank/observed strings that omit share. The combined strings are dead translation data.
Was this helpful? React with 👍 or 👎 to provide feedback.
| filled_sq = float(filled * filled) | ||
| unexplained_sq = float(unexplained * unexplained) | ||
| if filled_sq > _LEFTOVER_SINGULAR_FLOOR: | ||
| return float(unexplained_sq / filled_sq) | ||
| if unexplained_sq <= _LEFTOVER_SINGULAR_FLOOR: | ||
| return 0.0 | ||
| return None |
There was a problem hiding this comment.
📝 Info: Share guard compares squared value to a singular-value floor
_unexplained_leftover_share tests filled_sq (R̃²) against _LEFTOVER_SINGULAR_FLOOR (1e-12), a floor meant for singular-value magnitudes. Any centered leftover below ~1e-6 magnitude enters the near-zero branch. Behavior is correct for all tested cases, but a tiny legitimate R̃ paired with a large reconstruction returns null instead of a large share.
Was this helpful? React with 👍 or 👎 to provide feedback.
| assert not hasattr(pair, "leftover_map_reconstruction") | ||
|
|
||
|
|
||
| def test_pad_map_axes_truncates_hidden_svd_components() -> None: |
|
Cycle comment: leftover increment still waits for independent APPROVE + exact-head required success. Author must not self-approve. Do not squash-merge without that APPROVE. Recut leftover-map length onto protected main as independent v2.12.31 / ADR 0187 (0183–0186 claimed by open leftover share/reconstruction PRs). Do not mix leftover stacks. Issues #79 and #87 stay open. |
# Conflicts: # frontend/package.json # frontend/src/i18n.test.ts # pyproject.toml # uv.lock
# Conflicts: # AGENTS.md # ARCHITECTURE.md # CHANGELOG.md # lineageweave/leftover_pairs.py # scripts/seed_demo_data.py
…ncrement 0202 is now claimed by the ask event-time filter ADR (#581), so this increment takes 0203. Doc conflicts resolve toward main's canonical agent-guide structure while keeping this branch's share sentence and amendment list; CLAUDE.md takes main's restructured section wholesale.
| [ADR 0164](0164-leftover-map-rank.md) (full map rank); | ||
| [ADR 0182](0182-leftover-map-unexplained.md) (unexplained leftover U) | ||
| [ADR 0182](0182-leftover-map-unexplained.md) (unexplained leftover U); | ||
| [ADR 0202](0202-leftover-map-unexplained-share.md) (unexplained leftover share s) |
There was a problem hiding this comment.
🟡 ADR link points to a nonexistent file
This amended-by entry links ADR 0202, but the decision record is 0203-leftover-map-unexplained-share.md; 0202 is only the migration filename, so the link resolves to nothing. The same wrong number recurs at 0048-persist-lsirm-leftover-pairs.md, 0003-fast-mlsirm-report-integration.md, and CHANGELOG.md, while ADR 0049 and AGENTS.md cite 0203.
| [ADR 0202](0202-leftover-map-unexplained-share.md) (unexplained leftover share s) | |
| [ADR 0203](0203-leftover-map-unexplained-share.md) (unexplained leftover share s) |
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Closed because protected main now normatively rejects this per-cell share alias. ADR 0003 defines the auditable raw-residual identity through U = R − R̂ and x = 2 R̂U/R², and explicitly says not to persist explained share e or unexplained share s. Keeping this branch open would conflict with the accepted exact-main measurement contract. |
Buyer next action
After
make seed, leftover post–criterion pairs sit above the member list with unexplained leftover shareU²/R̃²next to leftover-map distanced. Click the pair to open that post. The share badge is omitted when the pair has no complete-case leftover map (fallback rows do not invent a share). Never invent leftover score or theta.A leftover residual
Ris not the leftover the two leftover-map axes do not reconstruct. Unexplained leftover share namess = U_c² / R̃²of centered leftoverR̃ = R − centerafter two-axis reconstructionR̂_c = ξ_{1:2} · ζ_{1:2}(U_c = R̃ − R̂_c). Using rawRin the denominator (orU = R − recon) leaves the grand mean inside the named leftover, so a fully reconstructed rank-1 cell would look unexplained whenevercenter ≠ 0. This PR centers first.R̂_candU_cstay internal and are not persisted.Scope (this PR only)
leftover_map_unexplained_shareon leftover pair rowsleftover_map_unexplained_sharewhen complete-case ξ, ζ exist (s = U_c² / R̃²; unused axes pad with 0; hidden SVD axes after the second are dropped)U²/R̃²badge + named next action; i18n en/ko/zh/ja/vimean(R) ≠ 0; rank-3 synthetic residual provess ≠ R,≠ leftover_distance, and≠uncenteredU; leftover_distance stays full-rank Euclideans = 0; fallback omits shareleftover_map_unexplainedorleftover_map_reconstructionaccessToken(login screen has no token) and drop unusedoidcReturnUrlimport so leftover UI typechecksIndependent of leftover stacks already in review
Do not mix into:
Merge contract
9a6395546b03f6edc040492fef8f597bdca94212APPROVErequired (Copilot or human who is not the author)