diff --git a/AGENTS.md b/AGENTS.md
index 154addfbc..2755bcd17 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -846,6 +846,11 @@ returns a leftover-map origin tick caption independently of leftover-map axis sh
`leftover axis {axis} origin tick {value}`. Rank-0 unused axes still name leftover-map origin `0.00`. Share and singular omit independently.
Do not invent leftover-map origin from leftover-map axis share or leftover-map singular values `σ_k`. leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map
axis origin tick keys. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map axis origin tick keys. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys this increment.
+ADR 0347 captions leftover-map graphic leftover-map origin with leftover-map origin when leftoverMapPlotOriginBadge
+returns a leftover-map origin caption independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values. Distinct accessible name
+`leftover-map origin {origin}`. Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`.
+Do not invent leftover-map origin from leftover-map axis share or leftover-map singular values `σ_k`. leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map
+axis origin tick keys. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map axis origin tick keys. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys. leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys this increment. leftover-map comparison graphic leftover-map origin stays unnamed this increment.
When `R`, `R̂`, `U`, `x`,
`s`, and `e` are finite, `e + s + x = 1`. When `Y`, `E`, and `R` are
finite, `Y − E = R`. When `R`, `R̂`, and `U` are
@@ -1027,6 +1032,8 @@ Leftover-map comparison leftover-axis origin ticks independently of leftover-map
(ADR 0345) captions leftover-map comparison leftover-axis origin ticks when leftoverMapCompareAxisTickBadge returns a leftover-map origin tick caption.
Leftover-map leftover-axis origin ticks independently of leftover-map axis share and leftover-map singular values
(ADR 0346) captions leftover-map leftover-axis origin ticks when leftoverMapAxisTickBadge returns a leftover-map origin tick caption.
+Leftover-map graphic leftover-map origin independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values
+(ADR 0347) captions leftover-map graphic leftover-map origin when leftoverMapPlotOriginBadge returns a leftover-map origin caption.
Item complete-case coverage
(ADR 0282) captions the leftover-map graphic with how many scored criteria
entered the map. Item complete-case coverage on the pair list (ADR 0285)
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 9a6d0a627..81a5d2ce0 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -817,6 +817,8 @@ ADR 0345 captions leftover-map comparison leftover-axis origin ticks when leftov
returns a leftover-map origin tick caption independently of leftover-map axis share and leftover-map singular values.
ADR 0346 captions leftover-map leftover-axis origin ticks when leftoverMapAxisTickBadge
returns a leftover-map origin tick caption independently of leftover-map axis share and leftover-map singular values.
+ADR 0347 captions leftover-map graphic leftover-map origin when leftoverMapPlotOriginBadge
+returns a leftover-map origin caption independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values.
Click a post marker or a pair button
opens that post. Those ADRs are the normative mathematical, storage, and
display contracts. Leftover-map axis share
diff --git a/CHANGELOG.d/2.104.0-leftover-map-plot-origin-badge.md b/CHANGELOG.d/2.104.0-leftover-map-plot-origin-badge.md
new file mode 100644
index 000000000..358d639ee
--- /dev/null
+++ b/CHANGELOG.d/2.104.0-leftover-map-plot-origin-badge.md
@@ -0,0 +1,12 @@
+## 2.104.0 — Leftover-map origin on leftover-map graphic independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values
+
+- Caption leftover-map graphic leftover-map origin through leftoverMapPlotOriginBadge
+ independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values (ADR 0347). After `make seed`, leftover-map graphic leftover-map
+ origin names leftover-map origin when leftoverMapPlotOriginBadge returns a usable leftover-map origin caption. Distinct accessible name
+ `leftover-map origin {origin}`. Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`. leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map
+ axis origin tick keys. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map
+ axis origin tick keys. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys.
+ leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys this increment. leftover-map comparison graphic leftover-map origin stays unnamed this increment.
+ Do not invent leftover-map origin from leftover-map axis share or leftover-map singular values `σ_k`.
+ Do not mix #877. A finite negative leftover is shown, never clamped. Never invent a leftover score.
+ Never invent a theta. No new columns.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d8896f3e..f6dc4583e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,16 @@ All notable changes to this project are documented here. Format follows
### Added
+- Period leftover pairs now caption leftover-map graphic leftover-map origin
+ through leftoverMapPlotOriginBadge independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values (ADR 0347 / v2.104.0). After `make seed`, leftover-map graphic leftover-map
+ origin names leftover-map origin when leftoverMapPlotOriginBadge returns a usable leftover-map origin caption. Distinct accessible name
+ `leftover-map origin {origin}`. Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`. leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map
+ axis origin tick keys. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map
+ axis origin tick keys. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys.
+ leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys this increment. leftover-map comparison graphic leftover-map origin stays unnamed this increment.
+ Do not invent leftover-map origin from leftover-map axis share or leftover-map singular values `σ_k`.
+ Do not mix #877. A finite negative leftover is shown, never clamped. Never invent a leftover score or a theta. No new columns.
+
- Period leftover pairs now caption leftover-map leftover-axis origin ticks
through leftoverMapAxisTickBadge independently of leftover-map axis share and leftover-map singular values (ADR 0346 / v2.103.0). After `make seed`, leftover-map leftover-axis
origin ticks name leftover-map origin when leftoverMapPlotTickIsOrigin returns true. Distinct accessible name
diff --git a/CLAUDE.md b/CLAUDE.md
index 4c09ad523..6b92d1ddf 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -49,7 +49,7 @@ cutoff. Global Ask optional `knowledge_cutoff` uses the same cover
Create/start endpoint rules (ADR 0017 / 0021), tie-vs-miss similarity
(ADR 0026), R&R catalog ids (ADR 0019 / 0027), leftover pairs
-(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322 / 0323 / 0324 / 0325 / 0326 / 0327 / 0328 / 0329 / 0330 / 0331 / 0332 / 0333 / 0334 / 0335 / 0336 / 0337 / 0338 / 0339 / 0340 / 0341 / 0342 / 0343 / 0344 / 0345 / 0346), occupational construct catalog search
+(ADR 0048–0164 / 0182 / 0185 / 0201 / 0233 / 0266 / 0267 / 0268 / 0269 / 0270 / 0271 / 0272 / 0273 / 0274 / 0275 / 0276 / 0277 / 0278 / 0279 / 0280 / 0281 / 0282 / 0283 / 0284 / 0285 / 0286 / 0287 / 0288 / 0289 / 0290 / 0291 / 0292 / 0293 / 0294 / 0295 / 0296 / 0297 / 0298 / 0299 / 0300 / 0301 / 0302 / 0303 / 0304 / 0305 / 0306 / 0307 / 0308 / 0309 / 0310 / 0311 / 0312 / 0313 / 0314 / 0315 / 0316 / 0317 / 0318 / 0319 / 0320 / 0321 / 0322 / 0323 / 0324 / 0325 / 0326 / 0327 / 0328 / 0329 / 0330 / 0331 / 0332 / 0333 / 0334 / 0335 / 0336 / 0337 / 0338 / 0339 / 0340 / 0341 / 0342 / 0343 / 0344 / 0345 / 0346 / 0347), occupational construct catalog search
(ADR 0257), the text-channel embedding swap and cosine
clamp (ADR 0190), per-edge channel-score persistence (ADR 0195),
token-backed status notices (ADR 0220),
diff --git a/docs/adr/0343-leftover-map-plot-tick-origin-badge.md b/docs/adr/0343-leftover-map-plot-tick-origin-badge.md
index f2c1f24c2..bff363731 100644
--- a/docs/adr/0343-leftover-map-plot-tick-origin-badge.md
+++ b/docs/adr/0343-leftover-map-plot-tick-origin-badge.md
@@ -4,7 +4,9 @@
**Date:** 2026-08-31
**Amended by:** [ADR 0344](0344-leftover-map-compare-plot-tick-origin-badge.md)
-(leftover-map origin on leftover-map comparison graphic leftover-map axis ticks independently of leftover-map axis share and leftover-map singular values)
+(leftover-map origin on leftover-map comparison graphic leftover-map axis ticks independently of leftover-map axis share and leftover-map singular values);
+[ADR 0347](0347-leftover-map-plot-origin-badge.md)
+(leftover-map origin on leftover-map graphic independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values)
Amends leftover-map coordinate ticks
([ADR 0270](0270-leftover-map-coordinate-ticks.md)), leftover-map graphic leftover-map axis ticks leftover-map singular values
diff --git a/docs/adr/0346-leftover-map-axis-tick-origin-badge.md b/docs/adr/0346-leftover-map-axis-tick-origin-badge.md
index 931b4d15d..c5eafcb2b 100644
--- a/docs/adr/0346-leftover-map-axis-tick-origin-badge.md
+++ b/docs/adr/0346-leftover-map-axis-tick-origin-badge.md
@@ -3,6 +3,9 @@
**Decision status:** Accepted
**Date:** 2026-08-31
+**Amended by:** [ADR 0347](0347-leftover-map-plot-origin-badge.md)
+(leftover-map origin on leftover-map graphic independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values)
+
Amends leftover-axis ticks leftover-map singular values
([ADR 0330](0330-leftover-map-axis-tick-badge.md)) and leftover-axis ticks leftover-map axis share
([ADR 0334](0334-leftover-map-axis-tick-share-badge.md)). Independent of leftover-map
diff --git a/docs/adr/0347-leftover-map-plot-origin-badge.md b/docs/adr/0347-leftover-map-plot-origin-badge.md
new file mode 100644
index 000000000..6913b1b75
--- /dev/null
+++ b/docs/adr/0347-leftover-map-plot-origin-badge.md
@@ -0,0 +1,96 @@
+# ADR 0347 — Name leftover-map origin on leftover-map graphic independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values
+
+**Decision status:** Proposed
+**Date:** 2026-08-31
+
+**Numbering status:** provisional conflict. ADR 0347 is also claimed by the active leftover-map criterion-origin stack (#891). This proposal was retargeted behind the current #917 stack after that collision was detected and MUST receive a new non-conflicting ADR/version identity during conflict-preserving reconstruction before it can become Accepted or merge.
+
+**Amended by:** none yet
+
+Amends leftover-map graphic leftover-map axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0343](0343-leftover-map-plot-tick-origin-badge.md)). Independent of leftover-map leftover-axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0346](0346-leftover-map-axis-tick-origin-badge.md)). Independent of leftover-map
+comparison leftover-axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0345](0345-leftover-map-compare-axis-tick-origin-badge.md)). Independent of leftover-map
+comparison graphic leftover-map axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0344](0344-leftover-map-compare-plot-tick-origin-badge.md)). Independent of leftover-map
+comparison graphic leftover-map origin (do not mix #877).
+
+## Context
+
+ADR 0343 already names leftover-map graphic leftover-map axis origin ticks
+`leftover-map axis {axis} origin tick {value}` when leftoverMapPlotTickIsOrigin returns true.
+Rank-0 unused axes still persist leftover-map origin `0.00`
+(`formatSignedLeftoverValue(0)` emits no plus). Leftover-map origin on leftover-map
+graphic still interpolates leftover-map origin only through leftover-map axis origin tick keys.
+Share and singular omit independently. A finite negative leftover is shown,
+never clamped. Do not invent leftover-map origin from leftover-map axis share
+or leftover-map singular values `σ_k`. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map
+axis origin tick keys from ADR 0344. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys from ADR 0345.
+leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys from ADR 0346.
+
+This increment proposes naming leftover-map graphic leftover-map origin as
+leftoverMapPlotOriginBadge. Leftover-map graphic leftover-map origin stays
+`leftover-map origin {origin}`
+when leftoverMapPlotOriginBadge returns a usable leftover-map origin caption, so it stays distinct from leftover-map
+graphic leftover-map axis origin ticks
+`leftover-map axis {axis} origin tick {value}`,
+leftover-map comparison graphic leftover-map axis origin ticks
+`leftover map comparison graphic leftover-map axis {axis} origin tick {value}`,
+leftover-map comparison leftover-axis origin ticks
+`leftover map comparison leftover axis {axis} origin tick {value}`,
+and leftover-map leftover-axis origin ticks
+`leftover axis {axis} origin tick {value}`.
+Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`
+(`formatLeftoverMapCoordinatePair(0, 0)`). leftoverMapComparePlotOriginBadge stays unnamed
+this increment. It does not add columns. Do not invent a leftover score. Do not invent a theta.
+
+The original branch allocated **0347**, but the live queue now contains a separate active ADR 0347 in #891 and its descendants. That allocation is therefore not authoritative. Preserve the feature delta, keep this decision Proposed, and renumber/version it only after reconstructing it on the current predecessor without overwriting intervening stack changes.
+
+## Decision
+
+On leftover-map graphic, caption leftover-map origin
+when leftoverMapPlotOriginBadge returns a usable leftover-map origin caption. Rank-0 unused axes still name leftover-map
+origin `(0.00, 0.00)`. Do not invent leftover-map origin from leftover-map axis share or leftover-map singular values `σ_k`.
+leftoverMapPlotTickAxisBadge, leftoverMapComparePlotTickAxisBadge, leftoverMapCompareAxisTickBadge, and leftoverMapAxisTickBadge
+do not change leftover-map origin naming this increment. leftover-map comparison graphic leftover-map origin stays unnamed this increment.
+
+Do not add SQL. Do not edit shipped migrations. Do not invent a leftover
+score. Do not invent a theta.
+
+## Consequences
+
+After the proposal is reconstructed with a unique ADR/version identity and accepted, leftover-map graphic leftover-map origin names
+leftover-map origin independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map
+singular values. Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`.
+leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map axis origin tick keys.
+leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map axis origin tick keys.
+leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys.
+leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys.
+leftover-map graphic leftover-map axis origin ticks independently of leftover-map axis share and leftover-map
+singular values (ADR 0343) remain.
+
+## Related
+
+Independent of leftover-map leftover-axis origin ticks independently of leftover-map
+axis share and leftover-map singular values
+([ADR 0346](0346-leftover-map-axis-tick-origin-badge.md)). Independent of leftover-map
+comparison leftover-axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0345](0345-leftover-map-compare-axis-tick-origin-badge.md)). Independent of leftover-map
+comparison graphic leftover-map axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0344](0344-leftover-map-compare-plot-tick-origin-badge.md)). Independent of leftover-map
+graphic leftover-map axis origin ticks independently of leftover-map axis share and leftover-map singular values
+([ADR 0343](0343-leftover-map-plot-tick-origin-badge.md)).
+
+## References
+
+Gabriel, K. R. (1971). The biplot graphic display of matrices with
+application to principal component analysis. *Biometrika, 58*(3),
+453–467. https://doi.org/10.1093/biomet/58.3.453
+
+Jeon, M., Jin, I. H., Schweinberger, M., & Baugh, S. (2021). Mapping
+unobserved item–respondent interactions: A latent space item response
+model with interaction map. *Psychometrika, 86*(2), 378–403.
+https://doi.org/10.1007/s11336-021-09762-5
+(LSIRM interaction `−γ‖ξ_j − ζ_i‖` after main effects
+`α_j − β_i`; typically `p = 2` for the interaction map.)
diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md
index 401b1b5ac..723509df8 100644
--- a/docs/product-technical-gap-baseline.md
+++ b/docs/product-technical-gap-baseline.md
@@ -1,5 +1,15 @@
# Product & Technical Gap Baseline
+> Exact-head loop overlay: 2026-08-31 20:05 KST. Protected `main` is
+> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not
+> self-approve. #889 leftover-map leftover-axis origin ticks independently of leftover-map
+> axis share and leftover-map singular values (v2.103.0 / ADR 0346) is
+> `4bfb490`, stacked on #887 @ `7637fc8`. Independent APPROVE is missing (Devin COMMENT is
+> not APPROVE). Next buyer increment on this cycle: leftover-map graphic leftover-map
+> origin independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map singular values as leftoverMapPlotOriginBadge (ADR 0347 / v2.104.0) delivered locally on
+> `feat/leftover-map-plot-origin-badge-v21040`. Distinct accessible name
+> `leftover-map origin {origin}`. Rank-0 unused axes still name leftover-map origin `(0.00, 0.00)`. leftoverMapPlotTickAxisBadge stays leftover-map graphic leftover-map axis origin tick keys. leftoverMapComparePlotTickAxisBadge stays leftover-map comparison graphic leftover-map axis origin tick keys. leftoverMapCompareAxisTickBadge stays leftover-map comparison leftover-axis origin tick keys. leftoverMapAxisTickBadge stays leftover-map leftover-axis origin tick keys. leftover-map comparison graphic leftover-map origin stays unnamed this increment. Do not invent leftover-map origin from leftover-map axis share or `σ_k`. Do not mix #877. Stack onto #889. Leave open for independent review. Do not squash-merge. Do not self-approve. Issues #79 and #87 stay open. Do not merge #808 without independent APPROVE.
+
> Exact-head loop overlay: 2026-08-31 19:32 KST. Protected `main` is
> `cb187cadee5fb6c46d8a944815ccc154a1e028d1`. Writes work. Do not
> self-approve. #887 leftover-map comparison leftover-axis origin ticks independently of leftover-map
diff --git a/frontend/package.json b/frontend/package.json
index 9aed0741c..8401b2758 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
- "version": "2.103.0",
+ "version": "2.104.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/LeftoverMapPlot.css b/frontend/src/components/LeftoverMapPlot.css
index 45bfe213e..4c63f9fb0 100644
--- a/frontend/src/components/LeftoverMapPlot.css
+++ b/frontend/src/components/LeftoverMapPlot.css
@@ -73,6 +73,10 @@
stroke-width: 1;
}
+.leftover-map-plot-origin circle {
+ fill: var(--color-text);
+}
+
.leftover-map-plot-tick line {
stroke: var(--color-text);
stroke-width: 1;
diff --git a/frontend/src/components/LeftoverMapPlot.test.tsx b/frontend/src/components/LeftoverMapPlot.test.tsx
index ddde84b74..4cbb80eae 100644
--- a/frontend/src/components/LeftoverMapPlot.test.tsx
+++ b/frontend/src/components/LeftoverMapPlot.test.tsx
@@ -74,6 +74,7 @@ describe("LeftoverMapPlot", () => {
expect(screen.getByLabelText("leftover-map axis 1 tick +0.50")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map axis 2 tick −0.02")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map axis 1 origin tick 0.00")).toBeInTheDocument();
+ expect(screen.getByLabelText("leftover-map origin (0.00, 0.00)")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map distance d 0.12")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map distance d 1.84")).toBeInTheDocument();
expect(screen.getByLabelText("leftover-map reconstruction R̂ +0.25")).toBeInTheDocument();
@@ -2824,6 +2825,10 @@ describe("LeftoverMapPlot", () => {
expect(
screen.getByLabelText("leftover map comparison graphic leftover-map axis 1 origin tick 0.00 σ 1.84 82%"),
).toBeInTheDocument();
+ expect(screen.queryByLabelText("leftover-map origin (0.00, 0.00)")).not.toBeInTheDocument();
+ expect(
+ screen.queryByLabelText("leftover map comparison graphic leftover-map origin (0.00, 0.00)"),
+ ).not.toBeInTheDocument();
expect(
screen.queryByLabelText("leftover map comparison graphic leftover-map axis 1 tick +0.50"),
).not.toBeInTheDocument();
diff --git a/frontend/src/components/LeftoverMapPlot.tsx b/frontend/src/components/LeftoverMapPlot.tsx
index fd6aea44a..bdd22acb3 100644
--- a/frontend/src/components/LeftoverMapPlot.tsx
+++ b/frontend/src/components/LeftoverMapPlot.tsx
@@ -27,6 +27,7 @@ import {
leftoverMapComparePlotAxisBadge,
leftoverMapComparePlotTickAxisBadge,
leftoverMapPlotAxisBadge,
+ leftoverMapPlotOriginBadge,
leftoverMapPlotTickAxisBadge,
leftoverSingularForAxis,
} from "../leftoverMapPlotAxisSingular";
@@ -101,6 +102,17 @@ function leftoverMapPlotAxisText(
return tf(badge.key, badge.values);
}
+function leftoverMapPlotOriginText(variant: LeftoverMapPlotVariant): string | null {
+ if (variant === "comparison") {
+ return null;
+ }
+ const badge = leftoverMapPlotOriginBadge();
+ if (badge === null) {
+ return null;
+ }
+ return tf(badge.key, badge.values);
+}
+
function leftoverMapPlotTickText(
axisIndex: number,
tickLabel: string,
@@ -293,6 +305,9 @@ function leftoverMapPlotPostText(
* ADR 0346 fail-closes leftover-map leftover-axis origin ticks through
* leftoverMapAxisTickBadge independently of leftover-map axis share and leftover-map
* singular values.
+ * ADR 0347 fail-closes leftover-map graphic leftover-map origin through
+ * leftoverMapPlotOriginBadge independently of leftover-map axis origin ticks, leftover-map
+ * axis share, and leftover-map singular values.
* Never invent a leftover score.
*/
export function LeftoverMapPlot({
@@ -311,6 +326,7 @@ export function LeftoverMapPlot({
const itemCoverageCounts = leftoverMapItemCoverageCounts(leftoverMapCoverage);
const incompletePostCount = leftoverMapIncompletePostCount(leftoverMapCoverage);
const incompleteItemCount = leftoverMapIncompleteItemCount(leftoverMapCoverage);
+ const originLabel = leftoverMapPlotOriginText(variant);
const openPost = (postId: string) => {
const pair = firstPlottablePairForPost(pairs, postId);
@@ -416,6 +432,11 @@ export function LeftoverMapPlot({
{leftoverMapPlotAxisText(2, leftoverMapAxes, variant)}
+ {originLabel !== null ? (
+
+
+
+ ) : null}
{layout.ticks.map((tick) => (
{
"leftover-map axis {axis} origin tick {value} σ {singular}",
"leftover-map axis {axis} origin tick {value} {share}%",
"leftover-map axis {axis} origin tick {value} σ {singular} {share}%",
+ "leftover-map origin {origin}",
"leftover-map distance {label}",
"leftover-map reconstruction {label}",
"leftover-map explained leftover share {label}",
@@ -1486,6 +1487,16 @@ describe("i18n", () => {
},
);
+ it.each([
+ ["ko", "잔여 지도 원점 (0.00, 0.00)"],
+ ["zh", "残差图原点 (0.00, 0.00)"],
+ ["ja", "残差マップ原点 (0.00, 0.00)"],
+ ["vi", "gốc bản đồ phần dư (0.00, 0.00)"],
+ ] as const)("formats leftover-map graphic leftover-map origin in %s", (locale, expected) => {
+ setLocale(locale);
+ expect(tf("leftover-map origin {origin}", { origin: "(0.00, 0.00)" })).toBe(expected);
+ });
+
it.each([
["ko", "잔여 지도 거리 d 0.12"],
["zh", "残差图距离 d 0.12"],
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index cfb8a4160..d0f7357fd 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -716,6 +716,7 @@ const TRANSLATIONS: Partial>> = {
"잔여 지도 축 {axis} 원점 눈금 {value} {share}%",
"leftover-map axis {axis} origin tick {value} σ {singular} {share}%":
"잔여 지도 축 {axis} 원점 눈금 {value} σ {singular} {share}%",
+ "leftover-map origin {origin}": "잔여 지도 원점 {origin}",
"leftover-map distance {label}": "잔여 지도 거리 {label}",
"leftover-map reconstruction {label}": "잔여 지도 재구성 {label}",
"leftover-map explained leftover share {label}": "잔여 지도 설명 잔여 점유율 {label}",
@@ -1434,6 +1435,7 @@ const TRANSLATIONS: Partial>> = {
"残差图轴 {axis} 原点刻度 {value} {share}%",
"leftover-map axis {axis} origin tick {value} σ {singular} {share}%":
"残差图轴 {axis} 原点刻度 {value} σ {singular} {share}%",
+ "leftover-map origin {origin}": "残差图原点 {origin}",
"leftover-map distance {label}": "残差图距离 {label}",
"leftover-map reconstruction {label}": "残差图重建 {label}",
"leftover-map explained leftover share {label}": "残差图已解释残差份额 {label}",
@@ -2156,6 +2158,7 @@ const TRANSLATIONS: Partial>> = {
"残差マップ軸 {axis} 原点目盛 {value} {share}%",
"leftover-map axis {axis} origin tick {value} σ {singular} {share}%":
"残差マップ軸 {axis} 原点目盛 {value} σ {singular} {share}%",
+ "leftover-map origin {origin}": "残差マップ原点 {origin}",
"leftover-map distance {label}": "残差マップ距離 {label}",
"leftover-map reconstruction {label}": "残差マップ再構成 {label}",
"leftover-map explained leftover share {label}": "残差マップ説明済み残差割合 {label}",
@@ -2883,6 +2886,7 @@ const TRANSLATIONS: Partial>> = {
"vạch gốc trục bản đồ phần dư {axis} {value} {share}%",
"leftover-map axis {axis} origin tick {value} σ {singular} {share}%":
"vạch gốc trục bản đồ phần dư {axis} {value} σ {singular} {share}%",
+ "leftover-map origin {origin}": "gốc bản đồ phần dư {origin}",
"leftover-map distance {label}": "khoảng cách bản đồ phần dư {label}",
"leftover-map reconstruction {label}": "tái dựng bản đồ phần dư {label}",
"leftover-map explained leftover share {label}": "phần dư giải thích bản đồ phần dư {label}",
diff --git a/frontend/src/leftoverMapAxisBadge.ts b/frontend/src/leftoverMapAxisBadge.ts
index 76f74bdf2..eb0820ed1 100644
--- a/frontend/src/leftoverMapAxisBadge.ts
+++ b/frontend/src/leftoverMapAxisBadge.ts
@@ -30,6 +30,8 @@
* leftoverMapCompareAxisTickBadge, not this helper.
* ADR 0346 names leftover-map leftover-axis origin ticks as leftoverMapAxisTickBadge
* independently of leftover-map axis share and leftover-map singular values.
+ * ADR 0347 captions leftover-map graphic leftover-map origin as leftoverMapPlotOriginBadge,
+ * not this helper.
*/
import type { LeftoverMapAxis } from "./api";
diff --git a/frontend/src/leftoverMapPlotAxisSingular.test.ts b/frontend/src/leftoverMapPlotAxisSingular.test.ts
index ba519a87e..ca922ede2 100644
--- a/frontend/src/leftoverMapPlotAxisSingular.test.ts
+++ b/frontend/src/leftoverMapPlotAxisSingular.test.ts
@@ -6,6 +6,7 @@ import {
leftoverMapComparePlotAxisBadge,
leftoverMapComparePlotTickAxisBadge,
leftoverMapPlotAxisBadge,
+ leftoverMapPlotOriginBadge,
leftoverMapPlotTickAxisBadge,
leftoverMapPlotTickIsOrigin,
leftoverSingularForAxis,
@@ -33,6 +34,7 @@ import {
LEFTOVER_MAP_COMPARE_PLOT_ORIGIN_TICK_SINGULAR_SHARE,
LEFTOVER_MAP_PLOT_AXIS_SINGULAR,
LEFTOVER_MAP_PLOT_AXIS_SINGULAR_SHARE,
+ LEFTOVER_MAP_PLOT_ORIGIN,
LEFTOVER_MAP_PLOT_ORIGIN_TICK,
LEFTOVER_MAP_PLOT_ORIGIN_TICK_SHARE,
LEFTOVER_MAP_PLOT_ORIGIN_TICK_SINGULAR,
@@ -1030,3 +1032,46 @@ describe("leftover-map comparison leftover-axis tick leftover-map axis share", (
);
});
});
+
+describe("leftover-map graphic leftover-map origin independently of leftover-map axis origin ticks", () => {
+ it("names leftover-map origin independently of leftover-map axis share and leftover-map singular values", () => {
+ expect(leftoverMapPlotOriginBadge()).toEqual({
+ key: LEFTOVER_MAP_PLOT_ORIGIN,
+ values: { origin: "(0.00, 0.00)" },
+ });
+ });
+
+ it("names rank-0 leftover-map origin (0.00, 0.00)", () => {
+ expect(leftoverMapPlotOriginBadge()?.values.origin).toBe("(0.00, 0.00)");
+ });
+
+ it("does not invent leftover-map origin from leftover-map axis share or leftover-map singular values", () => {
+ expect(leftoverMapPlotOriginBadge()?.values).toEqual({ origin: "(0.00, 0.00)" });
+ expect(leftoverMapPlotOriginBadge()?.key).toBe(LEFTOVER_MAP_PLOT_ORIGIN);
+ });
+
+ it("stays distinct from leftover-map graphic leftover-map axis origin ticks, leftover-map comparison graphic leftover-map axis origin ticks, leftover-map comparison leftover-axis origin ticks, and leftover-axis origin ticks", () => {
+ expect(LEFTOVER_MAP_PLOT_ORIGIN).toBe("leftover-map origin {origin}");
+ expect(LEFTOVER_MAP_PLOT_ORIGIN).not.toBe(LEFTOVER_MAP_PLOT_ORIGIN_TICK);
+ expect(LEFTOVER_MAP_PLOT_ORIGIN).not.toBe(LEFTOVER_MAP_COMPARE_PLOT_ORIGIN_TICK);
+ expect(LEFTOVER_MAP_PLOT_ORIGIN).not.toBe(LEFTOVER_MAP_COMPARE_AXIS_ORIGIN_TICK);
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapPlotTickAxisBadge(1, "0.00", null).key,
+ );
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapPlotTickAxisBadge(1, "0.00", 0, 0).key,
+ );
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapComparePlotTickAxisBadge(1, "0.00", null).key,
+ );
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapCompareAxisTickBadge(1, "0.00", null).key,
+ );
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapAxisTickBadge(1, "0.00", null).key,
+ );
+ expect(leftoverMapPlotOriginBadge()?.key).not.toBe(
+ leftoverMapPlotAxisBadge(1, 0, 0)?.key,
+ );
+ });
+});
diff --git a/frontend/src/leftoverMapPlotAxisSingular.ts b/frontend/src/leftoverMapPlotAxisSingular.ts
index 344bbdea7..657161fbc 100644
--- a/frontend/src/leftoverMapPlotAxisSingular.ts
+++ b/frontend/src/leftoverMapPlotAxisSingular.ts
@@ -40,9 +40,13 @@
* leftover-map singular values.
* ADR 0346 names leftover-map leftover-axis origin ticks as leftoverMapAxisTickBadge
* independently of leftover-map axis share and leftover-map singular values.
+ * ADR 0347 names leftover-map graphic leftover-map origin as leftoverMapPlotOriginBadge
+ * independently of leftover-map axis origin ticks, leftover-map axis share, and leftover-map
+ * singular values.
*/
import type { LeftoverMapAxis } from "./api";
+import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
import {
formatLeftoverMapPlotAxisShare,
LEFTOVER_MAP_COMPARE_PLOT_AXIS_SHARE,
@@ -107,6 +111,8 @@ export const LEFTOVER_MAP_COMPARE_PLOT_ORIGIN_TICK_SHARE =
export const LEFTOVER_MAP_COMPARE_PLOT_ORIGIN_TICK_SINGULAR_SHARE =
"leftover map comparison graphic leftover-map axis {axis} origin tick {value} σ {singular} {share}%";
+export const LEFTOVER_MAP_PLOT_ORIGIN = "leftover-map origin {origin}";
+
export const LEFTOVER_MAP_COMPARE_AXIS_SINGULAR =
"leftover map comparison leftover axis {axis} σ {value}";
@@ -155,6 +161,11 @@ export type LeftoverMapCompareAxisBadge = {
values: { axis: number; value?: string; share?: string; singular?: string };
};
+export type LeftoverMapPlotOriginBadge = {
+ key: string;
+ values: { origin: string };
+};
+
export function leftoverSingularForAxis(
axes: ReadonlyArray | null | undefined,
axisIndex: number,
@@ -185,6 +196,14 @@ export function leftoverMapPlotTickIsOrigin(tickLabel: string): boolean {
return originLabel !== null && tickLabel === originLabel;
}
+export function leftoverMapPlotOriginBadge(): LeftoverMapPlotOriginBadge | null {
+ const origin = formatLeftoverMapCoordinatePair(0, 0);
+ if (origin === null) {
+ return null;
+ }
+ return { key: LEFTOVER_MAP_PLOT_ORIGIN, values: { origin } };
+}
+
export function leftoverMapPlotAxisBadge(
axisIndex: number,
leftoverSingular: number | null | undefined,
diff --git a/frontend/src/leftoverMapPlotLayout.ts b/frontend/src/leftoverMapPlotLayout.ts
index 3acd106c4..0c2c77afa 100644
--- a/frontend/src/leftoverMapPlotLayout.ts
+++ b/frontend/src/leftoverMapPlotLayout.ts
@@ -159,6 +159,9 @@
* ADR 0346 fail-closes leftover-map leftover-axis origin ticks through
* leftoverMapAxisTickBadge independently of leftover-map axis share and leftover-map
* singular values, not leftover-map comparison leftover-axis ticks.
+ * ADR 0347 fail-closes leftover-map graphic leftover-map origin through
+ * leftoverMapPlotOriginBadge independently of leftover-map axis origin ticks, leftover-map
+ * axis share, and leftover-map singular values, not leftover-map comparison graphic leftover-map origin.
*/
import { formatLeftoverMapCoordinatePair } from "./leftoverMapCoordinates";
diff --git a/pyproject.toml b/pyproject.toml
index 8af048443..f82235b76 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
-version = "2.103.0"
+version = "2.104.0"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }