feat: explainability — décomposition des 4 composantes du score dans le SpotPanel
Expose breakdown {distance, density, velocity, trend} dans l'API /spots/{id}/score
(pour le score courant reconstruit depuis les champs stockés, et pour chaque horizon
avec confidence appliquée). Affiche les barres avec valeur/max dans le SpotPanel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
|
||||
level: data.horizons[activeStep].level,
|
||||
distance: data.horizons[activeStep].distanceKm,
|
||||
confidence: data.horizons[activeStep].confidence,
|
||||
breakdown: data.horizons[activeStep].breakdown,
|
||||
} : null);
|
||||
|
||||
const lvl = displayScore?.level;
|
||||
@@ -137,6 +138,8 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
|
||||
<dd>{new Date(displayScore.computedAt).toLocaleString('fr-FR')}</dd></>
|
||||
)}
|
||||
</dl>
|
||||
|
||||
<ScoreBreakdown breakdown={displayScore.breakdown} />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
@@ -148,6 +151,39 @@ export default function SpotPanel({ spot, activeStep, onClose }) {
|
||||
);
|
||||
}
|
||||
|
||||
const BREAKDOWN_ITEMS = [
|
||||
{ key: 'distance', label: 'Distance', max: 40, icon: '📏' },
|
||||
{ key: 'density', label: 'Densité', max: 30, icon: '🌿' },
|
||||
{ key: 'velocity', label: 'Vitesse', max: 20, icon: '➡' },
|
||||
{ key: 'trend', label: 'Tendance', max: 10, icon: '📈' },
|
||||
];
|
||||
|
||||
function ScoreBreakdown({ breakdown }) {
|
||||
if (!breakdown) return null;
|
||||
|
||||
return (
|
||||
<div className="score-breakdown">
|
||||
<p className="score-breakdown__title">Détail du score</p>
|
||||
{BREAKDOWN_ITEMS.map(({ key, label, max, icon }) => {
|
||||
const val = breakdown[key] ?? 0;
|
||||
const pct = max > 0 ? (val / max) * 100 : 0;
|
||||
return (
|
||||
<div key={key} className="score-breakdown__row">
|
||||
<span className="score-breakdown__icon">{icon}</span>
|
||||
<span className="score-breakdown__label">{label}</span>
|
||||
<div className="score-breakdown__track">
|
||||
<div className="score-breakdown__fill" style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className="score-breakdown__value">
|
||||
{val}<span className="score-breakdown__max">/{max}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FeedbackButton({ spot }) {
|
||||
const [sent, setSent] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user