// Renderuje sub-scores merge candidate jako horizontal bars zamiast JSON dumpa. // Wagi: fp 0.20 / title 0.15 / performers 0.35 / duration 0.25 / date 0.05 (aggregator) // Wartości w `reasons` to top-level keys (fp, title, performers, duration, date) jako float [0..1]. import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { theme } from '../theme'; const SUBSCORES: { key: string; label: string }[] = [ { key: 'fp', label: 'fingerprint' }, { key: 'title', label: 'title' }, { key: 'performers', label: 'performers' }, { key: 'duration', label: 'duration' }, { key: 'date', label: 'date' }, ]; function barColor(v: number): string { if (v >= 0.85) return theme.good; if (v >= 0.5) return theme.warn; if (v > 0) return theme.bad; return theme.border; } export function SubScoreBars({ reasons }: { reasons: Record }) { // reasons.path = "composite" / "cross_source" / "fp_phash" / etc. // reasons.{fp,title,performers,duration,date} are floats or null. // reasons.reasons.aggregator_weights or reasons.reasons.weights — for context label. const path = (reasons.path as string) ?? 'unknown'; const studioMatch = reasons.studio_match as boolean | null | undefined; const inner = (reasons.reasons as Record) ?? {}; const flags: string[] = []; if (inner.aggregator_low_performer_cap) flags.push('cap: low perf'); if (inner.duration_perf_strong_match_bump) flags.push('bumped (dur+perf)'); if (inner.studio_ignored_aggregator) flags.push('studio ignored'); if (inner.studio_mismatch_overridden_by_fp) flags.push('fp overrode studio'); return ( path {path} {studioMatch !== null && studioMatch !== undefined ? ( studio match {studioMatch ? 'yes' : 'no'} ) : null} {SUBSCORES.map(({ key, label }) => { const v = reasons[key] as number | null | undefined; const present = typeof v === 'number'; return ( {label} {present ? ( ) : null} {present ? (v as number).toFixed(2) : '—'} ); })} {flags.length > 0 ? ( {flags.map((f) => ( {f} ))} ) : null} ); } const styles = StyleSheet.create({ container: { backgroundColor: theme.card, borderColor: theme.border, borderWidth: 1, borderRadius: 8, padding: 12, marginBottom: 12, gap: 6, }, metaRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, metaLabel: { color: theme.muted, fontSize: 12, textTransform: 'uppercase' }, metaValue: { color: theme.fg, fontSize: 13, fontWeight: '600' }, barRow: { flexDirection: 'row', alignItems: 'center', gap: 8, }, barLabel: { color: theme.muted, fontSize: 12, width: 80 }, barTrack: { flex: 1, height: 8, backgroundColor: theme.bg, borderRadius: 4, overflow: 'hidden', }, barFill: { height: '100%', borderRadius: 4, }, barValue: { color: theme.fg, fontSize: 12, fontFamily: 'Courier', width: 40, textAlign: 'right' }, barValueMuted: { color: theme.muted }, flagsRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 4, marginTop: 4 }, flagPill: { backgroundColor: theme.bg, borderColor: theme.warn, borderWidth: 1, borderRadius: 10, paddingHorizontal: 8, paddingVertical: 2, }, flagText: { color: theme.warn, fontSize: 11 }, });