Data interchange
Data interchange is how a facility gets its inventory into Ovahol. It is also how a facility gets its data out in a form another system can read.
There is exactly one import path: the interchange bundle. The older, library-specific importer and its facility-route import are gone, so a bug fixed here is fixed for every customer.
The bundle
A bundle is a JSON document conforming to the ovahol/interchange-schema v0.1.0 draft, vendored byte-identical at backend/internal/modules/interchange/schema/v0.1.0.json. Every bundle Ovahol emits validates against the unmodified draft.
Beyond the letter of the schema, Ovahol has defined behaviour for the cases the draft leaves open — unknown model names, unplaced assets, nomenclature fallbacks. Those are recorded in the implementation profile, which a second system should read alongside the schema.
The two route families
| Tenant routes | Targeted routes | |
|---|---|---|
| Base path | /api/v1/tenant/interchange | /api/v1/core/admin/interchange/facilities/:facility_id |
| Facility | The caller's ambient facility | Chosen by the caller |
| Roles | Facility roles and org admins | SUPER_ADMIN, ORG_ADMIN, FACILITY_ADMIN |
| Scope | Context middleware | service.AuthorizeFacilityAccess |
The targeted family exists because a super admin migrating data on behalf of a facility has no ambient facility context. It deliberately sits outside the tenant middleware chain and authorizes the target facility in the service layer: super admins may target any facility, org admins only facilities in their organization, facility admins only their own.
The operations are identical:
| Operation | Purpose |
|---|---|
GET /export | Emit the facility as one bundle |
GET /catalog/names?kind=device|model|brand|manufacturer | Scoped name list for match correction |
POST /validate | Semantic validation, no writes |
POST /convert | Normalize a third-party spreadsheet-shaped bundle; optionally recommend missing catalog entries |
POST /import | All-or-nothing apply |
Request bodies are capped at 32 MiB (413 beyond that).
The import flow
third-party data ──convert──> bundle ──validate──> import preview ──import──> facility
│
└── match report (create vs. reuse)- Convert (optional) — a spreadsheet export becomes a bundle: column mapping, composite unknown-model naming, holding location for unplaced rows, UMDNS fallback. Conversion never touches the database;
recommend_missingadditionally proposes catalog entries for a human to approve. - Validate — schema validation plus referential checks against the target facility: which devices already exist, which definitions can be reused, which rows would be rejected and why. Nothing is written.
- Import — the whole bundle is applied in a single transaction. A failure anywhere rolls back everything, so a half-migrated facility is not a state that can exist.
Import history is recorded so a re-run can be traced.
The engine
The engine lives in backend/internal/modules/interchange; its service package holds the moving parts:
| File | Responsibility |
|---|---|
convert.go | Column mapping and normalization into the bundle shape |
validate.go | Schema + semantic validation, match planning |
matcher.go | Resolving names to existing catalog records |
recommend.go | Suggesting catalog entries for unmatched rows |
import.go | The transactional apply |
export.go | Reading a facility into a bundle |
codemap.go | Vocabulary code translation |
identity.go | Device identity/deduplication rules |
target.go | Facility targeting and authorization |
catalog_names.go | Scoped name lookups |
It owns no persistence. It composes the public facades of the devices, maintenance, lifecycle, training, facilities and counterparties modules — which is why the module sits above them rather than beside them.
Why one path
Two importers meant two normalization engines, two sets of edge-case fixes and two sets of tests for the same problem. The bundle is the standard; the spreadsheet converter is an adapter at the front of it, not a parallel implementation. Adding a source format means adding a converter, never a second import engine.
Related
- Interchange API — the authoritative endpoint contract.
- Implementation profile — where Ovahol goes beyond the schema draft.