"""Validation of /api/decide question sets. Pure Python, no web framework: raises ValueError with the message shown to the client. app/main.py turns it into HTTP 422. Moved from app/main.py `_validate` in 1.2.0; checks and messages unchanged. """ from __future__ import annotations LIMITS = {"questions": 20, "name": 64, "instructions": 2000, "option": 500} def _text_len(x) -> int: return len(x) if isinstance(x, str) else len(str(x)) def validate_questions(questions: dict[str, dict], max_options: int) -> None: if not questions: raise ValueError("Add at least one question.") if len(questions) > LIMITS["questions"]: raise ValueError(f"At most {LIMITS['questions']} questions per request (got {len(questions)}).") for name, q in questions.items(): if len(name) > LIMITS["name"]: raise ValueError(f"Question names are limited to {LIMITS['name']} characters.") qtype = q.get("type", "choice") if qtype not in ("choice", "score", "noul"): raise ValueError(f"Question '{name}': type must be choice, score or noul (got '{qtype}').") if not (q.get("instructions") or q.get("question")): raise ValueError(f"Question '{name}': add 'instructions' with the question text.") if qtype in ("choice", "score"): crit = q.get("criteria", q.get("options")) n = len(crit) if isinstance(crit, (dict, list)) else 0 if n < 2: raise ValueError(f"Question '{name}': give 'criteria' with at least 2 options.") if n > max_options: raise ValueError(f"Question '{name}': {n} options is over the lab's limit of {max_options}.") pairs = crit.items() if isinstance(crit, dict) else ((c, "") for c in crit) for k, v in pairs: if isinstance(k, (dict, list)) or isinstance(v, (dict, list)): raise ValueError(f"Question '{name}': options must be text.") if _text_len(k) > LIMITS["option"] or _text_len(v) > LIMITS["option"]: raise ValueError(f"Question '{name}': each option (key and text) is limited to {LIMITS['option']} characters.") text = q.get("instructions") or q.get("question") or "" if _text_len(text) > LIMITS["instructions"]: raise ValueError(f"Question '{name}': instructions are limited to {LIMITS['instructions']} characters.") def validate_models(requested: list[str] | None, known: list[str]) -> list[str]: """The models to run: every known model when None, else the requested keys de-duplicated in order. An unknown key is an error, not a silent skip.""" if requested is None: return list(known) for k in requested: if k not in known: raise ValueError(f"Unknown model '{k}'. Known models: {', '.join(known)}.") return list(dict.fromkeys(requested))