"""Explicit forecast-only contract; v1 files remain unchanged.

All values are uncalibrated assumptions. Units: reference WT synthesis and
reference WT-only current. No source abundance score mapping is provided.
"""
from dataclasses import dataclass
import math

@dataclass(frozen=True)
class Assumptions:
    burden_model: str
    delivery_model: str
    survival: float
    competition: float
    susceptibility: float
    capacity: float
    mutant_activity: float
    coupling: float

def bounded(**values):
    # Explicit numerical domain, not a biologically established range.
    for name,value in values.items():
        if isinstance(value,bool) or not isinstance(value,(int,float)) or not math.isfinite(value) or not 0<=value<=1e6:
            raise ValueError(f'{name} outside numerical domain [0,1e6]')

def predict(w,m,*,assumptions):
    p=assumptions
    bounded(w=w,m=m,survival=p.survival,competition=p.competition,
            susceptibility=p.susceptibility,capacity=p.capacity,
            mutant_activity=p.mutant_activity,coupling=p.coupling)
    if p.survival>1:raise ValueError('Survival outside [0,1]')
    u,k=p.survival,p.capacity
    burdens={'null':0.,'surviving_burden':u,'failed_processing':1-u,
             'surviving_defective':u*(1-u)}
    if p.burden_model not in burdens:raise ValueError('Explicit burden model required')
    if p.delivery_model not in ('independent','mutant_total_input_attenuation','joint_total_input'):
        raise ValueError('Explicit delivery model required')
    q=p.competition+p.susceptibility*burdens[p.burden_model]
    # joint_total_input applies the same supply attenuation to both alleles;
    # allele-specific burden remains a separate assumed loss, not conservation.
    wt_den=1+k*(w+m if p.delivery_model=='joint_total_input' else w)
    mut_den=1+k*(m if p.delivery_model=='independent' else w+m)
    fraction=1/(1+q*m/w) if w>0 else 0.
    sw=(1+k)*w/wt_den*fraction
    sm=u*(1+k)*m/mut_den
    aw=1/(1+p.coupling*u*m/w) if w>0 else 0.
    i=aw*sw+p.mutant_activity*sm
    if not all(math.isfinite(x) and x>=0 for x in (sw,sm,aw,i)):
        raise ArithmeticError('Nonfinite forecast')
    return {'kind':'conditional_forecast','wt_surface':sw,'mutant_surface':sm,
            'wt_activity':aw,'current':i,'measured':False}

def require_measurement(record):
    """Forecast records can never satisfy a measured-observation input gate."""
    raise ValueError('This module produces no qualified measurements')
