mattrguerra/meridian-outreach-vaultmainPrivate on GitHub, published here in full

Weekly-Partner-Pipeline-SOP.md

Language Markdown23.5 KB338 lines

07-Automation/Weekly-Partner-Pipeline-SOP.md

  1. ---
  2. type: workflow-sop
  3. scope: 07-Automation
  4. tags: [claude, outreach, automation, workflow]
  5. created: 2026-07-22
  6. last_modified: 2026-09-21
  7. ---
  8. # Weekly Partner Client Pipeline: SOP
  9. The end-to-end weekly refresh that turns live Plinth order data into a partner client
  10. classification, a spreadsheet, updated stubs + XML map, enrichment, and (on approval)
  11. Bitrix deals. This note is both the human procedure and the script the scheduled task
  12. runs. Config lives in [[partner-thresholds]]; the deterministic work lives in
  13. `weekly_partner_pipeline.py`; the scheduler definition is in [[scheduled-task]].
  14. > **Current autonomy: `through_vault`** (enabled 2026-07-23). The unattended Monday 06:00
  15. > America/Chicago run goes all the way through the vault on its own (delta pull, classify,
  16. > verify, **write and upgrade stubs, and refresh the XML census**), then reads Bitrix for
  17. > the reconcile, route and outreach-audit stages. It does **not** enrich, write to Bitrix,
  18. > or route anyone into an outreach pipeline; those stay human-gated. Change `autonomy` in
  19. > [[partner-thresholds]] to widen or narrow this.
  20. > **The pipeline is ARMED.** `min_orders_to_qualify` was the arming switch and is now set
  21. > (`1`), and `mode: ltv_bands` carries the live program bands, so the write stages fire. If
  22. > any threshold in the *active* mode is ever set back to `TBD`, stages A to D still run
  23. > read-only and everything from E onward refuses.
  24. > **The scheduler runs from a file outside this vault.** [[scheduled-task]] is a *copy*.
  25. > The prompt that actually fires lives at
  26. > `~/Documents/Claude/Scheduled/weekly-partner-client-pipeline/SKILL.md`, which no session
  27. > can read or verify, and which has silently drifted from the vault copy before. Any change
  28. > to paths or stage order here must be pasted into that file by hand, or Monday's run and
  29. > this SOP will disagree. Tell: if the Monday summary reports one spreadsheet instead of
  30. > two, or the audit Summary sheet says "INCOMPLETE RUN", the external file has drifted.
  31. > **Two spreadsheets per run (2026-08-10).** The weekly run now produces
  32. > **(1)** `Complete-Partner-Clients-List_updated-<date>.xlsx`, the Drive-swap partner
  33. > roster (Stage F2), and **(2)** `Bitrix_Pipeline_Outreach_Audit_<date>.xlsx`, one sheet
  34. > per outreach funnel showing who is in it, how to find them in Plinth, and when they were
  35. > last actually contacted (Stage F5). (2) resolves Plinth identity out of (1), so **F5 must
  36. > run after F2.** They are otherwise independent; neither overwrites the other.
  37. ---
  38. ## The chain at a glance
  39. ```
  40. A0 Baseline seed ONE TIME: Ines's roster workbook -> order_history.json (seed-baseline)
  41. A Delta pull orders.fetch, trailing pull_window_days, DAY-sized slices,
  42. offset+dedupe until a page adds 0 new ids -> ingest ─┐
  43. B Compute lifetime = baseline + post-baseline history (local) │ deterministic
  44. C Classify apply partner-thresholds.md │ (weekly_partner_pipeline.py)
  45. D Export xlsx 05-Outputs/partner-runs/… + verify (no-shrink guardrail) ─┘
  46. │ ── autonomy=through_vault → the unattended run continues from here ──
  47. E Diff + write create missing stubs / upgrade existing (+ last_modified)
  48. F XML refresh partner-client-master-list.xml (+ <last_modified>)
  49. F2 Workbook refresh Complete-Partner-Clients-List_updated-<date>.xlsx (Drive-swap copy)
  50. F3 Deal reconcile pull all deals in cats 24/26/36 → reconcile-deals → census blocks
  51. F4 Route route → weekly candidate lists (winback import CSV + cat24/dormant)
  52. F5 Outreach audit outreach-pull-list → pull stage history for what changed →
  53. outreach-audit → Bitrix_Pipeline_Outreach_Audit_<date>.xlsx
  54. G Enrich client-enrich skill on new/changed stubs
  55. H Push partner-pipeline-push skill → Bitrix deals (human-approved)
  56. │ ── every stage that changes the vault → append one LEDGER entry ──
  57. ```
  58. ---
  59. ## Why incremental (read this before "improving" Stage A)
  60. **Plinth `orders.fetch` silently truncates large result sets**: roughly 70 orders per
  61. response, no "has more" flag, and `offset` does not skip by order rows, so pages overlap.
  62. Verified 2026-07-22 on one long-standing account (Plinth customer 40115 in the sample's
  63. numbering): one all-time call returned 70 of its 74 orders and dropped the newest ones,
  64. which understated LTV **and** can make an active client look like a Winback candidate. Clients with the
  65. longest histories (exactly the Rhodium/Iridium segment) are the ones the truncation hits.
  66. Full story in [[DECISIONS]] (2026-07-22). The shipped sample reproduces the failure on
  67. 2026-09-17: the first page for that day stops at 50 rows, and only the offset loop finds
  68. the rest. Consequences:
  69. - **Never** ask the API for an all-time total. Lifetime value lives in the local history
  70. (`07-Automation/data/order_history.json`) = Ines's roster baseline + ingested deltas.
  71. - **Never** trust one response to be complete. Page with offset + dedupe until a page
  72. adds zero new ids (`ingest` prints `added=N`; stop at 0).
  73. - **Never** demote a tier or route to Winback when a lifetime total *decreased*: history
  74. only grows; a decrease means data loss. `verify` (exit 2) catches this.
  75. ## Stage A0: Baseline seed *(one time, before first run)*
  76. `seed-baseline --sheet <Ines's partner roster workbook> --tab <tab> --as-of <sheet cutoff date>`
  77. (dry-run first; see the [[Activation-Runbook]] and the activation checklist in
  78. [[partner-thresholds]]). Two passes: the **All Accounts** tab, then the **Complete Partner
  79. Clients List** tab with `--force`. Per-customer lifetime aggregates keyed by Plinth
  80. customer ID. The weekly delta counts only orders **after** `as_of`, so it must match the
  81. sheet's actual cutoff.
  82. ## Stage A: Weekly delta pull from Plinth
  83. - Window: trailing `pull_window_days` (default 8: one day of overlap; dedupe makes
  84. overlap free).
  85. - Slice by **single day** (`since` = `until`, `yyyymmdd`), `limit` = `page_size`
  86. (**hard cap 100**: the API rejects more).
  87. - Per slice: pull → save raw JSON page → `ingest --orders <page>` → if `added > 0` OR
  88. the page had ≥ `suspect_row_count` rows, pull again with a higher `offset` and repeat
  89. **until `added=0`**. Offsets overlap; the dedupe on `order_ref` absorbs it.
  90. - Plinth caps at **10 requests / 10 seconds** and throttles (slows, does not error). A 6 AM
  91. unattended run does not care.
  92. - Read-only. No identity exposure beyond what Matt already owns.
  93. ## Stage B: Compute lifetime metrics (local, no API)
  94. Per customer, from the history store:
  95. - `ltv`: baseline LTV + sum of `amount_field` (`grand_total`) over **counted**
  96. post-baseline orders.
  97. - `total_orders_count`: baseline count + counted post-baseline orders.
  98. - `repeat_revenue`: baseline repeat + post-baseline counted spend.
  99. - `first_order_date` / `last_order_date`: baseline vs ingested, whichever bounds.
  100. - Orders dated **on or before** `as_of` never re-count (already inside the baseline).
  101. - Undated orders (not yet finished) count as recent.
  102. - Customers seen in a delta with **no baseline row** compute from ingested orders alone
  103. and surface with `has_baseline=False`; review these, and backfill via windowed pulls if
  104. one looks like a long-tenured client.
  105. - **Approved merges fold here (2026-08-06).** `data/merge_registry.json` maps absorbed
  106. Plinth accounts to their surviving account. After per-account metrics are computed, each
  107. absorbed account is folded into its survivor (sums; date bounds), so classification
  108. sees one combined client. Absorbed accounts never classify, never get stubs, never
  109. enter the XML census, and `refresh-xml` removes an absorbed `pl-<id>` chunk if one
  110. exists (self-healing). Entries are added ONLY on Matt's explicit approval, one LEDGER
  111. entry each; see [[DECISIONS]] 2026-08-06. A missing or malformed registry warns and
  112. runs unmerged; it never halts the 06:00 run.
  113. ## Stage C: Classify
  114. Apply the active `mode` from [[partner-thresholds]]:
  115. - `ltv_bands` → tier by lifetime spend band.
  116. - `multi_factor` → weighted score, then band.
  117. - `simple_gate` → single cutoff, partner client vs. not.
  118. Customers below `min_orders_to_qualify` or the lowest band are marked **Not a partner client**.
  119. A mailbox listed under `tier_overrides.emails` takes its override tier outright, before the
  120. bands and before `min_orders_to_qualify` (the per-email relationship override, 2026-08-12;
  121. scope is deliberately per-email, not per-domain).
  122. If thresholds are `TBD`, tier = `UNCLASSIFIED: thresholds not set` and the run is flagged
  123. read-only.
  124. ## Stage D: Export spreadsheet + verify
  125. - Written to `05-Outputs/partner-runs/partner-classification_<YYYY-MM-DD>.xlsx`.
  126. - One row per customer: identity, computed metrics, assigned tier, store group (for
  127. reconciliation), `has_baseline` / `post_baseline_orders`, and `stub_status`
  128. (new / exists / exists-changed).
  129. - A `Summary` sheet: counts per tier, new vs. existing, baseline `as_of`, last ingest,
  130. and whether the run was read-only or armed.
  131. - **Then run `verify`: mandatory.** It compares this run against the previous one and
  132. exits 2 if any customer's lifetime total or order count shrank. On exit 2 the run
  133. STOPS regardless of autonomy: report the flagged customers to Matt, change nothing.
  134. - `autonomy: through_vault` → the unattended run continues past this point into E/F/F2 and
  135. the read-only Bitrix stages F3 to F5. Only G (enrich) and H (push) wait for a human.
  136. ## Stage E: Diff + write stubs *(human-approved)*
  137. - Match each classified **partner client** against `03-Clients/client-stubs/*.md` by email,
  138. then by `first_name + last_name`. All stubs live flat in `client-stubs/`.
  139. - **Missing** → create a stub via `_create_stub()` in the driver, which is the canonical
  140. shape. `_templates/client-stub.md` was rebuilt 2026-08-12 to mirror that function exactly,
  141. so the template and the driver now agree; build any hand-made stub from the template.
  142. `## Research` starts `[TBD]`; `## Human Notes` empty; `last_modified` set to run date.
  143. - **Exists** → do **not** touch `## Human Notes` or `## Research`. Update computed
  144. frontmatter (ltv, repeat_revenue, order counts, dates, user_group_correct) only where it
  145. changed, and set/refresh `last_modified`. Record the client as `exists-changed`.
  146. - Follow [[CLAUDE-clients]]: the scraping-dump contract still governs. Note the retired
  147. fields listed there (`enrichment_status`, `email_one_status`, `email_two_status`,
  148. `outreach_type`, `seniority`, `total_orders_usd`): do not reintroduce them.
  149. ## Stage F: Refresh the XML map *(human-approved)*
  150. - `03-Clients/partner-client-master-list.xml` (**schema 3.0**, root
  151. `<partner_client_master_list>`; renamed from `crm_pipeline_mapping.xml`). Back up first
  152. to `05-Outputs/partner-runs/`.
  153. - Every `<client>` carries both a `<bitrix_id>` (CRM contact id) and a `<plinth_id>`
  154. (Plinth backend user id). A blank `<bitrix_id />` means the client isn't in the CRM yet.
  155. - **Full partner coverage:** the map is a complete census of the partner base. Every
  156. classified partner client (Palladium and up) must have a `<client>` chunk, whether or
  157. not they sit in a pipeline. `refresh-xml` does four things each run:
  158. 1. **Stamp** `<last_modified>=TODAY` on every partner client already in the map
  159. (pipeline-mapped Bitrix chunks and previously-added map-only chunks alike).
  160. 2. **Add** a chunk for every partner client missing from the map: identity from the
  161. stub, an auto-generated `<summary>`, a stub pointer, and an **empty pipeline section
  162. `<pipelines count="0" />`** to be populated when they enter a Bitrix pipeline.
  163. 3. **Key** each added chunk `id="pl-<Plinth user_id>"` (stable, collision-proof vs Bitrix
  164. ids). Same-person duplicate Plinth accounts (same name + email) share one chunk;
  165. colleagues on one company mailbox each get their own.
  166. 4. **Remove** any chunk whose `pl-<id>` is an absorbed account in
  167. `data/merge_registry.json`: a merged client is one chunk, keyed by the survivor.
  168. - Idempotent: re-running only re-stamps; it never duplicates a chunk. `<summary>` is
  169. auto-generated; never hand-edit it. Non-partner customers are not auto-added.
  170. ## Stage F2: Refresh the Drive-swap workbook
  171. `refresh-workbook --plan <plan.json>` regenerates the manual Google-Drive-swap workbook
  172. `05-Outputs/partner-runs/Complete-Partner-Clients-List_updated-<date>.xlsx` so it matches
  173. the run. The vault copy is **not** connected to Google Drive (Matt uploads it by hand), so
  174. each run carries the human-maintained columns forward from the **previous** generated copy
  175. (the base; `--base` overrides, else the latest `…_updated-*.xlsx` in `05-Outputs/partner-runs/`).
  176. On the **Complete Partner Clients List** tab it refreshes only the Plinth-derived columns
  177. (User Group, the dated `Plinth LTV MM/DD/YY` column with its header renamed to the run date,
  178. Total Spent (Orders), # Orders, Repeat Revenue (Plinth), First/Last Order) matched by `Id`
  179. (Plinth user_id), and **appends** newly-qualified partner clients not already present (new
  180. rows get their computed tier in `Correct Group`). It rebuilds the **All Accounts** base to
  181. every current customer. It **never** writes a human-maintained column on an existing row
  182. (Correct Group, Comments, Contacted, contact dates, Times/Total Pipeline, Current Stage(s),
  183. Pipeline Owner/Rep) and leaves the curated **2026 Partner Working List** tab
  184. byte-identical. **Formatting matches the master exactly:** appended rows and the rebuilt
  185. All Accounts tab clone the master's per-column style (currency `"$"#,##0.00`, `mm/dd/yyyy`
  186. dates, Arial), and Plinth cells on existing rows are normalized to it too, so a diff against
  187. the master shows only real data changes, never formatting drift. Human-column styles are
  188. left alone. Read-only on the base; writes a new dated file (never overwrites the base).
  189. ## Stage F3: Deal reconcile *(2026-08-06)*
  190. Claude pulls every deal in cats 24/26/36 (per-stage `list_records`, date-window pagination
  191. past 50) into a deals JSON, then `reconcile-deals --deals <json>` absorbs it into the
  192. census: one block per (client, funnel) recording the newest deal, statuses
  193. active/completed/**lost**, summaries + stub `outreach_pipelines` mirrored, backup first.
  194. Name-matched; unmatched/ambiguous deals are reported, never guessed. Idempotent.
  195. ## Stage F4: Route *(2026-08-06)*
  196. `route` evaluates [[pipeline-routing-rules]] read-only and writes the weekly candidate
  197. artifacts to `05-Outputs/partner-runs/`: routing-candidates_<date>.json, the Winback
  198. import CSV (person-named; role-named to a review CSV), with cat24/dormant lists in the
  199. JSON. **Suppression is absolute:** `data/suppression_list.json` (names, emails and whole
  200. never-contact domains) + any Not-Interested closure → excluded from everything, with the
  201. evidence unioned across identity twins. **Deal creation stays human-gated**: the run emits
  202. lists; loading them into Bitrix is done by a human (or an explicitly instructed session).
  203. ## Stage F5: Outreach audit *(2026-08-10)*
  204. Produces the second weekly spreadsheet: `05-Outputs/Bitrix_Pipeline_Outreach_Audit_<date>.xlsx`,
  205. one sheet per outreach funnel: **24** Partner Tier Upgrade · **26** Winback ·
  206. **36** Quarterly Check-In · **16** Abandoned Carts. **Those four and only those four**
  207. (scope set by Matt 2026-08-10; 7 Days Delivery Up-Keep and Cold Outreach are deliberately
  208. out of scope. `AUDIT_FUNNELS` in the driver is the single place that defines this, and
  209. adding to it is a decision, not a tweak). Each row is a client with the details Ines needs
  210. to find them in Plinth (Plinth ID, email, phone, company, tier, LTV) plus **the date they
  211. were last actually reached out to**. Read-only on Bitrix and on vault data.
  212. **Two passes, because the full sweep is too expensive to repeat weekly.**
  213. 1. `outreach-pull-list --deals <enumeration.json>` reads the weekly deal enumeration and
  214. `data/outreach_touch_cache.json` and writes `outreach-pull-list_<date>.json` with
  215. `need_history` (deals to re-pull) and `need_contact` (deals needing a `get_record`
  216. contact lookup). A deal is served from cache only when its stage is **unchanged** since
  217. the cache was written AND either (a) that stage is terminal, or (b) the cached touch count
  218. is **zero**: it has never entered a send stage, so it cannot be sitting in one, and a send
  219. would have moved it. (b) is what keeps the big parked cohorts cheap. Deals *with* touches
  220. sitting in a send stage are always re-pulled, because a repeat send re-enters the same
  221. stage (cat 26 Follow-Up Email does exactly this). **Measured 2026-08-10** (four funnels):
  222. with the cache warm, the next run needed **a fraction of the first full run's history
  223. pulls and contact lookups**. The sample shows the same shape: 54 histories fresh and 9
  224. contact lookups on 2026-09-14, then 18 fresh, 36 from cache and 2 lookups on 2026-09-21.
  225. 2. Claude pulls `get_stage_history` for the `need_history` ids and `get_record` for the
  226. `need_contact` ids, attaches them to the enumeration, then `outreach-audit --deals
  227. <that file>` computes the touches, resolves Plinth identity, writes the workbook, and
  228. updates the cache.
  229. **What counts as contact.** Only stage entries with a real outbound email, call or LinkedIn
  230. DM behind them: the per-funnel `SEND_TOKENS` sets in `partner_pipeline/stages.py`. System
  231. reactions (No - Reply, No Reply Nth Email (Auto), Email Defunct, Changed Companies, Not
  232. Interested, Completed) are **not** contact. One trap is encoded there and must not be
  233. "simplified": **cat 26 `FINAL_INVOICE` is called "Pre-Call Email" and IS a real send**,
  234. while an out-of-scope funnel uses that identical label for its creation stage, which sends
  235. nothing. Never key the rule on the stage *name*. Cat 26 `UC_L2JMHY` ("Activated") is left
  236. out on purpose: it is the intake bucket, and nothing is sent on that move.
  237. **Retired stages.** Six stage ids were deleted from their funnel config, so Bitrix returns a
  238. bare token with no name and a naive audit silently drops them. They were identified on
  239. 2026-08-10 by matching each stage-entry timestamp against the deal's outbound email timeline
  240. (see [[DECISIONS]]): cat 24 `PREPARATION` / `UC_CEAGAV` / `UC_V6JARB` are sends,
  241. `UC_N4JCMW` is a bulk move that is not; cat 26 `UC_PMLDJW` / `UC_GDHDJR` are the Feb 2026
  242. feedback-survey send + reminder. `RETIRED_STAGE_NAMES` keeps them named. **Do not delete
  243. them**: without them clients reached only through those stages read as never contacted.
  244. **Touch count is best-effort, the date is not.** Same-minute workflow re-entries into one
  245. stage collapse to a single send (`SAME_SEND_GAP_MIN`, 2 minutes); a bulk re-stage days apart
  246. still counts. Read "Last reached out" as reliable and "Touches" as indicative. A last contact older
  247. than `AUDIT_STALE_DAYS` (90) is shaded red on the sheet and counted on the Summary.
  248. **Enumerating the big parked stages: solved 2026-08-11.** A whole-day `created_on` filter
  249. truncates at 200 rows, which used to leave `C26:UC_JUUNES` ("Client") and `C16:NEW`
  250. ("New Abandoned Cart"), both larger than that, partially listed (measured 2026-08-10).
  251. Sub-day time bounds **do** work; they are not ignored. The bounds resolve against Bitrix's
  252. own server offset, so pass `tz=Europe/Moscow` alongside the CDT wall-clock window and walk
  253. narrow windows; a 15-minute window returns a complete, untruncated set (the connector
  254. reports "showing N of N"). The identical call **without** `tz` returns "No matching deals". **Always verify
  255. coverage** (the union of your windows must equal the stage's reported total) rather than
  256. trusting a single call. The recipe is verified; the mechanism inside the connector is not.
  257. **Gaps are recorded, not hidden.** The enumeration JSON still carries an `enumeration_gaps`
  258. block. If a whole funnel goes missing the run warns loudly and stamps "INCOMPLETE RUN" on the
  259. Summary sheet rather than shipping a clean-looking partial workbook.
  260. **Anyone the audit cannot place in Plinth** lands in `outreach-unresolved_<date>.json`, and
  261. the next `outreach-pull-list` adds them to `need_contact` so one `get_record` closes the gap
  262. permanently: the contact email persists in the cache from then on.
  263. ## Stage G: Enrich *(human-approved)*
  264. - Run the **`client-enrich`** skill on the new + changed stubs. It appends only under
  265. `## Research`, sources every claim, and logs its own LEDGER entry. Needs Apollo + Apify
  266. connected.
  267. ## Stage H: Push to Bitrix *(human-approved, always)*
  268. - Run the **`partner-pipeline-push`** skill on the approved cohort. One deal per client,
  269. deal name = `first_name + " " + last_name`, tagged by User Group, enrichment profile
  270. posted as a deal comment. Needs Bitrix connected. **Never unattended.**
  271. ---
  272. ## Guardrails (non-negotiable)
  273. 1. **Shared vault → LEDGER.** Any stage that creates/edits/moves a vault file appends one
  274. batched entry to [[LEDGER]], attributed by session email (`hello@meridiandisplay.example`
  275. = Matt, `accounts@meridiandisplay.example` = Hazel), timestamp from the system clock.
  276. See [[CLAUDE]].
  277. 2. **Human Notes are sacred.** Automation never writes `## Human Notes`. `## Research` is
  278. only ever touched by `client-enrich`, never by the classifier.
  279. 3. **Facts are sourced.** Enrichment claims carry a source URL or a `[TBD]` marker.
  280. 4. **PII stays put.** Plinth returns live customer PII. It goes into the spreadsheet and
  281. stubs, not into chat summaries or anywhere external.
  282. 5. **Bitrix is human-gated** regardless of `autonomy`.
  283. 6. **The outreach audit is read-only on Bitrix.** Stage F5 reads stage history and deal
  284. records and writes nothing back. Reading Bitrix is not the same as routing into it:
  285. the hold in [[pipeline-routing-rules]] is on *creating deals and moving stages*, which
  286. stays human-gated. Never let the audit "fix" a stage it thinks is wrong.
  287. 7. **No-shrink.** Lifetime history only grows. If `verify` exits 2, the run stops and a
  288. human looks at it. No tier demotion, no Winback routing, no stub downgrades off a run
  289. whose totals went backwards: that is the Plinth truncation bug's signature, not reality.
  290. ## Run log
  291. This table was never kept up. [[LEDGER]] is the reliable per-run record. The rows up to
  292. 2026-08-10 record which runs happened on the full book; their figures are not carried into
  293. this repo. The last two rows are the
  294. shipped sample's replay (`07-Automation/sample/run_sample.py`), with the real numbers from
  295. `05-Outputs/partner-runs/run-log_2026-09-14.txt` and `run-log_2026-09-21.txt`.
  296. | Run date | Mode | Armed? | Partner Clients | New stubs | Changed | Pushed | Notes |
  297. |----------|------|--------|----------|-----------|---------|--------|-------|
  298. | 2026-07-23 | ltv_bands | yes | n/a | n/a | n/a | no | Partner backfill: the run that grew the vault from the founding cohorts to the whole partner base |
  299. | 2026-07-27 | ltv_bands | yes | [TBD] | [TBD] | [TBD] | no | Artifacts existed; figures not recorded |
  300. | 2026-08-03 | ltv_bands | yes | n/a | n/a | n/a | no | verify OK |
  301. | 2026-08-10 | ltv_bands | yes | [TBD] | [TBD] | [TBD] | no | First run producing both spreadsheets (F2 + F5 audit) |
  302. | 2026-09-14 | ltv_bands | yes | 128 | 128 | 0 | no | Sample, first run after the 2026-09-07 seed (381 accounts). 29 orders added; verify OK; tiers Palladium 85 · Platinum 28 · Iridium 8 · Rhodium 7; 1 FLAGGED messy name; XML 128 chunks added; reconcile 45 new blocks; route winback 50 (1 role-named held) / cat24 29 / dormant 4 / excluded 45; audit 54 deals, 26 touched, 3 unresolved, 54 histories fresh |
  303. | 2026-09-21 | ltv_bands | yes | 130 | 2 | 128 upgraded | no | Sample. 93 orders added; 2026-09-17 truncated at 50 and paged to convergence (+50, +19, +0); 1 new customer (has_baseline=False); verify OK; tiers Palladium 86 · Platinum 29 · Iridium 8 · Rhodium 7; XML 128 stamped + 2 added = 130; reconcile 2 updated, 43 current; route winback 50 / cat24 31 / dormant 4 / excluded 45; audit 54 deals, 28 touched, 18 histories fresh, 36 from cache |