The fourth pillar (alongside SO / PO / WO)
NetSuite holds locked annual prices. Bid season runs all year between annual rolls. The Bid Center is the system that captures customer-specific quotes, external bids, regional pricing grids, and program allowances as they evolve through the year — then converts that work into next year's NS named price lists on a single annual flip (July 1).
Three intake paths converge on a standardized 11-skill review: Path 1 email intake at bids@ai-globalfoodsolutions.co; Path 2 direct chat (Mike kicks off); Path 3 July 1 rollover (annual flip). The 11 skills cover everything from onboarding a new customer to comparing quote versions to logging an external bid. Output writes to 8 new D1 bid_* tables (migration 136 in flight, Agent M owns), regenerates per-customer .docx quotes, refreshes STATUS.json, and re-renders the live dashboard at gfs-pricing.pages.dev.
The Bid Center reads commercial items (127 SKUs × 8 brands), commercial regional prices (4 regions: Northeast / Southeast / Midwest / West Coast), 14 USDA commodity items (3 sections), per-customer JSON for 69 customers, and a pipeline log of 44 external bids (30 submitted, 3 pending). The trace thread is customer + SY-version — analogous to the SO PO# thread.
Diagram: ns-bid-center-master.html. Path detail docs: Path 1 · Path 2 · Path 3.
Trigger conditions
- Customer or vendor emails a bid / quote update to
bids@ai-globalfoodsolutions.co(Path 1). - Mike opens chat and pastes a PDF or names a customer for re-quote (Path 2).
- July 1 cron fires (Path 3) OR Mike manually triggers
POST /api/bid/rollover/startfor a dry run. - An external RFP arrives that needs logging into the 44-bid pipeline.
- Mid-year program update (HPS MAP GPO allowance change for SY2526 or SY2627).
Bid-season (rolling): Path 1 + Path 2 run continuously through the year, updating D1 + .docx + dashboard. NO NS writes happen here. July 1 rollover (annual flip): Path 3 once per year converts the latest SY-version per customer into NS named price lists via batch HITL + NS_PUSH_QUEUE.
Path 3 is the only path that writes to NS. ADR-031 invariant: every NS push goes through proposed_actions with explicit Mike approval. The 69 customer named price lists are staged as a single batch_id (e.g. jul1-2026) so Mike can approve the cohort in one action.
ACC Distributors' SY2627 update cycle
April 14. Cheddar cost moved 8% in the last two weeks. ACC Distributors emails bids@ai-globalfoodsolutions.co asking for a fresh quote on their school-year contract.
Path 1 fires: src/email.ts parses the email, document_converter normalizes the attached PDF, name_synonyms.json resolves "ACC Dist" -> "ACC Distributors", and a proposed_actions row stages "New bid inbound - customer match ACC Distributors (conf 0.94) - run review?" Mike opens admin-dashboard, taps Approve. Chat session opens with the bid PDF + ACC's customer.json + latest bid_quote_versions row (SY2627-v2.4) already in context.
The ADD_UPDATE_CUSTOMER_PRICING skill runs: bumps to SY2627-v2.5, snapshots the 47 affected line items, applies the HPS MAP GPO allowance (SY2627 program), writes 8 D1 tables, regenerates quotes/SY2627-v2.5.docx, refreshes STATUS.json. The dashboard at gfs-pricing.pages.dev now shows ACC's freshness flipped to "today, v2.5."
No NS write yet. The new pricing sits in the Bid Center until July 1.
July 1 at 05:00 UTC: the cron fires prepare_next_school_year. It enumerates all 69 active customers, picks each customer's latest SY-version (ACC's is SY2627-v2.5), applies programs, and stages a single batch proposed_action with batch_id='jul1-2026' and 69 line items. Mike sees: "69 named price lists ready to write back to NS - approve?" He scans, sees nothing weird, taps Approve All.
NS_PUSH_QUEUE drains: PushMutexDO per customer prevents collisions, the NS RESTlet writes each PriceList record, and customer.pricelevel on each NS customer record is pointed at the new list. Three customers fail (a transient NS RESTlet timeout); they re-stage in the partial-failure UI and Mike re-approves the subset, which drains successfully. events.bid.rollover_completed fires.
July 2 morning: ACC's CS rep opens the customer in NS and sees the new ACC SY2627 v2.5 named price list attached. The first SO entered for ACC that week reads the new pricing.
Intake → 11-skill review → outputs → ★ Jul 1 rollover
- 01
Intake (one of 3 paths)
Path 1 email at
bids@; Path 2 chat; Path 3 Jul 1 cron. - 02
Normalize customer + SY-version
The trace thread (e.g.
ACC Distributors / SY2627-v2.5) is established here and carried through every downstream row. - 03
Dispatch one of 11 skills
The library:
CREATE_NEW_CUSTOMER,ADD_UPDATE_CUSTOMER_PRICING,ADD_UPDATE_ITEM,FILL_REGIONAL_PRICES,REBUILD_REGIONAL_SHEETS,COMPARE_CUSTOMER_QUOTES,LOG_EXTERNAL_BID,BID_PIPELINE,ARCHIVE_CUSTOMER,SYSTEM_HEALTH_CHECK,PREPARE_NEXT_SCHOOL_YEAR. - 04
Read data sources
commercial_items.json(127 × 8) ·commercial_regional_prices.json(4 regions) · per-customercustomer.json·External_Bids/bids_log.json(44 bids). - 05
Write D1 bid_* mirror (8 tables)
Migration 136 in flight (Agent M owns). Folder remains authoritative for content; D1 is the queryable mirror for the dashboard and Jul 1 NS push.
- 06
Programs sidecar (compliance)
HPS MAP GPO allowance program for SY2526 + SY2627 is applied alongside every skill that touches customer pricing.
- 07
Outputs
Per-customer
.docxquote;STATUS.jsonfreshness; gfs-pricing.pages.dev re-renders. - 08
★ Jul 1 rollover
69 customers × latest SY-version → 69 NS named-price-list payloads → single batch HITL → NS_PUSH_QUEUE drain.
- 09
NS records updated
customer.pricelevelper customer points at the new named list. Next SO reads the new pricing.
What's different after the cycle
- Customer-specific pricing tracked all year between annual NS rolls.
- External bid pipeline visible (44 bids, 30 submitted, 3 pending review, 23 review .md analyses).
- Programs (HPS MAP GPO) compliance threaded into every customer quote.
- July 1 = single batch-HITL flip writes 69 named price lists in cohort.
What can go wrong
Email sender doesn't match any known customer. Today HITL surfaces this and Mike picks the right customer (or rejects the email). Auto-classifier confidence is under bake.
If 5 of 69 NS pushes fail (RESTlet timeout, transient auth), they land in the partial-failure UI for Mike to re-approve as a subset. Cohort still completes.
Currently writes go to folder + STATUS.json only; the D1 bid_* mirror lights up when migration lands (Agent M). Dashboard reads from STATUS.json as fallback.
Without PushMutexDO two pushes for the same customer could collide. The DO mutex is mandatory before any NS push.
Adjacent flows + diagrams
Code paths + invariants
| Concern | Where |
|---|---|
| Mailbox | bids@ai-globalfoodsolutions.co |
| Email pipeline | src/email.ts |
| Document converter | src/document_converter.ts |
| Workflow class | src/annual_roll_workflow.ts (prepare_next_school_year) |
| Durable Object | PushMutexDO (per-customer) |
| D1 tables | 8 bid_* (migration 136) |
| NS RESTlet | customscript_gfs_platform_push_pricelist |
| Live dashboard | https://gfs-pricing.pages.dev |
| Source folder | ~/Desktop/GFS-NetSuite-Cloudflare/source-documents/Customer-Pricing/ |
Dated trail
| Date | Round | Change | Touched by |
|---|---|---|---|
2026-05-26 | R597 | Bid Center pillar shipped — master + 3 path subdocs + 4 wikis. Customer + SY-version threading documented. 8 D1 tables (migration 136) referenced. First live Jul 1 deploy. | Mike + Claude |
The machine-readable spec
Master workflow_type · bid_center_lifecycle · risk_level 4. Sub-contracts: bid_center_email_intake_path (3), bid_center_direct_chat_path (2), bid_center_july_rollover_path (5).
Customer + SY-version threading (the trace thread)
Analogous to the SO PO# (otherrefnum) thread. Grep this pair across every downstream artifact for a complete audit trail of one customer's pricing journey.
| Record / table | Field carrying customer + SY-version | Sample value |
|---|---|---|
bid_customers | customer_id | "ACC Distributors" |
bid_quote_versions | customer_id + sy_version (thread origin) | "ACC Distributors / SY2627-v2.5" |
bid_price_snapshots | customer_id + sy_version + sku | "ACC / SY2627-v2.5 / SKU-419" |
proposed_actions (batch HITL) | batch_id + customer_id + sy_version | "jul1-2026 / ACC / SY2627-v2.5" |
ns_pending_pushes | customer_id + payload.source_sy_version | "ACC Distributors / SY2627-v2.5" |
NS PriceList (custom record) | name · source_sy_version | "ACC SY2627 v2.5" |
NS customer.pricelevel | points at named list ID | traces to bid_quote_versions |
D1 tables (migration 136 - Agent M owns)
| Table | Purpose |
|---|---|
bid_customers | 69 active cohort · normalized identity |
bid_items | 127 commercial items × 8 brands |
bid_regional_prices | 4 regions (NE / SE / MW / WC) |
bid_external_pipeline | 44 external bids tracked |
bid_reviews | 23 review .md analyses |
bid_programs | HPS MAP GPO allowance (SY2526 / SY2627) |
bid_price_snapshots | line-level snapshot per (customer, SY-version) |
bid_quote_versions | SY-version per customer · is_latest=1 drives rollover |
Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /api/bid/rollover/start | manual Jul 1 trigger / dry run |
POST | /api/ns/push/named-price-list | per-customer NS write (preview + confirm) |
POST | /api/proposed-actions/bulk-decide | cohort approve / subset / reject |
POST | /api/proposed-actions/decide | single approve / reject |
Events fired
| event_type | When |
|---|---|
bid.email_received | Path 1 email arrives |
bid.intake_approved | Mike approves customer match |
bid.review_completed | 11-skill review finishes |
bid.quote_versioned | bid_quote_versions write |
bid.namedlist_pushed | per-customer Jul 1 NS write success |
bid.rollover_completed | cohort done |
It broke - what now
Scenario · Jul 1 cohort half-finished, dashboard says "in flight"
Cron fired, 40 of 69 succeeded, 5 failed, 24 still in queue. Mike needs visibility.
- Check queue state:
SELECT status, COUNT(*) FROM ns_pending_pushes WHERE batch_id='jul1-2026' GROUP BY status - Check workflow run log:
SELECT * FROM workflow_run_log WHERE workflow_type='prepare_next_school_year' ORDER BY started_at DESC LIMIT 1 - Surface partial failures: open admin-dashboard cohort panel, expand failing rows, decide re-approve or skip.
- Force-drain if stuck:
POST /api/ns/push-queue/drain?batch_id=jul1-2026
Scenario · A Path 1 email never staged a proposed_action
Customer says they emailed a bid, no row in proposed_actions, dashboard shows nothing.
- Check email landed:
SELECT * FROM events WHERE event_type='bid.email_received' AND created_at > datetime('now','-1 day') - Check Email Routing rule: CF dashboard → Email Routing → bids@ rule active?
- Check Worker logs:
npx wrangler tailfilter on email events - Manual re-stage: upload PDF via admin-dashboard chat, run Path 2 instead
Scenario · New customer name doesn't match any record
name_synonyms fuzzy match below 0.85 threshold. Email lands but customer_id is null.
- Surface in admin-dashboard: ambiguous matches list pending for Mike
- Run CREATE_NEW_CUSTOMER: if it's a brand-new customer
- Add alias: update
data/name_synonyms.jsonwith the alias and re-run intake
Logs to check
workflow_run_log· prepare_next_school_year auditns_pending_pushes· Jul 1 queue stateproposed_actions· HITL queue (kind='bid_email_intake' / 'ns_named_price_list')events· bid.* event trailcron_locks· stuck rollover detectionnpx wrangler tail· live Worker logs
Kill switches
kill:ns_writes· stops every NS push (including Jul 1)kill:proposed_apply· stops HITL approvals from executing fan-outkill:bid_rollover· specific to the prepare_next_school_year workflow
What's not done · what's uncertain
- STUBMigration 136 (8 bid_* tables)
Agent M owns. Until landed, D1 mirror is empty; STATUS.json + folder is the only source-of-truth.
- STUBBatch-HITL cohort UI for 69 rows
Agent N owns. Pattern: single batch_id, 69 expandable line items, approve-all / subset / reject-row controls. Partial-failure follow-up panel.
- STUBAuto-customer-match accuracy on Path 1
Currently HITL gates everything. Once classifier confidence is measured, auto-approve threshold can be raised.
- OPENDry-run mode for Jul 1
?dry_run=truerenders all 69 payloads but skips NS_PUSH_QUEUE - validates the cohort before the live flip. Scheduled for June 28 2026. - DECISIONMid-year named-list refresh
Should we allow PREPARE_NEXT_SCHOOL_YEAR mid-year for a single customer? Today it's annual-only.