126 lines
5.3 KiB
Python
126 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Render held ingestion drafts and correction proposals as an owner decision sheet."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
|
||
|
|
def render(items: list[dict[str, Any]]) -> str:
|
||
|
|
sections = [
|
||
|
|
"# GEWU owner review sheet",
|
||
|
|
"",
|
||
|
|
(
|
||
|
|
"Mark one decision for each item and add a short rationale. This sheet records review "
|
||
|
|
"intent only; it does not alter GEWU state or grant acceptance authority."
|
||
|
|
),
|
||
|
|
"",
|
||
|
|
(
|
||
|
|
"For held drafts: choose keep held, request revision, or initiate the authorized owner "
|
||
|
|
"challenge workflow. For corrections: choose approve for fresh owner workflow, request "
|
||
|
|
"revision, or reject. Proposal base commits may be stale; every later action must "
|
||
|
|
"context and re-evaluate the proposal."
|
||
|
|
),
|
||
|
|
"",
|
||
|
|
]
|
||
|
|
for index, item in enumerate(items, start=1):
|
||
|
|
proposal = item.get("proposal", {})
|
||
|
|
operations = proposal.get("operations", [])
|
||
|
|
nodes = [operation["node"] for operation in operations if operation.get("node")]
|
||
|
|
edges = [operation["edge"] for operation in operations if operation.get("edge")]
|
||
|
|
node = nodes[0] if nodes else {}
|
||
|
|
is_correction = item.get("source_stage") == "supersession"
|
||
|
|
kind = "Correction proposal" if is_correction else "Policy-held draft"
|
||
|
|
proposal_id = proposal.get("proposal_id", "unknown")
|
||
|
|
revision = proposal.get("revision", "?")
|
||
|
|
node_id = node.get("node_id", "not available")
|
||
|
|
node_type = node.get("node_type", "unknown")
|
||
|
|
sections.extend(
|
||
|
|
[
|
||
|
|
f"## {index}. {kind}: {node.get('title', proposal.get('proposal_id', 'Untitled'))}",
|
||
|
|
"",
|
||
|
|
f"- Proposal: `{proposal_id}` revision {revision}",
|
||
|
|
f"- Current state: {item.get('storage_state', 'evaluated; needs-review')}",
|
||
|
|
f"- Proposal base commit: `{proposal.get('base_commit', 'unknown')}`",
|
||
|
|
f"- Node: `{node_id}` ({node_type})",
|
||
|
|
f"- Summary: {node.get('summary', 'No summary recorded.')}",
|
||
|
|
"",
|
||
|
|
"### Claims and attached evidence",
|
||
|
|
"",
|
||
|
|
]
|
||
|
|
)
|
||
|
|
claim_groups = [
|
||
|
|
("Claim", node.get("claims", [])),
|
||
|
|
("Assumption", node.get("assumptions", [])),
|
||
|
|
("Limitation", node.get("limitations", [])),
|
||
|
|
]
|
||
|
|
if not any(claims for _, claims in claim_groups):
|
||
|
|
sections.append("No node claim payload was included in this review item.")
|
||
|
|
sections.append("")
|
||
|
|
for group_label, claims in claim_groups:
|
||
|
|
for claim in claims:
|
||
|
|
sections.extend(
|
||
|
|
[
|
||
|
|
f"- **{group_label} {claim.get('claim_id', 'claim')}:** "
|
||
|
|
f"{claim.get('text', '')}"
|
||
|
|
]
|
||
|
|
)
|
||
|
|
for evidence in claim.get("evidence", []):
|
||
|
|
locator = evidence.get("locator", {})
|
||
|
|
location = ", ".join(
|
||
|
|
f"{key}={locator[key]}"
|
||
|
|
for key in ("page", "block_id", "start", "end")
|
||
|
|
if key in locator
|
||
|
|
)
|
||
|
|
quote = evidence.get("quote", "").splitlines()
|
||
|
|
sections.append(
|
||
|
|
f" - Evidence `{evidence.get('source_revision_id', '?')}` ({location}):"
|
||
|
|
)
|
||
|
|
sections.extend(f" > {line}" for line in quote)
|
||
|
|
for edge in edges:
|
||
|
|
sections.append(
|
||
|
|
f"- Supersession edge: `{edge.get('source', {}).get('id', '?')}` → `"
|
||
|
|
f"{edge.get('target', {}).get('id', '?')}` "
|
||
|
|
f"({edge.get('edge_type', 'relationship')})"
|
||
|
|
)
|
||
|
|
sections.extend(["", "### Owner decision", ""])
|
||
|
|
if is_correction:
|
||
|
|
sections.extend(
|
||
|
|
[
|
||
|
|
"- [ ] Approve this replacement for the owner workflow "
|
||
|
|
"(fresh evaluation still required)",
|
||
|
|
"- [ ] Request revision",
|
||
|
|
"- [ ] Reject this correction",
|
||
|
|
]
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
sections.extend(
|
||
|
|
[
|
||
|
|
"- [ ] Keep held; do not create an exception",
|
||
|
|
"- [ ] Request revision",
|
||
|
|
"- [ ] Initiate the authorized owner challenge workflow",
|
||
|
|
]
|
||
|
|
)
|
||
|
|
sections.extend(["- Rationale: ", "", "---", ""])
|
||
|
|
return "\n".join(sections)
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
||
|
|
parser.add_argument("--input", type=Path, required=True)
|
||
|
|
parser.add_argument("--output", type=Path, required=True)
|
||
|
|
args = parser.parse_args()
|
||
|
|
items = json.loads(args.input.read_text(encoding="utf-8"))
|
||
|
|
if not isinstance(items, list) or len(items) != 19:
|
||
|
|
parser.error("Expected the frozen 19-item held-draft and correction review packet")
|
||
|
|
output = render(items)
|
||
|
|
with args.output.open("x", encoding="utf-8") as stream:
|
||
|
|
stream.write(output)
|
||
|
|
print(json.dumps({"status": "created", "items": len(items), "output": str(args.output)}))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|