The machine page now shows the PC that controls it. The endpoint was correct all along and returned both directions; AssetTriageBand discarded every peer whose type was not 'machine' and was mounted only on the PC page. The counterpart type, label and relationship types are now props. Fixed while adopting it more widely: every band chip linked to /assets/<id>, and there is no such route. The resolver that AssetRelationships.vue kept privately is now frontend/src/utils/assetRoute.js, used by both - and its type lookup is case-insensitive, because seed data capitalises the type names while this site's database does not. The legacy import is now safe to resume. The 409-resolve asked for per_page=10000 against a server that clamps to 100, so it silently searched the first 100 rows and dropped links past that on every re-run. The crosswalk is now bound to the database it describes, so one carried across a rebuild is refused rather than attaching rows to the wrong assets. Source database names are resolved from the environment, so the loader can be aimed without editing it. Full suite 2178 passed, frontend 280 passed.
WJ classic-ASP import loader (reference implementation)
This is site-specific reference glue, not product code. It maps West
Jefferson's classic-ASP shopdb / cmmc_usb / wjf_employees schema onto the
maintained, schema-agnostic import contract in the IMPORT-API wiki page.
Every adopting site has its own source database. Nobody else runs this loader. Instead, copy the pattern:
- Point the harness at your source DB(s) (edit
harness.Source). - Write per-entity stages that read your tables and POST to the same
the IMPORT-API wiki page endpoints with
Authorization: Bearer <admin PAT>andX-Import-Mode: true. - Persist legacy-id -> new-id crosswalks (see
harness.IdMap) so later stages resolve foreign keys and a crashed run resumes.
The import API is the stable contract; loaders are per-site. The mapping itself is the bulk of the work: enumerate the source tables against this contract and write one stage per entity (this loader is the worked example).
Running (against a THROWAWAY import database)
DATABASE_URL='mysql+pymysql://root:PW@127.0.0.1:3306/shopdb_flask_import?charset=utf8mb4' \
venv/bin/python -m scripts.site_imports.wjf.run --stages reference,employees
Prereqs: a fresh target DB built with flask db upgrade + flask plugin upgrade-all + flask seed permissions/settings/reference-data, and the three
source dumps loaded into scratch DBs (shopdb_src, cmmc_usb_src,
wjf_employees_src). See scratchpad/IMPORT-PLAN.md for the full mapping,
resolved decisions, and remaining stages.
Status
All 17 stages built: reference, employees, catalog, assets (the hub +
machineid->assetid crosswalk), locations, printers, metrology,
communications, applications (+ installs), warranties, notifications,
knowledgebase, relationships, defaultprinters, subnets, usb, verify.
The counts below are from the 2026-07-13 run and are STALE. Two routing
changes landed after them - the isactive=1 filter and the metrology routing
rework - so the asset mix has moved substantially. They are kept only as a
shape, not as a checklist: re-measure from the next full run before quoting
them anywhere, and do not verify a fresh import against them.
Last full run (2026-07-13, superseded): 983 assets (computer 663, machine 76, network 58, measuring-tool 136, printer 50), 24 locations, 415 employees, 850 installs, 461 primary IPs, 464 warranties, 261 notifications, 341 KB, 93 relationships, 37 subnets, 18 USB devices + 232 events.
Known gaps, none of them blocking a scratch run:
warranties,notificationsandknowledgebasePOST without a duplicate check on the API side, so resuming ACROSS one of those stages doubles its rows. Resuming at or after them is safe. Re-run into a clean target when in doubt.- There is no
--dry-runand no guard on the target: the harness mints an admin PAT and setssetup_completein whateverDATABASE_URLpoints at. Check the target before running. - The
verifystage counts targetassetsagainst sourcemachines, which is apples to oranges - the target also holds printers and synthesized metrology tools.
Known follow-ups: general asset locationid is null (no source column outside
printers); the ~146 duplicate machinenumbers are first-wins-skipped by decision.