2026-10-15 19:48:18 +08:00
|
|
|
|
"""统一证据模型:多来源(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")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-10-16 23:07:23 +08:00
|
|
|
|
def empty_envelope(total=0):
|
|
|
|
|
|
"""构造空的**结构化信封** `{"total": N, "items": []}`。
|
|
|
|
|
|
|
|
|
|
|
|
CAD 侧各查询工具统一返回此形(`items` 明细 + `total` 计数)。非 CAD 源
|
|
|
|
|
|
(PDF / 图片)没有结构化数据,用本工厂补齐同形键,使下游 handle 收集 /
|
|
|
|
|
|
派生检查无需分支即可统一遍历(空 items 自然不产出任何检查项)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
return {"total": int(total), "items": []}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-10-16 19:15:32 +08:00
|
|
|
|
def make_image(b64, mime="image/png", label=None, window=None):
|
|
|
|
|
|
"""构造 images 列表元素。
|
|
|
|
|
|
|
|
|
|
|
|
`window` 为该截图**实际可见的世界范围** `(min_x,min_y,max_x,max_y)`(可选)。
|
|
|
|
|
|
审查阶段的 `inspect_region` 靠它把「截图上的归一化坐标」映射回 WCS(逐级下钻)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
img = {"b64": b64, "mime": mime or "image/png", "label": label}
|
|
|
|
|
|
if window is not None:
|
|
|
|
|
|
img["window"] = window
|
|
|
|
|
|
return img
|
2026-10-15 19:48:18 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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` 单图字段。
|
2026-10-16 19:15:32 +08:00
|
|
|
|
统一返回 `[{"b64","mime","label","window"}]`,过滤掉空 b64。
|
2026-10-15 19:48:18 +08:00
|
|
|
|
"""
|
|
|
|
|
|
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"),
|
2026-10-16 19:15:32 +08:00
|
|
|
|
"window": item.get("window"),
|
2026-10-15 19:48:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
if images:
|
|
|
|
|
|
return images
|
|
|
|
|
|
# 回退:旧的单图字段
|
|
|
|
|
|
b64 = evidence.get("image_b64")
|
|
|
|
|
|
if b64:
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
|
|
|
|
|
"b64": b64,
|
|
|
|
|
|
"mime": evidence.get("image_mime") or "image/png",
|
|
|
|
|
|
"label": None,
|
2026-10-16 19:15:32 +08:00
|
|
|
|
"window": None,
|
2026-10-15 19:48:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
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)]
|