Rthur2003 commited on
Commit
21d9516
·
1 Parent(s): 45def97

feat: add XAI inference for explainable predictions in analysis results

Browse files
Files changed (1) hide show
  1. app/routes/analyze.py +50 -0
app/routes/analyze.py CHANGED
@@ -434,6 +434,56 @@ async def _analyze_file(
434
  f"confidence={fusion.confidence}, source={fusion.decision_source}"
435
  )
436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  # ── Step 4: Build response ─────────────
438
  vocal_response = None
439
  if vocals and vocals.has_vocals:
 
434
  f"confidence={fusion.confidence}, source={fusion.decision_source}"
435
  )
436
 
437
+ # ── Step 5b: XAI inference (explainable) ────
438
+ xai_payload: Optional[XAIExplanation] = None
439
+ try:
440
+ if xai_service.available:
441
+ xai_result = xai_service.predict(features, vocals)
442
+ if xai_result is not None:
443
+ xai_dict = xai_service.to_dict(xai_result)
444
+ # Convert dicts to pydantic models
445
+ xai_payload = XAIExplanation(
446
+ probability=xai_dict["probability"],
447
+ threshold=xai_dict["threshold"],
448
+ baseProbability=xai_dict["baseProbability"],
449
+ confidenceBand=ConfidenceBandModel(**xai_dict["confidenceBand"]),
450
+ modelVotes=[ModelVoteModel(**v) for v in xai_dict["modelVotes"]],
451
+ bestModel=xai_dict["bestModel"],
452
+ topContributions=[
453
+ FeatureContributionModel(**c)
454
+ for c in xai_dict["topContributions"]
455
+ ],
456
+ allFeatures={
457
+ k: FeatureContributionModel(**v)
458
+ for k, v in xai_dict["allFeatures"].items()
459
+ },
460
+ featureCount=xai_dict["featureCount"],
461
+ )
462
+ logger.info(
463
+ f"[{request_id}] XAI: p={xai_result.probability:.3f}, "
464
+ f"band={xai_result.confidence_band.tier}, "
465
+ f"top={xai_result.top_contributions[0].name if xai_result.top_contributions else 'n/a'}"
466
+ )
467
+ except Exception as e:
468
+ logger.warning(f"[{request_id}] XAI failed: {e}")
469
+ warnings.append("xai_unavailable")
470
+
471
+ # Build tower scores dict for UI (multi-signal breakdown)
472
+ tower_scores: Dict[str, float] = {
473
+ "local_features": round(
474
+ (features.spectral_regularity + features.temporal_patterns
475
+ + features.harmonic_structure) / 3.0, 3,
476
+ ),
477
+ }
478
+ if vocals and vocals.has_vocals:
479
+ tower_scores["vocals"] = round(vocals.vocal_ai_score, 3)
480
+ if clap_result and clap_result.available:
481
+ tower_scores["clap"] = round(clap_result.confidence, 3)
482
+ if fst_result and fst_result.available:
483
+ tower_scores["fst"] = round(fst_result.confidence, 3)
484
+ if xai_payload is not None:
485
+ tower_scores["xai_ensemble"] = round(xai_payload.probability, 3)
486
+
487
  # ── Step 4: Build response ─────────────
488
  vocal_response = None
489
  if vocals and vocals.has_vocals: