import { memo, useMemo } from 'react' import { Box, Typography } from '@mui/material' import type { AgentMetric, TeamAgent } from '../../domain/teamAgent' import { EmptyState } from '../ui' import { useMatchRuns } from '../../hooks/useMatchRuns' import { useFutureSignals } from '../../hooks/useFutureSignals' import { isRisiko } from '../../lib/leadSignal' import { DS_BG, DS_BORDER, DS_TEXT } from '../../lib/ds' /** * Reiter «Kennzahlen». * * Zeigt die Kennzahlen, die das Personalblatt führt. Es wird nichts * hochgerechnet und nichts ergänzt — was der Katalog nicht führt, steht hier * auch nicht. * * Ausnahme seit Runde 10 (§3.11): Noras beide Kennzahlen kommen aus dem * laufenden Stand der Anwendung und nicht aus dem Katalog. Ein fester Wert * wäre dort eine Behauptung — die eine Zahl zählt tatsächlich ausgeführte * Matching-Läufe, die andere tatsächlich zugeordnete Objekte. */ /** Kennungen, deren Wert zur Laufzeit entsteht. */ const MATCH_RUNS_METRIC = 'nora-metric-match-runs' const AFFECTED_OBJECTS_METRIC = 'nora-metric-affected-objects' const MetricTile = memo(function MetricTile({ metric }: { metric: AgentMetric }) { return ( {metric.value} {metric.label} {metric.hint && ( {metric.hint} )} ) }) export function AgentMetricsTab({ agent }: { agent: TeamAgent }) { const { data: matchRuns = [] } = useMatchRuns() const { data: signals = [] } = useFutureSignals() /** * Die beiden Laufzeit-Kennzahlen einsetzen. * * «Betroffene Objekte» zählt Risikosignale mit eindeutig zugeordnetem * Objekt — und zwar die Objekte, nicht die Signale: Zwei Meldungen über * dasselbe Objekt sind ein betroffenes Objekt, nicht zwei. */ const metrics = useMemo((): AgentMetric[] => { const laeufe = matchRuns.length const betroffene = new Set( signals.filter(sig => isRisiko(sig) && sig.propertyId).map(sig => sig.propertyId!), ).size return agent.metrics.map((m) => { if (m.id === MATCH_RUNS_METRIC) { return { ...m, value: String(laeufe), hint: laeufe === 0 ? 'Noch kein Matching ausgeführt. Die Zahl steigt nur durch einen tatsächlichen Lauf.' : `Zuletzt am ${new Date(matchRuns[0].timestamp).toLocaleString('de-CH', { dateStyle: 'short', timeStyle: 'short' })} für «${matchRuns[0].leadTitle}».`, } } if (m.id === AFFECTED_OBJECTS_METRIC) { return { ...m, value: String(betroffene), hint: betroffene === 0 ? 'Derzeit ist keinem Risikosignal ein Objekt aus dem Bestand eindeutig zugeordnet.' : m.hint, } } return m }) }, [agent.metrics, matchRuns, signals]) if (metrics.length === 0) { return ( ) } return ( {metrics.map((metric) => ( ))} ) }