Skip to content

feat: add ItemsSyncStep and VehiclesSyncStep for ISSUE-198#229

Merged
GitAddRemote merged 13 commits into
mainfrom
feature/ISSUE-198
May 30, 2026
Merged

feat: add ItemsSyncStep and VehiclesSyncStep for ISSUE-198#229
GitAddRemote merged 13 commits into
mainfrom
feature/ISSUE-198

Conversation

@GitAddRemote
Copy link
Copy Markdown
Owner

@GitAddRemote GitAddRemote commented May 26, 2026

Summary

  • Adds VehiclesSyncStep to sync station_vehicle and station_vehicle_loaner from the UEX /vehicles and /vehicle_loaners endpoints
    • Two-pass execution: (1a) upsert vehicles with parent_uex_id=NULL, (1b) back-fill parent_uex_id; pass 1c NULLs de-parented rows; loaner pass deletes stale then re-inserts current rows
    • Guards company_uex_id and parent_uex_id FKs by validating against preloaded Sets; emits warnings and coerces to NULL for unknown references
  • Adds ItemsSyncStep to sync station_item and station_item_attribute from the UEX /items endpoint
    • Three-pass execution: (1a) upsert items with parent_uex_id=NULL, (1b) back-fill parent_uex_id, (1c) NULL de-parented rows; (2) upsert each item's attributes into station_item_attribute
    • Builds attributes_summary JSONB inline from the item's attributes array, keyed by category_attribute_uex_id — stored on the item row for fast display reads without joins; excludes attributes with falsy id_category_attribute
    • Guards parent_uex_id FK with the same preloaded-Set pattern used by VehiclesSyncStep
  • Both steps registered in CatalogEtlModule and CatalogEtlService pipeline

Schema alignment

Implements against the actual baseline migration schema (station_vehicle / station_vehicle_loaner / station_item / station_item_attribute) rather than the speculative schema in the issue description.

Test plan

  • pnpm test passes (unit tests across vehicles-sync.step.spec.ts, items-sync.step.spec.ts, and catalog-etl.service.spec.ts)
  • Vehicle upsert: parent_uex_id=NULL literal in pass 1a; pass 1b UPDATE only when id_parent is set and present in upsertedUexIds; pass 1c clears de-parented rows
  • Loaner delete scoped to all upserted vehicle uex_ids (not just loaner origins)
  • company_uex_id FK guard: unknown id_company coerced to NULL with warning
  • Item upsert: same three-pass pattern; parent_uex_id FK guard
  • attributes_summary JSONB built correctly; falsy id_category_attribute entries excluded
  • Attribute upsert: correct params, skips attributes missing category_attribute_uex_id, ON CONFLICT (uex_id) DO UPDATE
  • Edge cases: no-name records skipped with warn, empty list produces no inserts

Closes #198

Copilot AI review requested due to automatic review settings May 26, 2026 19:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new Catalog ETL steps to ingest UEX vehicles and items data into the baseline catalog schema, including item attributes and an attributes_summary JSONB denormalization for faster reads. This extends the existing ETL pipeline by introducing vehicles-sync (needed for item vehicle FK resolution) and items-sync (ISSUE-198).

Changes:

  • Add VehiclesSyncStep to upsert station_vehicle and station_vehicle_loaner with a two-pass parent backfill.
  • Add ItemsSyncStep to upsert station_item, backfill parent_uex_id, and upsert station_item_attribute, including attributes_summary JSONB construction.
  • Register both steps in CatalogEtlModule and CatalogEtlService pipeline, and add unit tests for each step.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backend/src/modules/catalog-etl/steps/vehicles-sync.step.ts New ETL step syncing vehicles + vehicle loaners from UEX into station_vehicle* tables.
backend/src/modules/catalog-etl/steps/vehicles-sync.step.spec.ts Unit tests covering vehicle upsert params, parent backfill pass, and loaner inserts.
backend/src/modules/catalog-etl/steps/items-sync.step.ts New ETL step syncing items + attributes into station_item* tables and building attributes_summary.
backend/src/modules/catalog-etl/steps/items-sync.step.spec.ts Unit tests covering item upsert params, parent backfill pass, attributes_summary behavior, and attribute upserts.
backend/src/modules/catalog-etl/catalog-etl.service.ts Registers vehicles-sync then items-sync in the ETL pipeline.
backend/src/modules/catalog-etl/catalog-etl.service.spec.ts Adds mocked providers for the new steps to keep DI tests passing.
backend/src/modules/catalog-etl/catalog-etl.module.ts Registers the new steps as Nest providers in the module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread backend/src/modules/catalog-etl/steps/vehicles-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/vehicles-sync.step.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread backend/src/modules/catalog-etl/catalog-etl.service.ts
@GitAddRemote GitAddRemote changed the title feat: add ItemsSyncStep for ISSUE-198 feat: add ItemsSyncStep and VehiclesSyncStep for ISSUE-198 May 26, 2026
@GitAddRemote GitAddRemote requested a review from Copilot May 26, 2026 22:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment thread backend/src/modules/catalog-etl/steps/vehicles-sync.step.ts
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Copilot AI review requested due to automatic review settings May 26, 2026 23:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Copilot AI review requested due to automatic review settings May 27, 2026 03:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts
GitAddRemote and others added 6 commits May 28, 2026 18:04
- Fetches /items from UEX API and upserts into station_item by uex_id
- Pass 1a inserts all items with parent_uex_id=NULL literal to satisfy
  the self-referential FK regardless of payload order
- Pass 1b back-fills parent_uex_id via UPDATE for items with id_parent set
- Builds attributes_summary JSONB keyed by category_attribute_uex_id from
  the item's attributes array; stored inline on the item row
- Pass 2 upserts each attribute into station_item_attribute by its own uex_id
  with FK to station_category_attribute.uex_id; attributes with no
  category_attribute_uex_id are skipped with a warn
- Registers ItemsSyncStep in CatalogEtlModule and CatalogEtlService
  ETL_STEPS pipeline (after vehicles-sync)
- 19 unit tests covering item upsert, two-pass parent FK, attributes_summary
  construction, attribute upsert, ordering, and edge cases
… comment

- buildAttributesSummary now skips attrs with falsy id_category_attribute,
  keeping attributes_summary JSONB consistent with station_item_attribute rows
- Correct the column layout comment ($2=category_uex_id, $7=uuid, not swapped)
- Add spec: summary excludes attrs with id_category_attribute=0
…scope

Both steps:
- Remove parent_uex_id=EXCLUDED.parent_uex_id from ON CONFLICT DO UPDATE so
  reruns no longer temporarily clear parent links during pass 1a
- Pass 1b: guard unknown id_parent against FK violations (warn + skip)
- Pass 1c: explicitly NULL parent_uex_id for de-parented rows after pass 1b,
  so removals are reflected without risking degradation on partial failure

VehiclesSyncStep:
- Collect upsertedUexIds in pass 1a; use it for loaner validation and DELETE scope
- DELETE station_vehicle_loaner scoped to all upserted vehicles (not just loaner
  origins), so vehicles that drop to zero loaners are also reconciled
- Remove now-redundant SELECT uex_id FROM station_vehicle preload
… items sync steps

vehicles-sync:
- Preload station_company uex_ids; coerce unknown id_company to NULL with warning

items-sync:
- Preload station_company, station_vehicle, station_category_attribute uex_ids
  before pass 1a using Promise.all to avoid sequential round-trips
- Coerce unknown id_company / id_vehicle to NULL with warnings rather than
  letting the INSERT propagate an FK violation
- Skip station_item_attribute rows whose id_category_attribute is absent from
  station_category_attribute, emitting a warning instead of failing the step

Specs updated to route each SELECT preload to a per-table mock and cover all
new FK guard paths (known, null, and unknown→null+warn).
…JSONB

Pass knownCategoryAttributeUexIds into buildAttributesSummary so that
attributes absent from station_category_attribute are filtered out of the
denormalized JSONB, keeping it consistent with what is actually written to
station_item_attribute in pass 2.

