Chronaxis用户手册English
本页目录

chronaxis.results.summary

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""定义按需执行的精简指标行,不要求不存在的完整输入证明。23只保留结果身份、业务数值、单位及完成/质量状态。事件或片段通过parent_id关联,4不逐行重复配置、信号定义、时间对照或贡献;公开结果只接受当前扁平模型。5本模型只校验结果字段,不读取输入或执行指标计算。6"""78from ..core.moments import MOMENTS9from ..core.quantiles import SAMPLE_QUANTILES10from dataclasses import dataclass11import json12from math import isfinite1314from ..core.types import QualityFlag as Q15from ..core.counts_v4 import COUNT_OPERATIONS, OBJECT_COUNTS16from ..core.validation import require_identifier, require_text17from ..timebase.conversion import require_int64_ns181920@dataclass(frozen=True)21class SummaryCalculationRow:22    """一条精简指标;未知用None,不能把缺失转换为正常零。"""2324    # 固定身份只用于关联本次结果,不代替原始输入哈希或逐条来源。25    sequence: int26    dataset_id: str27    entity_id: str28    session_id: str29    calculation_id: str30    # 算子及父范围给出业务含义;session没有父对象。31    operation: str32    scope: str33    parent_id: str | None34    # 数值与质量必须一起读取,None不等于有效零。35    value: int | float | None36    unit: str37    status: str38    reason: str39    quality: Q4041    def __post_init__(self):42        """核对身份、数值类型及状态,不补造来源或时间信息。4344        参数: 无。返回: None。异常: TypeError/ValueError为字段或组合错误。45        """46        if type(self.sequence) is not int or self.sequence < 0:47            raise ValueError('精简指标序号需要非负原生int')48        for name in ('dataset_id', 'entity_id', 'session_id', 'calculation_id'):49            require_identifier(getattr(self, name), 'summary.'+name)50        if type(self) is not SummaryHostedCalculationRow and self.operation not in ('integral', 'duration', 'min_time', 'max_time', 'time_weighted_mean',51                           'sample_minimum', 'sample_maximum', 'sample_mean', 'legacy_sum', *COUNT_OPERATIONS, *MOMENTS, *SAMPLE_QUANTILES):52            raise ValueError('精简指标不支持该算子')53        # 即使外部重新读取结果行,计数也必须是无量纲定宽整数,且对象归属整次会话。54        if self.operation in COUNT_OPERATIONS and (self.unit != '1' or self.value is not None55                and (type(self.value) is not int or not 0 <= self.value < 1 << 64)):56            raise ValueError('计数结果需要单位1及可用的uint64整数或None')57        if self.operation in OBJECT_COUNTS and self.scope != 'session':58            raise ValueError('对象计数结果必须属于session')59        if self.scope not in ('session', 'event', 'segment'):60            raise ValueError('精简指标scope无效')61        if self.scope == 'session':62            if self.parent_id is not None:63                raise ValueError('会话指标不能声明父事件或片段')64        else:65            require_text(self.parent_id, 'summary.parent_id')66        if self.value is not None and (type(self.value) not in (int, float)67                or type(self.value) is float and not isfinite(self.value)):68            raise ValueError('精简指标值需要有限数值或None')69        require_text(self.unit, 'summary.unit')70        require_text(self.reason, 'summary.reason')71        if self.status not in ('complete', 'partial', 'indeterminate'):72            raise ValueError('精简指标status无效')73        if self.status == 'indeterminate' and self.value is not None:74            raise ValueError('无法确定的指标不能含可用数值')75        if not isinstance(self.quality, Q):76            raise TypeError('精简指标质量需要QualityFlag')777879SUMMARY_FIELDS = ('sequence', 'dataset_id', 'entity_id', 'session_id', 'calculation_id', 'operation',80                  'scope', 'parent_id', 'value', 'unit', 'status', 'reason', 'quality')818283@dataclass(frozen=True)84class SummaryHostedCalculationRow(SummaryCalculationRow):85    """宿主归约产生的纯数据行;读取版本和标识不允许动态执行插件。"""8687    extension_version: str88    coverage_status: str | None = None8990    def __post_init__(self):91        """验证固定行协议和清晰的宿主身份。参数: 无。返回: None。异常: 身份或覆盖状态错误。"""92        super().__post_init__()93        require_identifier(self.operation,'summary.hosted_operation')94        require_text(self.extension_version,'summary.extension_version')95        if self.coverage_status not in (None,'sufficient','indeterminate'):96            raise ValueError('宿主归约覆盖判定无效')979899@dataclass(frozen=True)100class SummaryCoverageCalculationRow(SummaryCalculationRow):101    """仅明确设置覆盖门槛时补一个判定,不重复输出时长、比例或输入明细。"""102103    coverage_status: str104105    def __post_init__(self):106        """保留数值政策与覆盖门槛两个不同事实。参数: 无。返回: None。异常: 非法判定。"""107        super().__post_init__()108        if self.coverage_status not in ('sufficient','indeterminate'):109            raise ValueError('覆盖门槛判定无效')110111112def _require_row_identity(row):113    """核精简事件和配对的公共身份。参数: 冻结行。返回: None。异常: 类型或字段错误。"""114    if type(row.sequence) is not int or row.sequence < 0:115        raise ValueError('精简结果序号需要非负原生int')116    for name in ('dataset_id','entity_id','session_id','rule_id'):117        require_identifier(getattr(row,name),'summary.'+name)118    if not isinstance(row.quality,Q):119        raise TypeError('精简结果质量需要QualityFlag')120121122@dataclass(frozen=True)123class SummaryEventRow:124    """一条业务事件;开始、确认、结束各有不同含义,不含原帧或逐条来源副本。"""125126    sequence: int127    dataset_id: str128    entity_id: str129    session_id: str130    rule_id: str131    event_id: str132    start_ns: int133    confirmed_ns: int134    end_ns: int | None135    status: str136    trigger: str137    quality: Q138139    def __post_init__(self):140        """核实际业务端点,不能把未知结束填为零。参数: 无。返回: None。异常: 合同错误。"""141        _require_row_identity(self)142        require_text(self.event_id,'event_id')143        start=require_int64_ns(self.start_ns,'start_ns')144        confirmed=require_int64_ns(self.confirmed_ns,'confirmed_ns')145        if confirmed < start:146            raise ValueError('确认不能早于开始')147        if self.status == 'point':148            if self.end_ns is not None or start != confirmed:149                raise ValueError('点事件没有活动结束时间')150        elif self.status in ('closed','interrupted','truncated','timeout'):151            end=require_int64_ns(self.end_ns,'end_ns')152            if not start < end or confirmed > end:153                raise ValueError('活动实际端点顺序无效')154        else:155            raise ValueError('精简事件状态无效')156        require_text(self.trigger,'trigger')157158159@dataclass(frozen=True)160class SummarySequenceRow:161    """一条配对结论,只关联既有事件ID并保存所选口径的延迟。"""162163    sequence: int164    dataset_id: str165    entity_id: str166    session_id: str167    rule_id: str168    result_id: str169    request_id: str170    response_id: str | None171    decided_ns: int172    delay_ns: int | None173    status: str174    quality: Q175176    def __post_init__(self):177        """核配对及未知结果,不逐行重复规则和事件。参数: 无。返回: None。异常: 合同错误。"""178        _require_row_identity(self)179        for name in ('result_id','request_id'):180            require_text(getattr(self,name),name)181        require_int64_ns(self.decided_ns,'decided_ns')182        if self.status == 'matched':183            require_text(self.response_id,'response_id')184            if require_int64_ns(self.delay_ns,'delay_ns') < 0:185                raise ValueError('配对延迟不能为负')186        elif self.status in ('no_response','indeterminate'):187            if self.response_id is not None or self.delay_ns is not None:188                raise ValueError('未匹配结果不能伪造响应和延迟')189            if self.status == 'indeterminate' and self.quality == Q.NONE:190                raise ValueError('未知配对必须说明质量原因')191            if self.status == 'no_response' and self.quality != Q.NONE:192                raise ValueError('缺失观测不能声明确定未响应')193        else:194            raise ValueError('精简配对状态无效')195196197@dataclass(frozen=True)198class SummarySegmentRow:199    """片段只输出实际边界、状态和父事件ID,不重复请求边界/原事件/来源。"""200    sequence: int201    dataset_id: str202    entity_id: str203    session_id: str204    window_id: str205    segment_id: str206    start_ns: int | None207    end_ns: int | None208    status: str209    parent_event_ids: tuple[str,...]210    quality: Q211212    def __post_init__(self):213        """检查实际范围和必需父关联。参数: 无。返回: None。异常: 字段/组合错误。"""214        if type(self.sequence) is not int or self.sequence<0:215            raise ValueError('片段sequence需非负原生int')216        for name in ('dataset_id','entity_id','session_id','window_id'):217            require_identifier(getattr(self,name),name)218        require_text(self.segment_id,'segment_id')219        if not isinstance(self.quality,Q) or type(self.parent_event_ids) is not tuple or not self.parent_event_ids:220            raise TypeError('片段需质量及非空父ID tuple')221        if len(set(self.parent_event_ids))!=len(self.parent_event_ids):222            raise ValueError('片段父ID不能重复')223        for parent in self.parent_event_ids:224            require_text(parent,'parent_event_id')225        if self.status=='empty':226            if self.start_ns is not None or self.end_ns is not None:227                raise ValueError('空片段不能伪造实际范围')228        elif self.status in ('complete','partial'):229            if require_int64_ns(self.start_ns)>=require_int64_ns(self.end_ns):230                raise ValueError('片段需要正实际范围')231        else:232            raise ValueError('片段状态错误')233234235SUMMARY_EVENT_FIELDS = ('sequence','dataset_id','entity_id','session_id','rule_id','event_id',236    'start_ns','confirmed_ns','end_ns','status','trigger','quality')237SUMMARY_SEQUENCE_FIELDS = ('sequence','dataset_id','entity_id','session_id','rule_id','result_id',238    'request_id','response_id','decided_ns','delay_ns','status','quality')239SUMMARY_MODELS = {SummaryCalculationRow: ('calculations',SUMMARY_FIELDS,7),240    SummaryEventRow: ('events',SUMMARY_EVENT_FIELDS,8), SummarySequenceRow: ('sequences',SUMMARY_SEQUENCE_FIELDS,9)}241SUMMARY_MODELS[SummarySegmentRow]=('segments',('sequence','dataset_id','entity_id','session_id','window_id',242    'segment_id','start_ns','end_ns','status','parent_event_ids','quality'),11)243@dataclass(frozen=True)244class SummaryContributionRow:245    """一条明确请求的逐消息贡献;仅定位计算,不复制原帧或输入值。"""246247    sequence: int248    dataset_id: str249    entity_id: str250    session_id: str251    calculation_id: str252    source: str253    original_sequence: int254    evaluation_index: int255    value: float | None256    reason: str257    quality: Q258259    def __post_init__(self):260        """核定位及贡献/跳过互斥。参数: 无。返回: None。异常: 类型或字段错误。"""261        for name in ('sequence','original_sequence','evaluation_index'):262            if type(getattr(self,name)) is not int or getattr(self,name)<0:263                raise ValueError('贡献定位需非负原生整数')264        for name in ('dataset_id','entity_id','session_id','calculation_id','source'):265            require_identifier(getattr(self,name),name)266        if not isinstance(self.quality,Q):267            raise TypeError('贡献质量需QualityFlag')268        if self.value is None:269            if self.reason not in ('missing_input','condition_false','evaluation_error','nonfinite_input'):270                raise ValueError('跳过贡献需要明确原因')271        elif type(self.value) is not float or not isfinite(self.value) or self.reason!='contributed':272            raise ValueError('有效贡献需要有限float及contributed状态')273274275SUMMARY_MODELS[SummaryContributionRow]=('contributions',('sequence','dataset_id','entity_id','session_id',276    'calculation_id','source','original_sequence','evaluation_index','value','reason','quality'),12)277@dataclass(frozen=True)278class SummaryInputRow:279    """按请求保存一次所需输入;字段原值一份,不保存双时间表示或原帧副本。"""280    sequence: int281    dataset_id: str282    entity_id: str283    session_id: str284    source: str285    original_sequence: int286    time_ns: int | None287    record_type: str288    locator: str289    values_json: str290    quality: Q291292    def __post_init__(self):293        """核定位与有限JSON标量,拒绝任意嵌套模型。参数: 无。返回: None。异常: 字段/格式错误。"""294        for name in ('sequence','original_sequence'):295            if type(getattr(self,name)) is not int or getattr(self,name)<0:raise ValueError('追溯序号需非负int')296        for name in ('dataset_id','entity_id','session_id','source'):require_identifier(getattr(self,name),name)297        if self.time_ns is not None:require_int64_ns(self.time_ns)298        if self.record_type not in ('decoded_message','signal_sample','signal_clock') or not isinstance(self.quality,Q):299            raise ValueError('追溯类型/质量无效')300        require_text(self.locator,'trace.locator')301        if type(self.values_json) is not str:raise TypeError('追溯值需JSON文本')302        values=json.loads(self.values_json)303        if type(values) is not dict:raise ValueError('追溯值需字段对象')304        for name,value in values.items():305            require_text(name,'trace.field')306            if type(value) is dict:307                if set(value)!= {'nonfinite'} or value['nonfinite'] not in ('nan','inf','-inf'):308                    raise ValueError('追溯只允许明确非有限值标签')309            elif value is not None and (type(value) not in (str,int,float,bool)310                    or type(value) is float and not isfinite(value)):311                raise ValueError('追溯字段需有限标量或明确非有限标签')312313314SUMMARY_MODELS[SummaryInputRow]=('input_trace',('sequence','dataset_id','entity_id','session_id',315    'source','original_sequence','time_ns','record_type','locator','values_json','quality'),13)316@dataclass(frozen=True)317class SummarySelectionRow:318    """一条真实更新的选择结果;保业务值及三值选择,不重复输入证明。"""319    sequence: int320    dataset_id: str321    entity_id: str322    session_id: str323    calculation_id: str324    scope: str325    parent_id: str | None326    time_ns: int327    value: int | float | bool | str | None328    dtype: str329    unit: str330    valid: bool331    selection: str332    quality: Q333334    def __post_init__(self):335        """核选择行身份和值类型。参数: 无。返回: None。异常: 类型/结构错误。"""336        if type(self.sequence) is not int or self.sequence<0:raise ValueError('选择序号需要非负int')337        for name in ('dataset_id','entity_id','session_id','calculation_id'):require_identifier(getattr(self,name),name)338        if self.scope not in ('session','event','segment'):raise ValueError('选择范围类型无效')339        if self.scope=='session':340            if self.parent_id is not None:raise ValueError('会话选择不能声明父对象')341        else:require_text(self.parent_id,'selection.parent_id')342        require_int64_ns(self.time_ns);require_text(self.unit,'selection.unit')343        kinds={'float64':float,'int64':int,'uint64':int,'bool':bool,'enum':str}344        if self.dtype not in kinds or type(self.valid) is not bool or not isinstance(self.quality,Q):345            raise TypeError('选择类型/有效性/质量错误')346        if self.selection not in ('true','unknown') or self.selection=='unknown' and self.quality==Q.NONE:347            raise ValueError('选择仅支持TRUE或有质量原因的UNKNOWN')348        if self.valid:349            if type(self.value) is not kinds[self.dtype] or type(self.value) is float and not isfinite(self.value):350                raise ValueError('选择有效值与dtype不符')351            if self.dtype=='int64' and not -(1<<63)<=self.value<(1<<63) or self.dtype=='uint64' and not 0<=self.value<(1<<64):352                raise ValueError('选择整数超出dtype')353        elif self.value is not None or self.quality==Q.NONE:354            raise ValueError('选择无效值必须为空并保留质量原因')355356357SUMMARY_MODELS[SummarySelectionRow]=('selections',('sequence','dataset_id','entity_id','session_id','calculation_id',358    'scope','parent_id','time_ns','value','dtype','unit','valid','selection','quality'),14)359from .aggregation_row import SummaryAggregationRow, AGGREGATION_FIELDS360SUMMARY_MODELS[SummaryAggregationRow] = ('aggregations',AGGREGATION_FIELDS,15)361SUMMARY_COLUMNS = {table: names for table,names,_ in SUMMARY_MODELS.values()}362SUMMARY_MODELS[SummaryCoverageCalculationRow] = ('calculations',SUMMARY_FIELDS+('coverage_status',),10)363SUMMARY_MODELS[SummaryHostedCalculationRow] = ('calculations',SUMMARY_FIELDS+('extension_version','coverage_status'),16)364365366def summary_fields(row):367    """转换必要平坦字段,CSV和JSONL共用,不附另一份row_json。368369    参数: SummaryCalculationRow。返回: 固定字段dict。异常: TypeError为错误根类型。370    """371    try:372        if type(row) not in SUMMARY_MODELS:373            raise TypeError('精简字段需要SummaryCalculationRow')374        result = {name: getattr(row, name) for name in SUMMARY_MODELS[type(row)][1]}375        result['quality'] = int(row.quality)376        return result377    finally:378        row = None379
Chronaxis 用户手册 · 离线阅读