# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview This is a **database-first pricing engine** that computes product pricing recommendations. Business logic lives entirely in SQL stored procedures and functions. There is no application server — consumers call the database directly. Both SQL Server (`.ms.sql`) and PostgreSQL (`.pg.sql`) implementations exist in parallel. ## Testing a Pricing Scenario ```sql -- PostgreSQL (primary) SELECT *, ui_json->'data' FROM pricequote.single_price_call( 'GRIF0001', -- bill-to customer code 'GRIF0001', -- ship-to customer code 'XNS0T1G3G18B096', -- part number 'v1:B..PLT..', -- product data segment 9600 -- volume (pieces) ); -- SQL Server EXEC pricing.single_price_call @bill = 'GRIF0001', @ship = 'GRIF0001', @part = 'XNS0T1G3G18B096', @v1ds = 'v1:B..PLT..', @vol = 9600; ``` Test `guidance_logic` in isolation (SQL Server): ```sql SELECT g.* FROM (SELECT TRY_CAST(.33275 AS NUMERIC(20,5)) AS tprice, TRY_CAST(.758 AS NUMERIC(20,5)) AS last_price_norm, TRY_CAST(null AS NUMERIC(20,5)) AS listprice_eff, TRY_CAST('2025-06-01' AS DATE) AS last_date ) q CROSS APPLY pricing.guidance_logic(q.tprice, q.last_price_norm, q.listprice_eff, q.last_date, 0.05, 1.0, 1.0) g; ``` ## Data Rebuild (PostgreSQL) Run in order after source data changes: ```sql REFRESH MATERIALIZED VIEW pricequote.lastpricedetail; -- ~37s CALL pricequote.rebuild_pricelist(); -- ~32s CALL pricequote.refresh_target_prices_base(); -- ~45s REFRESH MATERIALIZED VIEW rlarp.cost_v1ds; REFRESH MATERIALIZED VIEW rlarp.cost_v0ds; ``` Batch pricing for the full matrix (seeds `pricequote.queue` from `rlarp.osm_stack`, then merges results back): ```sql CALL pricequote.process_queue(); ``` ## Architecture ### `single_price_call` — the main entry point Located in `procs/single_price_call.pg.sql` (and `.ms.sql`). Accepts `(bill, ship, part, v1ds, vol)` and returns a fully enriched pricing row including `guidance_price`, `expl` (JSONB explanation), and `ui_json` (structured panels for UI rendering). **Processing steps:** 1. Resolve `chan` / `tier` / `cust` from bill/ship codes via `rlarp.cust` 2. Look up historical prices from `pricequote.lastpricedetail` (materialized view over `rlarp.osm_stack`) 3. Look up target price from `pricequote.target_prices_base` (keyed by stlc, ds, chan, tier, volume range) 4. Resolve cost standards for cross-segment normalization (`rlarp.cost_v1ds`, `rlarp.cost_v0ds`) 5. Normalize last price when the historical sale was a different data segment (`last_isdiff`) 6. Look up list price from `pricequote.pricelist_ranged` via `CMS.CUSLG.IPRCBHC` price-level mapping 7. Apply `pricequote.guidance_logic` to produce final recommended price 8. Build `expl` and `ui_json` JSON ### `process_queue` — batch version `procs/matrix_guidance.pg.sql`. Seeds `pricequote.queue` from `rlarp.osm_stack`, runs the same 12-step logic as `single_price_call` via set-based UPDATEs, then merges `expl`/`ui` back into `rlarp.osm_stack.pricing`. ### Key helper functions - `pricequote.pick_last_price_from_hist(hist JSONB, v1ds TEXT)` — selects the best historical price from the packed `part_stats` JSONB using source precedence: `dss` → `mrs` → fallback (18-month age threshold). Source codes: `dss`=direct sale, `dsq`=direct quote, `mrs`=most-recent-similar sale, `mrq`=most-recent-similar quote. - `pricequote.guidance_logic(target, last_norm, list_eff, last_date, ...)` — picks base price (target → last → list), prevents price drops below last, caps at list price. - `pricequote.build_pricing_path_base(options JSONB)` — recursive CTE that expands a JSON array of pricing options (Anchor, Color Tier, Packaging, Channel, Volume, etc.) into a full combinatorial pricing matrix. ### Target price computation `pricequote.core_target` holds anchor prices + options JSON per product line. `pricequote.refresh_target_prices_base()` calls `build_pricing_path_base()` to expand those into every `(stlc, ds, chan, tier, vol_range)` combination and stores results in `pricequote.target_prices_base`. ### Product data segments (`v1ds` / `v0ds`) Format: `v1:B..PLT..` encodes color tier (position 4), branding (position 6), packaging (position 8-10), etc. The `v0ds` simplification reduces to just color tier + branding suffix. Customized parts have a `v1ds` that differs from their item master `v1ds`. ### Channel/tier resolution - Bill-to class `DIS` + ship-to class `DIS` → channel `WHS` (wholesale); tier/plevel from bill-to distributor - Bill-to class `DIS` + ship-to class non-`DIS` → channel `DRP` (drop-ship); tier/plevel from ship-to - Bill-to class `DIR` → channel `DIR`; tier/plevel from bill-to ## Database Schemas | Schema | Database | Purpose | |--------|----------|---------| | `pricequote.*` | PostgreSQL | All pricing functions, tables, materialized views | | `pricing.*` | SQL Server | Parallel SQL Server implementation | | `rlarp.*` | Both | Source data: `osm_stack` (order/quote history), `cust`, `cost_v1ds`, `cost_v0ds` | | `CMS.CUSLG.*` | Both | ERP item master (`itemm`), price level assignments (`IPRCBHC`) | ## File Layout ``` procs/ -- stored procedures and functions (core logic) tables/ -- table/view DDL builder/ -- build_pricing_path_base and related option-cost helpers rebuild/ -- refresh scripts for materialized views and derived tables quote_review/ -- reporting views over osm_stack archive/ -- legacy TypeScript and older SQL approaches (reference only) ``` ## Quote Review (Power BI dashboard source) `rlarp.quote_review` (PostgreSQL view, DDL at `quote_review/quote_review.pg.sql`) is the source for the Power BI quote dashboard — a per-quote-line list with a related-sales scatter plot. Full lineage crosses platforms: ``` SQL Server view fanalysis.rlarp.live_quotes DDL: SQL repo quotes/live_quotes.sql (ALTER VIEW) ↓ postgres_fdw (server usmidsql01) Postgres foreign table pricequote.live_quotes DDL: /opt/sync/usmidsql01/live_quotes/ddl.sql ↓ Postgres view rlarp.quote_review DDL: quote_review/quote_review.pg.sql ↓ Power BI dashboard ``` - `rlarp.live_quotes` is a **SQL Server** view (in the SQL repo), surfaced to Postgres only as the FDW table `pricequote.live_quotes` (note: `pricequote` schema, not `rlarp`). It filters to quotes touched in the last 60 days, status IN (3,8,9,11,4). - `quote_review.pg.sql` filters `pricequote.live_quotes` to `qstat LIKE '%Submitted%'`, then layers guidance logic, customer tier, price limits/midrange, target price, part group. - **Name collision:** SQL Server objects also named `rlarp.quote_review` exist in the SQL repo (`quotes/quote_reviews/quote_review.ms.sql` is an `ALTER PROC`; `sql/mssql/pricing/reporting/quote_review.sql` is an `ALTER VIEW`) — these are NOT the dashboard source. - CASCADE dependent of `osm_stack`; auto-rebuilt by salesmatrix's `tables/pg/rebuild_osm_stack_dependents.sh`. ## Open Issue: Last Price Is Net of Discounts ### Problem Last price is currently computed as `sales_usd / qty` where `sales_usd = fb_val_loc * r_rate`. `fb_val_loc` is the **net value after discounts**, so last price reflects a discounted unit price rather than a base/list price. This causes price_api to anchor guidance off a lower-than-intended price for customers who receive line discounts. ### Data Available to Fix It In `rlarp.osm` (both PG and MS SQL), `fb_val_loc_dis` holds the discount amount and `fb_val_loc` holds the net value. Gross/base value = `fb_val_loc + fb_val_loc_dis`. However, **`fb_val_loc_dis` is not carried into `osm_stack`** on either database — it is dropped during `osm_stack_refresh()`. ### Required Changes To fix this properly, changes are needed in both databases (MS SQL feeds `rebuild_lastprice`; PostgreSQL feeds `pricequote.lastpricedetail`): **PostgreSQL (`/opt/salesmatrix`):** 1. `tables/pg/osm_stack.pg.sql` — add `gross_usd NUMERIC` and `gross_local NUMERIC` columns 2. `procs/pg/osm_stack_refresh.pg.sql` — populate: `gross_local = fb_val_loc + fb_val_loc_dis`, `gross_usd = gross_local * r_rate` 3. `tables/pg/lastpricedetail.pg.sql` (`pricequote.lastpricedetail`) — change price calc from `sales_usd/qty` to `gross_usd/qty` **MS SQL (`/opt/salesmatrix`):** 1. `tables/ms/osm_stack.ms.sql` — add `gross_usd` and `gross_local` columns 2. `procs/ms/osm_stack_refresh.sql` — populate same as PG 3. `rebuild/rebuild_lastprice.ms.sql` — change price calc to use `gross_usd/qty` **Note on PG DROP CASCADE**: Changing `osm_stack` DDL cascades to `osm_stack_pretty`, `osm_powerbi`, `price_pool`, `quote_review`, `pricequote.lastpricedetail` — all must be rebuilt after any DDL change. Adding a column avoids the drop; use `ALTER TABLE rlarp.osm_stack ADD COLUMN`. ## Deployment Order When making schema changes, deploy in dependency order: 1. `tables/` DDL 2. `builder/` functions 3. `procs/guidance_logic`, `procs/lastprice_logic`, `procs/approval_logic` 4. `procs/single_price_call`, `procs/matrix_guidance`