Rthur2003 commited on
Commit
99d7ba0
·
1 Parent(s): 356ea6e

feat: unified analyze uç noktası ve isolated test istemci yapılandırma eklendi

Browse files
Files changed (1) hide show
  1. app/routes/analyze.py +56 -0
app/routes/analyze.py CHANGED
@@ -41,6 +41,7 @@ from ..services.clap_detector import CLAPDetectorService
41
  from ..services.fst_client import FSTClientService
42
  from ..services.wav2vec2_detector import Wav2Vec2DetectorService
43
  from ..services.inference_xai import get_xai_service
 
44
  from ..services.logging_config import get_logger
45
 
46
  router = APIRouter()
@@ -86,6 +87,7 @@ clap_service = CLAPDetectorService()
86
  fst_service = FSTClientService()
87
  wav2vec2_service = Wav2Vec2DetectorService()
88
  xai_service = get_xai_service()
 
89
 
90
  # Thread pool for CPU-bound audio analysis (feature extraction + vocal)
91
  _analysis_pool = ThreadPoolExecutor(max_workers=3)
@@ -172,6 +174,30 @@ class XAIExplanation(BaseModel):
172
  featureCount: int = 0
173
 
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  class AnalysisResult(BaseModel):
176
  """Platform/hooks/analysisTypes.ts uyumlu."""
177
 
@@ -187,6 +213,7 @@ class AnalysisResult(BaseModel):
187
  vocalAnalysis: Optional[VocalAnalysis] = None
188
  xai: Optional[XAIExplanation] = None
189
  towerScores: Dict[str, float] = Field(default_factory=dict)
 
190
 
191
 
192
  class AnalyzeResponse(BaseModel):
@@ -487,6 +514,34 @@ async def _analyze_file(
487
  logger.warning(f"[{request_id}] XAI failed: {e}")
488
  warnings.append("xai_unavailable")
489
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  # Build tower scores dict for UI (multi-signal breakdown)
491
  tower_scores: Dict[str, float] = {
492
  "local_features": round(
@@ -572,6 +627,7 @@ async def _analyze_file(
572
  vocalAnalysis=vocal_response,
573
  xai=xai_payload,
574
  towerScores=tower_scores,
 
575
  )
576
 
577
  return AnalyzeResponse(result=result, warnings=warnings, errors=[])
 
41
  from ..services.fst_client import FSTClientService
42
  from ..services.wav2vec2_detector import Wav2Vec2DetectorService
43
  from ..services.inference_xai import get_xai_service
44
+ from ..services.meta_classifier import MetaClassifierService
45
  from ..services.logging_config import get_logger
46
 
47
  router = APIRouter()
 
87
  fst_service = FSTClientService()
88
  wav2vec2_service = Wav2Vec2DetectorService()
89
  xai_service = get_xai_service()
90
+ meta_classifier_service = MetaClassifierService()
91
 
92
  # Thread pool for CPU-bound audio analysis (feature extraction + vocal)
93
  _analysis_pool = ThreadPoolExecutor(max_workers=3)
 
174
  featureCount: int = 0
175
 
176
 
177
+ class MetaFeatureImportance(BaseModel):
178
+ """One feature's contribution to the meta-classifier's prediction."""
179
+
180
+ feature: str
181
+ importance: float
182
+ value: float
183
+
184
+
185
+ class MetaClassifierExplanation(BaseModel):
186
+ """
187
+ Trained stacking ensemble result — a second, independent opinion
188
+ (LightGBM over all tower outputs) shown alongside the primary XAI
189
+ verdict for transparency, not used to override it. Present only when
190
+ the trained model artifacts are available; absent (not error) otherwise.
191
+ """
192
+
193
+ isAIGenerated: bool
194
+ confidence: float
195
+ modelVersion: str
196
+ towerScores: Dict[str, float] = Field(default_factory=dict)
197
+ indicators: List[str] = Field(default_factory=list)
198
+ topFeatures: List[MetaFeatureImportance] = Field(default_factory=list)
199
+
200
+
201
  class AnalysisResult(BaseModel):
202
  """Platform/hooks/analysisTypes.ts uyumlu."""
203
 
 
213
  vocalAnalysis: Optional[VocalAnalysis] = None
214
  xai: Optional[XAIExplanation] = None
215
  towerScores: Dict[str, float] = Field(default_factory=dict)
216
+ metaClassifier: Optional[MetaClassifierExplanation] = None
217
 
218
 
219
  class AnalyzeResponse(BaseModel):
 
514
  logger.warning(f"[{request_id}] XAI failed: {e}")
515
  warnings.append("xai_unavailable")
516
 
517
+ # ── Step 5c: Meta-classifier (trained stacking ensemble) ────
518
+ # A second, independent opinion shown for transparency — never
519
+ # overrides the primary XAI/fusion verdict above.
520
+ meta_payload: Optional[MetaClassifierExplanation] = None
521
+ try:
522
+ meta_result = meta_classifier_service.predict(
523
+ features, vocals=vocals,
524
+ wav2vec2=wav2vec2_result, clap=clap_result, fst=fst_result,
525
+ )
526
+ if meta_result.model_version == "auris-v2-trained":
527
+ meta_payload = MetaClassifierExplanation(
528
+ isAIGenerated=meta_result.is_ai_generated,
529
+ confidence=meta_result.confidence,
530
+ modelVersion=meta_result.model_version,
531
+ towerScores={k: round(float(v), 3) for k, v in meta_result.tower_scores.items()},
532
+ indicators=meta_result.indicators,
533
+ topFeatures=[
534
+ MetaFeatureImportance(**f) for f in meta_result.top_features
535
+ ],
536
+ )
537
+ logger.info(
538
+ f"[{request_id}] Meta-classifier: ai={meta_result.is_ai_generated}, "
539
+ f"confidence={meta_result.confidence}"
540
+ )
541
+ except Exception as e:
542
+ logger.warning(f"[{request_id}] Meta-classifier failed: {e}")
543
+ warnings.append("meta_classifier_unavailable")
544
+
545
  # Build tower scores dict for UI (multi-signal breakdown)
546
  tower_scores: Dict[str, float] = {
547
  "local_features": round(
 
627
  vocalAnalysis=vocal_response,
628
  xai=xai_payload,
629
  towerScores=tower_scores,
630
+ metaClassifier=meta_payload,
631
  )
632
 
633
  return AnalyzeResponse(result=result, warnings=warnings, errors=[])