ADR-0007: score_value Deprecation + Data-Model Column-Naming Hygiene
Status: Accepted (2026-05-22) Supersedes: — Superseded by: —
Context
Migration 011 added score_coverage_weighted as the canonical renormalised score
and score_raw_v04 as a duplicate of the existing score_value column (both on
score_stage1_esg and score_pillar). The two values are numerically identical
except for institutions with null pillars — for the 10 null-S-pillar financials,
score_coverage_weighted renormalises over the two covered pillars (E, G) and
produces a higher value; score_value (and its alias score_raw_v04) does not.
This divergence silently misled six cycles of handoffs: the C16 reporting-hygiene
catch (ADR-0004 amendment) found that handoffs had been reporting score_value
(range 0–27 under current sparsity) rather than score_coverage_weighted (range
0–39). The column names gave no signal that score_value was internal and
score_coverage_weighted was canonical.
A Stage 1 reader audit (2026-05-22) mapped every live reference to score_raw_v04
and score_value across the codebase before any changes were made. The audit also
found that ranking.js was computing peer and universe rankings on score_value
(the pre-renormalisation intermediate) rather than score_coverage_weighted — a
pre-existing defect that affects the 10 null-S-pillar institutions.
Decision
Rename score_raw_v04 → score_raw_intermediate on both score_stage1_esg
and score_pillar (migration 043). The rename announces the column's internal-only
status. No values change; score_coverage_weighted is byte-identical before and
after. score_value continues to exist as a column (it carries the same numeric
value as score_raw_intermediate) and is read by score-cli.js for display
purposes; it is not the target of this migration.
score_raw_intermediate is a pre-renormalisation diagnostic intermediate. It must
not appear on any user-facing surface and must not be used in handoffs. Canonical
surfaced score is score_coverage_weighted.
Drop the "Raw v0.4" line from the institution hero block (views/institution.ejs
lines 151–152 pre-migration). The institution hero is a user-facing read surface;
the canonical score_coverage_weighted is the only score that belongs there. The
raw intermediate retains diagnostic value in the database column; dropping the hero
line costs nothing in debuggability. After this change no surface renders the
intermediate value.
Update all pipeline code readers (composite.js, pillar.js, ranking.js,
routes/index.js, routes/holdings.js) to reference score_raw_intermediate.
In ranking.js the identifier update changes score_value → score_raw_intermediate
in both the SQL SELECT and all downstream JS property accesses. The ranking
computation itself is unchanged (see ranking defect note below).
Consequences
After this migration:
- No active code references
score_raw_v04. - No surface renders the pre-renormalisation intermediate.
score_raw_intermediateis the single named handle for the internal diagnostic column; callers that need to inspect the intermediate can find it unambiguously.score_coverage_weightedis unambiguously the canonical score on every surface and in every handoff.
Rejected alternative
Hard removal of score_raw_v04 — dropping the column was considered. Rejected
because:
1. Blast radius: score_value would remain, creating a different column with the
same value and the same naming confusion — the problem is not solved by dropping
the alias.
2. Loss of diagnostic handle: the pre-renormalisation intermediate is useful during
scoring debug to confirm that renormalisation is doing what it should. Keeping
it under a name that announces "internal only" preserves this with zero cost.
The rename satisfies both objectives: the column is clearly internal, and it is accessible for diagnostic use.
Ranking.js peer-ladder fix — DONE (2026-05-22)
ranking.js originally ranked peer and universe positions on score_raw_intermediate
(previously score_value), not score_coverage_weighted. This was a pre-existing
defect, not introduced by migration 043, but deferred from it because re-ranking
moves positions and would have broken the byte-identical verification that the rename
depended on.
The fix was applied in a separate commit (follow-up to this ADR). computeRankings
now reads score_coverage_weighted for all peer-group ladder, universe ranking, and
peer_distribution snapshot computations. Null score_coverage_weighted values are
handled defensively (skipped from distributions; institution gets null rank).
Position movement confirmed correct: 12 financials and 2 non-financials have diverging raw vs CW scores (null S-pillar or null G-pillar). Among the 16 Diversified Banks sub-industry cohort, null-pillar financials shifted up (BNP/Citi +20pp, JPMorgan/ING +13pp, AIB/BoI +7pp) relative to the S-data financials, which dropped (Barclays −27pp, HSBC Holdings −27pp, NatWest/Lloyds −13pp). Santander/Deutsche/ UniCredit unchanged (tied at top in both orderings). Goldman Sachs unchanged (same relative position in sector-40 group). All non-financial sector groups stable. No movement outside divergence prediction. Score_coverage_weighted byte-identical to pre-fix state; only ranks changed.