"""Conservative confidence defaults for underrepresented classes. When calibration data is insufficient for a specific document type or event class, returns conservative values (0.3-0.5) and marks the result as under-calibrated per Requirement 10.7. """ from __future__ import annotations import logging from services.intelligence_pipeline_v3.confidence.models import ConfidenceResult logger = logging.getLogger(__name__) # Conservative default probabilities by document type. # These are intentionally low (0.3-0.5) to avoid overconfidence # when insufficient calibration data exists. _DOCUMENT_TYPE_DEFAULTS: dict[str, float] = { "news": 0.45, "filing": 0.40, "transcript": 0.40, "press_release": 0.45, "macro_event": 0.35, "unknown": 0.30, } # Conservative default probabilities by event class. # More complex or rare event types get lower defaults. _EVENT_CLASS_DEFAULTS: dict[str, float] = { "earnings_beat": 0.50, "earnings_miss": 0.50, "guidance_raise": 0.45, "guidance_cut": 0.45, "merger_acquisition": 0.40, "product_launch": 0.45, "regulatory_action": 0.40, "management_change": 0.45, "legal_proceeding": 0.40, "supply_chain": 0.35, "rating_change": 0.45, "dividend_change": 0.45, "buyback": 0.45, "macro_policy": 0.35, "geopolitical": 0.30, "sector_rotation": 0.35, "unknown": 0.30, } # Features used when returning conservative defaults _DEFAULT_FEATURES_USED = [ "document_type_prior", "event_class_prior", ] def get_default_confidence( document_type: str, event_class: str, ) -> ConfidenceResult: """Return a conservative confidence result for underrepresented classes. Used when calibration data is insufficient for the given document type and event class combination. Returns conservative probabilities (0.3-0.5) and marks the result as under-calibrated. Parameters ---------- document_type The document type (news, filing, transcript, etc.). event_class The classified event type (earnings_beat, merger_acquisition, etc.). Returns ------- ConfidenceResult A conservative confidence result with under_calibrated=True. """ doc_default = _DOCUMENT_TYPE_DEFAULTS.get( document_type, _DOCUMENT_TYPE_DEFAULTS["unknown"] ) event_default = _EVENT_CLASS_DEFAULTS.get( event_class, _EVENT_CLASS_DEFAULTS["unknown"] ) # Take the minimum of document and event defaults for extra conservatism probability = min(doc_default, event_default) logger.debug( "Using conservative default confidence: doc_type=%s (%.2f), event=%s (%.2f) -> %.2f", document_type, doc_default, event_class, event_default, probability, ) return ConfidenceResult( probability=probability, features_used=_DEFAULT_FEATURES_USED, is_calibrated=False, under_calibrated=True, calibration_version="conservative-default-v1", ) def is_underrepresented( document_type: str, event_class: str, min_samples: int = 30, known_counts: dict[tuple[str, str], int] | None = None, ) -> bool: """Check if a document_type + event_class combination is underrepresented. Parameters ---------- document_type The document type. event_class The event class. min_samples Minimum number of calibration samples to consider a class well-represented. known_counts Optional mapping of (doc_type, event_class) -> sample count. If None, treats any unknown combination as underrepresented. Returns ------- bool True if the class has insufficient calibration data. """ if known_counts is None: # Without explicit counts, use heuristic: unknown types are underrepresented if document_type not in _DOCUMENT_TYPE_DEFAULTS: return True if event_class not in _EVENT_CLASS_DEFAULTS: return True return False key = (document_type, event_class) count = known_counts.get(key, 0) return count < min_samples