tokmd-analysis 1.10.0

Analysis logic and enrichers for tokmd receipts.
Documentation
use tokmd_analysis_types::{EffortConfidence, EffortDriver, EffortResults};

pub fn apply_uncertainty(
    base: EffortResults,
    confidence: &EffortConfidence,
    basis_confidence: f64,
    drivers: &[EffortDriver],
) -> EffortResults {
    if base.effort_pm_p50 <= 0.0 {
        return base;
    }

    let evidence_pressure = drivers
        .iter()
        .map(|driver| driver.weight.max(0.0))
        .sum::<f64>()
        .clamp(0.0, 0.85);

    let confidence_penalty = (1.0 - confidence.data_coverage_pct.unwrap_or(0.0)).clamp(0.0, 0.8);
    let basis_penalty = (1.0 - basis_confidence).clamp(0.0, 0.6);

    let mut width = match confidence.level {
        tokmd_analysis_types::EffortConfidenceLevel::High => 0.10,
        tokmd_analysis_types::EffortConfidenceLevel::Medium => 0.22,
        tokmd_analysis_types::EffortConfidenceLevel::Low => 0.35,
    };
    width += evidence_pressure * 0.06;
    width += confidence_penalty * 0.45;
    width += basis_penalty * 0.35;
    width = width.clamp(0.04, 0.85);

    let effort_low = (base.effort_pm_p50 * (1.0 - width)).max(0.0);
    let effort_p80 = (base.effort_pm_p50 * (1.0 + width)).max(0.0);

    let schedule_width = (width * 0.62).clamp(0.03, 0.70);
    let schedule_low = (base.schedule_months_p50 * (1.0 - schedule_width)).max(0.0);
    let schedule_p80 = (base.schedule_months_p50 * (1.0 + schedule_width)).max(0.0);

    let staff_low = if schedule_p80 > 0.0 {
        effort_low / schedule_p80
    } else {
        0.0
    };
    let staff_p80 = if schedule_low > 0.0 {
        effort_p80 / schedule_low
    } else {
        0.0
    };

    EffortResults {
        effort_pm_p50: base.effort_pm_p50,
        schedule_months_p50: base.schedule_months_p50,
        staff_p50: base.staff_p50,
        effort_pm_low: effort_low,
        effort_pm_p80: effort_p80,
        schedule_months_low: schedule_low,
        schedule_months_p80: schedule_p80,
        staff_low,
        staff_p80,
    }
}