Adds spec: 'excludes attributes absent from station_category_attribute from summary'.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Scoped DELETE to upsertedUexIds ensures attributes dropped from the UEX
payload are removed and stay consistent with attributes_summary JSONB.
Spec asserts DELETE is issued, covers the upserted ids, and precedes INSERTs.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Pre-validate all attributes before entering the transaction so warnings
are emitted first. The DELETE and replacement INSERTs then execute atomically
per item, ensuring a failed INSERT cannot leave attributes_summary and
station_item_attribute inconsistent.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
…conciliation

- Move the pass 1a item upsert into the same dataSource.transaction() as the
  attribute DELETE + re-inserts so attributes_summary and station_item_attribute
  are written atomically — a failed attribute INSERT now rolls back the item
  upsert for that item, keeping the two in sync
- Add pass 1a-uuid: after the uex_id-based upsert, issue a CTE query that finds
  any existing row with the same uuid but a different uex_id (UEX ID instability),
  updates that row's uex_id and all columns, then deletes the phantom row inserted
  by the uex_id pass — satisfies the ISSUE-198 DoD acceptance criterion
- Consolidate attribute validation (warn collection) into the main per-item loop
  to avoid a separate pass
- Add tests: transaction() called once per item; uuid-match query issued for
  items with a uuid, skipped for null uuid; uuid-match uses same 23 itemParams
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
The previous CTE approach (insert-then-detect) had two defects:
- Inserting the new uex_id row first created a phantom; the subsequent
  UPDATE ... SET uex_id=new_id on the canonical row then violated the
  UNIQUE constraint because both rows shared the same uex_id momentarily.
- Updating uex_id on the canonical row broke station_item_attribute.item_uex_id
  and station_item.parent_uex_id FKs (no ON UPDATE CASCADE in schema).

Replaced with a lookup-first strategy:
- SELECT for an existing row by uuid with a different uex_id before any INSERT.
- If found: re-key station_item_attribute.item_uex_id and station_item.parent_uex_id
  from old to new uex_id, then UPDATE the canonical row (uex_id + all columns).
  No INSERT is issued, so no phantom is ever created.
- If not found: normal ON CONFLICT (uex_id) DO UPDATE INSERT path.

All re-keying and the item update run inside the same per-item transaction.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comment thread backend/src/modules/catalog-etl/steps/items-sync.step.ts Outdated
The UUID reconciliation path must update station_item.uex_id on the canonical
row and re-key station_item_attribute.item_uex_id / station_item.parent_uex_id
within a single transaction. With immediate (non-deferrable) constraints,
PostgreSQL checks each FK on every statement — re-keying dependents before the
referenced uex_id exists, or updating the referenced uex_id while dependents
still hold the old value, both produce FK violations.

Migration 1780020000000 drops the auto-named inline FKs on
station_item_attribute.item_uex_id and station_item.parent_uex_id and
recreates them as DEFERRABLE INITIALLY DEFERRED. PostgreSQL then checks those
constraints at COMMIT rather than per-statement, making the multi-step re-key
sequence safe within a transaction.

Step updated to use the correct order: update canonical row first (so the new
uex_id exists in station_item), then re-key attribute and child-item rows.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread backend/src/migrations/1780020000000-MakeItemFksDeferrable.ts
The baseline non-unique idx_items_uuid on station_item.uuid contradicts the
uniqueness guarantee established by uq_station_item_uuid and would leave the
uuid reconciliation SELECT free to return an arbitrary row if a duplicate
slipped through the deduplication step. Drop it in the same migration so there
is exactly one index covering station_item.uuid and it is the partial unique one.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread backend/src/migrations/1780010901444-AddUniqueUuidToStationItem.ts
Migrations added since the initial three were never registered in the
AppDataSource migrations array, so pnpm migration:run would not apply them.
Adds all seven missing migrations in chronological order.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

@GitAddRemote GitAddRemote merged commit 76eb469 into main May 30, 2026
10 checks passed
@GitAddRemote GitAddRemote deleted the feature/ISSUE-198 branch May 30, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ETL: sync items, item attributes, and attributes_summary JSONB

2 participants