| """Server-side range validation for the Framingham tabular fields. |
| |
| Blank / missing fields are skipped here (they get KNN-imputed downstream). |
| Only *provided* values are checked: they must parse as numbers and fall inside |
| clinically plausible bounds. Returns a list of human-readable error strings |
| (empty == valid). |
| """ |
| from __future__ import annotations |
|
|
| from typing import Any |
|
|
| |
| FIELD_SPECS: dict[str, dict[str, Any]] = { |
| "male": {"choices": {0, 1}}, |
| "age": {"min": 1, "max": 120}, |
| "education": {"choices": {1, 2, 3, 4}}, |
| "currentSmoker": {"choices": {0, 1}}, |
| "cigsPerDay": {"min": 0, "max": 100}, |
| "BPMeds": {"choices": {0, 1}}, |
| "prevalentStroke": {"choices": {0, 1}}, |
| "prevalentHyp": {"choices": {0, 1}}, |
| "diabetes": {"choices": {0, 1}}, |
| "totChol": {"min": 50, "max": 700}, |
| "sysBP": {"min": 50, "max": 300}, |
| "diaBP": {"min": 30, "max": 200}, |
| "BMI": {"min": 10, "max": 70}, |
| "heartRate": {"min": 20, "max": 300}, |
| "glucose": {"min": 20, "max": 700}, |
| } |
|
|
|
|
| def validate_tabular(fields: dict[str, Any]) -> list[str]: |
| errors: list[str] = [] |
| for name, spec in FIELD_SPECS.items(): |
| raw = fields.get(name) |
| if raw is None or str(raw).strip() == "": |
| continue |
| try: |
| v = float(raw) |
| except (TypeError, ValueError): |
| errors.append(f"{name}: '{raw}' is not a number") |
| continue |
| if "choices" in spec and v not in spec["choices"]: |
| errors.append(f"{name}: must be one of {sorted(spec['choices'])} (got {raw})") |
| if "min" in spec and v < spec["min"]: |
| errors.append(f"{name}: must be >= {spec['min']} (got {raw})") |
| if "max" in spec and v > spec["max"]: |
| errors.append(f"{name}: must be <= {spec['max']} (got {raw})") |
| return errors |
|
|