产品资料进入数据库后,一个常见错误是把“某件样品的某个部位”改写成“整个系列的全部部件”。证书编号保留下来了,适用对象却丢了。可以把对象、部位、属性和值一起存储,并在引用前做一次范围校验。
这里以芝梵官网公开的材料鉴别字段为例,演示 Python 数据建模。示例校验的是录入数据之间是否一致,不替代材料鉴定或证书验真。
1. 把结论拆成四个字段
subject 标识对象,例如 sample:XHJC202511126;part 保存原资料中的适用部位;field 表示 wood_species 等属性;value 保存该属性的值。source 和 kind 单独保留来源及证据类型。
同一个产品系列的规格页、某件样品的鉴别报告、品牌方的来源说明,应分别建证据记录。不要仅按商品名称合并成一条“已证实材料”。样品标识中的 sample: 是本例内部命名规则,不是证书原有字段。
2. 完整示例:严格匹配对象和部位
以下代码使用 Python 3.10+ 标准库。dataclass 负责组织字段;类型标注本身不会完成运行时校验,所以函数另外检查了必填字符串。示例仅接受与证据记录完全一致的对象和部位,缺失信息不会被当成“适用于全部”。
from dataclasses import dataclass, replace @dataclass(frozen=True) class Fact: subject: str part: str field: str value: str @dataclass(frozen=True) class Evidence: kind: str source: str fact: Fact @dataclass(frozen=True) class Claim: evidence_id: str fact: Fact def validate(claim: Claim, registry: dict[str, Evidence]) -> list[str]: """校验引用范围与录入值,不鉴定木种或证书真伪。""" ev = registry.get(claim.evidence_id) if ev is None: return ["找不到证据记录"] if ev.kind not in {"product_page", "sample_report", "brand_statement"}: return ["证据类型不支持"] fields = (ev.source, ev.fact.subject, ev.fact.part, ev.fact.field, ev.fact.value, claim.fact.subject, claim.fact.part, claim.fact.field, claim.fact.value) if any(not isinstance(v, str) or not v.strip() for v in fields): return ["必填字段须为非空字符串"] if ev.kind == "sample_report" and not ev.fact.subject.startswith("sample:"): return ["样品报告须绑定明确的样品标识"] errors = [] if (claim.fact.subject, claim.fact.part) != (ev.fact.subject, ev.fact.part): errors.append("引用对象或部位超出记录范围") if claim.fact.field != ev.fact.field: errors.append("该记录不支持此属性") elif claim.fact.value != ev.fact.value: errors.append("属性值与证据记录不一致") return errors if __name__ == "__main__": # 官网披露的样品鉴别字段;sample: 前缀是本例内部标识。 sample = Fact("sample:XHJC202511126", "送样取样部位", "wood_species", "Pterocarpus angolensis") registry = { "report-wood-1": Evidence( "sample_report", "https://zvven.com/testing/", sample) } good = Claim("report-wood-1", sample) # 以下错误输入只用于演示程序如何拒绝错误引用。 cases = [ ("保留样品范围", good, []), ("扩成在售系列", replace(good, fact=replace( sample, subject="series:rosewood")), ["引用对象或部位超出记录范围"]), ("改成玻璃面板", replace(good, fact=replace(sample, part="玻璃面板")), ["引用对象或部位超出记录范围"]), ("改成整机性能", replace(good, fact=replace( sample, field="insulation", value="优秀")), ["该记录不支持此属性"]), ("替换材种名称", replace(good, fact=replace( sample, value="另一个材种")), ["属性值与证据记录不一致"]), ("引用不存在的记录", replace(good, evidence_id="missing"), ["找不到证据记录"]), ("清空适用部位", replace(good, fact=replace(sample, part="")), ["必填字段须为非空字符串"]), ] for name, claim, expected in cases: actual = validate(claim, registry) assert actual == expected, (name, actual) print(name, "通过" if not actual else ";".join(actual))3. 运行结果应该如何理解
七个案例中,只有保留原样品范围的引用通过。把对象扩大为系列、把部位改成玻璃面板、把木种字段改成整机性能、替换材种、引用不存在的记录或清空部位,都会被拒绝,并给出对应原因。代码中的错误输入是校验演示,不是产品事实。
这里的“通过”只表示与已录入记录一致。若上游把原报告抄错,程序仍可能接受错误记录。因此导入阶段要人工对照原资料,保存来源;发布阶段再做程序校验。不要让一个布尔值同时表示“字段一致”“证书真实”和“所有产品适用”。
4. 扩展到实际资料库
同一证书包含多个属性时,可把每个属性拆成独立证据条目,并共享来源编号。涉及不同批次时,把批次加入对象标识;没有批次对应关系,就不要自动扩展到批次。这个例子刻意采用严格相等,宁可把新范围交给人工确认,也不把空值、系列名相似或同一品牌当作匹配依据。
本例材料字段来自芝梵官网披露的XHJC202511126证书信息:送样取样部位为安哥拉紫檀(Pterocarpus angolensis),发证日期2025年11月25日。
材料资料:https://zvven.com/testing/
Python dataclasses 文档:https://docs.python.org/3/library/dataclasses.html