Chronaxis用户手册English
本页目录

chronaxis.planning.preset_models

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""保存预设v1的公开不可变声明、角色契约及可序列化计划片段。23输入JSON参数及类型化信号引用,输出冻结spec、角色追溯和公共节点声明。4先校验spec结构并深复制参数,再由外部构建器检查工程契约;本模块不运行I/O或分析。5声明模型独立于构建器/AnalysisPlan,两个入口共用真实类型,避免循环导入及不可解析注解。6候选语义名仅是领域预设v1本地协议;不建立核心目录,不伪造时钟/来源/工程结论。7序列化产生独立JSON副本,节点仍须由正常公共编译器检查依赖与单位。8"""910# 不可变模型及结构校验不依赖预设算法或公开计划。11from collections.abc import Mapping12from dataclasses import dataclass13from ..core.binding import SignalDefinition14from ..core.expression import DerivedDefinition15from ..core.types import SignalDType as D16from ..core.windows import WindowSpec17from .checks import (PlanValidationError, choice_at, json_value, metadata_at,18                     object_at, require_json_value, text_at)19from .sequences import SequencePlan202122@dataclass(frozen=True)23class RoleContract:24    """公开本预设版本的角色类型、单位、语义候选、方向与位置要求。2526    有效期必须由SignalDefinition明确提供且正;基础质量使用其固定九位禁用策略。27    semantic_id/version仅用于自动查找,显式绑定仍逐字段检查,不能只凭版本宣称符合。28    """2930    role: str  # mode/current/voltage/alarm;不注册全量领域名称。31    dtypes: tuple[D, ...]  # 转换后的可接受逻辑类型。32    unit: str  # 精确目标单位,关系须verified。33    semantic_id: str  # 本模块v1候选标识,大小写敏感。34    sign_convention: str | None = None  # 需要时为明确转换后方向。35    measurement_location: str | None = None  # 需要时为明确测量位置。36    version: str = '1'  # 本公开候选契约版本。37    forbidden_quality: int = 511  # 核心已定义九位基础质量全部禁止。383940# 每个角色仅声明预设实际需要的内容;无通用汽车/电池本体。41CHARGE_ROLES = (42    RoleContract('mode', (D.INT64, D.UINT64), '1', 'battery.mode'),43    RoleContract('current', (D.FLOAT64,), 'A', 'battery.current', 'positive_is_charge', 'pack_terminal'),44    RoleContract('voltage', (D.FLOAT64,), 'V', 'battery.voltage', measurement_location='pack_terminal'),45)46FAULT_ROLES = (RoleContract('alarm', (D.BOOL,), '1', 'fault.alarm'),)47COMMAND_ROLES = (RoleContract('request', (D.BOOL,), '1', 'command.request'),48                 RoleContract('response', (D.BOOL,), '1', 'command.response'))495051@dataclass(frozen=True)52class PresetSpec:53    """冻结预设ID、preset_type/version、角色别名及工程参数的独立JSON快照。5455    id只用简单标识;role_bindings值只引用当前计划简单别名,不能写领域属性路径。56    parameters不保留调用者可变对象,实际preset_type参数在build_preset中统一校验。57    """5859    id: str  # 生成节点的明确前缀,不自动加后缀解冲突。60    preset_type: str  # charge_session、fault_context、command_response或threshold_excursion。61    version: str  # 当前仅'1'。62    role_bindings: Mapping  # 只读角色→简单别名。63    parameters: Mapping  # 深冻结JSON工程声明。6465    def __post_init__(self):66        """检查公开spec结构并复制冻结,不运行数据计算。6768        参数: 无;读取构造字段。69        返回: None,字段保存独立只读副本。70        异常: PlanValidationError为名称、版本、JSON结构或角色别名错误。71        """72        # 公共入口与JSON路径使用同一严格名称/版本检查。73        text_at(self.id, '$.preset.name', identifier=True)74        choice_at(self.preset_type, ('charge_session', 'fault_context', 'command_response', 'threshold_excursion'), '$.preset.preset_type')75        choice_at(self.version, ('1',), '$.preset.version')76        # Mapping只用于冻结后再构建;不接受数组或任意对象作声明。77        if not isinstance(self.role_bindings, Mapping) or not isinstance(self.parameters, Mapping):78            raise PlanValidationError('$.preset', 'role_bindings/parameters必须为对象')79        roles = dict(self.role_bindings)80        parameters = dict(self.parameters) if type(self.parameters) is dict else json_value(self.parameters)81        require_json_value(roles, '$.preset.role_bindings')82        require_json_value(parameters, '$.preset.parameters')83        for role, alias in roles.items():84            text_at(role, '$.preset.role_bindings', identifier=True)85            text_at(alias, '$.preset.role_bindings.' + role, identifier=True)86        object.__setattr__(self, 'role_bindings', metadata_at(roles, '$.preset.role_bindings'))87        object.__setattr__(self, 'parameters', metadata_at(parameters, '$.preset.parameters'))8889    @classmethod90    def from_dict(cls, value):91        """从当前配置的预设对象构造公开不可变声明。9293        参数: value为原生JSON对象,必填name/preset_type/parameters,可选version/role_bindings。94        返回: PresetSpec,role_bindings缺省为空。95        异常: PlanValidationError为未知键、缺项或构造协议错误。96        """97        # 自由元数据不能借预设参数绕过契约。98        raw = object_at(value, '$.preset', ('name', 'preset_type', 'parameters'), ('version', 'role_bindings'))99        return cls(raw['name'], raw['preset_type'], raw.get('version', '1'), raw.get('role_bindings', {}), raw['parameters'])100101102@dataclass(frozen=True)103class RoleBinding:104    """保存已通过字段契约的角色及其原信号定义,供调用者追溯转换与来源。105106    自动/显式查找方式不同,但definition保留完整来源、单位、方向转换和质量身份。107    不保存样本值,也不伪称元数据的物理事实已由系统测量核实。108    """109110    contract: RoleContract  # 本次实际校验的v1角色。111    definition: SignalDefinition  # 原不可变完整绑定,包含转换证据。112    resolution: str  # explicit或semantic_unique。113114115@dataclass(frozen=True)116class PresetRule:117    """保存生成的持续、rising_edge或迟滞声明,尚未绑定来源时钟或执行状态。118119    condition是由合法简单别名/原生有限常量生成的受限DSL;正常计划编译仍检查类型和依赖。120    selection_origin仅持续规则为onset,point没有持续选择;实例不产生事件。121    """122123    id: str  # 生成节点ID。124    rule_type: str  # condition_duration、rising_edge或hysteresis。125    condition: str | None  # 公共DSL;迟滞改用两个明确条件。126    minimum_duration_ns: int | None = None  # condition_duration门槛,单位ns。127    selection_origin: str | None = None  # condition_duration为onset。128    emit_initial: bool = False  # rising_edge首次初始化政策。129    enter_condition: str | None = None  # 迟滞进入条件,普通规则不使用。130    exit_condition: str | None = None  # 迟滞退出条件。131    enter_delay_ns: int = 0  # 进入确认等待,纳秒。132    exit_delay_ns: int = 0  # 退出确认等待,纳秒。133134135@dataclass(frozen=True)136class PresetCalculation:137    """保存按原活动范围计算的积分或时长声明及独立覆盖/部分结果政策。138139    积分signal是公共信号或派生别名,unit为Wh/Ah;condition_duration用明确BOOL条件和秒。140    partial_policy=None表示继承execution,不是默认强制strict;覆盖质量由既有CalculationDefinition判断。141    """142143    id: str  # 生成指标ID。144    scope: str  # event:生成活动ID。145    signal: str | None  # 简单别名;condition-only duration不提供。146    unit: str  # 明确积分输出单位。147    minimum_coverage: int | float = 0  # 0—1,只影响覆盖质量判断。148    partial_policy: str | None = None  # 未提供则由完整计划执行选项继承。149    operation: str = 'integral'  # integral或按实际事件区间计算duration。150    condition: str | None = None  # duration可用恒真条件计整个已知活动区间。151152153@dataclass(frozen=True)154class PlanFragment:155    """保存预设生成的公共节点,不含来源、执行器或真实样本。156157    derived/rules/windows/calculations/sequences为类型化不可变tuple;序列化只是声明副本。158    ID不隐式改变,合入完整计划时继续检查用户节点及其他预设冲突。159    """160161    derived: tuple[DerivedDefinition, ...] = ()162    rules: tuple[PresetRule, ...] = ()163    windows: tuple[WindowSpec, ...] = ()164    calculations: tuple[PresetCalculation, ...] = ()165    sequences: tuple[SequencePlan, ...] = ()  # 可选点事件配对声明;空值不输出sequences键。166167    def to_dict(self):168        """将类型化片段序列化为当前配置节点副本;没有配对时省略空sequences键。169170        参数: 无;读取生成的不可变声明。171        返回: dict,可合入AnalysisPlan.from_dict;不含伪造来源或执行结果。172        异常: 无主动异常;实例由build_preset构造。173        """174        # 派生元数据不伪造领域意义,公共名称与表达式保持。175        derived = {item.name: {'expr': item.expr, 'unit': item.unit} for item in self.derived}176        rules, windows, calculations = [], [], []177        for item in self.rules:178            row = {'name': item.id, 'rule_type': item.rule_type}179            if item.rule_type == 'hysteresis':180                row.update(enter_condition=item.enter_condition, exit_condition=item.exit_condition,181                    enter_delay=f'{item.enter_delay_ns}ns', exit_delay=f'{item.exit_delay_ns}ns')182                rules.append(row)183                continue184            row['condition'] = item.condition185            if item.rule_type == 'condition_duration':186                row.update(minimum_duration=f'{item.minimum_duration_ns}ns', duration_comparison='>=',187                           selection_origin=item.selection_origin)188            else:189                row['emit_initial'] = item.emit_initial190            rules.append(row)191        for item in self.windows:192            windows.append({'name': item.window_id, 'rule': item.event_rule_id, 'anchor': item.anchor,193                            'pre': f'{item.pre_ns}ns', 'post': f'{item.post_ns}ns', 'merge': item.merge})194        for item in self.calculations:195            row = {'name': item.id, 'scope': item.scope, 'operation': item.operation, 'unit': item.unit,196                   'minimum_coverage': item.minimum_coverage}197            if item.signal is not None:198                row['expr'] = item.signal199            if item.condition is not None:200                row['condition'] = item.condition201            if item.operation == 'integral':202                row['method'] = 'zoh'203            if item.partial_policy is not None:204                row['partial_policy'] = item.partial_policy205            calculations.append(row)206        result = {'derived': derived, 'rules': rules, 'windows': windows, 'calculations': calculations}207        if self.sequences:208            result['sequences'] = [dict(name=item.sequence_id, start=item.start_rule, response=item.response_rule,209                min_delay=f'{item.min_delay_ns}ns', max_delay=f'{item.max_delay_ns}ns', max_pending=item.max_pending)210                for item in self.sequences]211        from .config_groups import public_configuration212        return public_configuration(result)213
Chronaxis 用户手册 · 离线阅读