refactor: consolidate duplicated UI score/color/badge logic into lib/

- Add matchScoreHex(), criterionScoreColor(), criterionScoreTextColor() to lib/utils.ts
- Add RESULT_TYPE_META (labels + colors) to lib/ds.ts as single source of truth
- Remove 5 local scoreColor() functions: StrongMatchMiniCard, MatchListCard,
  pipelineUtils (re-exported as matchScoreHex), CriterionRow, ScoreBreakdownPanel,
  ScoreInlineBreakdown
- Remove local RESULT_TYPE_META/TYPE_META from MatchCardHeader, ResultTypeBadge;
  remove RESULT_TYPE_LABEL/COLOR from pipelineConstants — all now use lib/ds.ts
- Replace local confidenceColor() in MatchCardHeader, ConfidenceFieldBadge,
  ReliabilityScorePanel with confidenceHex() from lib/utils.ts
- Replace local qualityColor() in propertyHelpers, DataQualityWidget with
  dataQualityColor() from lib/utils.ts
- Fix FUTURE_AVAILABILITY label inconsistency ('Future'/'Zukunft' → 'Zukunftssignal')
- Fix EXTERNAL_MARKET label inconsistency ('Direktinserat' → 'Plattform')
- tsc --noEmit passes with zero errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 01:47:08 +02:00
parent 5d53f35dea
commit 86bdf142b4
16 changed files with 60 additions and 138 deletions
+3 -14
View File
@@ -3,21 +3,10 @@ import { Building2 } from 'lucide-react'
import { MatchScoreDisplay } from './MatchScoreDisplay'
import { HeatBadge } from '../shared'
import { useSessionStore } from '../../stores/sessionStore'
import { RESULT_TYPE_META } from '../../lib/ds'
import { confidenceHex } from '../../lib/utils'
import type { MatchCardViewModel } from './MatchCardViewModel'
const RESULT_TYPE_META: Record<string, { label: string; color: string }> = {
VERIFIED_PORTFOLIO: { label: 'Plattform', color: '#1e3a5f' },
EXTERNAL_MARKET: { label: 'Plattform', color: '#1e3a5f' },
MAISON_WORK: { label: 'Maison Work', color: '#0369a1' },
FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed' },
}
function confidenceColor(score: number): string {
if (score >= 0.75) return '#1a7a4a'
if (score >= 0.55) return '#d97706'
return '#c0392b'
}
interface Props {
vm: MatchCardViewModel
compact?: boolean
@@ -61,7 +50,7 @@ export function MatchCardHeader({ vm, compact }: Props) {
<Chip
label={`${confPct}% Konfidenz`}
size="small"
sx={{ bgcolor: confidenceColor(vm.confidenceScore), color: 'white', fontSize: 11 }}
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 11 }}
/>
{vm.availabilityLabel && (
<Chip label={vm.availabilityLabel} size="small" variant="outlined" sx={{ fontSize: 11 }} />
@@ -1,5 +1,6 @@
import { Box, Divider, LinearProgress, Typography } from '@mui/material'
import type { ScoreFactor } from '../../domain/match'
import { criterionScoreColor, criterionScoreTextColor } from '../../lib/utils'
interface ScoreBreakdownData {
hardMatchScore: number
@@ -22,14 +23,6 @@ const CRITERION_LABEL: Record<string, string> = {
const HARD_KEYS = new Set(['area', 'location', 'budget', 'timing'])
function scoreColor(v: number): 'success' | 'warning' | 'error' {
return v >= 70 ? 'success' : v >= 50 ? 'warning' : 'error'
}
function scoreTextColor(v: number): string {
return v >= 70 ? '#1a7a4a' : v >= 50 ? '#d97706' : '#c0392b'
}
function importanceLabel(weight: number, maxWeight: number): { label: string; color: string } {
const ratio = maxWeight > 0 ? weight / maxWeight : 0
if (ratio >= 0.85) return { label: 'Entscheidend', color: '#1e3a5f' }
@@ -43,7 +36,7 @@ function FactorRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: numb
const label = CRITERION_LABEL[factor.criterion] ?? factor.criterion
const pct = Math.round(factor.weight * 100)
const imp = importanceLabel(factor.weight, maxWeight)
const color = scoreColor(factor.score)
const color = criterionScoreColor(factor.score)
return (
<Box sx={{ mb: 1.25 }}>
@@ -54,7 +47,7 @@ function FactorRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: numb
<Typography variant="caption" sx={{ color: '#cbd5e1', fontSize: '0.65rem' }}>{pct}%</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.75 }}>
<Typography variant="caption" sx={{ fontWeight: 700, color: scoreTextColor(factor.score) }}>
<Typography variant="caption" sx={{ fontWeight: 700, color: criterionScoreTextColor(factor.score) }}>
{factor.score}/100
</Typography>
<Typography variant="caption" sx={{ color: '#94a3b8', fontSize: '0.68rem', minWidth: 40, textAlign: 'right' }}>
@@ -82,7 +75,7 @@ export function ScoreInlineBreakdown({ scoreBreakdown: sb, allFactors, compact =
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: '#1e293b', display: 'block', lineHeight: 1.5 }}>
Hart {sb.hardMatchScore} × 60% + Soft {sb.softFactorScore} × 40%
{' = '}{hardContrib} + {softContrib}{' = '}
<span style={{ fontWeight: 800, color: scoreTextColor(sb.totalScore) }}>{sb.totalScore}</span>
<span style={{ fontWeight: 800, color: criterionScoreTextColor(sb.totalScore) }}>{sb.totalScore}</span>
</Typography>
</Box>
)