Establishes the framework's foundation as a multi-site adoptable platform. ADRs (migrations/adr/): - ADR-001 (ACCEPTED): Asset is the platform contract; Machine retires. Three relationship types (partof, controls, connectedto) with free-text label, position-resolution chain (asset > related > location), hierarchical locations, sibling-bay propagation. - ADR-002 (ACCEPTED): Plugin contract semver via __contract_version__. - ADR-003 (ACCEPTED): Hybrid plugin distribution (in-tree bundled + filesystem-based external). - ADR-004 (ACCEPTED): Per-site instances, not multi-tenant. - ADR-005 (ACCEPTED): Equipment plugin (manufacturing) split from measuringtools plugin (metrology). Subtype-table pattern for protocol data (FOCAS, CLM, MTConnect). - ADR-006 (ACCEPTED): Plugin collector contract via get_collector_schema hook with API-key auth and identity-based upsert. Naming convention v1 (CONTRIBUTING.md): - DB tables/columns: lowercase concatenated, no underscores or dashes - DB-mirrored Python/JS variables match column names exactly; pure code follows host-language convention (PEP 8 / camelCase) - Closed acronym allowlist (universal + shop-floor domain), banned shorthand list with suffix exception (printers_bp etc allowed) - Plain ASCII everywhere: chat, docs, comments, string literals Style enforcement (scripts/check-naming-and-style.sh): - Pre-commit-runnable check script: non-ASCII, banned shorthand, snake_case DB names, snake_case API params in frontend - Fixes 14 violations across 11 files (Unicode arrows, snake_case params, ctx -> canvasContext, res -> response, req -> request_obj) Project state (CLAUDE.md, README.md, frontend/CLAUDE.md): - De-staled CLAUDE.md to reflect actual current state - README unifies DB story (MySQL canonical, SQLite test-only) - frontend/CLAUDE.md points at root convention Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
174 lines
8.4 KiB
Markdown
174 lines
8.4 KiB
Markdown
# ADR-001: Asset model is the platform contract
|
|
|
|
- **Status:** ACCEPTED
|
|
- **Date:** 2026-05-08
|
|
- **Deciders:** cproudlock
|
|
- **Supersedes:** none
|
|
|
|
## Context
|
|
|
|
shopdb-flask is being shaped as a framework that sister GE Aerospace facilities can adopt. The framework defines a stable core; sites install plugins for the asset classes they care about (equipment, computers, printers, measuring tools, network gear, etc.).
|
|
|
|
The codebase ran two parallel object models:
|
|
|
|
1. **Legacy `Machine` model** in `shopdb/core/models/machine.py`. Original schema inherited from the classic-ASP shopdb. Tables: `machines`, `pctypes`, `machinetypes`. Plugins like printers stored extension data via `PrinterData` keyed by `machineid`.
|
|
2. **New `Asset` model** in `shopdb/core/models/asset.py`. Generic asset abstraction with `AssetType`, `AssetStatus`, `AssetRelationship`. Plugins also exposed asset-based tables keyed by `assetid`.
|
|
|
|
For the framework to be adoptable, the platform contract has to be one model, documented, versioned, and stable.
|
|
|
|
## Decision
|
|
|
|
**`Asset` is the platform contract.** Plugin authors target the asset-based API. The `Machine` model and its dependents (`PCType`, `MachineType`, `PrinterData` keyed by `machineid`) are legacy and will be retired through a tracked migration.
|
|
|
|
### Platform contract surface
|
|
|
|
The following are the public, versioned surface. Plugin authors may depend on them. Breaking changes require a major version bump per ADR-002.
|
|
|
|
#### Models
|
|
|
|
- `Asset` (core entity)
|
|
- `AssetType` (asset classification, registered by plugins)
|
|
- `AssetStatus` (active / inactive / decommissioned / retired)
|
|
- `AssetRelationship` + `RelationshipType` (cross-plugin links)
|
|
- `Vendor`, `Location`, `LocationType`, `BusinessUnit`, `Model`, `OperatingSystem` (shared reference data)
|
|
|
|
#### Helpers
|
|
|
|
- `AuditLog` API: `audit_log(action, entitytype, entityid, ...)` for plugins to record audit entries with consistent schema
|
|
- `Setting` API: `plugin.get_setting(key)` and `plugin.set_setting(key, value)` for plugin-scoped config persisted via the core `Setting` model
|
|
|
|
#### Plugin contract
|
|
|
|
- `BasePlugin` ABC and its hooks (search, navigation, dashboard, relationships, collector schema)
|
|
|
|
#### Excluded from the contract for v1
|
|
|
|
- Event bus (`get_event_handlers` removed from `BasePlugin`). Add later if a real use case appears.
|
|
|
|
### Relationship types (seeded core values)
|
|
|
|
`RelationshipType` is seeded with three rows. Plugin authors do not add new types in v1.
|
|
|
|
| Type | Meaning | Boundary rule |
|
|
|------|---------|---------------|
|
|
| `partof` | Composition, siblings, sub-assemblies | Parent dies without it |
|
|
| `controls` | One asset has operational authority over another | PC commands a machine, measuring tool, etc. |
|
|
| `connectedto` | Network or data link without operational authority | Cable, switch port, NAS mount |
|
|
|
|
### AssetRelationship columns
|
|
|
|
```
|
|
AssetRelationship
|
|
- relationshipid PK
|
|
- sourceassetid FK to assets
|
|
- targetassetid FK to assets
|
|
- relationshiptypeid FK to relationshiptypes (one of partof, controls, connectedto)
|
|
- label text, free description (e.g., "ethernet PoE", "DNC feed")
|
|
- inheritsposition bool, true means resolved-position walk follows this edge
|
|
- propagatesthroughid FK to relationshiptypes, nullable, see propagation below
|
|
- notes text
|
|
- isactive bool
|
|
- createddate, modifieddate
|
|
```
|
|
|
|
Free-text `label` carries the domain nuance (`controls` with label "DNC feed" vs `controls` with label "operator workstation"). Avoids inflating the type list.
|
|
|
|
### Sibling propagation
|
|
|
|
When a relationship is created or deleted, the framework checks `RelationshipType.propagatesthroughid`. If set, it finds all assets related to the source via the propagation type and applies the same change.
|
|
|
|
Default seeding:
|
|
|
|
| RelationshipType | propagatesthroughid |
|
|
|------------------|---------------------|
|
|
| `partof` | null (the propagation rail itself) |
|
|
| `controls` | `partof` (controls relationships propagate across siblings) |
|
|
| `connectedto` | null (network paths don't propagate) |
|
|
|
|
Cycle protection: max walk depth 3, visited-set during traversal.
|
|
|
|
### Position resolution
|
|
|
|
Resolved map position for any asset follows this priority chain:
|
|
|
|
1. Asset-specific override (`assets.mapx`, `assets.mapy` if non-null)
|
|
2. Walk relationships where `inheritsposition=true`, ordered by relationship type priority (`partof` first, then `controls`), recursively resolve the related asset's position with cycle detection
|
|
3. Fall back to the asset's location coords (`locations.mapx`, `locations.mapy` via `assets.locationid`)
|
|
4. If none of the above, asset is "unplaced" and rendered in a tray, not on the map
|
|
|
|
API exposes resolved position with a source indicator: `{"mapx": 234, "mapy": 567, "positionsource": "self|related|location|none"}`.
|
|
|
|
### Hierarchical locations
|
|
|
|
`locations` is extended with `parentlocationid` (self-FK, nullable). Sites define their own location tree (cells, sub-cells, network closets, meeting rooms, labs).
|
|
|
|
`locationtypes` lookup is added with seeded values: `section`, `cell`, `subcell`, `meetingroom`, `lab`, `office`, `storage`, `hallway`, `networkcloset`, `building`. Sites can extend.
|
|
|
|
Asset-to-location is the existing `assets.locationid` FK only (single primary location for v1). Multi-location and transient placement are deferred.
|
|
|
|
## Migration scope (narrowed)
|
|
|
|
Only one class of legacy data migrates: physical manufacturing equipment with a `machinenumber` (5-axis mills, lathes, broachers, heat treatment, CMMs, etc.).
|
|
|
|
Migration filter:
|
|
|
|
```sql
|
|
SELECT * FROM legacy.machines
|
|
WHERE category = 'Equipment'
|
|
AND machinenumber IS NOT NULL
|
|
```
|
|
|
|
Rows without `machinenumber`: **skipped.** Add manually post-migration if any matter.
|
|
|
|
Migrated rows become `assets` with `assettype='Equipment'` plus a row in the equipment plugin's `equipment` table. Legacy `machinetypeid` is preserved on the equipment row to enable later reclassification (some "Equipment" rows are actually measuring tools, see ADR-005).
|
|
|
|
Skipped from migration:
|
|
|
|
- PCs (rebuilt via PXE collector pipeline, see ADR-006)
|
|
- Printers, USB devices, network gear, notifications, KB articles (operational or re-collectable, decision per class deferred until needed)
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
|
|
- Plugin authors at sister sites have a single, documented contract to target
|
|
- Cross-plugin features (relationships, search, map) work uniformly
|
|
- Three relationship types are easy to learn; free-text label captures nuance
|
|
- Sibling propagation handles dual-path machines without code changes per use case
|
|
- Position resolution gives users one map without per-plugin map code
|
|
|
|
### Negative
|
|
|
|
- Migration of equipment data must run cleanly; `migrating-asset-schema` skill owns the procedure
|
|
- Frontend `views/machines/` will be repointed or replaced; effort tracked in Phase 5
|
|
- Legacy `core/api/machines.py` (641 LOC) becomes deprecated; deletion or shim deferred until equipment is migrated
|
|
|
|
### Neutral
|
|
|
|
- `Machine`, `PCType`, `MachineType`, `PrinterData` remain in the schema until the migration completes, behind a deprecation notice. New code does not touch them.
|
|
|
|
## Alternatives considered
|
|
|
|
1. **Keep both models indefinitely.** Doubles test surface, confuses contract docs, breaks cross-plugin features. Rejected.
|
|
2. **Make `Machine` the contract; retire `Asset`.** `Machine` has shop-floor-specific assumptions that don't generalize. Rejected.
|
|
3. **Define a higher abstraction above both.** Yet another layer. `Asset` is already the abstraction. Rejected.
|
|
4. **More relationship types** (`controls`, `operates`, `monitors`, `dncfeeds`, `mountedon`, `connectedvia`, `inspects`, `siblingbay`). Eight types proved unwieldy. Collapsed to three with free-text labels and per-row inheritance/propagation flags.
|
|
|
|
## Open questions deferred
|
|
|
|
- Lifecycle relationship type (`replaces`, `replacedby`). Add when first needed.
|
|
- Multi-location asset placement, transient placement (calibration trip, off-site repair). Add when first needed.
|
|
- Map editing UI (drag locations onto floor plan). Phase 6 polish.
|
|
- Plugin-extensible relationship types beyond the three seeded. Add when a real cross-plugin case can't be expressed via labels.
|
|
|
|
## References
|
|
|
|
- `shopdb/core/models/asset.py`
|
|
- `shopdb/core/models/machine.py` (legacy, deprecated)
|
|
- `shopdb/plugins/base.py`
|
|
- ADR-002 (versioning of the surface)
|
|
- ADR-003 (plugin distribution)
|
|
- ADR-004 (deployment topology)
|
|
- ADR-005 (equipment vs measuring tools split)
|
|
- ADR-006 (collector contract pattern)
|