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
@@ -134,6 +134,27 @@ export interface RadarAxis {
full?: string;
}
/** Canonical axis order — mirrors the backend's DIMENSIONS list
* (app/services/scoring_service.py). Every radar in the app must use this so
* fingerprint shapes stay comparable between the dashboard and ticker pages. */
const RADAR_DIMENSION_ORDER = ['technical', 'sr_quality', 'sentiment', 'fundamental', 'momentum'];
export function radarAxesFromDimensions(
dimensions: { dimension: string; score: number }[],
): RadarAxis[] {
const rank = (d: string) => {
const i = RADAR_DIMENSION_ORDER.indexOf(d.toLowerCase());
return i === -1 ? RADAR_DIMENSION_ORDER.length : i;
};
return [...dimensions]
.sort((a, b) => rank(a.dimension) - rank(b.dimension) || a.dimension.localeCompare(b.dimension))
.map((d) => ({
label: d.dimension.length > 9 ? `${d.dimension.slice(0, 8)}.` : d.dimension,
full: d.dimension,
value: d.score,
}));
}
export function RadarChart({
axes, size = 120, labels = true,
}: { axes: RadarAxis[]; size?: number; labels?: boolean }) {
+2 -9
View File
@@ -1,6 +1,6 @@
import { useState } from 'react';
import { DimensionBreakdownPanel } from '../ticker/DimensionBreakdownPanel';
import { RadarChart } from '../charts/horizon';
import { RadarChart, radarAxesFromDimensions } from '../charts/horizon';
import type { DimensionScoreDetail, CompositeBreakdown } from '../../lib/types';
interface ScoreCardProps {
@@ -97,14 +97,7 @@ export function ScoreCard({ compositeScore, dimensions, compositeBreakdown, show
className={showComposite ? 'ml-auto' : 'mx-auto sm:mx-0'}
title="Score fingerprint — hover a corner for the exact value"
>
<RadarChart
axes={dimensions.map((d) => ({
label: d.dimension.length > 9 ? `${d.dimension.slice(0, 8)}.` : d.dimension,
full: d.dimension,
value: d.score,
}))}
size={104}
/>
<RadarChart axes={radarAxesFromDimensions(dimensions)} size={104} />
</div>
)}
</div>