Wave 3: Record by grade tier — shared TierRecord across dashboard, /u, ledger
Addition 2 (non-negotiable): the model's record must show PER GRADE TIER (A+ went X-Y, A X-Y, …) everywhere the record appears. A blended % hides the proof that higher grades win more — the tier calibration IS the credibility. The backend already computed `by_tier` in getModelAggregate; this is display propagation via ONE shared component (the class fix, not five one-offs). - web/src/lib/tierRecord.js — testable CommonJS row-builder. W-L counts ALWAYS (honest at any n); hit-% only when the upstream n≥20 gate passed (hit_pct != null), else "RECORD BUILDING · N settled". Order A+ A B C D F. A-tier is the only edge (green) tier — no glow below A, red reserved for outcomes. - web/src/components/vyndr/TierRecord.tsx — the ONE shared table. Presentational (byTier) for /u + ledger; self-fetch (/api/ledger/model, sport-scoped) for the dashboard. Fully self-hides until a tier has a settled read. - Ledger swaps its inline TierCalibration for the shared component (single source). /u PublicProfile renders it below the blended hero (by_tier added to the aggregate type; it already flows through the route + proxy untouched). Dashboard gains a compact per-tier surface. - Endpoint/proxy audit: profiles.js + ledger.js return the full aggregate (by_tier included); both Next proxies pass the body through — no threading needed. No change to the gate or math in getModelAggregate. Tests: tests/unit/tierRecord.test.js (row logic + tier order + edge contract + source-assert all three surfaces render the shared component). ledgerService test gains an A+-stands-alone bucketing case. Full suite green (243 suites / 2961 tests); web next build exits 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { GradePill } from '@/components/GradeCard';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { Skeleton, EmptyState, ArchetypeBadge, BookWordmark } from '@/components/vyndr';
|
||||
import { Skeleton, EmptyState, ArchetypeBadge, BookWordmark, TierRecord } from '@/components/vyndr';
|
||||
import { clvMode, CLV_FLAT_LINE } from '@/lib/clvDisplay';
|
||||
|
||||
/**
|
||||
@@ -177,30 +177,6 @@ export default function LedgerPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const TIER_ORDER = ['A+', 'A', 'B', 'C', 'D', 'F'];
|
||||
|
||||
/** Session 60 (5.5) — per-tier calibration: the separation between tiers is
|
||||
* the proof the grades mean something. A tier under n≥20 shows "building",
|
||||
* never a small-sample percentage. Self-hides until ANY tier is ready. */
|
||||
function TierCalibration({ agg, minSample }: { agg: ModelAggregate; minSample: number }) {
|
||||
const tiers = agg.by_tier || {};
|
||||
const anyReady = TIER_ORDER.some((t) => tiers[t] && tiers[t].hit_pct != null);
|
||||
if (!anyReady) return null;
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--border)' }}>
|
||||
{TIER_ORDER.filter((t) => tiers[t] && tiers[t].settled > 0).map((t) => {
|
||||
const b = tiers[t];
|
||||
const ready = b.hit_pct != null;
|
||||
return (
|
||||
<span key={t} className="mono" style={{ fontSize: 11.5, fontWeight: 700, padding: '4px 10px', borderRadius: 6, border: '1px solid var(--border-hi)', color: ready ? 'var(--text-primary)' : 'var(--text-tertiary)' }}>
|
||||
{t}-TIER · {ready ? `${b.hits}-${b.misses} · ${b.hit_pct}%` : `building (${b.settled}/${minSample})`}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** S6 (A1 board) · reframed DS4 — settled-CLV display. The n≥20 gate lives
|
||||
* in the server (clv_distribution set). DESIGN-SPEC Part 3 #16: a near-flat
|
||||
* distribution renders as one giant bar + six empty stubs — it reads as
|
||||
@@ -305,7 +281,9 @@ function ModelHeader({ agg, minSample }: { agg: ModelAggregate; minSample: numbe
|
||||
</div>
|
||||
)}
|
||||
<ClvDistribution agg={agg} />
|
||||
<TierCalibration agg={agg} minSample={minSample} />
|
||||
{/* Wave 3 (Addition 2) — the ONE shared record-by-grade-tier table,
|
||||
identical here, on the dashboard, and on /u. */}
|
||||
<TierRecord byTier={agg.by_tier} minSample={minSample} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user