"""统一证据模型:多来源(CAD / PDF / 图片)证据的字段常量与访问器。 设计要点: - evidence 是跨阶段 + 落盘的「线格式」数据,统一用 dict 承载(不用 dataclass)。 - 图片统一为 `images: [{"b64","mime","label"}]`;访问器兼容旧的 `image_b64/image_mime` 单图字段(历史 evidence.json 与旧测试构造仍可读)。 - 本模块不含任何 CAD / 工具逻辑,可被 phases、findings、__main__、tests 共同引用。 """ SOURCE_CAD = "cad" SOURCE_PDF = "pdf" SOURCE_IMAGE = "image" # 结构化证据键(CAD 侧各查询工具产出的信封);下游 handle 收集 / 派生检查统一引用, # 避免在多处硬编码同一组键名。 STRUCTURED_SOURCES = ("entities", "texts", "dimensions", "blocks", "polylines") def make_image(b64, mime="image/png", label=None): """构造 images 列表元素。""" return {"b64": b64, "mime": mime or "image/png", "label": label} def source_type_of(container): """读取来源类型,缺省视为 CAD。 container 可以是审查单元(unit)/ 图框 / evidence 等任何 dict。 """ if isinstance(container, dict): value = container.get("source_type") if isinstance(value, str) and value: return value return SOURCE_CAD def evidence_images(evidence): """规范化证据里的图片列表(单点归一)。 优先读 `images`;否则回退旧的 `image_b64` / `image_mime` 单图字段。 统一返回 `[{"b64","mime","label"}]`,过滤掉空 b64。 """ if not isinstance(evidence, dict): return [] images = [] raw = evidence.get("images") if isinstance(raw, list): for item in raw: if not isinstance(item, dict): continue b64 = item.get("b64") if not b64: continue images.append( { "b64": b64, "mime": item.get("mime") or "image/png", "label": item.get("label"), } ) if images: return images # 回退:旧的单图字段 b64 = evidence.get("image_b64") if b64: return [ { "b64": b64, "mime": evidence.get("image_mime") or "image/png", "label": None, } ] return [] def primary_image(evidence): """返回首图(label == "overall" 优先),无图返回 None。""" images = evidence_images(evidence) if not images: return None for img in images: if img.get("label") == "overall": return img return images[0] def image_labels(evidence): """返回图片标签列表(用于日志 / payload 说明)。""" return [img.get("label") for img in evidence_images(evidence)]