Spaces:
Running
Running
feat: add parsing functions for CLAP and FST results in score fusion
Browse files- app/services/score_fusion.py +69 -19
app/services/score_fusion.py
CHANGED
|
@@ -200,64 +200,114 @@ def _parse_external_ses_analizi(data: Optional[dict]) -> Optional[_ExternalScore
|
|
| 200 |
return None
|
| 201 |
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
# ═══════════════════════════════════════════════════════════════════════
|
| 204 |
# PRIVATE — Score fusion
|
| 205 |
# ═══════════════════════════════════════════════════════════════════════
|
| 206 |
|
| 207 |
def _fuse(
|
| 208 |
local: float,
|
|
|
|
|
|
|
| 209 |
ext_mai: Optional[_ExternalScore],
|
| 210 |
ext_ses: Optional[_ExternalScore],
|
| 211 |
) -> tuple[float, str, str]:
|
| 212 |
"""
|
| 213 |
-
Fuse local + external scores.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
Returns: (confidence, decision_source, analysis_mode)
|
| 216 |
"""
|
| 217 |
-
scores: list[tuple[float, float, str]] = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
-
#
|
| 220 |
-
|
|
|
|
| 221 |
|
|
|
|
| 222 |
if ext_mai and ext_mai.available:
|
| 223 |
-
scores.append((ext_mai.confidence, 0.
|
| 224 |
|
| 225 |
if ext_ses and ext_ses.available:
|
| 226 |
-
scores.append((ext_ses.confidence, 0.
|
| 227 |
|
| 228 |
# Normalize weights
|
| 229 |
total_weight = sum(w for _, w, _ in scores)
|
| 230 |
confidence = sum(s * (w / total_weight) for s, w, _ in scores)
|
| 231 |
|
| 232 |
-
# Determine
|
| 233 |
-
if
|
| 234 |
-
|
| 235 |
-
mode = "production"
|
| 236 |
-
elif ext_ses and ext_ses.available:
|
| 237 |
decision_source = "auris_fusion"
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
| 239 |
else:
|
| 240 |
decision_source = "auris_local"
|
| 241 |
-
mode = "production" # Still production — we have real feature analysis
|
| 242 |
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
confidence = max(0.51, min(0.97, confidence))
|
| 245 |
confidence = round(confidence, 4)
|
| 246 |
|
| 247 |
-
# Agreement bonus
|
| 248 |
if len(scores) > 1:
|
| 249 |
all_ai = all(s > 0.5 for s, _, _ in scores)
|
| 250 |
all_human = all(s <= 0.5 for s, _, _ in scores)
|
| 251 |
if all_ai:
|
| 252 |
-
|
|
|
|
|
|
|
| 253 |
elif all_human:
|
| 254 |
-
|
|
|
|
| 255 |
|
| 256 |
confidence = round(max(0.51, min(0.97, confidence)), 4)
|
| 257 |
|
|
|
|
| 258 |
logger.info(
|
| 259 |
-
f"Fusion: {len(scores)} sources
|
| 260 |
-
f"
|
| 261 |
)
|
| 262 |
|
| 263 |
return confidence, decision_source, mode
|
|
|
|
| 200 |
return None
|
| 201 |
|
| 202 |
|
| 203 |
+
def _parse_clap_result(
|
| 204 |
+
result: Optional[CLAPResult],
|
| 205 |
+
) -> Optional[_ExternalScore]:
|
| 206 |
+
"""Convert CLAPResult to internal score format."""
|
| 207 |
+
if not result or not result.available:
|
| 208 |
+
return None
|
| 209 |
+
return _ExternalScore(
|
| 210 |
+
available=True,
|
| 211 |
+
is_ai=result.is_ai,
|
| 212 |
+
confidence=result.confidence,
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _parse_fst_result(
|
| 217 |
+
result: Optional[FSTResult],
|
| 218 |
+
) -> Optional[_ExternalScore]:
|
| 219 |
+
"""Convert FSTResult to internal score format."""
|
| 220 |
+
if not result or not result.available:
|
| 221 |
+
return None
|
| 222 |
+
return _ExternalScore(
|
| 223 |
+
available=True,
|
| 224 |
+
is_ai=result.is_ai,
|
| 225 |
+
confidence=result.confidence,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
# ═══════════════════════════════════════════════════════════════════════
|
| 230 |
# PRIVATE — Score fusion
|
| 231 |
# ═══════════════════════════════════════════════════════════════════════
|
| 232 |
|
| 233 |
def _fuse(
|
| 234 |
local: float,
|
| 235 |
+
clap: Optional[_ExternalScore],
|
| 236 |
+
fst: Optional[_ExternalScore],
|
| 237 |
ext_mai: Optional[_ExternalScore],
|
| 238 |
ext_ses: Optional[_ExternalScore],
|
| 239 |
) -> tuple[float, str, str]:
|
| 240 |
"""
|
| 241 |
+
Fuse local + CLAP + FST + legacy external scores.
|
| 242 |
+
|
| 243 |
+
Weight allocation (normalized when layers are missing):
|
| 244 |
+
Layer 1 (local features+vocals): 0.40
|
| 245 |
+
Layer 2 (CLAP embedding): 0.20
|
| 246 |
+
Layer 3 (FST external): 0.25
|
| 247 |
+
Legacy (music_ai): 0.10
|
| 248 |
+
Legacy (ses_analizi): 0.05
|
| 249 |
|
| 250 |
Returns: (confidence, decision_source, analysis_mode)
|
| 251 |
"""
|
| 252 |
+
scores: list[tuple[float, float, str]] = []
|
| 253 |
+
|
| 254 |
+
# Layer 1 — always present
|
| 255 |
+
scores.append((local, 0.40, "auris_local"))
|
| 256 |
+
|
| 257 |
+
# Layer 2 — CLAP embeddings
|
| 258 |
+
if clap and clap.available:
|
| 259 |
+
scores.append((clap.confidence, 0.20, "clap"))
|
| 260 |
|
| 261 |
+
# Layer 3 — FST external (highest individual accuracy)
|
| 262 |
+
if fst and fst.available:
|
| 263 |
+
scores.append((fst.confidence, 0.25, "fst"))
|
| 264 |
|
| 265 |
+
# Legacy external services
|
| 266 |
if ext_mai and ext_mai.available:
|
| 267 |
+
scores.append((ext_mai.confidence, 0.10, "music_ai"))
|
| 268 |
|
| 269 |
if ext_ses and ext_ses.available:
|
| 270 |
+
scores.append((ext_ses.confidence, 0.05, "ses_analizi"))
|
| 271 |
|
| 272 |
# Normalize weights
|
| 273 |
total_weight = sum(w for _, w, _ in scores)
|
| 274 |
confidence = sum(s * (w / total_weight) for s, w, _ in scores)
|
| 275 |
|
| 276 |
+
# Determine decision source based on available layers
|
| 277 |
+
n_layers = sum(1 for _, _, src in scores if src != "auris_local")
|
| 278 |
+
if n_layers >= 2:
|
|
|
|
|
|
|
| 279 |
decision_source = "auris_fusion"
|
| 280 |
+
elif n_layers == 1:
|
| 281 |
+
# Name includes the single extra layer
|
| 282 |
+
extra = [src for _, _, src in scores if src != "auris_local"][0]
|
| 283 |
+
decision_source = f"auris_fusion_{extra}"
|
| 284 |
else:
|
| 285 |
decision_source = "auris_local"
|
|
|
|
| 286 |
|
| 287 |
+
mode: str = "production"
|
| 288 |
+
|
| 289 |
+
# Clamp to valid range
|
| 290 |
confidence = max(0.51, min(0.97, confidence))
|
| 291 |
confidence = round(confidence, 4)
|
| 292 |
|
| 293 |
+
# Agreement bonus
|
| 294 |
if len(scores) > 1:
|
| 295 |
all_ai = all(s > 0.5 for s, _, _ in scores)
|
| 296 |
all_human = all(s <= 0.5 for s, _, _ in scores)
|
| 297 |
if all_ai:
|
| 298 |
+
# More layers agreeing → bigger boost
|
| 299 |
+
boost = 1.0 + 0.02 * len(scores)
|
| 300 |
+
confidence = min(0.97, confidence * boost)
|
| 301 |
elif all_human:
|
| 302 |
+
dampen = 1.0 - 0.02 * len(scores)
|
| 303 |
+
confidence = max(0.51, confidence * dampen)
|
| 304 |
|
| 305 |
confidence = round(max(0.51, min(0.97, confidence)), 4)
|
| 306 |
|
| 307 |
+
sources_str = ", ".join(f"{src}={s:.3f}" for s, _, src in scores)
|
| 308 |
logger.info(
|
| 309 |
+
f"Fusion: {len(scores)} sources [{sources_str}] "
|
| 310 |
+
f"-> confidence={confidence}, source={decision_source}"
|
| 311 |
)
|
| 312 |
|
| 313 |
return confidence, decision_source, mode
|