Skip to content

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_v04score_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_valuescore_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_intermediate is the single named handle for the internal diagnostic column; callers that need to inspect the intermediate can find it unambiguously.
  • score_coverage_weighted is 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.