Chronaxis用户手册English
本页目录

chronaxis.configuration.explanation

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""把已验证的分析声明讲成人可阅读的步骤;不加载来源、不展开运行时数据。23文本与JSON报告使用同一计划和能力报告。这里仅翻译已知术语、格式化精确时间,4并从原始声明区分显式设置与默认设置,不建立另一套配置默认值或执行规则。5"""6from collections.abc import Mapping789OPERATIONS={10    'integral':'积分(按前值保持累计)',11    'sample_mean':'样本均值(每个有效样本权重相同)',12    'time_weighted_mean':'时间加权均值(按有效持续时间加权)',13    'sample_minimum':'有效样本最小值','sample_maximum':'有效样本最大值',14    'min_time':'有效时间区间内最小值','max_time':'有效时间区间内最大值',15    'duration':'条件成立的累计时长','select':'选择符合条件的记录',16}17RULES={'condition_duration':'条件持续成立','rising_edge':'由假变真的上升沿',18       'falling_edge':'由真变假的下降沿','start_only':'触发后保留固定窗口',19       'window_delta':'回看窗口内变化量','debounce':'条件去抖','hysteresis':'进入与退出使用不同条件',20       'start_end':'开始条件启动、结束条件停止'}21SOURCES={'memory':'配置中的内存数据','signal_csv':'已解码信号CSV',22         'can_blf':'CAN二进制BLF','can_asc':'CAN文本ASC','asc_or_blf':'CAN连续ASC/BLF','raw_can_csv':'原始CAN帧CSV'}232425def time_text(nanoseconds):26    """无损显示纳秒时长。参数: 整数或None。返回: 带单位文本。异常: 无。"""27    if nanoseconds is None:28        return '未设置'29    for divisor,unit in ((10**9,'s'),(10**6,'ms'),(1000,'us')):30        if nanoseconds and nanoseconds%divisor==0:31            return str(nanoseconds//divisor)+unit32    return str(nanoseconds)+'ns'333435def _declared(plan,*parts):36    """检查原声明是否包含字段。参数: 计划/路径。返回: bool。异常: 无;不取文件默认值。"""37    current=plan.raw_config38    for part in parts:39        if isinstance(current,Mapping) and part in current:40            current=current[part]41        elif type(current) is tuple and type(part) is int and 0<=part<len(current):42            current=current[part]43        else:44            return False45    return True464748def _setting(plan,label,value,*parts):49    """显示实际生效值及其来源。参数: 计划/标签/值/路径。返回: 文本。异常: 无。"""50    if type(value) is bool:51        value='开启' if value else '关闭'52    source='配置指定' if _declared(plan,*parts) else '默认'53    return f'{label}={value}({source})'545556def _scope(value):57    """解释计算/输出范围。参数: 已校验范围文本。返回: 中文及具体ID。异常: 无。"""58    if value=='session':59        return '整个会话'60    prefix,_,identifier=value.partition(':')61    return ('每个事件 ' if prefix=='event' else '每个窗口片段 ')+identifier626364def _validation_lines(validation):65    """保留报告中全部错误与限制。参数: ValidationReport。返回: 文本行。异常: 无。"""66    lines=['静态检查通过:当前声明有可用执行路径。' if validation.valid and validation.execution_ready67           else '静态检查未通过:请修正以下问题。']68    lines.extend('错误:'+message for message in validation.errors)69    lines.append('此检查尚未读取日志、信号表或DBC;未验证实际字段、数据覆盖和内存峰值。')70    lines.extend('提示:'+message for message in validation.warnings)71    return lines727374def validation_text(plan,validation):75    """生成简短校验说明。参数: 已加载计划/同次报告。返回: 多行文本。异常: 无。"""76    return '\n'.join([f'分析 {plan.dataset.dataset_id}:{len(plan.sources)}个来源,'77        f'{len(plan.signals)}个信号,{len(plan.rules)}个事件规则,'78        f'{len(plan.calculations)+len(plan.message_calculations)}个计算项。',79        *_validation_lines(validation)])808182def _rule_lines(plan):83    """说明事件、窗口和请求响应的有效声明。参数: 计划。返回: 文本行。异常: 内部模型不符。"""84    lines=[]85    rule_indices={item['name']:index for index,item in enumerate(plan.raw_config.get('rules',()))}86    for rule in plan.rules:87        index=rule_indices.get(rule.rule_id)88        line=f'  {rule.rule_id}:{RULES[rule.rule_type]};条件 {rule.condition.text}'89        if rule.minimum_duration_ns is not None:90            line+=(';持续严格超过 ' if rule.duration_comparison == '>' else ';至少持续 ')+time_text(rule.minimum_duration_ns)91            line+=';统计从'+('首次成立' if rule.selection_origin=='onset' else '持续确认')+'时刻开始'92        if rule.window_duration_ns is not None:93            line+=';固定窗口 '+time_text(rule.window_duration_ns)94        if rule.exit_condition is not None:95            line+=';退出条件 '+rule.exit_condition.text96        if rule.stability is not None:97            line+=';进入稳定 '+time_text(rule.stability.enter_ns)+',退出稳定 '+time_text(rule.stability.exit_ns)98        if rule.window_delta is not None:99            spec=rule.window_delta100            line+=f';回看 {time_text(spec.window_ns)},门槛 {spec.threshold},方向 {spec.direction},单调约束 {spec.monotonic}'101        if rule.start_end is not None:102            line+=';等待上限 '+('不限制' if rule.start_end.timeout_ns is None else time_text(rule.start_end.timeout_ns))103        # 开始/结束规则的首次策略存于专用规格;活动和稳定性规则没有该配置项。104        if rule.rule_type not in ('condition_duration','debounce','hysteresis'):105            emit=rule.start_end.emit_initial if rule.start_end is not None else rule.emit_initial106            setting=_setting(plan,'首次已知条件触发',emit,'rules',index,'emit_initial')107            line+=';'+(setting if index is not None else setting.replace('(默认)','(由预设展开)'))108        lines.append(line)109    for window in plan.windows:110        merge={'none':'分别保留','overlap':'合并重叠片段','overlap_and_adjacent':'合并重叠或相邻片段',111               'exit_delay':'按退出延迟合并'}[window.merge]112        lines.append(f'  窗口 {window.window_id}:来自事件 {window.event},向前 {time_text(window.pre_ns)},'113            f'向后 {time_text(window.post_ns)},{merge},退出延迟 {time_text(window.exit_delay_ns)}')114    for sequence in plan.sequences:115        lines.append(f'  序列 {sequence.sequence_id}:请求 {sequence.start_rule} → 响应 {sequence.response_rule};'116            f'允许延迟 [{time_text(sequence.min_delay_ns)}, {time_text(sequence.max_delay_ns)}](包含两端);'117            f'最多等待 {sequence.max_pending} 个请求')118    return lines or ['  未声明事件、窗口或请求响应序列。']119120121def explanation_text(plan,report,*,backend_override=None):122    """按输入→信号→事件→计算→输出说明同一有效计划。参数: 计划/报告/CLI后端。返回: 多行文本。123124    异常: 内部接口不符传播,不用默认文案掩盖未实现类型;无输入I/O。125    """126    dataset=plan.dataset127    domain=dataset.domain128    declared_domain=plan.raw_config['dataset'].get('domain',{})129    start=declared_domain.get('start',time_text(domain.start_ns) if domain is not None else '')130    end=declared_domain.get('end',time_text(domain.end_ns) if domain is not None else '')131    lines=[f'分析 {dataset.dataset_id}:对象 {dataset.entity},会话 {dataset.session_id}',132        '请求范围:'+('整文件(没有时间筛选)' if domain is None else133        f'[{start}, {end}),包含起点,不包含终点'),134        '输入来源:']135    for source in plan.sources:136        config=source.config137        if source.source_type=='memory':138            paths='数据已在配置内声明'139        elif source.source_type=='signal_csv':140            paths=','.join(map(str,config.paths))141        elif source.source_type=='can_blf':142            paths=','.join(map(str,config.paths or (config.path,)))143        else:144            paths=','.join(str(item.path) for item in config.inputs)145        lines.append(f'  {source.name}:{SOURCES[source.source_type]};{paths};时间单位 {source.time_unit},偏移 {time_text(config.offset_ns)}')146        if source.source_type in ('can_blf','can_asc','asc_or_blf','raw_can_csv'):147            lines.append(f'    通道 {config.channel + 1},DBC {config.dbc_path}(缺省信号声明按需读取元数据,未读取报文)')148        execution=next((item for item in report.source_execution if item['source']==source.name),None)149        if execution is not None:150            merge='按文件顺序接续' if execution['file_merge']=='concatenate' else '按记录时间归并,需要磁盘暂存'151            lines.append(f"    {execution['file_count']}个文件;{merge};读取子进程上限{execution['read_process_limit']};保持一个连续会话(未实测输入时间)")152    lines.append('输入信号:')153    signal_paths={}154    for index,raw in enumerate(plan.raw_config.get('signals',())):155        if 'signals' in raw:156            for child_index,child in enumerate(raw['signals']):157                signal_paths[child['name']]=('signals',index,'signals',child_index)158        else:159            signal_paths[raw['name']]=('signals',index)160    source_types={source.name:source.source_type for source in plan.sources}161    for index,item in enumerate(plan.signals):162        signal=item.definition163        # 未读DBC时直说等待周期,不把None展示为没有有效期或假定无限保持。164        validity = ('DBC周期的3倍(运行时确定)' if signal.valid_for_ns is None165                    else time_text(signal.valid_for_ns))166        location=f'{signal.source}.{signal.field}'167        if source_types[signal.source] in ('can_blf','can_asc','asc_or_blf','raw_can_csv'):168            # 人读说明沿用配置通道;内部规范键和机器报告仍保持0基模型合同。169            from ..sources.can_spec import can_field_from_key170            field=can_field_from_key(signal.field)171            location=(f'{signal.source} 通道 {field.channel+1},报文0x{field.frame_id:X}'172                      f'({field.frame_id}),信号 {field.signal}')173        lines.append(f'  {signal.name} ← {location};单位 {signal.unit};'174            f'有效期 valid_for={validity};'175            +_setting(plan,'样本策略','在有效期内沿用最近有效值(backward_hold)',176                      *signal_paths.get(signal.name,('signals',index)),'sample_policy'))177    lines.append('  有效期表示一个有效样本最多沿用多久;到期即未知,无效更新覆盖前值,缺口不补零。')178    if plan.derived:179        lines.append('派生信号:')180        lines.extend(f'  {item.definition.name} = {item.definition.expr};单位 {expression.root.unit}'181                     for item,expression in zip(plan.derived,plan.derived_expressions))182    lines.append('事件与时间窗口:')183    lines.extend(_rule_lines(plan))184    lines.append('计算项:')185    for index,calculation in enumerate(plan.calculations):186        signal='无单独信号' if calculation.signal is None else calculation.signal.text187        condition='' if calculation.condition is None else ';筛选 '+calculation.condition.text188        lines.append(f'  {calculation.calculation_id}:{OPERATIONS[calculation.operation]};'189            f'{_scope(calculation.scope)};信号 {signal};结果单位 {calculation.unit}{condition};'190            +_setting(plan,'有效覆盖门槛',calculation.minimum_coverage,'calculations',index,'minimum_coverage')191            +f',部分结果策略 {calculation.partial_policy}')192    for calculation in plan.message_calculations:193        lines.append(f'  {calculation.calculation_id}:逐消息累计;来源 {calculation.source};'194            f'表达式 {calculation.expr.text};单位 {calculation.unit};按原消息参数求值,不额外乘时间间隔')195    if not plan.calculations and not plan.message_calculations:196        lines.append('  未声明计算项。')197    lines.append('计算间隔:')198    choices=report.intervals.resolved if report.intervals is not None else ()199    for name,choice in choices:200        description={'actual':'采用实际记录时间差','fixed':'采用指定间隔 '+time_text(choice.step_ns),201                     'dbc':'运行时从DBC读取周期'}[choice.mode]202        lines.append(f'  {name}:{description}'+(';驱动信号 '+choice.driver if choice.driver else ''))203    if not choices:204        lines.append('  没有使用普通计算间隔的消费者。')205    lines.append('  固定间隔或DBC周期不延长信号有效期;导出网格独立于计算间隔。')206    lines.append('输出与默认设置:')207    lines.append('  '+_setting(plan,'结果表格式',plan.outputs.tables,'outputs','tables')+';清单格式 JSON')208    contributions={'none':'关闭','full':'逐条保存'}[plan.outputs.contributions]209    lines.append('  '+_setting(plan,'逐消息贡献明细',contributions,'outputs','contributions'))210    for name,label,output in (('signal_csv','信号CSV导出',plan.outputs.signal_csv),211                              ('can_frames','CAN帧导出',plan.outputs.can_frames)):212        if output is None:213            lines.append('  '+label+':关闭(默认)')214            continue215        description='开启,范围 '+output.scope+(',窗口 '+output.window if output.window else '')216        grid=output.spec.resampling if name=='signal_csv' else output.resampling217        if name=='signal_csv':218            description+=';信号 '+', '.join(output.spec.signals)219        else:220            description+=';来源 '+output.source+',格式 '+output.spec.format221        if grid is None:222            description+=';保留原始采样,不重采样'223        else:224            description+=';独立导出网格 '+('运行时读取DBC周期' if grid.period_ns is None else time_text(grid.period_ns))225        lines.append('  '+label+':'+description)226    backend=plan.execution.backend if backend_override is None else backend_override227    lines.append('  执行后端='+backend+('(命令行覆盖)' if backend_override is not None else228        '(配置指定)' if _declared(plan,'execution','backend') else '(默认)'))229    if report.requested_backend == 'auto':230        lines.append('  本次选择='+report.selected_backend+';'+report.backend_reason)231    for field,label in (('process_count','读取解码工作进程上限'),('batch_rows','每批记录上限'),('memory_budget_bytes','分析层内存预算/字节'),232                        ('result_buffer_rows','结果缓冲行数'),('spool_quota_bytes','暂存磁盘预算/字节')):233        value=report.budget[field] if field=='process_count' else getattr(plan.execution,field)234        if field=='process_count' and value!=plan.execution.process_count:235            lines.append('  '+label+'='+str(value)+'(本次调用覆盖)')236            continue237        lines.append('  '+_setting(plan,label,'未设置' if value is None else value,'execution',field))238    for field,label in (('trace','详细追溯'),('integrity','完整性诊断'),('retain_work','保留现场')):239        lines.append('  '+_setting(plan,label,getattr(plan.diagnostics,field),'diagnostics',field))240    lines.append('检查结论:')241    lines.extend(_validation_lines(report.validation))242    return '\n'.join(lines)243
Chronaxis 用户手册 · 离线阅读