Spaces:
Running
Running
feat: unified analyze uç noktası ve isolated test istemci yapılandırma eklendi
Browse files- 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=[])
|