Chronaxis用户手册English
本页目录

chronaxis.core.temporal

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""保存条件在各时刻的证据、检测规则及已确认事件,供两个检测后端共用。23输入为整数纳秒区间、条件状态、实际更新与观测结束证据;输出不可变且经过4检查的模型。TRUE和FALSE必须没有结果质量原因,UNKNOWN必须保留原因。5连续区间左闭右开;真实末点可单独观察,但不延长活动。来源或会话输入结束6不自动等于条件变假。事件以稳定内容生成标识,并保存SourceRef用于追溯。7本模块只依赖标准库及核心模型,不读取来源、不执行检测、不持有规则状态。8"""910# 冻结模型与默认字段便于保留显式证据。11from dataclasses import dataclass12# 稳定内容身份,不用秒数文件名充当事件ID。13from hashlib import sha25614# 已有规范身份与来源定位结构,不读旧资源。15from .binding import _identity16# 区间模型已检查跨度和来源引用结构。17from .models import Interval, SourceRef18# 三值状态不得隐式bool化。19from .types import QualityFlag as Q, Truth20# 数据标识规则复用,无静默改名。21from .validation import require_identifier, require_text22# 所有时间仍为显式int64纳秒。23from ..timebase.conversion import require_int64_ns242526def _require_truth(state, quality):27    """检查已知结果无质量原因与UNKNOWN有原因的基础契约。2829    参数: state为Truth,quality为已知Q组合。30    返回: None,不把UNKNOWN变False。31    异常: TypeError/ValueError为类型或质量矛盾。32    """33    # 已知结果不能携带基础策略禁止的质量位。34    if not isinstance(state, Truth) or not isinstance(quality, Q):35        # 裸bool或任意质量数值不能替代经过检查的条件证据。36        raise TypeError('条件需要Truth及QualityFlag')37    # unknown原因不能空,正常真/假质量只能NONE。38    if (state is Truth.UNKNOWN) != (quality != Q.NONE):39        # 诊断与结果质量通过独立字段保留。40        raise ValueError('条件truth与quality矛盾')414243@dataclass(frozen=True)44class ConditionSpan:45    """保存条件连续成立、不成立或无法确定的一段纳秒区间。4647    start_ns包含起点,end_ns排除终点;区间必须为正长度且跨度可由int64表示。48    state和quality共同说明条件及未知原因。模型不可变,不根据消息周期补齐间隙。49    """5051    start_ns: int  # 连续区间包含起点。52    end_ns: int  # 连续区间不包含终点。53    state: Truth  # 真/假/未知,不靠数值truthiness。54    quality: Q = Q.NONE  # UNKNOWN原因,known为NONE。5556    def __post_init__(self):57        """检查正区间与truth质量,禁止补齐缺观测。5859        参数: 无;读取四个字段。60        返回: None,保持原声明。61        异常: TypeError/ValueError/OverflowError为结构/区间/质量。62        """63        # 复用已有半开时间区间不变量。64        interval = Interval(self.start_ns, self.end_ns)65        # 零长度不是连续条件区间。66        if interval.end_ns == interval.start_ns:67            # 真实点由ConditionTick.point_state另表达。68            raise ValueError('ConditionSpan需正长度')69        # 区间状态和原因严格一致。70        _require_truth(self.state, self.quality)717273@dataclass(frozen=True)74class ConditionTick:75    """保存一个完整时间组的连续状态、点状态及实际更新证据。7677    state描述从当前时刻到下一时刻的连续状态;point_state描述当前点,包括真实78    末点。actual_unknown表示真实更新明确导致点结果未知,用于区分实际中断与79    同刻发生的有效期到期。observation_end和terminal分别表示依赖来源及会话80    观测结束。字段不可变,默认点状态继承连续状态,不制造实际更新。81    """8283    time_ns: int  # 已归一化int64纳秒。84    state: Truth  # 连续右侧状态,不扩展终点。85    quality: Q = Q.NONE  # 连续unknown原因。86    actual_update: bool = True  # 完整组确有条件依赖更新/明确条件证据。87    observation_end: bool = False  # 当前有依赖来源观测结束。88    terminal: bool = False  # 当前会话真实连续终点,只能全输入末组。89    point_state: Truth | None = None  # None表示与continuous同证据。90    point_quality: Q | None = None  # None采用continuous质量。91    actual_unknown: bool = False  # 实际无效/实际更新导致未知,优先interrupted。92    diagnostic_quality: Q = Q.NONE  # 全部依赖和边界原因,保留原异常诊断。9394    def __post_init__(self):95        """规范默认点证据并检查actual/terminal/quality结构。9697        参数: 无,读取上述字段。98        返回: None,默认point只继承显式state,不制造新更新。99        异常: TypeError/ValueError为类型、状态或实际未知证据冲突。100        """101        # 精确纳秒,bool不作整数时间。102        require_int64_ns(self.time_ns)103        # 连续known/unknown契约独立校验。104        _require_truth(self.state, self.quality)105        # 每个证据标志严格bool。106        for value in (self.actual_update, self.observation_end, self.terminal, self.actual_unknown):107            # 不用任意字符串或整数真值推测证据。108            if type(value) is not bool:109                # 结构错误先于检测。110                raise TypeError('时序证据标志需bool')111        # 未提供point时继承当前明确状态。112        object.__setattr__(self, 'point_state', self.state if self.point_state is None else self.point_state)113        # 未提供point原因时只继承连续原因。114        object.__setattr__(self, 'point_quality', self.quality if self.point_quality is None else self.point_quality)115        # point与continuous质量政策相同,但范围语义不同。116        _require_truth(self.point_state, self.point_quality)117        # 诊断也需已知组合,不得存任意负位。118        if not isinstance(self.diagnostic_quality, Q):119            # 不静默cast非法mask。120            raise TypeError('diagnostic_quality需要QualityFlag')121        # 明确actual_unknown只能针对真正更新的点未知。122        if self.actual_unknown and (not self.actual_update or self.point_state is not Truth.UNKNOWN):123            # 有效期到期或观测结束而没有实际更新证据,不能算作实际中断。124            raise ValueError('actual_unknown需实际更新且点UNKNOWN')125126127@dataclass(frozen=True)128class TemporalRule:129    """声明一个数据集会话中的持续条件或边沿检测规则。130131    rule_type接受condition_duration、rising_edge、falling_edge和start_only。minimum_duration_ns是非负int64阈值,132    边沿规则必须为0;rising_edge/start_only可用emit_initial输出首次已知TRUE初始化点。133    start_only的window_duration_ns必须为正,真实触发后的固定时长内抑制新边沿。134    对象不可变,只保存配置,不初始化检测器状态。135    """136137    rule_id: str  # 稳定规则ID,与同名不同会话隔离。138    dataset_id: str  # 事件ID范围,不按秒数命名。139    session_id: str  # 当前分析会话。140    rule_type: str  # condition_duration/rising_edge/falling_edge/start_only实际已实现白名单。141    minimum_duration_ns: int = 0  # condition_duration阈值;edge必须0。142    emit_initial: bool = False  # rising_edge/start_only首次已知TRUE初始化策略。143    window_duration_ns: int = 0  # 仅start_only的固定抑制时长,其他模式必须0。144    duration_comparison: str = '>='  # 持续时长门槛:达到或严格超过;不改变实际起止。145146    def __post_init__(self):147        """校验规则范围、非负时间及基础rule_type参数。148149        参数: 无,读取规则字段。150        返回: None,不初始化任何全局运行状态。151        异常: TypeError/ValueError/OverflowError为配置错误。152        """153        # 独立命名范围不使用任意路径作为ID。154        for field in ('rule_id', 'dataset_id', 'session_id'):155            # 复用现有简单identifier协议。156            require_identifier(getattr(self, field), field)157        # 阈值精确ns,不猜秒或消息周期。158        require_int64_ns(self.minimum_duration_ns, 'minimum_duration_ns')159        # 严格比较只属于持续规则;其他规则不能携带无效开关。160        if self.duration_comparison not in ('>=', '>') or (self.rule_type != 'condition_duration' and self.duration_comparison != '>='):161            raise ValueError('duration_comparison仅持续条件支持>=或>')162        # 固定窗口和持续确认门槛不同,不能复用一个字段猜旧模式意义。163        object.__setattr__(self, 'window_duration_ns', require_int64_ns(self.window_duration_ns, 'window_duration_ns'))164        # 不负时长/隐式bool策略/未实现kind。165        if self.minimum_duration_ns < 0 or type(self.emit_initial) is not bool or self.rule_type not in ('condition_duration', 'rising_edge', 'falling_edge', 'start_only'):166            # sequence使用独立规则模型,不由本模型承载。167            raise ValueError('非法首版时序规则/阈值/初始化策略')168        # edge不能暗带持续门槛,falling_edge/condition_duration无初始化点参数。169        if self.rule_type != 'condition_duration' and self.minimum_duration_ns != 0 or self.emit_initial and self.rule_type not in ('rising_edge', 'start_only'):170            # 参数不混为旧模式的不同condition_duration。171            raise ValueError('规则参数与kind不匹配')172        # start_only窗口为正;其他kind不能暗带抑制或改变已有事件ID。173        if (self.rule_type == 'start_only' and self.window_duration_ns <= 0) or (self.rule_type != 'start_only' and self.window_duration_ns != 0):174            raise ValueError('window_duration_ns仅且必须用于正时长start_only')175176    @property177    def qualifying_duration_ns(self):178        """返回最早满足比较的整数纳秒时长,不改变用户声明的门槛。179180        参数: 无。返回: Python整数;严格大于最大int64时可超出int64,表示域内不可达到。181        异常: 无。时间模型精度为1ns,因此>60s的首个可表示时长为60000000001ns;182        这不是把用户阈值粗略提高到60.1秒,固定/DBC模式仍只随实际驱动记录确认。183        """184        return self.minimum_duration_ns + (self.duration_comparison == '>')185186187@dataclass(frozen=True)188class Event:189    """保存一段已确认活动或一个真实触发点,并关联检测规则和原来源。190191    point事件的start_ns等于confirmed_ns,end_ns为None,status为point。192    activity事件为正长度左闭右开区间;confirmed_ns可以恰好等于end_ns。193    活动的closed表示条件已知结束,interrupted表示连续性中断,truncated表示194    观测结束而没有实际结束证据。边界、质量、诊断和来源引用均不可变。195    timeout表示开始/结束规则在结束条件可判断时达到时限,结束质量必须为NONE。196    """197198    event_id: str  # 按稳定内容SHA,不依批次/遍历次序。199    rule: TemporalRule  # 完整规则及dataset/session身份。200    event_type: str  # point或activity。201    start_ns: int  # 真实onset或点时间。202    confirmed_ns: int  # 精确门槛时间,不当活动起点。203    end_ns: int | None  # point为None,activity结束边界。204    status: str  # closed/timeout/interrupted/truncated/point;timeout用于开始结束时限。205    trigger_kind: str  # condition_duration/rising_edge/falling_edge/initial。206    quality: Q  # 结束原因质量,不用0恢复失败。207    dependency_quality: Q  # 期间及结束证据的完整诊断。208    source_refs: tuple[SourceRef, ...] = ()  # 真实绑定原来源定位,合成span可空。209210    def __post_init__(self):211        """检查事件时间不变量、范围与来源,保留确认=end。212213        参数: 无,读取事件字段。214        返回: None,不修改边界或生成片段。215        异常: TypeError/ValueError/OverflowError为事件结构/时间/身份错误。216        """217        # 正常输出须携带真实规则/质量及来源容器。218        if not isinstance(self.rule, TemporalRule) or not isinstance(self.quality, Q) or not isinstance(self.dependency_quality, Q) or not isinstance(self.source_refs, tuple):219            # 不接无类型事件字典作为完成事实。220            raise TypeError('Event字段需typed模型')221        # start/confirmed都是明确ns。222        require_int64_ns(self.start_ns, 'start_ns')223        # 确认不能先于真实onset。224        require_int64_ns(self.confirmed_ns, 'confirmed_ns')225        # ID需真实非空文本,稳定SHA由make_event产生。226        require_text(self.event_id, 'event_id')227        # 缺数据不能伪装已证实的超时;依赖诊断仍可保留不影响结束判断的原因。228        if self.status == 'timeout' and self.quality != Q.NONE:229            raise ValueError('timeout不能带未知结束质量')230        # 点事件只有单时刻,无连续范围。231        if self.event_type == 'point':232            # 新模式的触发种类与规则一致,不允许自由组装相反边沿或伪初始化。233            if self.rule.rule_type == 'start_only' and (self.trigger_kind not in ('start_only', 'initial') or self.trigger_kind == 'initial' and not self.rule.emit_initial) or self.rule.rule_type != 'start_only' and self.trigger_kind == 'start_only':234                raise ValueError('start_only点触发种类与规则不匹配')235            # 初始点/边沿都不能增面积。236            if self.status != 'point' or self.end_ns is not None or self.start_ns != self.confirmed_ns or self.trigger_kind not in ('rising_edge', 'falling_edge', 'initial', 'start_only') or self.rule.rule_type == 'condition_duration' or self.quality != Q.NONE:237                # 不把point伪装零长度活动。238                raise ValueError('point事件边界/类型矛盾')239        else:240            # 活动严格正长,已确认且终点可相等。241            if self.event_type != 'activity' or self.status not in ('closed', 'timeout', 'interrupted', 'truncated') or self.trigger_kind != 'condition_duration' or self.end_ns is None or self.rule.rule_type != 'condition_duration':242                # 不输出内部pending/active占位。243                raise ValueError('activity事件结构非法')244            # 区间跨度也需合法int64。245            interval = Interval(self.start_ns, self.end_ns)246            # 终点真实新TRUE不得建立零长度活动。247            if interval.start_ns == interval.end_ns or not self.start_ns <= self.confirmed_ns <= self.end_ns:248                # 门槛恰好end仍合法确认。249                raise ValueError('activity确认/连续边界矛盾')250        # 来源属于该会话,不能继承其他任务引用。251        for ref in self.source_refs:252            # 合成ConditionSpan允许无原SourceRef,但非空必须真实typed。253            if not isinstance(ref, SourceRef) or ref.session_id != self.rule.session_id:254                # 不接受仅同名来源作为归属证明。255                raise ValueError('Event来源与session不一致')256257258def make_event(rule, start_ns, confirmed_ns, end_ns, status, quality, diagnostic, refs=(), *, trigger='condition_duration') -> Event:259    """根据规则和时间事实生成稳定事件标识,再构造已确认事件。260261    参数:262        rule (TemporalRule): 当前检测规则,包含数据集和会话身份。263        start_ns、confirmed_ns (int): 活动起点和确认时间,或触发点时间,单位为纳秒。264        end_ns (int | None): 活动结束时间,单位为纳秒;点事件使用None。265        status (str): closed、timeout、interrupted、truncated或point。266        quality、diagnostic (QualityFlag): 结束质量和完整依赖诊断。267        refs (tuple[SourceRef, ...]): 当前实际来源引用,默认空tuple。268        trigger (str): 触发种类,默认condition_duration,边沿或初始化点另行声明。269    返回:270        Event: 通过模型检查的事件。标识由规则及时间事实生成,不受批次切分、271        质量诊断或可选语义元数据变化影响。272    异常:273        TypeError、ValueError或OverflowError: 事件模型或身份材料不符合约束。274    副作用:275        无,不加载来源,不修改规则或检测器状态。276    """277    # 规则范围和事件内容稳定,质量/metadata变化不冒充另一时间事实。278    payload = (rule.dataset_id, rule.session_id, rule.rule_id, rule.rule_type, rule.minimum_duration_ns, rule.emit_initial, start_ns, confirmed_ns, end_ns, status, trigger)279    # 仅新模式增加固定窗口身份材料;已有三个kind的SHA内容逐字保持。280    if rule.rule_type == 'start_only':281        payload += (rule.window_duration_ns,)282    # 旧默认事件ID保持;严格比较作为新的规则身份,避免同端点混淆。283    if rule.duration_comparison != '>=':284        payload += (rule.duration_comparison,)285    # 完整SHA避免同起止秒数/不同规则碰撞。286    identity = sha256(_identity(payload).encode('utf-8')).hexdigest()287    # source_refs保留当前真实来源,不参与规则时间事实的隐式改写。288    return Event(identity, rule, 'point' if status == 'point' else 'activity', start_ns, confirmed_ns, end_ns, status, trigger, quality, diagnostic, refs)289290291@dataclass(frozen=True)292class ConditionTimeline:293    """保存一个完整会话的条件时间轴,检测器可以按批消费其中的时间组。294295    首末时间组必须对应start_ns和end_ns;最后一组是唯一的terminal。时间严格296    递增,同刻更新已经归并;模型不会排序或补齐缺口。source_refs保存原来源,297    与规则会话的归属关系由检测器检查。批次切分不改变条件证据。298    """299300    start_ns: int  # 连续分析起点。301    end_ns: int  # 连续分析终点,真实点仍可观察。302    ticks: tuple[ConditionTick, ...]  # 完整时间组,含TTL/source/EOF证据。303    source_refs: tuple[SourceRef, ...] = ()  # 实际condition依赖源定位。304305    def __post_init__(self):306        """检查时间轴完整性,不填充未知间隙或末周期。307308        参数: 无,读取domain/ticks/source_refs。309        返回: None,保持显式时间证据。310        异常: TypeError/ValueError/OverflowError为结构/边界/顺序。311        """312        # 域跨度必须可表示,零域只含真实末点。313        Interval(self.start_ns, self.end_ns)314        # 不能用可变列表或空轴冒充完整会话。315        if not isinstance(self.ticks, tuple) or not self.ticks or not isinstance(self.source_refs, tuple):316            # 空观测可显式一个terminal UNKNOWN点,不自动补。317            raise TypeError('timeline需非空tuple ticks及tuple refs')318        # typed组本身已检查continuous/point质量。319        if any(not isinstance(tick, ConditionTick) for tick in self.ticks):320            # 不接自由字典作为完整证据。321            raise TypeError('timeline需要ConditionTick')322        # 真实domain首末完整,不丢EOF边界。323        if self.ticks[0].time_ns != self.start_ns or self.ticks[-1].time_ns != self.end_ns or not self.ticks[-1].terminal:324            # 保证检测finish只发生真正会话末点。325            raise ValueError('timeline首末/terminal与domain不一致')326        # 全轴严格递增,原子同时间组已在上游收齐。327        for index, tick in enumerate(self.ticks):328            # terminal只能出现在真实末组。329            if index < len(self.ticks)-1 and tick.terminal or index and tick.time_ns <= self.ticks[index-1].time_ns:330                # 不将batch/file尾当EOF或排序掩盖回退。331                raise ValueError('timeline中途terminal/时间回退/重复组')332        # 来源结构真实typed,归属稍后与rule.session核对。333        if any(not isinstance(ref, SourceRef) for ref in self.source_refs):334            # 不用名字字符串冒充定位。335            raise TypeError('timeline source_refs需SourceRef')336337338def timeline_from_spans(spans: tuple[ConditionSpan, ...], *, terminal_state: Truth | None = None,339                        terminal_quality: Q = Q.NONE, terminal_actual: bool = False) -> ConditionTimeline:340    """把完整手算半开区间转换为明确边界证据,不伪造EOF FALSE。341342    参数: spans为正长度、相邻、完整tuple;terminal_state/quality为可选真实343        末点状态,terminal_actual明确是否真实更新,默认EOF沿用历史点状态。344    返回: ConditionTimeline;末端continuous UNKNOWN/MISSING,仅point保留证据。345    异常: TypeError/ValueError为结构/缺口/point更新声明矛盾。346    """347    # 合成条件资料也必须typed完整,不填补missing域。348    if not isinstance(spans, tuple) or not spans or any(not isinstance(item, ConditionSpan) for item in spans):349        # 零域用ConditionTick直接声明,无正区间不能猜范围。350        raise TypeError('spans需要非空tuple ConditionSpan')351    # 手算明确条件更新可作为边沿证据。352    ticks = []353    # 区间边界是静态输入协议,不是参考/数组状态算法。354    for index, span in enumerate(spans):355        # 间隙/重叠都不能静默补齐或合并真实活动。356        if index and spans[index-1].end_ns != span.start_ns:357            # 用显式UNKNOWN span表达缺口。358            raise ValueError('ConditionSpan需完整相邻,不自动填充')359        # 给定边界是真实状态证据;unknown说明实际无效。360        ticks.append(ConditionTick(span.start_ns, span.state, span.quality, actual_unknown=span.state is Truth.UNKNOWN, diagnostic_quality=span.quality))361    # 默认末点沿用最后明示span的历史状态,不制造更新。362    point_state = spans[-1].state if terminal_state is None else terminal_state363    # 未覆写point时保留末span的原质量。364    point_quality = spans[-1].quality if terminal_state is None else terminal_quality365    # 真实末点覆写必须明确actual,不能冒充纯EOF。366    if terminal_actual and terminal_state is None:367        # 默认历史值不算新末点证据。368        raise ValueError('terminal_actual需明确terminal_state')369    # 连续EOF排除而point包含,真实false/invalid优先结束事实。370    ticks.append(ConditionTick(spans[-1].end_ns, Truth.UNKNOWN, Q.MISSING, terminal_actual, True, True,371                               point_state, point_quality, terminal_actual and point_state is Truth.UNKNOWN, Q.MISSING | point_quality))372    # 域与完整时间组由模型统一校验。373    return ConditionTimeline(spans[0].start_ns, spans[-1].end_ns, tuple(ticks))374
Chronaxis 用户手册 · 离线阅读