docs(12): capture phase context
This commit is contained in:
parent
5044c2de8a
commit
410652dedb
2 changed files with 282 additions and 0 deletions
|
|
@ -0,0 +1,198 @@
|
|||
# Phase 12: Orders/Invoices & Company Matching - Context
|
||||
|
||||
**Gathered:** 2026-07-11
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## 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).
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## 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).
|
||||
|
||||
</decisions>
|
||||
|
||||
<canonical_refs>
|
||||
## 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<T>()` 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
|
||||
|
||||
</canonical_refs>
|
||||
|
||||
<code_context>
|
||||
## 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/<name>-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
|
||||
|
||||
</code_context>
|
||||
|
||||
<specifics>
|
||||
## 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.
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## 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.)
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 12-orders-invoices-company-matching*
|
||||
*Context gathered: 2026-07-11*
|
||||
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue