Chronaxis用户手册English
本页目录

chronaxis.planning.models

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""保存配置解析得到的来源、信号、计算节点和执行选项声明。23配置解析器先检查字段与引用,再构造本模块的冻结数据类;调用者的自由4元数据在解析边界复制为只读快照。输出记录分析对象、会话范围、来源定位、5显式信号转换、条件规则、窗口、统计指标、当前消息计算及预算和输出格式。6当前消息计算明确新policy,不表示旧完整触发/cache兼容。本层不读取数据、7不调用旧reader或DBC,也不运行表达式。8规则、窗口与预算是配置声明;实际可用能力由计划报告另行说明。9"""1011# 冻结数据类及明确元数据映射接口。12from dataclasses import dataclass, field13from collections.abc import Mapping14# 核心模型提供信号契约、静态表达式、时钟和内存来源声明。15from ..core.binding import SignalDefinition16from ..core.expression import DerivedDefinition, ExprNode, StaticExpression17from ..core.message_binding import ExpressionParameter, MessageDeclarations18from ..core.models import Interval, SourceSpec, TimebaseSpec19from ..core.types import SignalDType20from ..core.validation import require_identifier, require_text21from ..core.window_delta import WindowDeltaSpec22from ..core.stability import StabilitySpec23from ..core.start_end import StartEndSpec24# CSV/CAN仅导入静态配置,不实例化适配器、旧worker或打开文件。25from ..sources.csv_spec import SignalCSVSpec26from ..sources.can_spec import LegacyCANSpec27from ..sources.text_can_spec import CANTextSpec28from ..legacy.records import PROFILE_ID, normalize_frame_id29from ..legacy.disjunction import LegacyDisjunction30from .export_options import SignalCSVOutput,CANFramesOutput313233@dataclass(frozen=True)34class DatasetPlan:35    """保存单个分析对象、会话及请求时间范围。3637    dataset_id与entity命名数据集和对象,session_id确定会话。38    domain是用户请求的左闭右开区间,不证明实际输入已覆盖该范围。39    domain_explicit区分用户时间筛选与domain=None的整文件声明;后者不能40    当作观测时间或向运行结果报告为已采集范围。41    timebase保存来源声明确定的分析时钟,metadata保存只读附加信息。42    实例字段冻结,不维持运行状态。43    """4445    dataset_id: str  # 单计划数据集ID。46    entity: str  # 分析对象ID,不内置车辆含义。47    session_id: str  # 当前单会话ID。48    domain: Interval | None  # 用户请求半开范围;None为整文件无筛选,不能当作实际空范围。49    timebase: TimebaseSpec  # 同步分析基准。50    metadata: Mapping  # 深冻结自由JSON元数据。51    domain_explicit: bool = True  # 旧位置构造保留显式请求;False仅供合格整文件消息任务。525354@dataclass(frozen=True)55class SourcePlan:56    """保存一个已支持来源种类的配置和定位声明。5758    name是唯一来源名,source_type区分memory、signal_csv、can_blf、can_asc和raw_can_csv。59    config保存对应静态来源规格,文本CAN使用CANTextSpec,供实际适配器构造。60    time_unit与timezone_text记录时间表示;input_identity规范记录路径顺序、61    格式和时间映射声明,不能证明文件内容。metadata是只读自由元数据。62    CSV/BLF/DBC尚未打开,实际列、单位与范围留待读取核实;不创建适配器或持有句柄。63    """6465    name: str  # 当前唯一来源名。66    source_type: str  # 来源类型,由配置解析器检查允许的格式。67    config: SourceSpec | SignalCSVSpec | LegacyCANSpec | CANTextSpec  # 对应memory/信号表/旧BLF/新文本CAN。68    time_unit: str  # 原时间表示单位。69    timezone_text: str | None  # 明确声明原时区文本。70    input_identity: str  # 规范来源配置,包含路径顺序/格式/时间映射。71    metadata: Mapping  # 深冻结自由元数据。727374@dataclass(frozen=True)75class SignalPlan:76    """保存一个原字段到简单表达式别名的显式信号定义。7778    definition包含原字段、类型、单位、转换和valid_for_ns有效期等契约。79    有效期是有效样本最多保持的时长;None表示等待运行启动读取DBC周期,80    不能用于计算状态。实际过期判断由时间驱动层按真实更新时间执行。81    metadata独立冻结,不改变转换或基础质量规则。本类不保存信号值。82    """8384    definition: SignalDefinition  # 已校验归一化/保持契约。85    metadata: Mapping  # 自由键不会修改基础质量政策。868788@dataclass(frozen=True)89class DerivedPlan:90    """保存由已有信号计算的新信号声明及可选语义信息。9192    definition保存名称、表达式和显式单位,类型由表达式静态检查推断。93    semantic_id和semantic_contract_version声明语义及版本,sign_convention94    与measurement_location声明方向及位置;这些字段不证明角色已核实。95    metadata为只读附加信息。派生值不能另行声明更长有效期或覆盖推断类型,96    实际有效性仍取决于输入依赖。本类不执行计算。97    """9899    definition: DerivedDefinition  # 名称/表达式/显式单位。100    semantic_id: str | None  # 语义文本不是属性路径。101    semantic_contract_version: str | None  # 声明版本不代表角色已验。102    sign_convention: str | None  # 可选调用者方向声明。103    measurement_location: str | None  # 可选调用者位置声明。104    metadata: Mapping  # 深冻结自由JSON。105106107@dataclass(frozen=True)108class RulePlan:109    """保存持续、边沿、窗口变化量、迟滞和开始/结束规则的静态参数。110111    rule_id与rule_type确定规则,condition通常是布尔表达式;window_delta时保存数值signal树。112    minimum_duration_ns与selection_origin仅用于持续条件,表示确认时长和选择起点。113    emit_initial说明初始触发策略;window_duration_ns是start_only的固定抑制时长。114    stability保存防抖时长;exit_condition保存迟滞退出或start_end结束条件。115    start_end保存专用时限/首次策略,不借普通emit_initial启动condition_duration检测器。116    实例不保存状态;迟滞复用普通条件时间线,窗口变化量使用有界历史回读。117    """118119    rule_id: str  # 唯一规则ID。120    rule_type: str  # 明确首版规则种类。121    condition: StaticExpression  # 普通BOOL树或window_delta数值树,不能直接执行。122    minimum_duration_ns: int | None  # 仅condition_duration,非负纳秒。123    selection_origin: str | None  # 仅condition_duration的onset/confirmed。124    emit_initial: bool  # 边沿/start_only初始化策略。125    window_duration_ns: int | None  # 仅start_only正抑制窗口。126    window_delta: WindowDeltaSpec | None = None  # window_delta规则的完整回看契约。127    retrigger: str = 'edge'  # edge等待新False→True;once在本会话只触发一次。128    stability: StabilitySpec | None = None  # debounce/hysteresis的独立进入/退出防抖时长。129    exit_condition: StaticExpression | None = None  # hysteresis或start_end的独立退出条件。130    start_end: StartEndSpec | None = None  # 开始/结束的时限与首次启动,追加字段保持旧位置兼容。131    duration_comparison: str = '>='  # 持续条件达到/>门槛,其他规则只能保持默认。132133    def __post_init__(self):134        """检查窗口、迟滞和开始/结束规则,保持原字段位置参数兼容。135136        参数: 无。返回: None。异常: 新规则类型、配置或普通规则混入专用参数。137        """138        if self.duration_comparison not in ('>=', '>') or (self.rule_type != 'condition_duration' and self.duration_comparison != '>='):139            raise ValueError('duration_comparison仅持续条件支持>=或>')140        if self.rule_type == 'start_end':141            if (not isinstance(self.start_end, StartEndSpec)142                    or not isinstance(self.condition, StaticExpression)143                    or self.condition.root.dtype is not SignalDType.BOOL144                    or not isinstance(self.exit_condition, StaticExpression)145                    or self.exit_condition.root.dtype is not SignalDType.BOOL):146                raise TypeError('start_end需要两个BOOL条件及StartEndSpec')147            if (self.minimum_duration_ns is not None or self.selection_origin is not None148                    or self.emit_initial is not False or self.window_duration_ns is not None149                    or self.window_delta is not None or self.stability is not None or self.retrigger != 'edge'):150                raise ValueError('start_end不能混用其他规则参数')151            return152        if self.start_end is not None:153            raise ValueError('其他规则不接受StartEndSpec')154        if self.rule_type in ('debounce', 'hysteresis'):155            if (not isinstance(self.stability, StabilitySpec)156                    or not isinstance(self.condition, StaticExpression)157                    or self.condition.root.dtype is not SignalDType.BOOL):158                raise TypeError('迟滞/防抖需要BOOL条件及StabilitySpec')159            if self.rule_type == 'hysteresis':160                if not isinstance(self.exit_condition, StaticExpression) or self.exit_condition.root.dtype is not SignalDType.BOOL:161                    raise TypeError('hysteresis需要BOOL退出条件')162            elif self.exit_condition is not None:163                raise ValueError('debounce退出使用not condition,不接受exit_condition')164            if (self.minimum_duration_ns is not None or self.selection_origin is not None165                    or self.emit_initial is not False or self.window_duration_ns is not None166                    or self.window_delta is not None or self.retrigger != 'edge'):167                raise ValueError('迟滞/防抖不能混用其他规则参数')168        elif self.stability is not None or self.exit_condition is not None:169            raise ValueError('普通规则不接受迟滞/防抖参数')170        elif self.rule_type == 'window_delta':171            if (not isinstance(self.window_delta, WindowDeltaSpec)172                    or not isinstance(self.condition, StaticExpression)173                    or self.condition.root.dtype not in (SignalDType.INT64, SignalDType.UINT64, SignalDType.FLOAT64)):174                raise TypeError('window_delta需要数值signal与WindowDeltaSpec')175            if (self.minimum_duration_ns is not None or self.selection_origin is not None176                    or self.window_duration_ns is not None or type(self.emit_initial) is not bool177                    or self.retrigger not in ('edge', 'once')):178                raise ValueError('window_delta参数冲突或重触发策略非法')179        elif self.window_delta is not None or self.retrigger != 'edge':180            raise ValueError('普通规则不接受window_delta或retrigger参数')181182    @property183    def temporal_rule_type(self):184        """读取交给事件检测器的类型。参数: 无。返回: str。异常: 无。185186        window_delta使用rising_edge;迟滞/防抖确认后的状态使用condition_duration=0活动。187        完整窗口、条件、防抖和开始/结束时限保留在原计划;start_end也输出活动。188        """189        return ('rising_edge' if self.rule_type == 'window_delta' else190                'condition_duration' if self.rule_type in ('debounce', 'hysteresis', 'start_end') else self.rule_type)191192    @property193    def expressions(self):194        """列出规则全部静态条件。参数: 无。返回: tuple。异常: 无。195196        迟滞退出条件同样参与实际绑定、投影、同步与预算,不遗漏第二个来源。197        """198        return (self.condition,) if self.exit_condition is None else (self.condition, self.exit_condition)199200    @property201    def dependencies(self):202        """取得进出条件的去重依赖。参数: 无。返回: 名称tuple。异常: 无。"""203        return tuple(dict.fromkeys(name for expression in self.expressions for name in expression.dependencies))204205206@dataclass(frozen=True)207class WindowPlan:208    """保存事件的上下文窗口及片段合并声明。209210    window_id标识窗口,event引用规则,anchor区分时间点和持续活动。211    pre_ns与post_ns声明前后扩展纳秒范围,merge决定窗口的合并方式。212    exit_delay_ns只用于退出延迟合并,该模式要求活动锚点且post_ns为0。213    声明不会改写原事件边界;实际运行还须保存合并片段的全部父事件身份。214    """215216    window_id: str  # 唯一窗口ID。217    event: str  # 当前计划规则ID。218    anchor: str  # point或activity,与规则kind一致。219    pre_ns: int  # 非负请求前扩范围。220    post_ns: int  # 非负请求后扩范围。221    merge: str  # 显式none/overlap/overlap_and_adjacent/exit_delay。222    exit_delay_ns: int | None  # 仅exit_delay模式有效。223224225@dataclass(frozen=True)226class CalculationPlan:227    """保存要统计的信号、选择条件、范围和结果政策。228229    calculation_id标识指标,scope区分全会话、原事件和合并片段的统计范围。230    operation确定选择、积分、时长或统计口径,signal与condition是静态表达式结构。231    unit保存已检查的输出单位,partial_policy规定未知部分如何报告,232    minimum_coverage是覆盖率质量门槛;method仅用于积分,当前为左侧保持'zoh'。233    本类只保存声明,不生成数值或选择行;片段合并不改变原事件的统计范围。234    """235236    calculation_id: str  # 唯一指标ID。237    scope: str  # session/event:ID/segment:ID。238    operation: str  # 首版明确算子,不接受模糊mean/min/max。239    signal: StaticExpression | None  # 数值/BOOL信号或派生别名树。240    condition: StaticExpression | None  # 可选BOOL选择或condition_duration条件。241    unit: str  # 已推断/校验输出单位。242    partial_policy: str  # report或strict。243    minimum_coverage: int | float  # 0—1质量判断阈值,不补未知。244    method: str | None  # 仅integral为zoh。245    count_target: str | None = None  # v4对象计数显式引用的规则或窗口名。246    ddof: int | None = field(default=None, kw_only=True)  # 样本/时间方差和标准差的分母选择。247    q: int | float | None = field(default=None, kw_only=True)  # 仅sample_quantile显式指定概率。248249250@dataclass(frozen=True)251class LegacyMessageSelector:252    """明确选择当前消息计算的CAN帧,不猜旧profile的求值选择器默认。253254    通道与ID必须提供,两个帧类型可省略。ID支持整数或带进制文本,不接受bool或255    float;实际记录保留remote/error等标志,消息计算只处理数据帧。256    本声明不读DBC,也不证明该消息在输入中出现。257    """258259    channel: int  # 显式声明的原通道。260    frame_id: int  # 原报文ID,不以零代表缺项。261    is_extended: bool | None = None  # 省略时由实际帧确定;不能默认标准帧。262    is_fd: bool | None = None  # 省略时由实际帧确定经典CAN/FD。263264    def __post_init__(self) -> None:265        """核原生类型和帧ID范围,不进行输入过滤或DBC查找。266267        参数: 无,读取冻结字段。返回: None,ID统一为整数,省略类型保留None。268        异常: TypeError为非法类型;ValueError为ID格式、负通道或ID超相应帧范围。269        """270        if type(self.channel) is not int:271            raise TypeError('消息选择器channel需要原生int')272        object.__setattr__(self, 'frame_id', normalize_frame_id(self.frame_id))273        if any(value is not None and type(value) is not bool for value in (self.is_extended, self.is_fd)):274            raise TypeError('消息选择器is_extended/is_fd需要bool或省略')275        # 不根据数值大小猜帧类型,也不复制旧ChrgInfo对ID0的缺失判断。276        maximum = 0x7FF if self.is_extended is False else 0x1FFFFFFF277        if self.channel < 0 or not 0 <= self.frame_id <= maximum:278            raise ValueError('消息选择器通道或帧ID范围非法')279280281@dataclass(frozen=True)282class MessageCalculationPlan:283    """保存一个会话内按当前匹配消息逐次求值的明确计算声明。284285    expr来自本来源MessageDeclarations,parameters为同一冻结参数tuple。286    operation仅legacy_sum,message_policy必须显式current_message-v1;这里不设置287    旧触发器、跨消息缓存、普通TTL、连续时间覆盖率或事件/片段范围。288    profile标识原解码路径,与新计算policy分别保存,不冒充旧完整兼容。289    可选disjunction只把本条贡献路由到正/负桶;原方向定位关系由计划交叉校验。290    """291292    calculation_id: str  # 与rules/windows/普通calculations共用计划ID空间。293    source: str  # 当前单一CAN来源。294    profile: str  # 原解码profile,须与实际来源声明对应。295    operation: str  # 固定legacy_sum,不放入普通CalculationDefinition。296    update_message: LegacyMessageSelector  # 通道/ID必填,帧类型可省略的当前帧选择器。297    expr: StaticExpression  # 逐消息表达式的静态语法树;构建它不会读取或执行分析。298    unit: str  # 一次贡献及累计结果的单位,必须等于表达式结果单位。299    parameters: tuple[ExpressionParameter, ...]  # 原单位、规范FLOAT64参数。300    message_policy: str  # 固定current_message-v1,不能与旧完整cache等义。301    disjunction: LegacyDisjunction | None = None  # 可选原解码方向选择,不进入表达式或TTL。302303    def __post_init__(self) -> None:304        """检查固定模式及静态消息表达式的一致性,不打开任何来源。305306        参数: 无,读取冻结声明。返回: None,不重编译AST或重建原信号。307        异常: TypeError为模型/容器错误;ValueError为模式、来源、单位或参数冲突。308        """309        require_identifier(self.calculation_id, 'message_calculation.name')310        require_identifier(self.source, 'message_calculation.source')311        require_text(self.unit, 'message_calculation.unit')312        # 末尾缺省保既有位置构造;只有明确冻结选择器才启用分桶。313        if self.disjunction is not None and type(self.disjunction) is not LegacyDisjunction:314            raise TypeError('逐消息disjunction需要LegacyDisjunction或None')315        if (type(self.profile) is not str or self.profile not in (PROFILE_ID, 'can-text-v1')316                or type(self.operation) is not str or self.operation != 'legacy_sum'317                or type(self.message_policy) is not str or self.message_policy != 'current_message-v1'):318            raise ValueError('当前逐消息计算仅支持固定解码profile、legacy_sum及current_message-v1')319        if (type(self.update_message) is not LegacyMessageSelector320                or not isinstance(self.expr, StaticExpression)321                or type(self.expr.bindings) is not MessageDeclarations322                or not isinstance(self.expr.root, ExprNode)323                or type(self.parameters) is not tuple324                or any(type(item) is not ExpressionParameter for item in self.parameters)):325            raise TypeError('逐消息计划需要选择器、消息域StaticExpression及参数tuple')326        # 声明不是原始字符串;保原单位和参数,不能借包装重写编译结果。327        declaration = self.expr.bindings328        if (declaration.source != self.source or declaration.message_policy != self.message_policy329                or declaration.parameters != self.parameters or declaration.derived_names):330            raise ValueError('逐消息计划来源、policy或参数与消息表达式声明不符')331        if self.expr.root.dtype not in (SignalDType.INT64, SignalDType.UINT64, SignalDType.FLOAT64):332            raise ValueError('legacy_sum表达式必须为数值,不接受bool或enum')333        if self.expr.root.unit != self.unit:334            raise ValueError('legacy_sum单位必须与表达式结果一致')335336337@dataclass(frozen=True)338class ExecutionPlan:339    """保存后端和资源限额,实际执行器负责落实这些声明。340341    backend选择'numpy'、'reference'或按静态类别选择的'auto',batch_rows限制来源批次总样本数。342    memory_budget_bytes声明新分析层内存预算,result_buffer_rows限制结果缓冲,343    spool_quota_bytes声明磁盘暂存配额,None表示没有显式配额。344    partial_policy是全局默认结果政策;on_budget_exceeded当前只允许'error'。345    构造计划不测量内存或磁盘,也不启动运行、暂存或发布。346    """347348    backend: str  # numpy、reference或auto;运行前解析为具体后端。349    batch_rows: int  # 来源批次样本上限。350    memory_budget_bytes: int  # 新分析层RAM预算。351    result_buffer_rows: int  # 结果流缓冲上限。352    partial_policy: str  # 全局指标默认部分政策。353    spool_quota_bytes: int | None  # 磁盘配额,None表示未显式限额。354    on_budget_exceeded: str = 'error'  # 首版不丢数据或增加旧扫描。355    process_count: int = 1  # 本次分析读取/解码工作进程上限;不包含主分析进程。356357358359360@dataclass(frozen=True)361class OutputPlan:362    """保存新结果表和清单的输出格式声明。363364    tables接受普通使用默认'readable_csv',或供程序使用的'csv'/'jsonl';manifest使用'json'。365    contributions仅控制逐消息贡献明细,none仍保留统计汇总和其他请求结果;省略为none。366    本类不创建目录或写文件;run生成本次实际原始片段,write只复制已封口输出。367    """368369    tables: str  # readable_csv、csv或jsonl;只生成所选的一套表。370    manifest: str  # json。371    contributions: str = 'none'  # 只有明确请求full时生成逐消息贡献明细。372    signal_csv: SignalCSVOutput | None = None  # 显式请求的信号长表,可单独设置线性网格。373    can_frames: CANFramesOutput | None = None  # 显式原帧/重建帧输出,默认不保额外原始数据。374
Chronaxis 用户手册 · 离线阅读