-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidator.py
More file actions
79 lines (67 loc) · 2.99 KB
/
Copy pathvalidator.py
File metadata and controls
79 lines (67 loc) · 2.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""
validator.py - 剧本数据校验 (可复用模块)
两层校验:
1. JSON Schema 结构校验
2. 引用完整性校验 (代码侧,JSON Schema 不便跨引用):
- dialogue.character_id 必须存在于 characters[].id
- character.first_appearance 必须存在于 scenes[].id
"""
import json
from pathlib import Path
from jsonschema import Draft202012Validator
SCHEMA_PATH = Path(__file__).parent / "schema" / "screenplay.schema.json"
def load_schema() -> dict:
"""加载 JSON Schema"""
with open(SCHEMA_PATH, encoding="utf-8") as f:
return json.load(f)
def validate_structure(data: dict, schema: dict = None) -> list[str]:
"""JSON Schema 结构校验,返回错误消息列表(空列表=通过)"""
if schema is None:
schema = load_schema()
validator = Draft202012Validator(schema)
messages = []
for err in validator.iter_errors(data):
path = ".".join(str(p) for p in err.path) or "(根)"
messages.append(f"结构错误 @ {path}: {err.message}")
return messages
def validate_referential_integrity(data: dict) -> list[str]:
"""引用完整性校验,返回错误消息列表(空列表=通过)。
对畸形输入保持健壮:characters/scenes/elements 里混入的非 dict 项一律跳过,
交由结构校验层报错——校验器绝不能被它本应判非法的脏数据搞崩。"""
issues = []
characters = data.get("characters", [])
scenes = data.get("scenes", [])
character_ids = {c.get("id") for c in characters if isinstance(c, dict)}
scene_ids = {s.get("id") for s in scenes if isinstance(s, dict)}
# 1. 每句对白的 character_id 必须存在
for scene in scenes:
if not isinstance(scene, dict):
continue
sid = scene.get("id", "unknown")
for elem in scene.get("elements", []):
if isinstance(elem, dict) and elem.get("type") == "dialogue":
cid = elem.get("character_id")
if cid not in character_ids:
issues.append(
f"引用错误 @ 场次 {sid}: 对白引用了不存在的 character_id '{cid}'"
)
# 2. 每个角色的 first_appearance 必须存在 (若填写)
for char in characters:
if not isinstance(char, dict):
continue
fa = char.get("first_appearance")
if fa and fa not in scene_ids:
issues.append(
f"引用错误 @ 人物 {char.get('id')}: first_appearance '{fa}' 不存在于场次列表"
)
return issues
def validate(data: dict, schema: dict = None) -> tuple[bool, list[str]]:
"""
完整校验 (结构 + 引用完整性)
返回: (是否通过, 错误消息列表)
"""
if not isinstance(data, dict):
return False, [f"顶层必须是一个对象(包含 metadata/characters/scenes 的字典),当前是 {type(data).__name__}"]
errors = validate_structure(data, schema)
errors += validate_referential_integrity(data)
return len(errors) == 0, errors