tdb-intake / lib /schema.py
tttjjj's picture
Invalidate a question's review status when the question is edited
3475963
"""Constants and helpers for the intake form schema."""
import hashlib
import json
from typing import Literal, TypedDict, List
DESIGN_ELEMENTS: List[str] = [
"Hypotheses/Endpoints",
"Multiplicity control",
"Sample size and power",
"Interim analyses",
"Others",
]
QUESTION_TYPES: List[str] = [
"extraction_only",
"derivation_required",
]
QuestionType = Literal["extraction_only", "derivation_required", ""]
Status = Literal["pending", "reviewed", "needs_fix"]
VALID_STATUSES: List[str] = ["pending", "reviewed", "needs_fix"]
class Rubric(TypedDict):
artifact: str
dimension: str
points: str
criterion: str
tolerance: str
class Question(TypedDict):
id: str
design_element: str
design_element_other: str
question: str
question_type: str
rubrics: List[Rubric]
def blank_rubric(artifact: str = "", dimension: str = "") -> Rubric:
return {
"artifact": artifact,
"dimension": dimension,
"points": "",
"criterion": "",
"tolerance": "",
}
def rubrics_for_type(qt: str) -> List[Rubric]:
if qt == "extraction_only":
return [blank_rubric("output.json", "")]
if qt == "derivation_required":
return [
blank_rubric("output.json", "Inputs used"),
blank_rubric("output.json", "Calculated value"),
blank_rubric("output.json", "Method"),
blank_rubric("output.R", "Reproducibility"),
]
return []
def blank_question(qid: str) -> Question:
return {
"id": qid,
"design_element": "",
"design_element_other": "",
"question": "",
"question_type": "",
"rubrics": [],
}
def next_question_id(existing: List[Question]) -> str:
nums = []
for q in existing:
qid = q.get("id", "")
if qid.startswith("P-"):
try:
nums.append(int(qid[2:]))
except ValueError:
pass
return f"P-{(max(nums) + 1 if nums else 1):03d}"
def question_content_hash(q: dict) -> str:
"""Stable hash of a question's *content* (excludes its id).
Used to detect whether a question was edited since it was reviewed: if the
current content hash differs from the hash stored on a review, that review
no longer applies to the current content.
"""
canonical = {
"design_element": q.get("design_element", ""),
"design_element_other": q.get("design_element_other", ""),
"question": q.get("question", ""),
"question_type": q.get("question_type", ""),
"rubrics": [
{
k: r.get(k, "")
for k in ("artifact", "dimension", "points", "criterion", "tolerance")
}
for r in (q.get("rubrics") or [])
],
}
blob = json.dumps(canonical, sort_keys=True, ensure_ascii=False)
return hashlib.sha1(blob.encode("utf-8")).hexdigest()