Skip to content

feat: name leftover-map cross share on leftover pairs (v2.12.29) - #563

Open
seonghobae wants to merge 11 commits into
mainfrom
feat/leftover-map-cross-share-v21229
Open

feat: name leftover-map cross share on leftover pairs (v2.12.29)#563
seonghobae wants to merge 11 commits into
mainfrom
feat/leftover-map-cross-share-v21229

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Buyer next action

After make seed, leftover post–criterion pairs sit above the member list with leftover-map cross share 2R̂U/R̃² next to leftover-map distance d. 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). A signed remainder is shown, never clamped. Never invent leftover score or theta.

A leftover residual R is not the leftover the two leftover-map axes reconstruct. Truncated two-axis reconstruction of a higher-rank cell keeps unexplained leftover U_c = R̃ − R̂_c and a cross term 2 R̂_c U_c. The cell identity R̃² = R̂_c² + U_c² + 2 R̂_c U_c therefore yields e + s + x = 1 with explained leftover share e = R̂_c² / R̃², unexplained leftover share s = U_c² / R̃², and leftover-map cross share x = 2 R̂_c U_c / R̃². Hiding x lets a buyer read e + s as a complete leftover partition even though the truncated map leaves an identity remainder. x may be negative when reconstruction and unexplained leftover have opposite signs. R̂_c and U_c stay internal and are not persisted. This PR does not persist e or s.

A 0/0 origin cell (R̃ = 0, R̂_c = 0, U_c = 0) stores x = 0, including the origin cell of a rank-1 map. A reconstructed nonzero rank-1 cell stores x = 0 because U_c = 0 even when mean(R) ≠ 0.

Scope (this PR only)

  • ADR 0185 leftover-map cross share
  • migration 0185 nullable leftover_map_cross_share on leftover pair rows (no nonnegative CHECK)
  • persist/fetch/seed leftover_map_cross_share when complete-case ξ, ζ exist (x = 2 R̂_c U_c / R̃²; unused axes pad with 0; hidden SVD axes after the second are dropped)
  • frontend 2R̂U/R̃² badge + named identity-remainder next action; i18n en/ko/zh/ja/vi
  • rank-1 nonzero cell share is 0 even when mean(R) ≠ 0; rank-3 synthetic residual proves x ≠ R, ≠ leftover_distance, and internally e + s + x = 1 without persisting e/s//U; leftover_distance stays full-rank Euclidean
  • rank-0 maps store x = 0; fallback omits share; negative finite share is stored
  • do not persist leftover_map_explained_share, leftover_map_unexplained_share, leftover_map_unexplained, or leftover_map_reconstruction
  • tsc: optional AdminPanel accessToken (login screen has no token) and drop unused oidcReturnUrl import so leftover UI typechecks
  • release 2.12.29

Independent of leftover stacks already in review

Do not mix into:

Merge contract


Open in Devin Review

Summary by CodeRabbit

  • 새 기능

    • 기간 보고서의 leftover pair에 cross-share와 거리를 표시합니다.
    • 값이 제공되면 관련 post로 바로 이동할 수 있습니다.
    • cross-share가 없으면 기존 최근접·최원거리 동작을 유지합니다.
    • 영어, 중국어, 일본어, 베트남어 번역을 추가했습니다.
  • 버그 수정

    • 인증되지 않은 상태에서는 관리자 설정 저장을 차단하고 로그인 안내를 표시합니다.
  • 문서

    • leftover pair 및 cross-share 저장·표시 규칙과 변경 로그를 업데이트했습니다.

Persist leftover-map cross share x = 2 R̂_c U_c / R̃² of centered leftover
on leftover post–criterion pairs (ADR 0185). After make seed, closest and
farthest leftover pairs sit above the member list with 2R̂U/R̃² next to
leftover-map distance d; click opens that post. A signed remainder is
shown, never clamped. Do not invent a leftover score. Do not persist
explained leftover share e, unexplained leftover share s, unexplained
leftover U, or reconstruction R̂.
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 21 minutes.

View limit details

Limit 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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ad4334a-847c-43c3-bd94-930a6325b7d6

📥 Commits

Reviewing files that changed from the base of the PR and between 364f02b and ae74349.

📒 Files selected for processing (19)
  • 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/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
  • scripts/seed_demo_data.py
  • tests/test_leftover_pairs.py
  • tests/test_period_report.py
  • tests/test_schema.py
📝 Walkthrough

Walkthrough

기간 보고서 leftover pair가 leftover_map_cross_share를 계산하고 저장합니다. API는 값을 반환합니다. UI는 거리와 함께 값을 표시하고 관련 post로 연결합니다. 마이그레이션, 다국어 번역, 테스트, 문서가 갱신되었습니다.

Changes

Leftover-map cross-share

