"""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()