chronaxis.legacy.disjunction
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""声明按本条原解码方向值分正负桶,并保存不经转换的选择追溯。23LegacyDisjunction给出已绑定别名、positive/negative桶及固定raw_decoded-v1策略。4运行层须核同一完整消息定位,从当前decoded字典直接取方向原值,不能施加5SignalDefinition的scale、offset、direction_factor或TTL,也不能补前一次值。6原生True按正值、False按零;缺字段、非有限值和未选桶的跳过由运行层决定,7本模块不执行分类、表达式、累计或数值转换。89LegacyDisjunctionTrace保存冻结选择器、原field定位、原生方向值和SourceRef。10None明确表示方向字段缺失;NaN/Inf保留原诊断,不改成零或字符串。模型只核11结构,不声称来自真实消息、文件EOF或旧ChrgInfo整条调用链。配置和返回追溯12由调用方拥有;本模块不创建预算、I/O、缓存或NumPy依赖。13"""1415# 两端共享冻结选择器和追溯,不在计划、结果和运行层各造一套可变字典。16from dataclasses import dataclass1718from ..core.models import SourceRef19from ..core.validation import require_identifier, require_text202122@dataclass(frozen=True)23class LegacyDisjunction:24 """选择同条消息的原数值方向桶,不修改表达式贡献的正负号。2526 signal是分析别名,原字段和完整消息定位仍由当前绑定解析;bucket只允许27 positive/negative。该声明不自行拆分指标,也不证明方向在表达式依赖中。28 """2930 signal: str # 已绑定简单别名,可不出现在表达式中。31 bucket: str # positive或negative,各指标独立累计。32 value_policy: str = 'raw_decoded-v1' # 只看原decoded值,不施转换或保持。3334 def __post_init__(self) -> None:35 """校验安全别名和固定桶/取值策略,不解析来源或输入值。3637 参数: 无,读取三个冻结字段。返回: None,保原生文本原样。38 异常: TypeError为非原生文本;ValueError为别名、桶或策略不支持。39 """40 # 不按truthiness或str隐式转换选桶,避免配置拼错后悄悄进入另一种策略。41 if type(self.signal) is not str or type(self.bucket) is not str or type(self.value_policy) is not str:42 raise TypeError('disjunction别名、桶和value_policy需要原生str')43 require_identifier(self.signal, 'disjunction.signal')44 if self.bucket not in ('positive', 'negative'):45 raise ValueError('disjunction.bucket仅支持positive或negative')46 if self.value_policy != 'raw_decoded-v1':47 raise ValueError('disjunction.value_policy仅支持raw_decoded-v1')484950@dataclass(frozen=True)51class LegacyDisjunctionTrace:52 """保存本次方向的原定位和值,结构冻结但不假造真实读取证明。5354 raw_value保int/float/bool原类型,None只表示明确缺字段;非有限float不在55 模型层改写或拒绝。source_ref原对象保持,实际CAN逐序身份由运行/结果层核。56 """5758 selection: LegacyDisjunction # 实际指标使用的同一选择器。59 field: str # 原SignalDefinition.field,CAN路径为规范六项定位。60 raw_value: int | float | bool | None # 未归一化方向值;None为缺失。61 source_ref: SourceRef # 同条原消息的真实来源引用。6263 def __post_init__(self) -> None:64 """核选择器、原字段、原生值及引用一致性,不作数值强制转换。6566 参数: 无,读取冻结追溯字段。返回: None;保NaN/Inf、负零和原bool。67 异常: TypeError为非模型/原生数值;ValueError为原字段与引用不一致。68 """69 # 选择器必须已由共享模型校验,字段全文保留,不从别名反猜原字典键。70 if type(self.selection) is not LegacyDisjunction or type(self.field) is not str:71 raise TypeError('disjunction追溯需要LegacyDisjunction及原生field文本')72 require_text(self.field, 'disjunction.field')73 # None不代表零或无效浮点;数值原类型限定,拒绝str、enum和NumPy scalar。74 if self.raw_value is not None and type(self.raw_value) not in (int, float, bool):75 raise TypeError('disjunction原值需要原生int/float/bool或明确缺失None')76 if not isinstance(self.source_ref, SourceRef):77 raise TypeError('disjunction追溯需要SourceRef')78 if self.source_ref.field != self.field:79 raise ValueError('disjunction原field与来源引用不一致')80