Radar axes: one canonical order everywhere
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 1m3s
Deploy / deploy (push) Successful in 35s

The dashboard fingerprints sorted score dimensions alphabetically
while the ticker page rendered them in API order, so the same ticker
drew a differently-shaped polygon on each page.

New shared helper radarAxesFromDimensions() pins the axis order to the
backend's DIMENSIONS list (technical, sr_quality, sentiment,
fundamental, momentum; unknown dimensions append alphabetically) and
centralizes the label truncation. Used by the dashboard fingerprint
strip, the ticker header radar, and ScoreCard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 15:25:26 +02:00
co-authored by Claude Fable 5
parent c7a198ba8e
commit 72054d06b0
4 changed files with 30 additions and 31 deletions
+5 -13
View File
@@ -11,7 +11,7 @@ import { regimeColor, regimeDot, regimeHeadline } from '../lib/regime';
import { Callout } from '../components/ui/Callout';
import { Section } from '../components/ui/Section';
import { OpenTradesPanel } from '../components/dashboard/OpenTradesPanel';
import { PriceRail, RadarChart } from '../components/charts/horizon';
import { PriceRail, RadarChart, radarAxesFromDimensions } from '../components/charts/horizon';
import type { RadarAxis } from '../components/charts/horizon';
import { SkeletonCard, SkeletonTable } from '../components/ui/Skeleton';
import { tradePnl } from '../lib/paperTrade';
@@ -259,22 +259,14 @@ export default function DashboardPage() {
}, [trades.data, activation.data]);
// Mini score fingerprints for the qualified setups (dimension scores come
// from the rankings endpoint; axis order is fixed alphabetically so shapes
// stay comparable across symbols).
// from the rankings endpoint; axis order is the canonical one shared with
// the ticker page so shapes stay comparable everywhere).
const rankings = useRankings();
const fingerprints = useMemo(() => {
const dimsBySymbol = new Map<string, RadarAxis[]>();
for (const r of rankings.data?.rankings ?? []) {
const dims = [...r.dimensions].sort((a, b) => a.dimension.localeCompare(b.dimension));
if (dims.length >= 3) {
dimsBySymbol.set(
r.symbol.toUpperCase(),
dims.map((d) => ({
label: d.dimension.length > 9 ? `${d.dimension.slice(0, 8)}.` : d.dimension,
full: d.dimension,
value: d.score,
})),
);
if (r.dimensions.length >= 3) {
dimsBySymbol.set(r.symbol.toUpperCase(), radarAxesFromDimensions(r.dimensions));
}
}
return radar.qualified