CLAUDE.md is read at the start of every session and claimed contract 0.16.0 and 1159 tests while the code was at 0.18.0 and 1567, along with a plugin count and a chain head that were both wrong. Hand-written facts about a moving codebase are wrong within a fortnight, and a reader who cannot tell which lines are stale has to re-derive all of them. So they are derived. scripts/gen_project_map.py reads the versions, the plugin inventory, every Alembic chain head, the ADR index with status and the size of the codebase out of the code, and --check fails a build where the committed map no longer matches. CLAUDE.md now points at it, leads with the multi-site rule, and lists the gates to run instead of restating the conventions they enforce. The changelog's Unreleased section covered 8 of the 46 commits since 0.9.0 and had no Changed, Fixed or Security sections at all. It now carries the whole fortnight, including both contract bumps - which had never been recorded even though ADR-002 makes contract versions their own series.
4.9 KiB
ShopDB Flask Project
Modern rewrite of the classic-ASP shopdb. Built as a framework so sister GE Aerospace sites can adopt it. The plugin system is the product.
This site is not the only site. Anything that names West Jefferson - a hostname, a share path, a subnet, a machine-number format, a label prefix - is a defect unless it is a setting with a neutral default, a site-namespaced directory, or seed data. That is ADR-015, and it is the rule most often broken in the places tooling cannot see: PowerShell, the installer, JSON, the imaging share.
Facts that move live in the map
docs/PROJECT-MAP.md is GENERATED. It carries the product and contract
versions, the plugin inventory, every Alembic chain head, the ADR index with
status, and the size of the codebase. Read it instead of asking; regenerate it
rather than correcting it by hand:
venv/bin/python scripts/gen_project_map.py # write
venv/bin/python scripts/gen_project_map.py --check # CI form
Hand-written version numbers in this file were wrong by two contract releases and 400 tests before the map existed. Nothing that the generator can derive should be typed here again.
Where to look
| question | read |
|---|---|
| what a name may be called | CONTRIBUTING.md |
| why the architecture is like that | docs/adr/ (index in the map) |
| what a plugin may touch | docs/PLUGIN-HOOKS.md |
| what the collector sends | docs/COLLECTOR-INTEGRATION.md, ADR-006 |
| how a site is deployed | docs/INSTALL-WINDOWS.md, docs/OPERATE-WINDOWS.md |
| how code reaches GitHub | tools/export-github.sh |
Standards are gates, not prose
Compliance is checked, not remembered. Run these rather than re-reading the rules:
bash scripts/check-naming-and-style.sh # naming, style, site literals (ADR-015)
venv/bin/python -m pytest -q # includes the docs publishability gate
venv/bin/python -m pytest tests/test_plugin_migrations.py # chain heads registry
venv/bin/python scripts/gen_project_map.py --check
A new plugin table means updating PLUGIN_TABLE_OWNERS and
EXPECTED_HEAD_REVISION in tests/test_plugin_migrations.py.
Database
- Active:
shopdb_flask(MySQL, asset-based schema) - Legacy:
shopdb(classic ASP schema; one-time import only) - Connection:
.env, see.env.example
Core migrations run with flask db upgrade; every plugin owns its own chain
(ADR-008) and runs with flask plugin upgrade-all. A deploy needs both -
skipping the second is how a new column reaches production as a 1054 error.
Tests run on SQLite, production is MySQL. Dialect drift is not caught by the
suite: ORDER BY ... NULLS LAST parses on SQLite and is rejected by MySQL. Sort
in Python when in doubt.
Coding convention
CONTRIBUTING.md is authoritative: lowercase concatenated DB columns, snake_case
pure-code identifiers, a closed acronym list, banned shorthand. The pre-commit
hook scripts/check-naming-and-style.sh enforces what it can.
Plugin structure
plugins/<name>/
manifest.json # metadata, single source of truth (ADR-002)
plugin.py # BasePlugin implementation
models/ # __init__.py exports every model
api/routes.py # Flask blueprint
migrations/ # this plugin's own Alembic chain
services/ schemas/ frontend/ client/ # as needed
No plugin imports core directly - everything goes through the shopdb.api
contract surface. A manifest-less directory under plugins/ is core frontend
surface and always ships.
Key files
shopdb/__init__.py- app factory, version constantsshopdb/plugins/base.py- BasePlugin, PluginMetashopdb/plugins/loader.py- discovery and dependency-aware loadingshopdb/plugins/alembic_template.py- the env every plugin chain runs throughfrontend/src/router/index.js,frontend/src/components/AppSidebar.vuetools/export-github.sh- scrub and publication gate
State
Refactor phases 0-5 landed; phase 6 (multi-site distribution readiness) largely
complete. The last milestone before 1.0 is unchanged and still open: the
legacy-data import plus a production pilot (scripts/site_imports/wjf/ is the
reference loader, validated end to end).
Pre-1.0 framework. Sister sites pin tight core_version ranges until the
contract reaches 1.0.
Deferred
- Equipment data migration (legacy ASP -> assets). Per ADR-001 only
category='Equipment' AND machinenumber IS NOT NULLmigrates. - Printers retirement: legacy
PrinterDatamodel, coordinated with the above.
Quick start
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # DB credentials, JWT secrets
flask db upgrade # core chain
flask plugin upgrade-all # every plugin chain
flask seed permissions && flask seed settings && flask seed reference-data
pm2 restart shopdb-flask-api shopdb-flask-ui
Dev API on :5001, dev UI on :5173. venv/bin/python tools/shot.py /route logs
in and screenshots a page.