mirror of
https://github.com/wassname/moral-maps.git
synced 2026-10-07 11:25:55 +08:00
audit respondent packet v2 panel
Co-Authored-By: PI[unknown] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
1 parent
c1ed7969b5
commit
e6578ff10b
15 files changed
+4547
-103
No files matched your search
@@ -220,8 +220,12 @@ def packet_schema(battery: list[dict]) -> dict:
|
||||
|
||||
CANONICAL_SHAPE = "canonical_schema"
|
||||
POSITIONAL_SHAPE = "positional_labels"
|
||||
Q_POSITIONAL_SHAPE = "q_positional_labels"
|
||||
PREFIX_POSITIONAL_SHAPE = "numbered_prefix_labels"
|
||||
ANSWER_LIST_SHAPE = "answer_list_question_indices"
|
||||
NO_RESPONSE_SHAPE = "no_usable_response"
|
||||
ACCEPTED_ENCODINGS = (CANONICAL_SHAPE, POSITIONAL_SHAPE)
|
||||
ACCEPTED_ENCODINGS = (CANONICAL_SHAPE, POSITIONAL_SHAPE, Q_POSITIONAL_SHAPE,
|
||||
PREFIX_POSITIONAL_SHAPE, ANSWER_LIST_SHAPE)
|
||||
|
||||
|
||||
def refusal_row(battery: list[dict]) -> dict:
|
||||
@@ -245,71 +249,118 @@ def substantive_row(selected: object, reason: object, shape: str = CANONICAL_SHA
|
||||
"reason_status": status, "source_shape": shape}
|
||||
|
||||
|
||||
def positional_labels(battery: list[dict]) -> dict:
|
||||
"""The labels render_packet displays: blocks numbered 1..9 in battery order, ordinary
|
||||
options lettered A.. per block, child qualities numbered 1..11."""
|
||||
labels = {}
|
||||
for number, q in enumerate(battery, 1):
|
||||
if q["id"] == "ChildQualities":
|
||||
labels[str(number)] = ("child", [str(j) for j in range(1, len(q["options"]) + 1)])
|
||||
else:
|
||||
labels[str(number)] = ("ordinary", [chr(64 + j) for j in range(1, len(q["options"]) + 1)])
|
||||
return labels
|
||||
def refused_entry(reason: object, shape: str) -> dict:
|
||||
return {"outcome": "refused", "selected": None, "reason": reason,
|
||||
"reason_status": "not_available", "source_shape": shape}
|
||||
|
||||
|
||||
def labeled_entry(q: dict, block: object, shape: str) -> dict:
|
||||
"""Map one block only when it contains exactly one answer field for its visible labels."""
|
||||
if not isinstance(block, dict):
|
||||
return refused_entry(None, shape)
|
||||
reason = block.get("reason")
|
||||
fields = ("answer", "choice", "option", "selected_option")
|
||||
if q["id"] == "ChildQualities":
|
||||
fields = fields + ("answers", "choices", "options", "selected_options")
|
||||
present = [field for field in fields if field in block]
|
||||
if len(present) != 1 or set(block) - {present[0], "reason"}:
|
||||
return refused_entry(reason, shape)
|
||||
value = block[present[0]]
|
||||
if q["id"] == "ChildQualities":
|
||||
if (not isinstance(value, list) or len(value) > 5
|
||||
or any(not isinstance(item, (str, int)) for item in value)
|
||||
or len(set(value)) != len(value)):
|
||||
return refused_entry(reason, shape)
|
||||
labels = [str(item) for item in value]
|
||||
if any(not item.isdecimal() or not 1 <= int(item) <= len(q["options"]) for item in labels):
|
||||
return refused_entry(reason, shape)
|
||||
return substantive_row([q["options"][int(item) - 1] for item in labels], reason, shape)
|
||||
if not isinstance(value, str) or len(value) != 1:
|
||||
return refused_entry(reason, shape)
|
||||
index = ord(value) - ord("A")
|
||||
if not 0 <= index < len(q["options"]):
|
||||
return refused_entry(reason, shape)
|
||||
return substantive_row(q["options"][index], reason, shape)
|
||||
|
||||
|
||||
def canonical_entry(q: dict, block: object, shape: str) -> dict:
|
||||
if not isinstance(block, dict) or set(block) - {"selected", "reason"}:
|
||||
return refused_entry(None, shape)
|
||||
selected, reason = block.get("selected"), block.get("reason")
|
||||
if q["id"] == "ChildQualities":
|
||||
if (isinstance(selected, list) and len(selected) <= 5
|
||||
and all(isinstance(item, str) for item in selected)
|
||||
and len(set(selected)) == len(selected) and set(selected) <= set(q["options"])):
|
||||
return substantive_row(selected, reason, shape)
|
||||
elif selected in q["options"]:
|
||||
return substantive_row(selected, reason, shape)
|
||||
return refused_entry(reason, shape)
|
||||
|
||||
|
||||
def positional_keys(raw: object, battery: list[dict]) -> tuple[list[str], str] | None:
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
numbered = [str(number) for number in range(1, len(battery) + 1)]
|
||||
if set(raw) == set(numbered):
|
||||
return numbered, POSITIONAL_SHAPE
|
||||
q_numbered = [f"q{number}" for number in range(1, len(battery) + 1)]
|
||||
if set(raw) == set(q_numbered):
|
||||
return q_numbered, Q_POSITIONAL_SHAPE
|
||||
prefix = {key.split("_", 1)[0]: key for key in raw if re.fullmatch(r"[1-9]_.+", key)}
|
||||
if len(prefix) == len(battery) and set(prefix) == set(numbered) and len(raw) == len(battery):
|
||||
return [prefix[str(number)] for number in range(1, len(battery) + 1)], PREFIX_POSITIONAL_SHAPE
|
||||
return None
|
||||
|
||||
|
||||
def normalize_positional(battery: list[dict], raw: object) -> dict | None:
|
||||
"""Narrow deterministic normalizer for the positional encoding: exact top-level keys
|
||||
1..9, each value exactly {answer, reason}, ordinary answers one displayed letter of that
|
||||
block, child answers a unique list of 0..5 displayed number strings. Anything else ->
|
||||
None (not this encoding; the caller records nonconforming refusal). Selected values map
|
||||
through the rendered battery labels to the same canonical option text, so scoring is
|
||||
identical to the canonical shape."""
|
||||
labels = positional_labels(battery)
|
||||
if not isinstance(raw, dict) or set(raw) != set(labels):
|
||||
"""Map complete visible 1..9 blocks. Field names vary, labels do not."""
|
||||
keyed = positional_keys(raw, battery)
|
||||
if keyed is None:
|
||||
return None
|
||||
row = {}
|
||||
for number, q in enumerate(battery, 1):
|
||||
key = str(number)
|
||||
block = raw[key]
|
||||
if not isinstance(block, dict) or set(block) != {"answer", "reason"}:
|
||||
keys, shape = keyed
|
||||
return {q["id"]: labeled_entry(q, raw[key], shape) for q, key in zip(battery, keys)}
|
||||
|
||||
|
||||
def normalize_answer_list(battery: list[dict], raw: object) -> dict | None:
|
||||
"""Map a list only through explicit visible question numbers or exact prompt IDs, never order."""
|
||||
if not isinstance(raw, dict) or set(raw) != {"answers"} or not isinstance(raw["answers"], list):
|
||||
return None
|
||||
by_id = {}
|
||||
for block in raw["answers"]:
|
||||
if not isinstance(block, dict):
|
||||
return None
|
||||
answer, reason = block["answer"], block["reason"]
|
||||
if reason_status(reason) != "valid":
|
||||
index_fields = [field for field in ("question", "question_number", "question_id") if field in block]
|
||||
if len(index_fields) != 1:
|
||||
return None
|
||||
kind, valid = labels[key]
|
||||
if kind == "child":
|
||||
if (not isinstance(answer, list) or len(answer) > 5
|
||||
or any(not isinstance(a, str) for a in answer)
|
||||
or len(set(answer)) != len(answer)
|
||||
or any(a not in valid for a in answer)):
|
||||
return None
|
||||
selected = [q["options"][int(a) - 1] for a in answer]
|
||||
index = block[index_fields[0]]
|
||||
if isinstance(index, int) and 1 <= index <= len(battery):
|
||||
qid = battery[index - 1]["id"]
|
||||
elif isinstance(index, str) and index in {q["id"] for q in battery}:
|
||||
qid = index
|
||||
else:
|
||||
if not isinstance(answer, str) or answer not in valid:
|
||||
return None
|
||||
selected = q["options"][ord(answer) - 65]
|
||||
row[q["id"]] = substantive_row(selected, reason, POSITIONAL_SHAPE)
|
||||
return row
|
||||
continue
|
||||
if qid in by_id:
|
||||
return None
|
||||
by_id[qid] = {key: value for key, value in block.items() if key != index_fields[0]}
|
||||
return {q["id"]: (labeled_entry(q, by_id[q["id"]], ANSWER_LIST_SHAPE)
|
||||
if q["id"] in by_id else refused_entry(None, ANSWER_LIST_SHAPE))
|
||||
for q in battery}
|
||||
|
||||
|
||||
def normalize_raw(battery: list[dict], raw: object) -> dict | None:
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
answers, child = raw.get("answers"), raw.get("child_qualities")
|
||||
if isinstance(answers, dict) and isinstance(child, dict):
|
||||
row = {q["id"]: canonical_entry(q, child if q["id"] == "ChildQualities" else
|
||||
answers.get(q["id"]), CANONICAL_SHAPE)
|
||||
for q in battery}
|
||||
return row
|
||||
return normalize_positional(battery, raw) or normalize_answer_list(battery, raw)
|
||||
|
||||
|
||||
def parse_packet(battery: list[dict], text: str) -> tuple[dict | None, str | None, bool]:
|
||||
"""Classify one response. Returns (row, refusal_kind, rescueable).
|
||||
|
||||
- (refusal_row, kind, False): no usable value despite the schema -> OBSERVED refusal, never
|
||||
rescued. kind: 'empty_content' (empty/no content), 'no_json' (a plain-text reply with no
|
||||
JSON object), or 'nonconforming' (parsed but structure/values outside the substantive
|
||||
space; offending questions are refused, conforming questions keep their answers).
|
||||
- a valid selected answer with a missing/too-long reason remains substantive and scored, but
|
||||
gets kind 'reason_format_failure' and is retained with its raw response for qualitative audit;
|
||||
it is never rescued.
|
||||
- (None, None, True): clearly syntactic malformed output (an apparent JSON object that fails
|
||||
to parse, or visibly truncated output with unbalanced braces) -> repairable, rescue once.
|
||||
Two accepted encodings map to identical canonical selections: the schema shape
|
||||
(answers/child_qualities objects) and the positional-labels shape (blocks 1..9 with one
|
||||
displayed letter / 0..5 displayed numbers plus reason each). Every row entry records its
|
||||
source_shape; shapes never affect scoring.
|
||||
Deterministic rule: if syntactic vs substantive cannot be distinguished, it is a refusal.
|
||||
"""
|
||||
"""Classify a response without letting reason formatting change its selected answer."""
|
||||
if not (text or "").strip():
|
||||
return refusal_row(battery), "empty_content", False
|
||||
objs = re.findall(r"\{.*\}", text, re.S)
|
||||
@@ -319,37 +370,12 @@ def parse_packet(battery: list[dict], text: str) -> tuple[dict | None, str | Non
|
||||
try:
|
||||
raw = json.loads(objs[-1])
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
return None, None, True # syntactic malformed (unparseable or truncated JSON): rescue once
|
||||
answers, child = raw.get("answers"), raw.get("child_qualities")
|
||||
if isinstance(answers, dict) and isinstance(child, dict):
|
||||
row = {}
|
||||
for q in battery:
|
||||
if q["id"] == "ChildQualities":
|
||||
selected, reason = child.get("selected"), child.get("reason")
|
||||
if (isinstance(selected, list) and len(selected) <= 5 and len(set(selected)) == len(selected)
|
||||
and set(selected) <= set(q["options"])):
|
||||
row[q["id"]] = substantive_row(selected, reason)
|
||||
else:
|
||||
row[q["id"]] = {"outcome": "refused", "selected": None, "reason": reason,
|
||||
"reason_status": "not_available",
|
||||
"source_shape": CANONICAL_SHAPE}
|
||||
continue
|
||||
answer = answers.get(q["id"])
|
||||
selected = answer.get("selected") if isinstance(answer, dict) else None
|
||||
reason = answer.get("reason") if isinstance(answer, dict) else None
|
||||
if selected in q["options"]:
|
||||
row[q["id"]] = substantive_row(selected, reason)
|
||||
else:
|
||||
row[q["id"]] = {"outcome": "refused", "selected": None, "reason": reason,
|
||||
"reason_status": "not_available",
|
||||
"source_shape": CANONICAL_SHAPE}
|
||||
else:
|
||||
positional = normalize_positional(battery, raw)
|
||||
if positional is None:
|
||||
return refusal_row(battery), "nonconforming", False
|
||||
row = positional
|
||||
outcomes = [v["outcome"] for v in row.values()]
|
||||
reasons = [v["reason_status"] for v in row.values()]
|
||||
return None, None, True
|
||||
row = normalize_raw(battery, raw)
|
||||
if row is None:
|
||||
return refusal_row(battery), "nonconforming", False
|
||||
outcomes = [value["outcome"] for value in row.values()]
|
||||
reasons = [value["reason_status"] for value in row.values()]
|
||||
kind = ("nonconforming" if "refused" in outcomes else
|
||||
"reason_format_failure" if any(status != "valid" for status in reasons) else None)
|
||||
return row, kind, False
|
||||
@@ -594,6 +620,103 @@ def prior_rows(rpath: Path, pid: str) -> dict[int, dict]:
|
||||
and record.get("protocol_id") == pid}
|
||||
|
||||
|
||||
def raw_shape_signature(text: str) -> str:
|
||||
"""A lossless-enough structural label for auditing endpoint serialization variation."""
|
||||
try:
|
||||
raw = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
return "unparseable_json"
|
||||
|
||||
def signature(value: object) -> str:
|
||||
if isinstance(value, dict):
|
||||
return "{" + ",".join(f"{key}:{signature(item)}" for key, item in sorted(value.items())) + "}"
|
||||
if isinstance(value, list):
|
||||
values = sorted({signature(item) for item in value})
|
||||
return "[" + ",".join(values) + "]"
|
||||
return type(value).__name__
|
||||
return signature(raw)
|
||||
|
||||
|
||||
def reprocessed_rows(rpath: Path, pid: str) -> dict[int, dict]:
|
||||
rows = {record["packet"]: record["row"] for record in load_events(rpath)
|
||||
if record.get("event") == "respondent_reprocessed"
|
||||
and record.get("protocol_id") == pid}
|
||||
if set(rows) != set(range(N_PACKETS)):
|
||||
raise RuntimeError(f"reprocessed rows incomplete for {rpath}: {len(rows)}/{N_PACKETS}")
|
||||
return rows
|
||||
|
||||
|
||||
def reprocess_existing(battery: list[dict]) -> None:
|
||||
"""Append, never overwrite, one parser-v2 event per completed packet without API calls."""
|
||||
inventory = {"eval_version": EVAL_VERSION, "models": {}}
|
||||
for model in MODELS:
|
||||
rpath = OUT / "records" / model.replace("/", "__") / "packets.jsonl"
|
||||
pid = protocol_id(model, battery)
|
||||
events = load_events(rpath)
|
||||
previous = [event for event in events if event.get("event") == "respondent_reprocessed"
|
||||
and event.get("protocol_id") == pid]
|
||||
if previous:
|
||||
if len(previous) != N_PACKETS or {event["packet"] for event in previous} != set(range(N_PACKETS)):
|
||||
raise RuntimeError(f"partial reprocessing already exists for {model} under {pid}")
|
||||
rows = {event["packet"]: event["row"] for event in previous}
|
||||
else:
|
||||
originals = [event for event in events if event.get("event") == "respondent_parsed"
|
||||
and event.get("parsed") is True]
|
||||
if len(originals) != N_PACKETS or {event["packet"] for event in originals} != set(range(N_PACKETS)):
|
||||
raise RuntimeError(f"expected one original parsed event per packet for {model}")
|
||||
rows = {}
|
||||
pending = []
|
||||
for original in originals:
|
||||
row, refusal_kind, rescueable = parse_packet(battery, original["text"])
|
||||
if rescueable:
|
||||
row, refusal_kind = refusal_row(battery), "unparseable_json"
|
||||
assert row is not None
|
||||
rows[original["packet"]] = row
|
||||
pending.append({"event": "respondent_reprocessed", "model": model,
|
||||
"packet": original["packet"], "seed": original["seed"],
|
||||
"run_id": original["run_id"], "protocol_id": pid,
|
||||
"eval_version": EVAL_VERSION,
|
||||
"source_event_recorded_at_utc": original["recorded_at_utc"],
|
||||
"source_protocol_id": original["protocol_id"],
|
||||
"source_request_id": original["request_id"],
|
||||
"normalizer_version": "encoding-aware-v1",
|
||||
"raw_serialization_shape": raw_shape_signature(original["text"]),
|
||||
"row": row, "refusal_kind": refusal_kind,
|
||||
"rescued": False})
|
||||
for event in pending:
|
||||
append_record(rpath, event)
|
||||
reprocessed = [event for event in load_events(rpath)
|
||||
if event.get("event") == "respondent_reprocessed"
|
||||
and event.get("protocol_id") == pid]
|
||||
shape_counts, raw_shape_counts, refusal_kinds = {}, {}, {}
|
||||
for row in rows.values():
|
||||
shapes = {value["source_shape"] for value in row.values()}
|
||||
if len(shapes) != 1:
|
||||
raise RuntimeError(f"reprocessed packet used mixed source shapes: {model}")
|
||||
shape = next(iter(shapes))
|
||||
shape_counts[shape] = shape_counts.get(shape, 0) + 1
|
||||
for value in row.values():
|
||||
if value["outcome"] == "refused":
|
||||
refusal_kinds[value["reason_status"]] = refusal_kinds.get(value["reason_status"], 0) + 1
|
||||
for event in reprocessed:
|
||||
shape = event["raw_serialization_shape"]
|
||||
raw_shape_counts[shape] = raw_shape_counts.get(shape, 0) + 1
|
||||
packet_kinds = {}
|
||||
for event in reprocessed:
|
||||
if event["refusal_kind"] not in (None, "reason_format_failure"):
|
||||
kind = event["refusal_kind"]
|
||||
packet_kinds[kind] = packet_kinds.get(kind, 0) + 1
|
||||
inventory["models"][model] = {"protocol_id": pid, "packets": len(rows),
|
||||
"canonicalization_shape_counts": shape_counts,
|
||||
"raw_serialization_shape_counts": raw_shape_counts,
|
||||
"residual_refused_packets": sum(packet_kinds.values()),
|
||||
"residual_refusal_packet_kinds": packet_kinds,
|
||||
"residual_refused_question_instances": sum(refusal_kinds.values()),
|
||||
"residual_refusal_reason_status_counts": refusal_kinds}
|
||||
atomic_json(OUT / "reprocessing_inventory.json", inventory)
|
||||
print("reprocessing complete: appended encoding-aware rows without API calls")
|
||||
|
||||
|
||||
def protocol_body(model: str, battery: list[dict]) -> dict:
|
||||
return {
|
||||
"schema": 1, "eval_version": EVAL_VERSION, "model": model,
|
||||
@@ -779,7 +902,7 @@ def offline_smoke(battery: list[dict], child_rows: list[dict]) -> None:
|
||||
# identity: v2 everywhere; protocol/cache identity includes accepted response encodings
|
||||
assert EVAL_VERSION == "wvs-respondent-packet-v2"
|
||||
body = protocol_body(MODELS[0], battery)
|
||||
assert body["accepted_response_encodings"] == ["canonical_schema", "positional_labels"]
|
||||
assert body["accepted_response_encodings"] == list(ACCEPTED_ENCODINGS)
|
||||
assert body["eval_version"] == EVAL_VERSION
|
||||
# fixture: visible prompt contains no refusal/null/AI-identity language
|
||||
prompt = render_packet(battery).lower()
|
||||
@@ -920,8 +1043,41 @@ def offline_smoke(battery: list[dict], child_rows: list[dict]) -> None:
|
||||
assert pos_row["ChildQualities"]["selected"] == [child_opts[0], child_opts[1],
|
||||
child_opts[5], child_opts[7],
|
||||
child_opts[9]]
|
||||
# fixture: positional ambiguity rejections. Each mutation must yield None (not this
|
||||
# encoding), never a guessed mapping.
|
||||
# fixture: endpoint serialization variants preserve the visible labels, not guessed meaning.
|
||||
# These are the observed q1..q9, numbered-prefix, and explicit-index list encodings.
|
||||
q_blocks = {f"q{number}": {"choice": raw_positional[str(number)]["answer"],
|
||||
"reason": raw_positional[str(number)]["reason"]}
|
||||
for number in range(1, 9)}
|
||||
q_blocks["q9"] = {"choices": [int(value) for value in raw_positional["9"]["answer"]],
|
||||
"reason": raw_positional["9"]["reason"]}
|
||||
q_row, q_kind, _ = parse_packet(battery, json.dumps(q_blocks))
|
||||
assert q_kind is None and all(value["outcome"] == "substantive" for value in q_row.values())
|
||||
assert {value["source_shape"] for value in q_row.values()} == {Q_POSITIONAL_SHAPE}
|
||||
prefix_blocks = {f"{number}_visible_label": {"option": raw_positional[str(number)]["answer"],
|
||||
"reason": raw_positional[str(number)]["reason"]}
|
||||
for number in range(1, 9)}
|
||||
prefix_blocks["9_visible_label"] = {"options": [int(value) for value in raw_positional["9"]["answer"]],
|
||||
"reason": raw_positional["9"]["reason"]}
|
||||
prefix_row, prefix_kind, _ = parse_packet(battery, json.dumps(prefix_blocks))
|
||||
assert prefix_kind is None and all(value["outcome"] == "substantive" for value in prefix_row.values())
|
||||
assert {value["source_shape"] for value in prefix_row.values()} == {PREFIX_POSITIONAL_SHAPE}
|
||||
answer_list = {"answers": [{"question_id": number,
|
||||
"selected_option": raw_positional[str(number)]["answer"],
|
||||
"reason": raw_positional[str(number)]["reason"]}
|
||||
for number in range(1, 9)] +
|
||||
[{"question_id": 9, "selected_options": [int(value) for value in raw_positional["9"]["answer"]],
|
||||
"reason": raw_positional["9"]["reason"]}]}
|
||||
list_row, list_kind, _ = parse_packet(battery, json.dumps(answer_list))
|
||||
assert list_kind is None and all(value["outcome"] == "substantive" for value in list_row.values())
|
||||
assert {value["source_shape"] for value in list_row.values()} == {ANSWER_LIST_SHAPE}
|
||||
duplicate_index = json.loads(json.dumps(answer_list))
|
||||
duplicate_index["answers"][1]["question_id"] = 1
|
||||
assert normalize_answer_list(battery, duplicate_index) is None
|
||||
unknown_index = json.loads(json.dumps(answer_list))
|
||||
unknown_index["answers"][4]["question_id"] = "Trust in people"
|
||||
unknown_row = normalize_answer_list(battery, unknown_index)
|
||||
assert unknown_row is not None and unknown_row["dealing with people?"]["outcome"] == "refused"
|
||||
# fixture: positional ambiguity rejections. No invalid or ambiguous label becomes substantive.
|
||||
def mutate(block_key, field, value):
|
||||
mutated = json.loads(json.dumps(raw_positional))
|
||||
if block_key not in mutated:
|
||||
@@ -946,15 +1102,17 @@ def offline_smoke(battery: list[dict], child_rows: list[dict]) -> None:
|
||||
mutate("9", "answer", ["1", "1"]),
|
||||
mutate("9", "answer", ["0"]),
|
||||
mutate("9", "answer", ["12"]),
|
||||
mutate("9", "answer", [1, 2]),
|
||||
mutate("9", "answer", ["1", "2", "3", "4", "5", "6"]),
|
||||
mutate("1", "reason", "These nine separate words exceed the permitted reason length now"),
|
||||
mutate("1", "reason", ""),
|
||||
mutate("2", "reason", None),
|
||||
mutate("3", "__extra_block_key__", ("confidence", "high")),
|
||||
]
|
||||
for i, bad in enumerate(rejections):
|
||||
assert normalize_positional(battery, bad) is None, f"rejection fixture {i} mapped"
|
||||
rejected = normalize_positional(battery, bad)
|
||||
assert rejected is None or any(value["outcome"] == "refused" for value in rejected.values()), (
|
||||
f"rejection fixture {i} mapped to substantive answers")
|
||||
long_reason = mutate("1", "reason", "These nine separate words exceed the permitted reason length now")
|
||||
long_row = normalize_positional(battery, long_reason)
|
||||
assert long_row is not None and long_row["Religion"]["outcome"] == "substantive"
|
||||
assert long_row["Religion"]["reason_status"] == "too_long"
|
||||
# fixture: duplicate / oversized child lists are nonconforming (uniqueness enforced in parsing,
|
||||
# since Alibaba rejects array schemas with the uniqueness keyword)
|
||||
dup_row, dup_kind, rescueable = parse_packet(battery, json.dumps(
|
||||
@@ -1356,6 +1514,7 @@ def main() -> None:
|
||||
actions.add_argument("--write-manifest", action="store_true")
|
||||
actions.add_argument("--paid-smoke", action="store_true")
|
||||
actions.add_argument("--reinterpret-smoke", action="store_true")
|
||||
actions.add_argument("--reprocess-existing", action="store_true")
|
||||
actions.add_argument("--run", action="store_true")
|
||||
actions.add_argument("--analyze", action="store_true")
|
||||
actions.add_argument("--synthetic-analysis-test", action="store_true")
|
||||
@@ -1385,6 +1544,8 @@ def main() -> None:
|
||||
paid_smoke(args.smoke_model, battery)
|
||||
if args.reinterpret_smoke:
|
||||
reinterpret_smoke_35plus(battery)
|
||||
if args.reprocess_existing:
|
||||
reprocess_existing(battery)
|
||||
if args.run:
|
||||
run(battery)
|
||||
if args.analyze:
|
||||
@@ -1795,7 +1956,7 @@ def analyze_real() -> None:
|
||||
packet_rows = {}
|
||||
for m in MODELS:
|
||||
rpath = OUT / "records" / m.replace("/", "__") / "packets.jsonl"
|
||||
packet_rows[m] = prior_rows(rpath, protocol_id(m, battery))
|
||||
packet_rows[m] = reprocessed_rows(rpath, protocol_id(m, battery))
|
||||
analysis = analyze_from_data(packet_rows, battery, child_rows, resolved)
|
||||
atomic_json(OUT / "analysis.json", analysis)
|
||||
print(json.dumps({"packet": {k: analysis["packet"][k]
|
||||
|
||||
Reference in new issue
Block a user