Files
shopdb-flask/CLAUDE.md
cproudlock 8f9805d964 Generate the facts that go stale, and record the fortnight in the changelog
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.
2026-08-14 13:47:48 -04:00

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 constants
  • shopdb/plugins/base.py - BasePlugin, PluginMeta
  • shopdb/plugins/loader.py - discovery and dependency-aware loading
  • shopdb/plugins/alembic_template.py - the env every plugin chain runs through
  • frontend/src/router/index.js, frontend/src/components/AppSidebar.vue
  • tools/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 NULL migrates.
  • Printers retirement: legacy PrinterData model, 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.