feat: math core v3 engine upgrade
This commit is contained in:
@@ -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.1–7.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))
|
||||
|
||||
Reference in New Issue
Block a user