tdb-intake / lib /schema.py
tttjjj's picture
Remove the tolerance field from criteria
842481b
Raw
History Blame Contribute Delete
3.51 kB
"""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"]
# Each criterion's importance (replaces the old numeric "points").
IMPORTANCE_OPTIONS: List[str] = ["High", "Medium", "Low"]
class Criterion(TypedDict):
criterion: str
importance: str
class Rubric(TypedDict):
# A dimension block; holds one or more criteria.
artifact: str
dimension: str
criteria: List[Criterion]
class Question(TypedDict):
id: str
design_element: str
design_element_other: str
question: str
question_type: str
rubrics: List[Rubric]
def dimensions_for_type(qt: str):
"""Fixed (artifact, dimension) blocks for a question type, each with the
number of criterion rows to show by default. The user fills in one or more
criteria under each; the first is primary, extras are optional."""
if qt == "extraction_only":
return [{"artifact": "output.json", "dimension": "", "default_criteria": 1}]
if qt == "derivation_required":
return [
{"artifact": "output.json", "dimension": "Inputs used", "default_criteria": 1},
{"artifact": "output.json", "dimension": "Calculated value", "default_criteria": 1},
{"artifact": "output.json", "dimension": "Method", "default_criteria": 3},
]
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": [
{
"artifact": r.get("artifact", ""),
"dimension": r.get("dimension", ""),
"criteria": [
{
"criterion": c.get("criterion", ""),
"importance": c.get("importance", ""),
}
for c in (r.get("criteria") or [])
],
}
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()