Coding standards
Enforced on every task in the local MVP lane.
Rules
| Rule |
Meaning |
| Atomic functions |
One responsibility; ~30 lines max |
| Chunk + test |
pytest green before next task |
| Protocols |
typing.Protocol for parsers — no vendor imports in core/ |
| Registry |
Plugins register in plugins/__init__.py only |
| Stable contracts |
Return core/models.py types from all plugins |
| Fail closed |
Missing creds → enabled=False, never silent wrong API |
| No PHI in logs |
Log job_id, parser_id, confidence only |
| Never drop an analyte |
Extra LLM processing is fine; a missing lab value is not. Every row a stage does not keep is named in the extraction funnel and, when uncoded, in /loinc-review |
UI rules (no build step)
| Rule |
Meaning |
| One shell |
Pages are composed by web/pages.py::render_page from static/_shell.html + static/pages/<name>.html; the nav is NAV_ITEMS. No inline scripts or style="" in fragments |
Modules with init() |
Every page script is an ES module importing static/common.js and exporting init(), guarded by if (typeof document !== "undefined") init(); so Node can import it |
| Escape at the render boundary |
Every value that reaches innerHTML goes through escapeHtml inside a builder in *-format.js; the page module only assigns builder output (tests/test_faxes_static.py guards this) |
Pure formatters under node --test |
Labels, validation and HTML builders live in review-format.js, faxes-format.js, harness-format.js and are tested in tests/web/*.test.mjs (bridged into pytest by tests/test_web_assets.py); each HTML builder is fed <x> |
| One vocabulary |
Decisions, scopes, specimens, sources and outcomes have one label map each; never a raw enum on screen |
| Status where the person looks |
An inline setStatus line beside the control that was used; a toast only for page-level outcomes; never alert/confirm (use confirmDialog) |
| 401 means sign in and come back |
api() redirects to /login?next=…; never show raw JSON to the person |
Test layout
tests/
test_core_models.py
test_registry.py
test_plugins_gcp.py # @pytest.mark.gcp skips without creds
test_orchestrator.py
test_api.py
test_elation_adapter.py
test_web_pages.py # shell composition, headers
test_web_assets.py # node --test bridge, module imports, one owner per helper, CSS tokens
test_faxes_static.py # innerHTML source guard, builders have a Node escape test
web/*.test.mjs # node:test — pure page logic
Adding code checklist
- Implement smallest unit
- Write test
- Run
pytest
- Update MkDocs page for the area you changed
- Entry in
.cursor/memory/CHANGELOG.md (no PHI)