Wiki · Bid Center master · R597

GFS Bid Center — master

The 4th pillar in the platform flow corpus, alongside SO / PO / WO. Runs between annual NetSuite pricing locks. Three intake paths feed a standardized 11-skill review; output is D1 + .docx + live dashboard at gfs-pricing.pages.dev; on July 1 the year of accumulated work becomes NS named price lists per customer. Trace thread: customer + SY-version.

Master · 3-path dispatcher 127 items · 8 brands · 69 customers · 4 regions · 44 external bids
What this is

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.

When to use it

Trigger conditions

Two operational modes

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.

★ The handoff to NetSuite

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.

Worked example

ACC Distributors' SY2627 update cycle

Scenario

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.

Step-by-step what happens

Intake → 11-skill review → outputs → ★ Jul 1 rollover

  1. 01

    Intake (one of 3 paths)

    Path 1 email at bids@; Path 2 chat; Path 3 Jul 1 cron.

  2. 02

    Normalize customer + SY-version

    The trace thread (e.g. ACC Distributors / SY2627-v2.5) is established here and carried through every downstream row.

  3. 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.

  4. 04

    Read data sources

    commercial_items.json (127 × 8) · commercial_regional_prices.json (4 regions) · per-customer customer.json · External_Bids/bids_log.json (44 bids).

  5. 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.

  6. 06

    Programs sidecar (compliance)

    HPS MAP GPO allowance program for SY2526 + SY2627 is applied alongside every skill that touches customer pricing.

  7. 07

    Outputs

    Per-customer .docx quote; STATUS.json freshness; gfs-pricing.pages.dev re-renders.

  8. 08

    ★ Jul 1 rollover

    69 customers × latest SY-version → 69 NS named-price-list payloads → single batch HITL → NS_PUSH_QUEUE drain.

  9. 09

    NS records updated

    customer.pricelevel per customer points at the new named list. Next SO reads the new pricing.

Outcomes

What's different after the cycle

Bid_*
8 tables
D1 mirror live
Customers
69
active cohort
External bids
44
tracked
NS write
Jul 1
named price lists
Failure modes

What can go wrong

Customer-match confidence too low (Path 1)

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.

Jul 1 partial failures

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.

Migration 136 not yet landed

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.

Concurrent writes to same customer

Without PushMutexDO two pushes for the same customer could collide. The DO mutex is mandatory before any NS push.

Related

Adjacent flows + diagrams

For developers

Code paths + invariants

ConcernWhere
Mailboxbids@ai-globalfoodsolutions.co
Email pipelinesrc/email.ts
Document convertersrc/document_converter.ts
Workflow classsrc/annual_roll_workflow.ts (prepare_next_school_year)
Durable ObjectPushMutexDO (per-customer)
D1 tables8 bid_* (migration 136)
NS RESTletcustomscript_gfs_platform_push_pricelist
Live dashboardhttps://gfs-pricing.pages.dev
Source folder~/Desktop/GFS-NetSuite-Cloudflare/source-documents/Customer-Pricing/
// Trace thread invariant type ThreadKey = { customer_id: string, sy_version: string }; // every bid_* INSERT/UPDATE carries this pair // Jul 1 cohort batch const batch_id = 'jul1-' + currentYear(); for (const c of activeCustomers /* 69 */) { const latest = pickLatestSyVersion(c.customer_id); stageProposedAction({ batch_id, customer_id: c.customer_id, sy_version: latest, payload: buildNamedPriceList(c, latest) }); } // Mike approves cohort; NS_PUSH_QUEUE drains with PushMutexDO per customer
Changelog

Dated trail

DateRoundChangeTouched by
2026-05-26R597Bid 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
Schema · data contract

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 / tableField carrying customer + SY-versionSample value
bid_customerscustomer_id"ACC Distributors"
bid_quote_versionscustomer_id + sy_version (thread origin)"ACC Distributors / SY2627-v2.5"
bid_price_snapshotscustomer_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_pushescustomer_id + payload.source_sy_version"ACC Distributors / SY2627-v2.5"
NS PriceList (custom record)name · source_sy_version"ACC SY2627 v2.5"
NS customer.pricelevelpoints at named list IDtraces to bid_quote_versions

D1 tables (migration 136 - Agent M owns)

TablePurpose
bid_customers69 active cohort · normalized identity
bid_items127 commercial items × 8 brands
bid_regional_prices4 regions (NE / SE / MW / WC)
bid_external_pipeline44 external bids tracked
bid_reviews23 review .md analyses
bid_programsHPS MAP GPO allowance (SY2526 / SY2627)
bid_price_snapshotsline-level snapshot per (customer, SY-version)
bid_quote_versionsSY-version per customer · is_latest=1 drives rollover

Endpoints

MethodPathPurpose
POST/api/bid/rollover/startmanual Jul 1 trigger / dry run
POST/api/ns/push/named-price-listper-customer NS write (preview + confirm)
POST/api/proposed-actions/bulk-decidecohort approve / subset / reject
POST/api/proposed-actions/decidesingle approve / reject

Events fired

event_typeWhen
bid.email_receivedPath 1 email arrives
bid.intake_approvedMike approves customer match
bid.review_completed11-skill review finishes
bid.quote_versionedbid_quote_versions write
bid.namedlist_pushedper-customer Jul 1 NS write success
bid.rollover_completedcohort done
Runbook · when it breaks

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.

  1. Check queue state: SELECT status, COUNT(*) FROM ns_pending_pushes WHERE batch_id='jul1-2026' GROUP BY status
  2. Check workflow run log: SELECT * FROM workflow_run_log WHERE workflow_type='prepare_next_school_year' ORDER BY started_at DESC LIMIT 1
  3. Surface partial failures: open admin-dashboard cohort panel, expand failing rows, decide re-approve or skip.
  4. 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.

  1. Check email landed: SELECT * FROM events WHERE event_type='bid.email_received' AND created_at > datetime('now','-1 day')
  2. Check Email Routing rule: CF dashboard → Email Routing → bids@ rule active?
  3. Check Worker logs: npx wrangler tail filter on email events
  4. 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.

  1. Surface in admin-dashboard: ambiguous matches list pending for Mike
  2. Run CREATE_NEW_CUSTOMER: if it's a brand-new customer
  3. Add alias: update data/name_synonyms.json with the alias and re-run intake

Logs to check

Kill switches

Backlog · open questions

What's not done · what's uncertain