feat: math core v3 engine upgrade

This commit is contained in:
Celes Renata
2026-06-27 12:21:41 +00:00
parent 365bc5d4b7
commit b4bf0f2361
34 changed files with 11693 additions and 3 deletions
+68
View File
@@ -10,10 +10,14 @@ from __future__ import annotations
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING
from services.aggregation.scoring import WeightedSignal
from services.shared.schemas import DisagreementDetail
if TYPE_CHECKING:
from services.aggregation.worker import EvidenceCluster
@dataclass
class CatalystEntry:
@@ -236,3 +240,67 @@ def _detect_catalyst_disagreement(
))
return details
# ---------------------------------------------------------------------------
# V3 LLR Entropy Contradiction
# ---------------------------------------------------------------------------
def compute_v3_contradiction(clusters: list[EvidenceCluster]) -> float:
"""Compute LLR entropy contradiction score.
Uses Shannon entropy over positive/negative cluster LLR magnitudes,
weighted by a volume factor that grows with total evidence mass.
Formula:
E_pos = sum(max(LLR_c, 0))
E_neg = sum(max(-LLR_c, 0))
E_total = E_pos + E_neg
f_pos = E_pos / E_total, f_neg = E_neg / E_total
H_conflict = -f_pos × log2(f_pos) - f_neg × log2(f_neg)
volume_factor = 1 - exp(-E_total / 3.0)
result = H_conflict × volume_factor, bounded in [0.0, 1.0]
Returns 0.0 when:
- clusters is empty
- E_total == 0 (all cluster LLRs are zero)
- Only one direction exists (E_pos == 0 or E_neg == 0)
Requirements: 7.17.7
"""
if not clusters:
return 0.0
e_pos = 0.0
e_neg = 0.0
for cluster in clusters:
llr_c = cluster.cluster_llr
if llr_c > 0.0:
e_pos += llr_c
elif llr_c < 0.0:
e_neg += -llr_c # max(-LLR_c, 0) when LLR_c < 0
e_total = e_pos + e_neg
# No evidence or unidirectional → no contradiction
if e_total == 0.0:
return 0.0
if e_pos == 0.0 or e_neg == 0.0:
return 0.0
# Compute fractions
f_pos = e_pos / e_total
f_neg = e_neg / e_total
# Shannon entropy H_conflict = -f_pos × log2(f_pos) - f_neg × log2(f_neg)
# 0 × log2(0) is treated as 0, but the early returns above guarantee
# both f_pos and f_neg are positive here.
h_conflict = -f_pos * math.log2(f_pos) - f_neg * math.log2(f_neg)
# Volume factor: suppresses score when total evidence mass is small
volume_factor = 1.0 - math.exp(-e_total / 3.0)
# Final score bounded to [0.0, 1.0]
result = h_conflict * volume_factor
return max(0.0, min(1.0, result))