price_api/CLAUDE.md
Paul Trowbridge 14269f64fc Add project documentation (CLAUDE.md)
Architecture, schema map, rebuild order, and testing notes for the
database-first pricing engine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:15:05 -04:00

9.0 KiB

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

-- 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):

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:

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):

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: dssmrs → 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