From 410652dedb06cf6d42ed90fe375659b9fdab6c2e Mon Sep 17 00:00:00 2001 From: lorentz Date: Fri, 10 Jul 2026 21:35:33 -0400 Subject: [PATCH] docs(12): capture phase context --- .../12-CONTEXT.md | 198 ++++++++++++++++++ .../12-DISCUSSION-LOG.md | 84 ++++++++ 2 files changed, 282 insertions(+) create mode 100644 .planning/phases/12-orders-invoices-company-matching/12-CONTEXT.md create mode 100644 .planning/phases/12-orders-invoices-company-matching/12-DISCUSSION-LOG.md diff --git a/.planning/phases/12-orders-invoices-company-matching/12-CONTEXT.md b/.planning/phases/12-orders-invoices-company-matching/12-CONTEXT.md new file mode 100644 index 0000000..e6779e3 --- /dev/null +++ b/.planning/phases/12-orders-invoices-company-matching/12-CONTEXT.md @@ -0,0 +1,198 @@ +# Phase 12: Orders/Invoices & Company Matching - Context + +**Gathered:** 2026-07-11 +**Status:** Ready for planning + + +## Phase Boundary + +Pulse gains historical PAX8 cost data for reconciliation over time (order/invoice +line items into `pax8_orders`/`pax8_order_items`), and every PAX8 company is +automatically linked to its Autotask counterpart by fuzzy name matching, or +explicitly flagged for review rather than silently guessed. No `/pax8` UI (Phase +14 — this phase only populates `pax8_company_match_review`), no scheduler/cron +wiring (Phase 13), no manual-resolution admin flow (Phase 14 — PAX8-12). + + + + +## Implementation Decisions + +### Match Confidence Threshold +- **D-01:** Auto-link (no human review) only on a high-confidence fuzzy match + — not exact-string-only, but a high similarity bar (near-identical names: + punctuation/case/whitespace differences, minor typos). The user explicitly + chose "be conservative — fewer auto-matches" over a looser threshold or + deferring the number to the planner's judgment alone: bias toward flagging + borderline cases for review rather than risking a wrong auto-link, since + this data feeds cost/billing reconciliation. The planner should document + the exact numeric threshold chosen (and the library/approach) directly in + the plan so it's easy to find and tune later — this is an initial number, + not a permanently fixed one. +- **D-02:** Even an otherwise-exact name match must be flagged for review + (not auto-linked) if it's ambiguous against more than one Autotask company + sharing that same/very-similar name (e.g. franchise locations, "Acme Inc" + vs "Acme Holdings Inc"). No silent tie-breaking — ever, even when the + string match itself looks perfect. + +### No-Match & Ambiguous-Candidate Handling +- **D-03:** When a PAX8 company has zero reasonably-similar Autotask + candidates at all, still create a `pax8_company_match_review` row with an + empty `candidate_company_ids` array — never silently drop it (PAX8-11). + This flags it into the admin queue so Phase 14's UI can offer a manual + search/pick, or confirm there truly isn't a matching Autotask company yet. +- **D-04:** Ambiguous-match review rows carry the **top 3** highest-scoring + Autotask candidates (matches the existing `device_link_review` precedent's + style of showing a small ranked list, not every plausible match). +- **D-05:** PAX8 companies still sitting unresolved in the review queue are + **re-scored on every subsequent full sync**, not matched once and left + alone — candidates can improve over time (e.g. a renamed/newly-created + Autotask company scores better later) without requiring a manual + re-trigger. This does NOT apply to already-resolved matches: SC#4's + idempotency guarantee (a manually-confirmed match is never overwritten by + a later sync) still holds — only rows with `resolved_at IS NULL` are + eligible for re-scoring. + +### Claude's Discretion +- **Exact threshold value / library choice** — user wants "conservative," + not a specific number. The planner should research common fuzzy + name-matching approaches (e.g., trigram similarity via Postgres + `pg_trgm`, or a JS library) and pick/document a concrete high threshold, + erring toward fewer auto-matches per D-01. +- **Company name normalization nuances** (legal suffixes like LLC/Inc/Corp, + punctuation, abbreviations) — not discussed in depth this session (user + deselected this gray area). Planner/researcher should investigate whether + Autotask company names in this instance commonly carry legal suffixes + PAX8 names don't (or vice versa) and decide normalization rules + accordingly; err toward the conservative stance (D-01/D-02) if uncertain. +- **Orders/invoices historical lookback window** — not discussed in depth + this session (user deselected this gray area). `migrations/091`'s comment + states "sync pulls full order history on first sync in a later phase" + (this phase) — the planner should confirm this is still the intent (full + history, no bounded window) during research, and flag to the user if + PAX8's invoices API makes "full history" impractical (e.g., no + pagination limit safety, or a very large per-company invoice count). + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Project scope & requirements +- `.planning/PROJECT.md` — Current Milestone: v2.0 PAX8 Integration section +- `.planning/REQUIREMENTS.md` — PAX8-06, PAX8-10, PAX8-11 (this phase's + requirement IDs); note PAX8-12/13/14 (manual resolution, `/pax8` UI) are + Phase 14, not this phase +- `.planning/ROADMAP.md` — Phase 12 section (goal, 4 success criteria, + depends on Phase 11) + +### Prior phase foundation this phase builds on +- `migrations/091_pax8_tables.sql` — `pax8_orders` (header, Invoice-shaped + per its own inline comment: total/status/currency mirror PAX8's Invoice + object, sourced from PAX8's `/invoices` resource not `/orders` — confirm + exact field mapping against the live API during research), + `pax8_order_items` (line items, hard FK to `pax8_orders`, soft ref to + `pax8_products`), and `pax8_company_match_review` (copied field-for-field + from `device_link_review` — `candidate_company_ids BIGINT[]`, + `match_confidences TEXT[]`, partial unique index enforcing one open + review per PAX8 company). All three tables already exist — this phase + populates them, does not alter schema unless research finds a genuine gap + (e.g., no column yet exists on `pax8_companies` to record a confident + auto-match — the planner must decide where a non-reviewed, auto-linked + match gets stored) +- `.planning/phases/11-company-catalog-subscription-sync/11-SUMMARY.md` / + `11-02-SUMMARY.md` — `Pax8SyncService.fullSync` shape, soft-delete + tombstone pattern (`id <> ALL($1::uuid[])`), `Pax8EntitySyncResult`/ + `Pax8SyncResult` types this phase's new entity syncs should conform to +- `lib/services/pax8-client.ts` — read-only pagination helpers + (`listAllCompanies`/`listAllSubscriptions`/`listAllProducts`); this phase + needs an equivalent for invoices (`listAllInvoices` or similar) following + the same `paginateAll()` shape +- `lib/services/pax8-sync-service.ts` — existing `Pax8SyncService` class + this phase extends (or a sibling matching service) with orders/invoices + sync + company-matching logic + +### Existing patterns to follow +- `lib/services/device-link-reconciler.ts` — direct precedent for the + match/review/confidence pattern this phase implements for companies: + cascading match strategies ranked by confidence, `LINK_CONFIDENCE_RANK` + ordering, `device_link_review` candidate storage shape. D-04's "top 3" + and D-02's tie-flagging should follow this file's structure/conventions + closely. +- `migrations/080_device_xref_company_id.sql` — schema `device_link_review` + was copied from; useful for understanding the review-row lifecycle + (detected_at, resolved_at, resolved_by_user_id, resolved_to_company_id, + resolution_note) that `pax8_company_match_review` mirrors +- Soft-delete / tombstone convention (`is_deleted`, `deleted_at`, + `id <> ALL($1::uuid[])`) — established in Phase 11, applies to + `pax8_orders`/`pax8_order_items` too + + + + +## Existing Code Insights + +### Reusable Assets +- `lib/services/device-link-reconciler.ts` — confidence-ranked matching + logic to adapt for company name fuzzy matching (different matching + criteria — name similarity instead of serial/MAC/hostname — but same + candidate-ranking and review-row-write shape) +- `lib/services/pax8-sync-service.ts` — existing tombstone/upsert + primitives from Phase 11 to extend for orders/order_items +- `postgresClient` singleton — parameterized queries, no ORM + +### Established Patterns +- Sync services live at `lib/services/-sync-service.ts`; this phase + likely extends the existing `pax8-sync-service.ts` rather than creating a + new file, since it's the same integration's sync orchestration +- `*_match_review` tables follow a detect → flag → admin-resolves lifecycle; + `resolved_at`/`resolved_to_company_id` gate re-scoring per D-05 + +### Integration Points +- Extends `lib/services/pax8-sync-service.ts` (or a new + `pax8-matching-service.ts` — planner's call) with orders/invoices sync + + company matching, invoked from the same `fullSync()` orchestration Phase + 11 built +- No route/nav/UI integration in this phase (`UI hint: no` per ROADMAP.md) + — `pax8_company_match_review` rows are written here, consumed by Phase + 14's UI + + + + +## Specific Ideas + +No specific UI or behavioral references beyond the discussed decisions. +The five numbered decisions above (D-01 through D-05) are the concrete +specifics: conservative high-confidence auto-match threshold (tune-able, +not a fixed permanent number), always-flag-on-any-tie even for exact +string matches, empty-candidate-list flagging for true no-match cases, +top-3 candidate cap for ambiguous cases, and continuous re-scoring of +unresolved (not yet human-confirmed) review rows on every sync. + + + + +## Deferred Ideas + +Two gray areas were surfaced but not discussed this session (user chose to +focus on match confidence and no-match/ambiguous handling instead) — not +deferred to a future phase, just left as Claude's Discretion within this +phase (see above): +- Company name normalization nuances (legal suffixes, punctuation, + abbreviations) +- Orders/invoices historical lookback window (full history vs. bounded) + +No capabilities were deferred outside this phase's boundary — discussion +stayed within Phase 12's scope. (The `/pax8` UI, manual resolution flow, and +scheduler cron wiring are already sequenced into Phases 13-14 per +ROADMAP.md/REQUIREMENTS.md, not deferred from this discussion.) + + + +--- + +*Phase: 12-orders-invoices-company-matching* +*Context gathered: 2026-07-11* diff --git a/.planning/phases/12-orders-invoices-company-matching/12-DISCUSSION-LOG.md b/.planning/phases/12-orders-invoices-company-matching/12-DISCUSSION-LOG.md new file mode 100644 index 0000000..ba47eaa --- /dev/null +++ b/.planning/phases/12-orders-invoices-company-matching/12-DISCUSSION-LOG.md @@ -0,0 +1,84 @@ +# Phase 12: Orders/Invoices & Company Matching - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-07-11 +**Phase:** 12-orders-invoices-company-matching +**Areas discussed:** Match confidence threshold, No-match & ambiguous-candidate handling + +--- + +## Match Confidence Threshold + +| Option | Description | Selected | +|--------|-------------|----------| +| Exact name match only | Only auto-link on byte-identical names (case/whitespace-insensitive). Safest but likely means most companies land in review. | | +| High-confidence fuzzy match | Auto-link when similarity clears a high bar (e.g. ≥90-95%), covering near-identical names. | ✓ | +| You decide | Planner picks a threshold based on research. | | + +**User's choice:** High-confidence fuzzy match + +**Follow-up: threshold tuning approach** + +| Option | Description | Selected | +|--------|-------------|----------| +| Set an initial number, tune later | Planner picks ~90-95% as a starting point, documented for later adjustment. | | +| Be conservative — fewer auto-matches | Bias toward flagging borderline cases; accept more manual review since this feeds cost/billing reconciliation. | ✓ | +| You decide | No strong preference. | | + +**User's choice:** Be conservative — fewer auto-matches +**Notes:** User explicitly prioritized avoiding wrong auto-links over reducing review burden, since this data feeds cost/billing reconciliation. + +**Follow-up: duplicate/tie edge case** + +| Option | Description | Selected | +|--------|-------------|----------| +| Yes — always flag on any tie | Even an exact name match gets flagged if ambiguous against multiple same/similar-named Autotask companies. | ✓ | +| No — exact match always wins | Exact string equality is strong enough signal on its own, even with a second similarly-named company present. | | + +**User's choice:** Yes — always flag on any tie +**Notes:** Reinforces the conservative stance — never silently pick between look-alike companies (e.g. franchise locations). + +--- + +## No-Match & Ambiguous-Candidate Handling + +| Option | Description | Selected | +|--------|-------------|----------| +| Flag with an empty candidate list | Still create a review row with an empty candidate array for true no-match cases, so it surfaces in the admin queue. | ✓ | +| You decide | Planner decides mechanics as long as PAX8-11 (never silently dropped) is satisfied. | | + +**User's choice:** Flag with an empty candidate list + +**Follow-up: ambiguous-candidate count** + +| Option | Description | Selected | +|--------|-------------|----------| +| Top 3 | Cap review candidates at the 3 highest-scoring Autotask companies, matching `device_link_review` precedent. | ✓ | +| All above a "plausible" floor | Show every candidate clearing a low relevance floor, regardless of count. | | +| You decide | Planner picks a reasonable cap. | | + +**User's choice:** Top 3 + +**Follow-up: re-scoring cadence for unresolved reviews** + +| Option | Description | Selected | +|--------|-------------|----------| +| Re-score every sync until resolved | Keep re-evaluating unresolved PAX8 companies each full sync; resolved matches still never overwritten. | ✓ | +| Match once, don't re-touch until resolved | Leave flagged companies alone until an admin resolves them — simpler, stable candidate list between admin visits. | | + +**User's choice:** Re-score every sync until resolved +**Notes:** Candidates can improve over time (e.g. a renamed/newly-created Autotask company scores better later) without requiring a manual re-trigger. Does not affect SC#4's idempotency guarantee for already-resolved matches. + +--- + +## Claude's Discretion + +- Exact numeric fuzzy-match threshold and matching library/approach (e.g. Postgres `pg_trgm` vs. a JS similarity library) — user wants "conservative," not a specific number; planner researches and documents the chosen value. +- Company name normalization nuances (legal suffixes like LLC/Inc/Corp, punctuation, abbreviations) — gray area was surfaced but not selected for discussion this session. +- Orders/invoices historical lookback window (full history vs. bounded window) — gray area was surfaced but not selected for discussion this session; `migrations/091`'s inline comment indicates full history was the original intent, to be confirmed during research. + +## Deferred Ideas + +None — discussion stayed within Phase 12's scope. No new capabilities were proposed; the two undiscussed gray areas above were left to Claude's discretion within this phase, not deferred to a future phase.