Layer / File(s) Summary
Cross-share 계산과 검증
lineageweave/leftover_pairs.py, tests/test_leftover_pairs.py, docs/adr/*
Leftover pair 계산이 최대 2개 Gabriel 축을 사용합니다. centered leftover 기반 2 R̂_c U_c / R̃² 값을 저장합니다. rank, sparse, 음수, 비유한 입력을 검증합니다.
Cross-share 저장과 API 연결
migrations/*, backend/app/report_ingestion.py, scripts/seed_demo_data.py, docker/postgres-init/migrate.sh, tests/test_schema.py, tests/test_api.py, tests/test_period_report.py, tests/test_migration_replay.py
report_leftover_pair에 nullable 컬럼을 추가합니다. seed와 report ingestion의 저장·조회 경로가 값을 전달합니다. API는 NULL을 None으로 반환합니다.
Cross-share UI 표시와 문서화
frontend/src/api.ts, frontend/src/App.tsx, frontend/src/leftoverMapCrossShare.ts, frontend/src/i18n.ts, frontend/src/*test.tsx, frontend/src/*.test.ts, CHANGELOG.md, CHANGELOG.d/*, AGENTS.md, ARCHITECTURE.md, CLAUDE.md, frontend/package.json, pyproject.toml
UI가 유효한 cross-share를 포맷하고 거리 옆에 표시합니다. 값이 있으면 관련 post로 연결하고, 없으면 기존 동작을 유지합니다. 번역과 릴리스 문서를 갱신합니다.

AdminPanel 인증 가드

Layer / File(s) Summary
AdminPanel 저장 인증 검증
frontend/src/components/AdminPanel.tsx
accessToken을 선택적 속성으로 변경합니다. 토큰이 없으면 "Log in" 오류를 설정하고 설정 저장을 중단합니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to 364f0

This PR adds and persists leftover-map cross-share values and exposes them in the UI, but the current implementation can omit valid shares for small finite inputs, and an unsafe rollback order can cause report queries to fail when the new column is removed before the application is downgraded. These bounded correctness and deployment risks should be fixed or explicitly accepted before merge.

Sequence Diagram(s)

sequenceDiagram
  participant PeriodReport
  participant leftover_pairs
  participant report_ingestion
  participant PostgreSQL
  participant App
  PeriodReport->>leftover_pairs: leftover pair 계산
  leftover_pairs-->>PeriodReport: cross-share 포함 LeftoverPair 반환
  PeriodReport->>report_ingestion: report 저장 요청
  report_ingestion->>PostgreSQL: cross-share 저장
  App->>PostgreSQL: leftover pair 조회
  PostgreSQL-->>App: cross-share와 distance 반환
  App-->>App: cross-share 포맷 및 post 링크 표시
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 leftover-map cross share를 leftover pair에 명명하고 표시하는 PR의 주요 변경 사항을 정확하고 간결하게 설명합니다.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/leftover-map-cross-share-v21229

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

github-code-quality[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Replace the self-comparison NaN check with math.isnan and the literal
infinity comparison with math.isinf so the assertion intent is explicit
(github-code-quality review thread).
coderabbitai[bot]

This comment was marked as resolved.

# Conflicts:
#	AGENTS.md
#	ARCHITECTURE.md
#	CHANGELOG.md
#	CLAUDE.md
#	backend/app/report_ingestion.py
#	backend/tests/test_api.py
#	docker/postgres-init/migrate.sh
#	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/App.tsx
#	frontend/src/api.ts
#	frontend/src/components/AdminPanel.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_migration_replay.py
#	tests/test_period_report.py
#	tests/test_schema.py
#	uv.lock
The 3-way merge of feat/leftover-map-cross-share-v21229 into main
silently dropped main's rememberOidcReturnUrl/returnUrlFromLocation
import (git resolved the adjacent import-block edits without a
conflict marker), breaking tsc with two TS2304 errors. Restore it.
devin-ai-integration[bot]

This comment was marked as resolved.

@seonghobae
seonghobae enabled auto-merge August 24, 2026 05:55
@github-actions
github-actions Bot disabled auto-merge August 24, 2026 06:17
@seonghobae
seonghobae enabled auto-merge August 24, 2026 07:18
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 08:42
seonghobae and others added 2 commits August 24, 2026 02:09
Squaring before the 1e-12 comparison made the effective threshold
sqrt(1e-12) = 1e-6, so a small-but-finite centered leftover (R-tilde =
1e-7, reconstruction 5e-8, x = 0.5) collapsed to an omitted badge.
Compare absolute values first; regression test added.

Also rename the Vietnamese cross-term label from phần dư đồng nhất
(reads as 'identical residual') to phần giao ('cross part'), matching
the 2 R-hat_c U_c / R-tilde-squared quantity it names; i18n expectation
updated (coderabbit review threads).

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +155 to +177
def _leftover_map_cross_share(filled: float, reconstruction: float) -> float | None:
"""Return ``x = 2 R̂_c U_c / R̃²`` when both terms are finite; otherwise omit.

``filled`` is centered leftover ``R̃ = R − center``. Unexplained
leftover ``U_c = R̃ − R̂_c`` is computed internally. Truncated
two-axis reconstruction of a higher-rank cell keeps a cross term
``2 R̂_c U_c``, so per-cell ``e + s ≠ 1``. The identity remainder
``x`` names that cross term as a share of centered leftover.
``x`` may be negative when reconstruction and unexplained leftover
have opposite signs; a negative finite share is stored, not omitted.
"""
if not np.isfinite(filled) or not np.isfinite(reconstruction):
return None
unexplained = float(filled - reconstruction)
# Threshold on absolute magnitudes, not squares: squaring first makes the
# effective floor sqrt(1e-12) = 1e-6 and collapses small-but-finite cells
# (e.g. R-tilde = 1e-7 with a valid cross term) to an omitted badge.
if abs(filled) > _LEFTOVER_SINGULAR_FLOOR:
share = float(2.0 * reconstruction * unexplained / (filled * filled))
return share if np.isfinite(share) else None
if abs(reconstruction) <= _LEFTOVER_SINGULAR_FLOOR and abs(unexplained) <= _LEFTOVER_SINGULAR_FLOOR:
return 0.0
return None

@devin-ai-integration devin-ai-integration Bot Aug 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Cross-share floor thresholds on magnitude, not square

_leftover_map_cross_share in leftover_pairs.py gates on abs(filled) > 1e-12 so a valid small centered leftover (e.g. R̃ = 1e-7) keeps its share instead of collapsing at an effective 1e-6 floor. Reconstructed rank-1 and origin cells return 0.0 because U_c = 0.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread backend/app/report_ingestion.py
@seonghobae
seonghobae enabled auto-merge August 24, 2026 11:50
# 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
seed() called _seed_demo_period_report, which persists rows into
report_leftover_map_axis, but never ran migration 0169 that creates
that table -- inherited from the leftover-map axis-share merge and
would crash `make seed` on a fresh database. The seeded_db/schema_db
pytest fixtures already ran 0169 directly, so no test caught this.
The merge changed _leftover_map_positions' third return value from an
int rank (PR563's own contract, needed only a count) to the raw kept
singular-value array (origin/main's axis-share contract, needed for
Gabriel inertia) -- both callers now use the array. This test still
unpacked it as a scalar and compared it directly to an int, which
raised on any residual matrix with more than one kept singular value.
@github-actions
github-actions Bot disabled auto-merge August 24, 2026 16:19
@seonghobae
seonghobae enabled auto-merge August 24, 2026 16:35
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 16:51
@seonghobae
seonghobae enabled auto-merge August 24, 2026 17:04
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 17:58
# 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
@seonghobae
seonghobae enabled auto-merge August 24, 2026 19:12

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment on lines +162 to +164
filled = float(residual[person, item]) - center
unexplained = _unexplained_leftover(float(residual[person, item]), reconstruction)
share = _leftover_map_cross_share(filled, reconstruction)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Cross share uses centered leftover, unexplained uses raw

_leftover_map_cross_share receives filled = residual − center (centered R̃), while _unexplained_leftover receives the raw residual R. The two adjacent persisted columns are therefore computed on different bases — deliberate per ADR 0185, but easy to misread when maintaining this code.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +237 to +239
if abs(reconstruction) <= _LEFTOVER_SINGULAR_FLOOR and abs(unexplained) <= _LEFTOVER_SINGULAR_FLOOR:
return 0.0
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Zero-cross-share branch can miss values straddling the floor

The return 0.0 branch requires both reconstruction and unexplained at or below the 1e-12 floor. Because unexplained = filled − reconstruction, two near-floor values with opposite signs push abs(unexplained) above the floor, so the badge is omitted rather than stored as 0.0. Degenerate boundary only; omitting is a safe fallback.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions
github-actions Bot disabled auto-merge August 24, 2026 19:16

Copy link
Copy Markdown
Contributor Author

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.

Merge commit only: two test-side gaps left by clean auto-merge.
_candidate_row() gained leftover_map_cross_share upstream; one direct
unit-test call site (the residual-boundary rejection test) wasn't
updated to pass it, so importing the test module raised a TypeError.
Separately, the API test's per-pair assertions both validated
leftover_map_unexplained's shape (added by an already-merged main PR)
and asserted its absence (this branch's original, now-stale
expectation) side by side -- drop the stale negative assertion.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Open in Devin Review

Comment thread scripts/seed_demo_data.py
cur.execute((migrations / "0012_report_leftover_pair.sql").read_text())
cur.execute((migrations / "0163_report_leftover_observed_expected.sql").read_text())
cur.execute((migrations / "0164_report_leftover_map_rank.sql").read_text())
cur.execute((migrations / "0169_report_leftover_map_axis.sql").read_text())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Seed gains the 0169 axis migration its axis insert relies on

The seed migration list now includes 0169_report_leftover_map_axis.sql. The seed already inserts into report_leftover_map_axis via _persist_seed_period_report (seed_demo_data.py), but the base-commit list omitted 0169. The migration is CREATE TABLE IF NOT EXISTS, so re-application is harmless.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant