# core/aggregator.py # Combines BERT score + stylometric score into a final probability. from core.stylometrics import compute_stylometric_score from core.explainer import find_ai_phrases, generate_explanation from config.settings import settings def aggregate_scores( bert_result: dict, text: str, sentence_scores: list[dict], ) -> dict: """ Two-signal hybrid: - BERT (70%): neural, trained on Wikipedia pairs - Stylometrics (30%): model-agnostic structural features """ stylo_result = compute_stylometric_score(text) stylo_score = stylo_result["stylometric_ai_score"] bert_prob = bert_result["ai_probability"] bert_weight = 0.70 stylo_weight = 0.30 final_probability = ( bert_prob * bert_weight + stylo_score * stylo_weight ) final_probability = round(max(0.0, min(1.0, final_probability)), 4) ai_phrases = find_ai_phrases(text) explanation = generate_explanation( ai_probability=final_probability, stylometric_data=stylo_result, sentence_scores=sentence_scores, ai_phrases=ai_phrases, ) is_ai = final_probability >= settings.CONFIDENCE_THRESHOLD return { "ai_probability": final_probability, "is_ai": is_ai, "verdict": explanation["verdict"], "confidence_threshold": settings.CONFIDENCE_THRESHOLD, "scores": { "bert_score": bert_prob, "stylometric_score": round(stylo_score, 4), "bert_weight": bert_weight, "stylometric_weight": stylo_weight, }, "stylometric_features": stylo_result["features"], "sentence_scores": sentence_scores, "explanation": explanation, "chunks_analyzed": bert_result.get("chunks_analyzed", 1), }