chronaxis.fast.blocks
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""贡献块的有界二进制编码。浮点使用binary64,时间和记录序号保持整数。23同一帧多个贡献共享时间和记录位置;CRC检查暂存意外损坏。只处理本次会话4内部数据,不加载任意对象,不保存完整CAN帧或解码字典。5"""6import math7import struct8import zlib910ROWS = 102411LIMIT = 26214412RAM = 4 * 104857613HEADER = struct.Struct('<4sQQI') # 固定版本、文件号、块号、帧数。14FRAME = struct.Struct('<qQI')15ENTRY = struct.Struct('<IdB')16CRC = struct.Struct('<I')171819def encode(file_index, number, records):20 """编码一个有界块。参数: 文件号/块号/贡献记录。返回: bytes。21 异常: 超大块或非法数值;不会改成低精度或丢弃尾部。22 """23 payload = bytearray(HEADER.pack(b'CFS1', file_index, number, len(records)))24 if not 0 < len(records) <= ROWS:25 raise ValueError('贡献块记录数量非法')26 for timestamp, sequence, entries in records:27 payload.extend(FRAME.pack(timestamp, sequence, len(entries)))28 for metric, value, keep in entries:29 if type(value) is not float or not math.isfinite(value) or type(keep) is not bool:30 raise ValueError('贡献须为有限float64和布尔入选标志')31 payload.extend(ENTRY.pack(metric, value, keep))32 if len(payload) + CRC.size > LIMIT:33 raise ValueError('贡献块超出字节上限')34 payload.extend(CRC.pack(zlib.crc32(payload)))35 return bytes(payload)363738def decode(payload, file_index, number, metric_count):39 """核验并逐帧解码。参数: bytes/预期身份/计算项数。返回: 记录生成器。40 异常: 身份、长度、CRC、序号、计算项或数值非法;不接受重复项。41 """42 if not HEADER.size + CRC.size <= len(payload) <= LIMIT:43 raise ValueError('贡献块长度非法')44 magic, actual_file, actual_number, count = HEADER.unpack_from(payload)45 if (magic, actual_file, actual_number) != (b'CFS1', file_index, number) or not 0 < count <= ROWS:46 raise ValueError('贡献块身份或数量不符')47 end = len(payload) - CRC.size48 if zlib.crc32(memoryview(payload)[:end]) != CRC.unpack_from(payload, end)[0]:49 raise ValueError('贡献块完整性校验失败')50 offset = HEADER.size51 previous = -152 for _ in range(count):53 if offset + FRAME.size > end:54 raise ValueError('贡献块缺少帧头')55 timestamp, sequence, size = FRAME.unpack_from(payload, offset)56 offset += FRAME.size57 if sequence <= previous or not 0 < size <= metric_count or offset + size * ENTRY.size > end:58 raise ValueError('贡献记录顺序或数量非法')59 previous = sequence60 entries = []61 seen = set()62 for _ in range(size):63 metric, value, keep = ENTRY.unpack_from(payload, offset)64 offset += ENTRY.size65 if metric >= metric_count or metric in seen or keep not in (0, 1) or not math.isfinite(value):66 raise ValueError('贡献计算项/数值非法')67 seen.add(metric)68 entries.append((metric, value, bool(keep)))69 yield timestamp, sequence, tuple(entries)70 if offset != end:71 raise ValueError('贡献块有未消费尾部')727374def batches(stream):75 """按记录数及字节双上限切块。参数: 单文件贡献流。返回: 有限列表生成器。76 异常: 单帧超限;不会拆散同帧元数据或整文件载入。77 """78 block = []79 size = HEADER.size + CRC.size80 for record in stream:81 cost = FRAME.size + ENTRY.size * len(record[2])82 if cost + HEADER.size + CRC.size > LIMIT:83 raise ValueError('单帧贡献超过字节上限')84 if len(block) == ROWS or size + cost > LIMIT:85 yield block86 block = []87 size = HEADER.size + CRC.size88 block.append(record)89 size += cost90 if block:91 yield block92