Compare commits

..

No commits in common. "master" and "refactor_batch" have entirely different histories.

43 changed files with 980 additions and 2164 deletions

127
WARP.md
View File

@ -1,127 +0,0 @@
# WARP.md
This file provides guidance to WARP (warp.dev) when working with code in this repository.
## Architecture Overview
This is a **pricing engine** that computes product pricing recommendations based on multiple data sources (target prices, historical sales, list prices). The system operates with both **SQL Server** and **PostgreSQL** implementations, providing pricing guidance through structured JSON responses.
### Core Components
**Database-First Architecture**: The system is primarily database-driven with stored procedures/functions as the main business logic layer. Each major component has both SQL Server (`.ms.sql`) and PostgreSQL (`.pg.sql`) implementations.
**Single Price Call Flow**: The main entry point is `single_price_call` which processes one pricing scenario through these stages:
1. **Input enrichment** - Resolves customer/channel/tier from bill-to/ship-to codes
2. **Historical price lookup** - Finds most recent/relevant sales from `lastpricedetail`
3. **Target price lookup** - Gets configured target pricing from `target_prices`
4. **Cost normalization** - Adjusts historical prices across different product data segments
5. **List price lookup** - Retrieves volume-banded list prices from `pricelist_ranged`
6. **Guidance logic** - Computes final recommended price using `guidance_logic`
7. **JSON generation** - Creates structured `ui_json` for frontend consumption
**Multi-Database Support**: Core logic exists in parallel implementations:
- **SQL Server**: `procs/*.ms.sql`, `tables/*.ms.sql`
- **PostgreSQL**: `procs/*.pg.sql`, `tables/*.pg.sql`
## Common Development Commands
### Testing Individual Pricing Scenarios
```sql
-- SQL Server
EXEC pricing.single_price_call
@bill = 'GRIF0001',
@ship = 'GRIF0001',
@part = 'XNS0T1G3G18B096',
@v1ds = 'v1:B..PLT..',
@vol = 9600;
-- PostgreSQL
SELECT ui_json->'data'
FROM pricequote.single_price_call(
'GRIF0001',
'GRIF0001',
'XNS0T1G3G18B096',
'v1:B..PLT..',
9600
);
```
### Running Test Examples
Execute the complete example scenarios:
```powershell
# SQL Server examples
sqlcmd -S server -d database -i example_usage.ms.sql
# PostgreSQL examples
psql -d database -f example_usage.pg.sql
```
### Database Schema Updates
Deploy schema changes in this order:
```powershell
# 1. Tables first
sqlcmd -S server -d database -i tables/target_prices.ms.sql
sqlcmd -S server -d database -i tables/pricelist_ranged.ms.sql
sqlcmd -S server -d database -i tables/lastpricedetail.ms.sql
# 2. Functions/procedures second
sqlcmd -S server -d database -i procs/guidance_logic.ms.sql
sqlcmd -S server -d database -i procs/single_price_call.ms.sql
```
### Data Rebuilds
Refresh core pricing data:
```sql
-- Rebuild price list with UOM conversions
EXEC [script from pricelist_ranged.ms.sql]
-- Refresh historical price summaries
EXEC [script from lastpricedetail.ms.sql]
-- Update target price configurations
INSERT INTO pricing.target_prices SELECT * FROM remote.target_prices_view;
```
## Key Data Structures
**Input Parameters**: All pricing calls require:
- `bill` - Bill-to customer code
- `ship` - Ship-to customer code
- `part` - Product part number
- `v1ds` - Product data segment (version 1)
- `vol` - Volume quantity
**UI JSON Structure**: The output `ui_json` contains:
- `details[]` - Array of pricing panels (History, List, Target Calculation, Guidance)
- `data` - Raw `expl` JSON with all calculation details
**Product Data Segments**: Products have versioned data segments (v1ds/v0ds) that affect pricing:
- Format: `v1:B..PLT..` (color tier, packaging, etc.)
- Cross-segment price normalization uses target price ratios or cost ratios
## Database Schemas
**SQL Server**: Uses `pricing.*` schema
- `pricing.single_price_call` - Main pricing procedure
- `pricing.target_prices` - Configured target pricing rules
- `pricing.lastpricedetail` - Historical price summaries per customer/product group
**PostgreSQL**: Uses `pricequote.*` schema
- `pricequote.single_price_call()` - Main pricing function
- `pricequote.target_prices` - Target pricing configurations
- `pricequote.lastpricedetail` - Historical price data
## Key Business Logic
**Guidance Logic**: Located in `guidance_logic.*sql`, determines final price using precedence:
1. Target price (if available)
2. Last normalized price (prevents price drops)
3. List price (as ceiling)
**Price Normalization**: When historical prices are from different product segments, they're adjusted using either target price ratios or cost ratios to enable fair comparison.
**Volume Banding**: List prices and target prices use volume ranges. The `pricelist_ranged` tables normalize all unit-of-measure to "PC" (pieces) for consistent volume comparisons.
## Archive Directory
Contains legacy TypeScript implementations and older SQL approaches. The `archive/apply_guidance.ts` shows the previous client-side pricing logic that has been moved to database stored procedures/functions.

View File

@ -1,167 +0,0 @@
DROP FUNCTION pricequote.build_pricing_path_base;
CREATE OR REPLACE FUNCTION pricequote.build_pricing_path_base(
_json JSONB
)
RETURNS TABLE (
stlc TEXT
,seq BIGINT
,srtcode TEXT
,ds TEXT
,chan TEXT
,tier TEXT
,vol INT4RANGE
,func TEXT
,val NUMERIC
,price NUMERIC
,math TEXT[]
,lastflag BOOLEAN
)
LANGUAGE plpgsql AS
$$
BEGIN
RETURN QUERY
WITH RECURSIVE
-- 1⃣ Parse JSONB into rows of (entity, attr, val)
parsed AS (
SELECT
e.entity,
COALESCE(e.attr, '') AS attr,
e.val,
e.func
FROM jsonb_to_recordset(_json)
AS e(entity TEXT, attr TEXT, val NUMERIC, func TEXT)
),
-- 2⃣ Attach sequence & func from master option_sequence table
sequenced AS (
SELECT
p.entity,
p.attr,
p.val,
p.func,
s.DOMAIN,
DENSE_RANK() OVER (ORDER BY s.seq) AS seq,
ROW_NUMBER() OVER (PARTITION BY p.entity ORDER BY COALESCE(p.val,0) ASC) srt
FROM parsed p
JOIN pricequote.option_sequence s
ON p.entity = s.entity
)
--select * from sequenced ORDER BY seq, srt
-- 3⃣ Recursively accumulate pricing path
,combos AS (
-- 🚀 Base case: first in sequence
SELECT
s.entity,
s.attr,
s.seq,
to_char(s.srt,'FM000') srtcode,
'' ds,
'' chan,
'' tier,
null::TEXT vol,
s.func,
s.val,
s.val agg,
CASE WHEN s.func = 'Price' THEN s.val ELSE NULL::NUMERIC END AS base,
ARRAY[
CASE
WHEN s.func = 'Price' THEN
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' + ' || LPAD(to_char(s.val, 'FM9999999990.00000'), 10, ' ')
WHEN s.func = 'Factor' THEN
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' + ' || LPAD(to_char(COALESCE(NULLIF(CASE WHEN s.func = 'Price' THEN s.val END, NULL), 0) * (s.val - 1), 'FM9999999990.00000'), 10, ' ')
ELSE
RPAD(s.entity || ':' || s.attr, 17, ' ') || ' ' || LPAD(to_char(s.val, 'FM9999999990.00000'), 10, ' ')
END
] math
FROM
sequenced s
WHERE
s.seq = (SELECT MIN(x.seq) FROM sequenced x)
UNION ALL
-- 🔁 Recursive step: process next in sequence
SELECT
c.entity,
c.attr,
o.seq,
c.srtcode || '.' || to_char(o.srt,'FM000'),
c.ds || CASE WHEN o.DOMAIN = 'Product' THEN '.' || o.attr ELSE '' END,
CASE WHEN o.DOMAIN = 'Channel' THEN o.attr ELSE c.chan END chan,
CASE WHEN o.DOMAIN = 'Tier' THEN o.attr ELSE c.tier END tier,
CASE WHEN o.DOMAIN = 'Volume' THEN o.attr ELSE c.vol END vol,
o.func,
o.val,
CASE
WHEN o.func = 'Price' THEN c.agg + o.val
WHEN o.func = 'Factor' THEN c.agg + COALESCE(c.base, 0) * (o.val - 1)
END agg,
COALESCE(c.base, CASE WHEN o.func = 'Price' THEN o.val ELSE NULL::NUMERIC END) AS base,
CASE WHEN (o.func = 'Price' AND o.val <> 0) OR (o.func = 'Factor' AND o.val <> 1) THEN
c.math ||
ARRAY[
CASE
WHEN o.func = 'Price' THEN
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' + ' || LPAD(to_char(o.val, 'FM9999999990.00000'), 10, ' ')
WHEN o.func = 'Factor' THEN
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' + ' || LPAD(to_char(COALESCE(c.base, 0) * (o.val - 1), 'FM9999999990.00000'), 10, ' ')
ELSE
RPAD(o.entity || ':' || o.attr, 17, ' ') || ' ' || LPAD(to_char(o.val, 'FM9999999990.00000'), 10, ' ')
END
]
ELSE
c.math
END math
FROM
combos c
JOIN sequenced o ON
o.seq = c.seq + 1
)
SELECT
-- c.entity
c.attr
,c.seq
,c.srtcode
,'v1:'||SUBSTRING(c.ds,2,100) ds
,c.chan
,c.tier
-- ,c.vol
,CASE
WHEN c.vol ~ '^[0-9]+-[0-9]+$' THEN
int4range(
split_part(c.vol, '-', 1)::int,
split_part(c.vol, '-', 2)::int,
'[)'
)
WHEN c.vol ~ '^[0-9]+$' THEN
int4range(
c.vol::int,
NULL,
'[)'
)
ELSE NULL
END AS vol
,c.func
,c.val
,c.agg
,c.math
,c.seq = (SELECT max(x.seq) FROM sequenced x) lastflag
FROM
combos c /*WHERE seq = (SELECT max(seq) FROM sequenced)*/
ORDER BY
c.srtcode ASC;
END;
$$;
/*
Anchor:EU170S50 + 0.08
Color Tier:P x 1.30
Branding: + 0.00
Packaging:SLV + 0.00
Suffix:PCR x 1.00
Accessories: + 0.00
Channel:WHS + 0.00
Volume:8 x 1.00
-----------------------
0.104
*/

184
esc.json
View File

@ -1,184 +0,0 @@
[
{
"folder": "01 Gal Can",
"entity": "Anchor",
"attr": "BM.03000",
"val": 0.15,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "B",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "C",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "L",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "M",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "P",
"val": 1.2,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Color Tier",
"attr": "T",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": null,
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": "L",
"val": 0.04,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Branding",
"attr": "P",
"val": 0.1,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "BDL",
"val": 0.005,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "CSE",
"val": 0.01,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "PLT",
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Packaging",
"attr": "SLV",
"val": 0.005,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Suffix",
"attr": null,
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Accessories",
"attr": null,
"val": 0,
"func": "Price"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "DIR",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "DRP",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Channel",
"attr": "WHS",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 1,
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 2,
"val": 1.05,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Tier",
"attr": 3,
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "00-01",
"val": 1.35,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "01-08",
"val": 1.1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "24",
"val": 1,
"func": "Factor"
},
{
"folder": "01 Gal Can",
"entity": "Volume",
"attr": "08-24",
"val": 1.05,
"func": "Factor"
}
]

View File

@ -7,22 +7,12 @@ EXEC pricing.single_price_call
@vol = 9600; @vol = 9600;
EXEC pricing.single_price_call EXEC pricing.single_price_call
@bill = 'FARM0001', @bill = 'BFGS0001',
@ship = 'KEYB0001', @ship = 'BOBS0002',
@part = 'HCA10000B661100', @part = 'HTI10754B12B024LXB04',
@v1ds = 'v1:T..CSE..D', @v1ds = 'v1:L.L.PLT..',
@vol = 172000; @vol = 172000;
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;
SELECT * FROM pricing.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100' SELECT * FROM pricing.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100'
SELECT * FROM pricing.pricelist_ranged pr WHERE pr.jcpart = 'XNS0T1G3G18B096' AND SELECT * FROM pricing.pricelist_ranged pr WHERE pr.jcpart = 'XNS0T1G3G18B096' AND

View File

@ -1,80 +1,42 @@
SELECT SELECT
ui_json->'data' ui_json->'details'
FROM pricequote.single_price_call( FROM pricequote.single_price_call(
'FARM0001', 'FARM0001',
'KEYB0001', 'KEYB0001',
'HCA10000B661100', 'HZP3E103E21D050',
'v1:T..CSE..D', 'v1:C..BDL..',
50000 50000
) f; ) f
SELECT
SELECT ui_json->'details'
*, ui_json->'data' FROM pricequote.single_price_call(
FROM pricequote.single_price_call( 'FARM0001',
'BFGS0001', 'KEYB0001',
'SPRI0019', 'HZP3E103E21D050',
'INT12040G18C100', 'v1:C..BDL..',
'v1:B..CSE..', 50000
7037 ) f
) f;
SELECT SELECT
*, ui_json->'data' ui_json
FROM pricequote.single_price_call( FROM pricequote.single_price_call(
'BELL0039', 'BFGS0001',
'BELL0039', 'BOBS0002',
'XNS0T1G3X19B096PZBND', 'HTI10754B12B024LXB04',
'v1:L.P.PLT..', 'v1:L.L.PLT..',
82420 172000
) f; ) f
SELECT * FROM rlarp.cust WHERE code = 'PACK0009'
-- SELECT * FROM pricequote.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100'; SELECT * FROM pricequote.lastpricedetail l WHERE customer = 'HYBELS' AND l.partgroup = 'HZP3E100'
--
SELECT
q.billto, q.shipto, q.part, q.v1ds, q.units_each, pc.tprice, pc.tmath
FROM
pricequote.live_quotes q
LEFT JOIN LATERAL pricequote.single_price_call(
billto, shipto, part, v1ds, units_each
) pc ON TRUE
WHERE
qid = 113761
-- AND qrn = 4;
create table pricequote.target_prices_base as (
with
expand AS (
SELECT SELECT
c.compset, pc.expl
c.stlc, FROM
c.floor, pricequote.live_quotes
b.ds, LEFT JOIN LATERAL pricequote.single_price_call(
b.chan, billto, shipto, part, v1ds, units_each
b.tier, ) pc ON TRUE
b.vol, WHERE
b.val, qid = 113278
b.price, AND qrn = 5
json_pretty(to_json(b.math)) math
FROM
pricequote.core_target c
LEFT JOIN LATERAL pricequote.build_pricing_path_base (options||jsonb_build_object('entity','Anchor','attr',c.stlc,'val',c.floor,'func','Price')) b ON b.lastflag
)
-- select count(*) from expand
select * from expand
) with data;
-- SELECT
-- stlc, ds, chan, tier, vol, price, math
-- FROM
-- pricequote.build_pricing_path_base(
-- $$
-- [{"folder":"01 Gal Can","entity":"Anchor","attr":"BM.03000","val":0.15,"func":"Price"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"B","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"C","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"L","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"M","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"P","val":1.2,"func":"Factor"},{"folder":"01 Gal Can","entity":"Color Tier","attr":"T","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Branding","attr":null,"val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Branding","attr":"L","val":0.04,"func":"Price"},{"folder":"01 Gal Can","entity":"Branding","attr":"P","val":0.1,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"BDL","val":0.005,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"CSE","val":0.01,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"PLT","val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Packaging","attr":"SLV","val":0.005,"func":"Price"},{"folder":"01 Gal Can","entity":"Suffix","attr":null,"val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Accessories","attr":null,"val":0,"func":"Price"},{"folder":"01 Gal Can","entity":"Channel","attr":"DIR","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Channel","attr":"DRP","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Channel","attr":"WHS","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":1,"val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":2,"val":1.05,"func":"Factor"},{"folder":"01 Gal Can","entity":"Tier","attr":3,"val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"00-01","val":1.35,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"01-08","val":1.1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"24","val":1,"func":"Factor"},{"folder":"01 Gal Can","entity":"Volume","attr":"08-24","val":1.05,"func":"Factor"}]
-- $$
-- )
-- WHERE
-- lastflag

View File

@ -1,81 +0,0 @@
-- CREATE OR ALTER FUNCTION pricing.approval_logic;
CREATE OR ALTER FUNCTION pricing.approval_logic(
@target numeric(20,5),
@last_norm numeric(20,5),
@list_eff numeric(20,5),
@last_date date,
@floor_pct numeric(10,5) = 0.95,
@cap_last_pct numeric(10,5) = 1.00,
@cap_list_pct numeric(10,5) = 1.00
)
RETURNS @ret TABLE (
approval_price numeric(20,5),
approval_reason nvarchar(4000)
)
AS
BEGIN
DECLARE
@base_price numeric(20,5), -- starting point (target if available, else last_norm, else list_eff)
@after_floor numeric(20,5), -- base but limited to x% lower than last price
@after_cap_last numeric(20,5), -- previous step but limited to x% higher than last price
@final_price numeric(20,5), -- previous step but limited to x% higher than list price
@reason nvarchar(4000) = N''; -- logic source of price
-- Early exit if nothing to work with
IF @target IS NULL AND @last_norm IS NULL AND @list_eff IS NULL
BEGIN
INSERT INTO @ret VALUES (NULL, N'No target, last, or list available');
RETURN;
END;
-- Pick starting base price
SET @base_price = COALESCE(@target, @last_norm, @list_eff);
-- Step 1: use base price unless it's more than x% below last price
SET @after_floor = @base_price;
IF @last_norm IS NOT NULL
SET @after_floor = ROUND(
CASE WHEN @base_price >= @last_norm*@floor_pct
THEN @base_price
ELSE @last_norm*@floor_pct
END, 5);
-- Step 2: use price from previous step but don't allow it to be x% above last price
SET @after_cap_last = @after_floor;
IF @last_norm IS NOT NULL
SET @after_cap_last = ROUND(
CASE WHEN @after_floor <= @last_norm*@cap_last_pct
THEN @after_floor
ELSE @last_norm*@cap_last_pct
END, 5);
-- Step 3: use price from last step, but don't allow it to be more than x% above list price
SET @final_price = @after_cap_last;
IF @list_eff IS NOT NULL
SET @final_price = ROUND(
CASE WHEN @after_cap_last <= @list_eff*@cap_list_pct
THEN @after_cap_last
ELSE @list_eff*@cap_list_pct
END, 5);
-- Reason text
SET @reason =
CASE
WHEN @target IS NOT NULL THEN N'Using target price'
WHEN @last_norm IS NOT NULL THEN N'Using last price as base'
WHEN @list_eff IS NOT NULL THEN N'Using list price as base'
END;
IF @last_norm IS NOT NULL AND @after_floor > @base_price
SET @reason = N'Last price drop limit';
IF @last_norm IS NOT NULL AND @after_cap_last < @after_floor
SET @reason = N'Last price increase limit';
IF @list_eff IS NOT NULL AND @final_price < @after_cap_last
SET @reason = N'List price ceiling';
INSERT INTO @ret VALUES (@final_price, @reason);
RETURN;
END;

View File

@ -1,72 +0,0 @@
CREATE OR REPLACE FUNCTION pricequote.approval_logic(
_target numeric(20,5),
_last_norm numeric(20,5),
_list_eff numeric(20,5),
_last_date date,
_floor_pct numeric(10,5) DEFAULT 0.95,
_cap_last_pct numeric(10,5) DEFAULT 1.00,
_cap_list_pct numeric(10,5) DEFAULT 1.00
)
RETURNS TABLE (
approval_price numeric(20,5),
approval_reason text
)
LANGUAGE plpgsql AS $$
DECLARE
base_price numeric(20,5);
after_floor numeric(20,5);
after_cap_last numeric(20,5);
final_price numeric(20,5);
reason text := '';
BEGIN
-- Early exit if nothing to work with
IF _target IS NULL AND _last_norm IS NULL AND _list_eff IS NULL THEN
RETURN QUERY SELECT NULL::numeric, 'No target, last, or list available';
RETURN;
END IF;
-- Pick starting base price
base_price := COALESCE(_target, _last_norm, _list_eff);
-- Step 1: use base price unless it's more than x% below last price
after_floor := base_price;
IF _last_norm IS NOT NULL THEN
after_floor := GREATEST(base_price, ROUND(_last_norm*_floor_pct,5));
END IF;
-- Step 2: use price from previous step but don't allow it to be x% above last price
after_cap_last := after_floor;
IF _last_norm IS NOT NULL THEN
after_cap_last := LEAST(after_floor, ROUND(_last_norm*_cap_last_pct,5));
END IF;
-- cap to list (binds if it lowers price)
final_price := after_cap_last;
IF _list_eff IS NOT NULL THEN
final_price := LEAST(after_cap_last, ROUND(_list_eff*_cap_list_pct,5));
END IF;
-- Reason text
reason :=
CASE
WHEN _target IS NOT NULL THEN 'Using target price'
WHEN _last_norm IS NOT NULL THEN 'Using last price as base'
WHEN _list_eff IS NOT NULL THEN 'Using list price as base'
END;
IF _last_norm IS NOT NULL AND after_floor > base_price THEN
reason := 'Last price drop limit';
END IF;
IF _last_norm IS NOT NULL AND after_cap_last < after_floor THEN
reason := 'Last price increase limit';
END IF;
IF _list_eff IS NOT NULL AND final_price < after_cap_last THEN
reason := 'List price ceiling';
END IF;
RETURN QUERY SELECT final_price, reason;
END $$;

View File

@ -1,61 +1,119 @@
-- CREATE OR ALTER FUNCTION pricing.guidance_logic;
CREATE OR ALTER FUNCTION pricing.guidance_logic( -- This function returns the least of two NUMERIC(20,5) values.
@target numeric(20,5), CREATE OR ALTER FUNCTION dbo.LEAST_NUMERIC205(
@last_norm numeric(20,5), @a NUMERIC(20,5),
@list_eff numeric(20,5), @b NUMERIC(20,5)
@last_date date,
@floor_pct numeric(10,5) = 0.95,
@cap_last_pct numeric(10,5) = 1.00,
@cap_list_pct numeric(10,5) = 1.00
) )
RETURNS @ret TABLE ( RETURNS NUMERIC(20,5)
guidance_price numeric(20,5), AS
guidance_reason nvarchar(4000) BEGIN
RETURN CASE
WHEN @a IS NULL THEN @b
WHEN @b IS NULL THEN @a
WHEN @a < @b THEN @a ELSE @b
END
END
GO
-- This function returns the greatest of two NUMERIC(20,5) values.
CREATE OR ALTER FUNCTION dbo.GREATEST_NUMERIC205(
@a NUMERIC(20,5),
@b NUMERIC(20,5)
)
RETURNS NUMERIC(20,5)
AS
BEGIN
RETURN CASE
WHEN @a IS NULL THEN @b
WHEN @b IS NULL THEN @a
WHEN @a > @b THEN @a ELSE @b
END
END
GO
-- This function implements the guidance logic for pricing based on target, last, and list prices.
CREATE OR ALTER FUNCTION pricing.guidance_logic (
@target_price NUMERIC(20,5),
@last_price NUMERIC(20,5),
@list_price NUMERIC(20,5),
@last_date DATE
)
RETURNS @result TABLE (
guidance_price NUMERIC(20,5),
guidance_reason NVARCHAR(MAX)
) )
AS AS
BEGIN BEGIN
DECLARE DECLARE @price NUMERIC(20,5);
@base_price numeric(20,5), -- starting point (target if available, else last_norm, else list_eff) DECLARE @reason NVARCHAR(MAX) = '';
@after_floor numeric(20,5), -- base but limited to x% lower than last price DECLARE @floored NUMERIC(20,5);
@after_cap_last numeric(20,5), -- previous step but limited to x% higher than last price DECLARE @capped NUMERIC(20,5);
@final_price numeric(20,5), -- previous step but limited to x% higher than list price DECLARE @use_last_price BIT = 0;
@reason nvarchar(4000) = N''; -- logic source of price
-- Early exit if nothing to work with -- Determine if last price is recent (within last 2 years)
IF @target IS NULL AND @last_norm IS NULL AND @list_eff IS NULL IF @last_price IS NOT NULL AND @last_date IS NOT NULL AND @last_date > DATEADD(YEAR, -2, CAST(GETDATE() AS DATE))
SET @use_last_price = 1;
IF @target_price IS NOT NULL AND @use_last_price = 1
BEGIN BEGIN
INSERT INTO @ret VALUES (NULL, N'No target, last, or list available'); SET @floored = dbo.GREATEST_NUMERIC205(@target_price, @last_price * 0.95);
RETURN; SET @capped = dbo.LEAST_NUMERIC205(@floored, @last_price);
END; SET @price = dbo.LEAST_NUMERIC205(ISNULL(@list_price, 1e9), @capped);
-- Pick starting base price IF @price = @last_price
SET @base_price = COALESCE(@target, @last_norm, @list_eff); BEGIN
-- Reason text SET @reason = 'Cap at last price';
SET @reason = END
CASE ELSE
WHEN @target IS NOT NULL THEN N'Using target price' BEGIN
WHEN @last_norm IS NOT NULL THEN N'Using last price as base' SET @reason = 'Using target price';
WHEN @list_eff IS NOT NULL THEN N'Using list price as base' IF @target_price < @last_price * 0.95
END; SET @reason += ', floored to 5% below last price';
IF @target_price > @last_price
-- Step 1: use base price less than last price, use last price SET @reason += ', capped to not exceed last price';
IF @base_price < @last_norm IF @list_price IS NOT NULL AND @price = @list_price AND @target_price > @list_price
BEGIN SET @reason += ', capped to not exceed list price';
SET @base_price = @last_norm; END
SET @reason = N'Last price';
END END
ELSE IF @use_last_price = 1
-- Step 2: use price from previous step but don't allow it to be x% above last price
IF @base_price > @list_eff
BEGIN BEGIN
SET @base_price = @list_eff; SET @price = @last_price;
SET @reason = N'List price ceiling'; SET @reason = 'Last price - no target';
END END
ELSE IF @target_price IS NOT NULL
SET @final_price = @base_price; BEGIN
SET @price = @target_price;
INSERT INTO @ret VALUES (@final_price, @reason); IF @last_price IS NOT NULL AND @last_date IS NOT NULL
BEGIN
SET @reason = CONCAT(
'Last price ignored (too old: ',
CONVERT(NVARCHAR(10), @last_date, 120),
'), using target price'
);
END
ELSE
BEGIN
SET @reason = 'Target price - no prior sale';
END
END
ELSE
BEGIN
SET @price = NULL;
IF @last_price IS NOT NULL AND @last_date IS NOT NULL
BEGIN
SET @reason = CONCAT(
'Last price ignored (too old: ',
CONVERT(NVARCHAR(10), @last_date, 120),
'), no pricing available'
);
END
ELSE
BEGIN
SET @reason = 'No pricing available';
END
END
INSERT INTO @result VALUES (@price, @reason);
RETURN; RETURN;
END; END
GO

View File

@ -1,56 +1,76 @@
CREATE OR REPLACE FUNCTION pricequote.guidance_logic( CREATE OR REPLACE FUNCTION pricequote.guidance_logic(
_target numeric(20,5), _target_price NUMERIC(20,5),
_last_norm numeric(20,5), _last_price NUMERIC(20,5),
_list_eff numeric(20,5), _list_price NUMERIC(20,5),
_last_date date, _last_date DATE
_floor_pct numeric(10,5) DEFAULT 0.95,
_cap_last_pct numeric(10,5) DEFAULT 1.00,
_cap_list_pct numeric(10,5) DEFAULT 1.00
) )
RETURNS TABLE ( RETURNS TABLE (
guidance_price numeric(20,5), guidance_price NUMERIC(20,5),
guidance_reason text guidance_reason TEXT
) ) AS $$
LANGUAGE plpgsql AS $$
DECLARE DECLARE
base_price numeric(20,5); _price NUMERIC(20,5);
after_floor numeric(20,5); _reason TEXT := '';
after_cap_last numeric(20,5); _floored NUMERIC(20,5);
final_price numeric(20,5); _capped NUMERIC(20,5);
reason text := ''; _use_last_price BOOLEAN := FALSE;
BEGIN BEGIN
-- Evaluate whether last price is recent enough
-- Early exit if nothing to work with IF _last_price IS NOT NULL AND _last_date IS NOT NULL AND _last_date > CURRENT_DATE - INTERVAL '2 years' THEN
IF _target IS NULL AND _last_norm IS NULL AND _list_eff IS NULL THEN _use_last_price := TRUE;
RETURN QUERY SELECT NULL::numeric, 'No target, last, or list available';
RETURN;
END IF; END IF;
IF _target_price IS NOT NULL AND _use_last_price THEN
-- Pick starting base price _floored := GREATEST(_target_price, _last_price * 0.95);
base_price := COALESCE(_target, _last_norm, _list_eff); _capped := LEAST(_floored, _last_price);
-- Reason text _price := LEAST(COALESCE(_list_price, 1e9), _capped);
reason :=
CASE
WHEN _target IS NOT NULL THEN 'Using target price'
WHEN _last_norm IS NOT NULL THEN 'Using last price as base'
WHEN _list_eff IS NOT NULL THEN 'Using list price as base'
END;
-- Step 1: use base price less than last price, use last price IF _price = _last_price THEN
IF base_price < _last_norm THEN _reason := 'Cap at last price';
base_price := _last_norm; ELSE
reason := 'Last price'; _reason := 'Using target price';
IF _target_price < _last_price * 0.95 THEN
_reason := _reason || ', floored to 5% below last price';
END IF;
IF _target_price > _last_price THEN
_reason := _reason || ', capped to not exceed last price';
END IF;
IF _list_price IS NOT NULL AND _price = _list_price AND _target_price > _list_price THEN
_reason := _reason || ', capped to not exceed list price';
END IF;
END IF;
ELSIF _use_last_price THEN
_price := _last_price;
_reason := 'Last price - no target';
ELSIF _target_price IS NOT NULL THEN
_price := _target_price;
IF _last_price IS NOT NULL AND _last_date IS NOT NULL THEN
_reason := format(
-- 'Last price ignored (too old: %s), using target price',
'Last price ignored, using target price',
to_char(_last_date, 'YYYY-MM-DD')
);
ELSE
_reason := 'Target price - no prior sale';
END IF;
ELSE
_price := NULL;
IF _last_price IS NOT NULL AND _last_date IS NOT NULL THEN
_reason := format(
-- 'Last price ignored (too old: %s), no pricing available',
'Last price too old, no pricing available',
to_char(_last_date, 'YYYY-MM-DD')
);
ELSE
_reason := 'No pricing available';
END IF;
END IF; END IF;
-- Step 2: use price from previous step but don't allow it to be x% above last price RETURN QUERY SELECT _price, _reason;
IF base_price > _list_eff THEN END;
base_price := _list_eff; $$ LANGUAGE plpgsql;
reason := 'List price ceiling';
END IF;
final_price := base_price;
RETURN QUERY SELECT final_price, reason;
END $$;

View File

@ -15,7 +15,7 @@ RETURNS @result TABLE (
) )
AS AS
BEGIN BEGIN
DECLARE @age_threshold DATE = DATEADD(month, -18, GETDATE()); DECLARE @age_threshold DATE = DATEADD(year, -1, GETDATE());
-- Extract all relevant objects from JSON -- Extract all relevant objects from JSON
DECLARE @dsq NVARCHAR(MAX), @dss NVARCHAR(MAX), @mrq NVARCHAR(MAX), @mrs NVARCHAR(MAX); DECLARE @dsq NVARCHAR(MAX), @dss NVARCHAR(MAX), @mrq NVARCHAR(MAX), @mrs NVARCHAR(MAX);
@ -51,57 +51,39 @@ BEGIN
-- Use the same selection logic as before -- Use the same selection logic as before
-- 1. Prefer the most recent of dss/dsq if either is within the age threshold -- 1. Prefer the most recent of dss/dsq if either is within the age threshold
IF (@dss_date IS NOT NULL AND @dss_date > @age_threshold) IF (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold)
BEGIN OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part); BEGIN
RETURN; IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold
END INSERT INTO @result VALUES (@dsq_price, 'dsq', @dsq_date, @dsq_qty, @dsq_dataseg, @dsq_ord, @dsq_quote, @dsq_part);
-- IF (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold) ELSE IF @dss_date IS NOT NULL AND @dss_date > @age_threshold
-- OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold) INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part);
-- BEGIN RETURN;
-- IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold END
-- INSERT INTO @result VALUES (@dsq_price, 'dsq', @dsq_date, @dsq_qty, @dsq_dataseg, @dsq_ord, @dsq_quote, @dsq_part);
-- ELSE IF @dss_date IS NOT NULL AND @dss_date > @age_threshold
-- INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part);
-- RETURN;
-- END
-- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists -- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists
IF @mrs_date IS NOT NULL AND @mrs_date > @age_threshold IF (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL)
BEGIN BEGIN
INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part); IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date)
RETURN; INSERT INTO @result VALUES (@mrq_price, 'mrq', @mrq_date, @mrq_qty, @mrq_dataseg, @mrq_ord, @mrq_quote, @mrq_part);
END ELSE IF @mrs_date IS NOT NULL
-- IF (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL) INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part);
-- BEGIN RETURN;
-- IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date) END
-- INSERT INTO @result VALUES (@mrq_price, 'mrq', @mrq_date, @mrq_qty, @mrq_dataseg, @mrq_ord, @mrq_quote, @mrq_part);
-- ELSE IF @mrs_date IS NOT NULL
-- INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part);
-- RETURN;
-- END
-- 3. If all are at least as old as the threshold, pick the least oldest price available -- 3. If all are at least as old as the threshold, pick the least oldest price available
-- DECLARE DECLARE @best_price NUMERIC(20,5) = NULL, @best_source NVARCHAR(10) = NULL, @best_date DATE = NULL, @best_qty NUMERIC(20,5) = NULL, @best_dataseg NVARCHAR(100) = NULL, @best_ord NVARCHAR(20) = NULL, @best_quote NVARCHAR(20) = NULL, @best_part NVARCHAR(100) = NULL;
-- @best_price NUMERIC(20,5) = NULL IF @dsq_date IS NOT NULL
-- ,@best_source NVARCHAR(10) = NULL SELECT @best_price = @dsq_price, @best_source = 'dsq', @best_date = @dsq_date, @best_qty = @dsq_qty, @best_dataseg = @dsq_dataseg, @best_ord = @dsq_ord, @best_quote = @dsq_quote, @best_part = @dsq_part;
-- ,@best_date DATE = NULL IF @dss_date IS NOT NULL AND (@best_date IS NULL OR @dss_date > @best_date)
-- ,@best_qty NUMERIC(20,5) = NULL SELECT @best_price = @dss_price, @best_source = 'dss', @best_date = @dss_date, @best_qty = @dss_qty, @best_dataseg = @dss_dataseg, @best_ord = @dss_ord, @best_quote = @dss_quote, @best_part = @dss_part;
-- ,@best_dataseg NVARCHAR(100) = NULL IF @mrq_date IS NOT NULL AND (@best_date IS NULL OR @mrq_date > @best_date)
-- ,@best_ord NVARCHAR(20) = NULL SELECT @best_price = @mrq_price, @best_source = 'mrq', @best_date = @mrq_date, @best_qty = @mrq_qty, @best_dataseg = @mrq_dataseg, @best_ord = @mrq_ord, @best_quote = @mrq_quote, @best_part = @mrq_part;
-- ,@best_quote NVARCHAR(20) = NULL IF @mrs_date IS NOT NULL AND (@best_date IS NULL OR @mrs_date > @best_date)
-- ,@best_part NVARCHAR(100) = NULL; SELECT @best_price = @mrs_price, @best_source = 'mrs', @best_date = @mrs_date, @best_qty = @mrs_qty, @best_dataseg = @mrs_dataseg, @best_ord = @mrs_ord, @best_quote = @mrs_quote, @best_part = @mrs_part;
-- IF @dsq_date IS NOT NULL
-- SELECT @best_price = @dsq_price, @best_source = 'dsq', @best_date = @dsq_date, @best_qty = @dsq_qty, @best_dataseg = @dsq_dataseg, @best_ord = @dsq_ord, @best_quote = @dsq_quote, @best_part = @dsq_part;
-- IF @dss_date IS NOT NULL AND (@best_date IS NULL OR @dss_date > @best_date)
-- SELECT @best_price = @dss_price, @best_source = 'dss', @best_date = @dss_date, @best_qty = @dss_qty, @best_dataseg = @dss_dataseg, @best_ord = @dss_ord, @best_quote = @dss_quote, @best_part = @dss_part;
-- IF @mrq_date IS NOT NULL AND (@best_date IS NULL OR @mrq_date > @best_date)
-- SELECT @best_price = @mrq_price, @best_source = 'mrq', @best_date = @mrq_date, @best_qty = @mrq_qty, @best_dataseg = @mrq_dataseg, @best_ord = @mrq_ord, @best_quote = @mrq_quote, @best_part = @mrq_part;
-- IF @mrs_date IS NOT NULL AND (@best_date IS NULL OR @mrs_date > @best_date)
-- SELECT @best_price = @mrs_price, @best_source = 'mrs', @best_date = @mrs_date, @best_qty = @mrs_qty, @best_dataseg = @mrs_dataseg, @best_ord = @mrs_ord, @best_quote = @mrs_quote, @best_part = @mrs_part;
-- IF @best_price IS NOT NULL IF @best_price IS NOT NULL
-- INSERT INTO @result VALUES (@best_price, @best_source, @best_date, @best_qty, @best_dataseg, @best_ord, @best_quote, @best_part); INSERT INTO @result VALUES (@best_price, @best_source, @best_date, @best_qty, @best_dataseg, @best_ord, @best_quote, @best_part);
RETURN; RETURN;
END END

View File

@ -12,7 +12,7 @@ DECLARE
BEGIN BEGIN
-- Central control for age threshold -- Central control for age threshold
DECLARE DECLARE
age_threshold INTERVAL := INTERVAL '18 months'; age_threshold INTERVAL := INTERVAL '1 year';
dsq_date DATE := NULL; dsq_date DATE := NULL;
dss_date DATE := NULL; dss_date DATE := NULL;
mrq_date DATE := NULL; mrq_date DATE := NULL;
@ -35,31 +35,40 @@ BEGIN
END IF; END IF;
-- 1. Prefer the most recent of dss/dsq if either is within the age threshold -- 1. Prefer the most recent of dss/dsq if either is within the age threshold
IF dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold) THEN IF (dsq_date IS NOT NULL AND dsq_date > (CURRENT_DATE - age_threshold))
result := dss || jsonb_build_object('source', 'dss'); OR (dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold)) THEN
IF dsq_date IS NOT NULL AND (dss_date IS NULL OR dsq_date >= dss_date) AND dsq_date > (CURRENT_DATE - age_threshold) THEN
result := dsq || jsonb_build_object('source', 'dsq');
ELSIF dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold) THEN
result := dss || jsonb_build_object('source', 'dss');
END IF;
-- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists -- 2. If both dss/dsq are older than the threshold, use the most recent of mrs/mrq if either exists
ELSIF mrs_date IS NOT NULL AND mrs_date > (CURRENT_DATE - age_threshold) THEN ELSIF (mrq_date IS NOT NULL OR mrs_date IS NOT NULL) THEN
result := mrs || jsonb_build_object('source', 'mrs'); IF mrq_date IS NOT NULL AND (mrs_date IS NULL OR mrq_date >= mrs_date) THEN
result := mrq || jsonb_build_object('source', 'mrq');
ELSIF mrs_date IS NOT NULL THEN
result := mrs || jsonb_build_object('source', 'mrs');
END IF;
-- 3. If all are at least as old as the threshold, pick the least oldest price available -- 3. If all are at least as old as the threshold, pick the least oldest price available
ELSE ELSE
best := NULL; best := NULL;
best_date := NULL; best_date := NULL;
-- IF dsq_date IS NOT NULL THEN IF dsq_date IS NOT NULL THEN
-- best := dsq || jsonb_build_object('source', 'dsq'); best := dsq || jsonb_build_object('source', 'dsq');
-- best_date := dsq_date; best_date := dsq_date;
-- END IF; END IF;
-- IF dss_date IS NOT NULL AND (best_date IS NULL OR dss_date > best_date) THEN IF dss_date IS NOT NULL AND (best_date IS NULL OR dss_date > best_date) THEN
-- best := dss || jsonb_build_object('source', 'dss'); best := dss || jsonb_build_object('source', 'dss');
-- best_date := dss_date; best_date := dss_date;
-- END IF; END IF;
-- IF mrq_date IS NOT NULL AND (best_date IS NULL OR mrq_date > best_date) THEN IF mrq_date IS NOT NULL AND (best_date IS NULL OR mrq_date > best_date) THEN
-- best := mrq || jsonb_build_object('source', 'mrq'); best := mrq || jsonb_build_object('source', 'mrq');
-- best_date := mrq_date; best_date := mrq_date;
-- END IF; END IF;
-- IF mrs_date IS NOT NULL AND (best_date IS NULL OR mrs_date > best_date) THEN IF mrs_date IS NOT NULL AND (best_date IS NULL OR mrs_date > best_date) THEN
-- best := mrs || jsonb_build_object('source', 'mrs'); best := mrs || jsonb_build_object('source', 'mrs');
-- best_date := mrs_date; best_date := mrs_date;
-- END IF; END IF;
result := best; result := best;
END IF; END IF;
RETURN result; RETURN result;

View File

@ -46,8 +46,6 @@ CREATE TABLE pricequote.queue (
list_relevance TEXT, list_relevance TEXT,
guidance_price NUMERIC, guidance_price NUMERIC,
guidance_reason TEXT, guidance_reason TEXT,
approval_price NUMERIC,
approval_reason TEXT,
expl JSONB, expl JSONB,
ui_json JSONB ui_json JSONB
); );
@ -68,7 +66,6 @@ BEGIN
-- 1) Seed queue from matrix -- 1) Seed queue from matrix
-------------------------------------------------------------------- --------------------------------------------------------------------
DELETE FROM pricequote.queue; DELETE FROM pricequote.queue;
-- 1:30
INSERT INTO pricequote.queue (bill, ship, part, stlc, v1ds, vol, expl, ui_json) INSERT INTO pricequote.queue (bill, ship, part, stlc, v1ds, vol, expl, ui_json)
SELECT DISTINCT SELECT DISTINCT
@ -87,7 +84,7 @@ BEGIN
AND o.version IN ('Actual', 'Forecast', 'Quotes') AND o.version IN ('Actual', 'Forecast', 'Quotes')
AND o.part IS NOT NULL AND o.part IS NOT NULL
AND SUBSTRING(o.glec, 1, 1) <= '2'; AND SUBSTRING(o.glec, 1, 1) <= '2';
-- 2:12 0:38 -- 46 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 2) Enrich: chan, tier, cust, pltq, plevel, partgroup (+stlc fix) -- 2) Enrich: chan, tier, cust, pltq, plevel, partgroup (+stlc fix)
@ -129,7 +126,7 @@ BEGIN
plevel = s.plevel, plevel = s.plevel,
partgroup = s.partgroup, partgroup = s.partgroup,
stlc = COALESCE(q.stlc, s.stlc_fix); stlc = COALESCE(q.stlc, s.stlc_fix);
-- 4:51 0:17 -- 16 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 3) Scenario fields from item master: part_v1ds, v0ds, orig costs -- 3) Scenario fields from item master: part_v1ds, v0ds, orig costs
@ -149,7 +146,7 @@ BEGIN
END END
FROM "CMS.CUSLG".itemm i0 FROM "CMS.CUSLG".itemm i0
WHERE i0.item = q.part; WHERE i0.item = q.part;
-- 3:21 0:20 -- 16 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 4) History: store hist, extract last_* with precedence helper -- 4) History: store hist, extract last_* with precedence helper
@ -187,7 +184,7 @@ BEGIN
AND lp2.partgroup = q2.partgroup AND lp2.partgroup = q2.partgroup
) AS x ) AS x
WHERE q.ctid = x.ctid; WHERE q.ctid = x.ctid;
-- 7:32 1:31 -- 2 min 3 sec
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 5) Target (requested v1ds): tprice, tmath, volume_range -- 5) Target (requested v1ds): tprice, tmath, volume_range
@ -197,14 +194,14 @@ BEGIN
tprice = tp.price, tprice = tp.price,
tmath = to_json(tp.math), tmath = to_json(tp.math),
volume_range = tp.vol::TEXT volume_range = tp.vol::TEXT
FROM pricequote.target_prices_base tp FROM pricequote.target_prices tp
WHERE WHERE
tp.stlc = q.stlc tp.stlc = q.stlc
AND tp.ds = q.v1ds AND tp.ds = q.v1ds
AND tp.chan = q.chan AND tp.chan = q.chan
AND tp.tier = q.tier AND tp.tier = q.tier
AND FLOOR(q.vol / NULLIF(q.pltq, 0))::INT <@ tp.vol; AND FLOOR(q.vol / NULLIF(q.pltq, 0))::INT <@ tp.vol;
-- 2:51 0:15 -- 22 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 6) Target for last_dataseg (tprice_last) -- 6) Target for last_dataseg (tprice_last)
@ -212,15 +209,15 @@ BEGIN
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
tprice_last = tp2.price tprice_last = tp2.price
FROM pricequote.target_prices_base tp2 FROM pricequote.target_prices tp2
WHERE WHERE
q.last_dataseg IS NOT NULL q.last_dataseg IS NOT NULL
AND tp2.stlc = q.stlc AND tp2.stlc = q.stlc
AND tp2.ds = q.last_dataseg AND tp2.ds = q.last_dataseg
AND tp2.chan = q.chan AND tp2.chan = q.chan
AND tp2.tier = q.tier AND tp2.tier = q.tier
AND FLOOR(q.last_qty / NULLIF(q.pltq, 0))::INT <@ tp2.vol; AND FLOOR(q.vol / NULLIF(q.pltq, 0))::INT <@ tp2.vol;
-- 1:26 0:08 -- 17 sec
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 7) Cost data for requested v1ds and last_dataseg -- 7) Cost data for requested v1ds and last_dataseg
@ -253,7 +250,7 @@ BEGIN
ON v0l.stlc = q2.stlc AND v0l.v0ds = q2.last_v0ds ON v0l.stlc = q2.stlc AND v0l.v0ds = q2.last_v0ds
) AS s ) AS s
WHERE q.ctid = s.ctid; WHERE q.ctid = s.ctid;
-- 4:15 0:25 -- 28 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 8) List price (lowest valid); allow open-ended ranges (vb_to IS NULL) -- 8) List price (lowest valid); allow open-ended ranges (vb_to IS NULL)
@ -283,7 +280,7 @@ BEGIN
listcode = p.jcplcd listcode = p.jcplcd
FROM best_price p FROM best_price p
WHERE q.ctid = p.ctid; WHERE q.ctid = p.ctid;
-- 2:48 0:18 -- 18 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 9) Normalize last (when last_dataseg != v1ds) + effective list flags -- 9) Normalize last (when last_dataseg != v1ds) + effective list flags
@ -300,7 +297,7 @@ BEGIN
AND q.curstd_last IS NOT NULL AND q.curstd_last IS NOT NULL
AND q.curstd IS NOT NULL AND q.curstd IS NOT NULL
AND q.curstd_last <> 0 AND q.curstd_last <> 0
THEN ROUND(q.curstd / q.curstd_last,5) THEN q.curstd / q.curstd_last
END, END,
last_premium_method = CASE last_premium_method = CASE
WHEN q.last_isdiff IS NOT NULL WHEN q.last_isdiff IS NOT NULL
@ -331,7 +328,7 @@ BEGIN
END, END,
listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END, listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END,
list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END; list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END;
-- 2:22 0:23 -- 21 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 10) Guidance using normalized last + effective list -- 10) Guidance using normalized last + effective list
@ -339,35 +336,22 @@ BEGIN
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
guidance_price = s.guidance_price, guidance_price = s.guidance_price,
guidance_reason = s.guidance_reason, guidance_reason = s.guidance_reason
approval_price = s.approval_price,
approval_reason = s.approval_reason
FROM ( FROM (
SELECT SELECT
q2.ctid, q2.ctid,
g.guidance_price, g.guidance_price,
g.guidance_reason, g.guidance_reason
a.approval_price, FROM pricequote.queue q2
a.approval_reason JOIN LATERAL pricequote.guidance_logic(
FROM q2.tprice,
pricequote.queue q2 q2.last_price_norm,
JOIN LATERAL pricequote.guidance_logic( q2.listprice_eff,
q2.tprice, q2.last_date
q2.last_price_norm, ) g ON TRUE
q2.listprice_eff,
q2.last_date,
1.0, 1.0, 1.0
) g ON TRUE
JOIN LATERAL pricequote.approval_logic(
q2.tprice,
q2.last_price_norm,
q2.listprice_eff,
q2.last_date,
1.0, 1.0, 1.0
) a ON TRUE
) s ) s
WHERE q.ctid = s.ctid; WHERE q.ctid = s.ctid;
-- 4:33 0:39 -- 31 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 11) Build expl and ui_json identical to single_price_call -- 11) Build expl and ui_json identical to single_price_call
@ -422,9 +406,7 @@ BEGIN
'list_relevance', q.list_relevance 'list_relevance', q.list_relevance
), ),
'guidance_price', q.guidance_price, 'guidance_price', q.guidance_price,
'guidance_reason', q.guidance_reason, 'guidance_reason', q.guidance_reason
'approval_price', q.approval_price,
'approval_reason', q.approval_reason
), ),
ui_json = jsonb_build_object( ui_json = jsonb_build_object(
'details', jsonb_build_array( 'details', jsonb_build_array(
@ -515,7 +497,7 @@ BEGIN
), ),
'data', q.expl 'data', q.expl
); );
-- 7:59 2:17 -- 2 minutes 33 seconds
-------------------------------------------------------------------- --------------------------------------------------------------------
-- 12) Merge back into matrix (store both expl and ui) -- 12) Merge back into matrix (store both expl and ui)
@ -539,7 +521,7 @@ BEGIN
AND o.version IN ('Actual', 'Forecast', 'Quotes') AND o.version IN ('Actual', 'Forecast', 'Quotes')
AND o.part IS NOT NULL AND o.part IS NOT NULL
AND SUBSTRING(o.glec, 1, 1) <= '2'; AND SUBSTRING(o.glec, 1, 1) <= '2';
-- 14:13 10:09 -- 9 minutes 35 seconds
RAISE NOTICE 'Queue processing complete.'; RAISE NOTICE 'Queue processing complete.';
END; END;

View File

@ -136,8 +136,6 @@ BEGIN
------------step 6 compute guidance------------ ------------step 6 compute guidance------------
guidance_price NUMERIC(20,5), guidance_price NUMERIC(20,5),
guidance_reason NVARCHAR(MAX), guidance_reason NVARCHAR(MAX),
approval_price NUMERIC(20,5),
approval_reason NVARCHAR(MAX),
------------step 7 build json------------------ ------------step 7 build json------------------
expl NVARCHAR(MAX), expl NVARCHAR(MAX),
ui_json NVARCHAR(MAX) ui_json NVARCHAR(MAX)
@ -175,7 +173,7 @@ BEGIN
WHEN 'DIS' THEN bc.dba WHEN 'DIS' THEN bc.dba
ELSE sc.dba ELSE sc.dba
END END
ELSE bc.dba ELSE q.bill
END, END,
pltq = i.mpck, pltq = i.mpck,
plevel = plevel =
@ -200,8 +198,8 @@ BEGIN
calculated_pallets = FLOOR(q.vol / NULLIF(i.mpck, 0)), calculated_pallets = FLOOR(q.vol / NULLIF(i.mpck, 0)),
exact_pallets = CAST(ROUND(q.vol / NULLIF(i.mpck, 0), 5) AS NUMERIC(20,5)) exact_pallets = CAST(ROUND(q.vol / NULLIF(i.mpck, 0), 5) AS NUMERIC(20,5))
FROM @queue q FROM @queue q
LEFT JOIN pricing.cust bc ON bc.code = q.bill LEFT JOIN rlarp.cust bc ON bc.code = q.bill
LEFT JOIN pricing.cust sc ON sc.code = q.ship LEFT JOIN rlarp.cust sc ON sc.code = q.ship
LEFT JOIN CMSInterfaceIn.[CMS.CUSLG].itemm i ON i.item = q.part; LEFT JOIN CMSInterfaceIn.[CMS.CUSLG].itemm i ON i.item = q.part;
@ -279,16 +277,16 @@ BEGIN
,curstd_last = CASE WHEN last_isdiff = '' THEN q.curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END ,curstd_last = CASE WHEN last_isdiff = '' THEN q.curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END
,futstd_last = CASE WHEN last_isdiff = '' THEN q.futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END ,futstd_last = CASE WHEN last_isdiff = '' THEN q.futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END
FROM @queue q FROM @queue q
LEFT JOIN pricing.cost_v1ds v1 ON LEFT JOIN rlarp.cost_v1ds v1 ON
v1.stlc = q.stlc v1.stlc = q.stlc
AND v1.v1ds = q.v1ds AND v1.v1ds = q.v1ds
LEFT JOIN pricing.cost_v0ds v0 ON LEFT JOIN rlarp.cost_v0ds v0 ON
v0.stlc = q.stlc v0.stlc = q.stlc
AND v0.v0ds = q.v0ds AND v0.v0ds = q.v0ds
LEFT JOIN pricing.cost_v1ds v1l ON LEFT JOIN rlarp.cost_v1ds v1l ON
v1l.stlc = q.stlc v1l.stlc = q.stlc
AND v1l.v1ds = q.last_dataseg AND v1l.v1ds = q.last_dataseg
LEFT JOIN pricing.cost_v0ds v0l ON LEFT JOIN rlarp.cost_v0ds v0l ON
v0l.stlc = q.stlc v0l.stlc = q.stlc
AND v0l.v0ds = q.last_v0ds; AND v0l.v0ds = q.last_v0ds;
@ -358,7 +356,7 @@ BEGIN
SET SET
listcode = rp.jcplcd listcode = rp.jcplcd
,listprice = rp.price ,listprice = rp.price
,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE rp.price END ,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END
,list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END ,list_relevance = CASE WHEN q.customized <> '' THEN 'Ignore - Customized' ELSE '' END
,list_from = vb_from ,list_from = vb_from
FROM @queue q FROM @queue q
@ -379,33 +377,13 @@ BEGIN
SET SET
guidance_price = g.guidance_price guidance_price = g.guidance_price
,guidance_reason = g.guidance_reason ,guidance_reason = g.guidance_reason
,approval_price = a.approval_price
,approval_reason = a.approval_reason
FROM @queue q FROM @queue q
CROSS APPLY pricing.guidance_logic( CROSS APPLY pricing.guidance_logic(
TRY_CAST(q.tprice AS NUMERIC(20,5)), TRY_CAST(q.tprice AS NUMERIC(20,5)),
TRY_CAST(q.last_price_norm AS NUMERIC(20,5)), TRY_CAST(q.last_price_norm AS NUMERIC(20,5)),
TRY_CAST(q.listprice_eff AS NUMERIC(20,5)), TRY_CAST(q.listprice_eff AS NUMERIC(20,5)),
TRY_CAST(q.last_date AS DATE), TRY_CAST(q.last_date AS DATE)
--allowable price drop percent ) g;
1.0,
--cap on last price
1.0,
--cap on list percent
1.0
) g
CROSS APPLY pricing.approval_logic(
TRY_CAST(q.tprice AS NUMERIC(20,5)),
TRY_CAST(q.last_price_norm AS NUMERIC(20,5)),
TRY_CAST(q.listprice_eff AS NUMERIC(20,5)),
TRY_CAST(q.last_date AS DATE),
--allowable price drop percent
1.0,
--cap on last price
1.0,
--cap on list percent
1.0
) a;
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Step 8: Assemble structured 'expl' JSON from populated columns. -- Step 8: Assemble structured 'expl' JSON from populated columns.
@ -452,8 +430,6 @@ BEGIN
,q.list_relevance AS list_relevance ,q.list_relevance AS list_relevance
,q.guidance_price AS guidance_price ,q.guidance_price AS guidance_price
,q.guidance_reason AS guidance_reason ,q.guidance_reason AS guidance_reason
,q.approval_price AS approval_price
,q.approval_reason AS approval_reason
-- JSON_QUERY(hist) AS [history] -- JSON_QUERY(hist) AS [history]
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) )
@ -468,28 +444,11 @@ BEGIN
( (
SELECT SELECT
panel.label, panel.label,
panel.detailLevel,
JSON_QUERY(panel.details) AS details JSON_QUERY(panel.details) AS details
FROM ( FROM (
-- Cost
SELECT
'Cost' AS label,
5 AS detailLevel,
(
SELECT
'Current Std' AS label,
5 AS detailLevel,
COALESCE(q.curstd,0) AS value,
'currency' AS type,
'' AS note
FOR JSON PATH
) AS details
UNION ALL
-- History Panel -- History Panel
SELECT SELECT
'History' AS label, 'History' AS label,
1 as detailLevel,
( (
SELECT SELECT
----------------------label------------------------------------------------ ----------------------label------------------------------------------------
@ -499,14 +458,12 @@ BEGIN
CASE ISNULL(q.last_source, '') CASE ISNULL(q.last_source, '')
WHEN 'mrq' THEN 'Similar Quote' WHEN 'mrq' THEN 'Similar Quote'
WHEN 'mrs' THEN 'Similar Sale' WHEN 'mrs' THEN 'Similar Sale'
WHEN 'dsq' THEN 'Last Quote' WHEN 'dsq' THEN 'Last Sale'
WHEN 'dss' THEN 'Last Sale' WHEN 'dss' THEN 'Last Quote'
ELSE '' ELSE ''
END END
ELSE 'No Recent' ELSE 'No Recent'
END AS label, END AS label,
----------------------detail-----------------------------------------------
1 AS detailLevel,
----------------------value------------------------------------------------ ----------------------value------------------------------------------------
ISNULL(q.last_price, 0) AS value, ISNULL(q.last_price, 0) AS value,
----------------------type------------------------------------------------- ----------------------type-------------------------------------------------
@ -528,12 +485,8 @@ BEGIN
END, END,
ISNULL(' | ' + CONVERT(varchar(10), q.last_date, 120), ''), ISNULL(' | ' + CONVERT(varchar(10), q.last_date, 120), ''),
' | Qty ' + format(q.last_qty,'#,###'), ' | Qty ' + format(q.last_qty,'#,###'),
CASE WHEN COALESCE(last_isdiff,'') <> '' CASE WHEN last_isdiff <> '' THEN ' | Normalized To: ' + cast(last_price_norm AS varchar(10)) ELSE '' END,
THEN ' | ' /*+ q.last_premium_method*/ + ' Last Target = ' + format(q.tprice_last,'0.0####') + ' | Current Target ' + format(q.tprice,'0.0####')
' | Normalized To: ' + cast(last_price_norm AS varchar(10))
+ ' | ' /*+ q.last_premium_method*/ + ' Last Target = ' + format(q.tprice_last,'0.0####') + ' | Current Target = ' + format(q.tprice,'0.0####')
ELSE ''
END
) )
ELSE ELSE
'' ''
@ -547,30 +500,25 @@ BEGIN
-- List Panel -- List Panel
SELECT SELECT
'List' AS label, 'List' AS label,
1 AS detailLevel,
( (
SELECT SELECT
COALESCE('Code: ' + q.listcode,'No List') AS label, COALESCE('Code: ' + q.listcode,'No List') AS label,
1 AS detailLevel,
COALESCE(q.listprice,0) AS value, COALESCE(q.listprice,0) AS value,
'currency' AS type, 'currency' AS type,
COALESCE('List Min Qty: ' + format(q.list_from,'#,###'),'') + CASE WHEN q.list_relevance = '' THEN '' ELSE ' (' + q.list_relevance + ')' END AS note COALESCE('List Min Qty: ' + format(q.list_from,'#,###'),'') + CASE WHEN q.list_relevance = '' THEN '' ELSE ' (' + q.list_relevance + ')' END AS note
FOR JSON PATH FOR JSON PATH
) AS details )
UNION ALL UNION ALL
-- Target Support Panel -- Target Support Panel
SELECT SELECT
'Target Calculation' AS label, 'Target Calculation' AS label,
5 AS detailLevel,
( (
SELECT * FROM ( SELECT * FROM (
SELECT SELECT
----------------------label------------------------------------------------ ----------------------label------------------------------------------------
CASE WHEN value <> '' THEN replace(RTRIM(SUBSTRING(value,1,18)),'Anchor:', '') ELSE 'No Target' END AS label, CASE WHEN value <> '' THEN replace(RTRIM(SUBSTRING(value,1,18)),'Anchor:', '') ELSE 'No Target' END AS label,
----------------------detailLevel------------------------------------------
10 AS detailLevel,
----------------------value------------------------------------------------ ----------------------value------------------------------------------------
CASE WHEN value <> '' THEN CASE WHEN value <> '' THEN
TRY_CAST(SUBSTRING(value,23,7) AS NUMERIC(20,5)) TRY_CAST(SUBSTRING(value,23,7) AS NUMERIC(20,5))
@ -595,8 +543,6 @@ BEGIN
SELECT SELECT
----------------------label------------------------------------------------ ----------------------label------------------------------------------------
'Target' AS label, 'Target' AS label,
----------------------detailLevel------------------------------------------
5 AS detailLevel,
----------------------value------------------------------------------------ ----------------------value------------------------------------------------
tprice AS value, tprice AS value,
----------------------type------------------------------------------------- ----------------------type-------------------------------------------------
@ -613,11 +559,9 @@ BEGIN
-- Guidance Panel -- Guidance Panel
SELECT SELECT
'Guidance' AS label, 'Guidance' AS label,
1 AS detailLevel,
( (
SELECT SELECT
'Price' AS label, 'Price' AS label,
1 AS detailLevel,
COALESCE(q.guidance_price,0) AS value, COALESCE(q.guidance_price,0) AS value,
'currency' AS type, 'currency' AS type,
q.guidance_reason AS note q.guidance_reason AS note
@ -634,5 +578,5 @@ BEGIN
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Final: Return all calculated fields and JSON payloads. -- Final: Return all calculated fields and JSON payloads.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
SELECT guidance_price, ui_json FROM @queue; SELECT guidance_price, ui_json FROM @queue;
END; END;

View File

@ -35,7 +35,7 @@
==================================================================================== ====================================================================================
*/ */
-- DROP FUNCTION pricequote.single_price_call(text,text,text,text,numeric) CASCADE; --DROP FUNCTION pricequote.single_price_call(text,text,text,text,numeric) CASCADE;
CREATE OR REPLACE FUNCTION pricequote.single_price_call( CREATE OR REPLACE FUNCTION pricequote.single_price_call(
_bill TEXT, _bill TEXT,
@ -89,8 +89,6 @@ RETURNS TABLE (
list_relevance TEXT, list_relevance TEXT,
guidance_price NUMERIC, guidance_price NUMERIC,
guidance_reason TEXT, guidance_reason TEXT,
approval_price NUMERIC,
approval_reason TEXT,
expl JSONB, expl JSONB,
ui_json JSONB ui_json JSONB
) AS $$ ) AS $$
@ -150,8 +148,6 @@ DECLARE
------------step 6 compute guidance------------ ------------step 6 compute guidance------------
_guidance_price NUMERIC; _guidance_price NUMERIC;
_guidance_reason TEXT; _guidance_reason TEXT;
_approval_price NUMERIC;
_approval_reason TEXT;
------------step 7 build json------------------ ------------step 7 build json------------------
_expl JSONB := '{}'::jsonb; _expl JSONB := '{}'::jsonb;
_ui_json JSONB := '{}'::jsonb; _ui_json JSONB := '{}'::jsonb;
@ -269,7 +265,7 @@ BEGIN
,_tmath ,_tmath
,_volume_range ,_volume_range
FROM FROM
pricequote.target_prices_base tp pricequote.target_prices tp
WHERE WHERE
tp.stlc = _stlc tp.stlc = _stlc
AND tp.ds = _v1ds AND tp.ds = _v1ds
@ -287,36 +283,38 @@ BEGIN
INTO INTO
_tprice_last _tprice_last
FROM FROM
pricequote.target_prices_base tp pricequote.target_prices tp
WHERE WHERE
tp.stlc = _stlc tp.stlc = _stlc
AND tp.ds = _last_dataseg AND tp.ds = _last_dataseg
AND tp.chan = _chan AND tp.chan = _chan
AND tp.tier = _tier AND tp.tier = _tier
AND FLOOR(_last_qty / NULLIF(_pltq, 0))::int <@ tp.vol; AND FLOOR(_vol / NULLIF(_pltq, 0))::int <@ tp.vol;
------------------------------------------------------------------ ------------------------------------------------------------------
-- Step 4: Cost data for normalization -- Step 4: Cost data for normalization
------------------------------------------------------------------ ------------------------------------------------------------------
-- Current/future standard for requested v1ds
SELECT
curstdus, futstdus
INTO
_curstd, _futstd
FROM
"CMS.CUSLG".itemm i
WHERE
i.item = _part
AND i.v1ds = _v1ds;
SELECT -- Current/future standard for last_dataseg
ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _curstd_orig ELSE COALESCE(v1.curstdus, v0.curstdus) END,5) AS curstd, SELECT
ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _futstd_orig ELSE COALESCE(v1.futstdus, v0.futstdus) END,5) AS futstd, curstdus, futstdus
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END,5) AS curstd_last, INTO
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END,5) AS futstd_last _curstd_last, _futstd_last
INTO FROM
_curstd, _futstd, _curstd_last, _futstd_last "CMS.CUSLG".itemm i
FROM (VALUES (1)) AS x(dummy) WHERE
LEFT JOIN rlarp.cost_v1ds v1 i.item = _part
ON v1.stlc = _stlc AND v1.v1ds = _v1ds AND i.v1ds = _last_dataseg;
LEFT JOIN rlarp.cost_v0ds v0
ON v0.stlc = _stlc AND v0.v0ds = _v0ds
LEFT JOIN rlarp.cost_v1ds v1l
ON v1l.stlc = _stlc AND v1l.v1ds = _last_dataseg
LEFT JOIN rlarp.cost_v0ds v0l
ON v0l.stlc = _stlc AND v0l.v0ds = _last_v0ds
LIMIT 1;
------------------------------------------------------------------ ------------------------------------------------------------------
-- Step 5: Normalize last price if needed -- Step 5: Normalize last price if needed
@ -327,7 +325,7 @@ BEGIN
_last_price_norm := ROUND(_last_price * (_tprice / _tprice_last), 5); _last_price_norm := ROUND(_last_price * (_tprice / _tprice_last), 5);
_last_premium_method := 'Target Price Ratio'; _last_premium_method := 'Target Price Ratio';
ELSIF _curstd_last IS NOT NULL AND _curstd IS NOT NULL AND _curstd_last <> 0 THEN ELSIF _curstd_last IS NOT NULL AND _curstd IS NOT NULL AND _curstd_last <> 0 THEN
_last_premium := ROUND(_curstd / _curstd_last, 5); _last_premium := _curstd / _curstd_last;
_last_price_norm := ROUND(_last_price * (_curstd / _curstd_last), 5); _last_price_norm := ROUND(_last_price * (_curstd / _curstd_last), 5);
_last_premium_method := 'Cost Ratio'; _last_premium_method := 'Cost Ratio';
ELSE ELSE
@ -376,16 +374,10 @@ BEGIN
SELECT SELECT
gl.guidance_price gl.guidance_price
,gl.guidance_reason ,gl.guidance_reason
,al.approval_price
,al.approval_reason
INTO INTO
_guidance_price _guidance_price
,_guidance_reason ,_guidance_reason
,_approval_price FROM pricequote.guidance_logic(_tprice, _last_price_norm, _listprice_eff, _last_date) gl;
,_approval_reason
FROM
pricequote.guidance_logic(_tprice, _last_price_norm, _listprice_eff, _last_date, 1.0, 1.0, 1.0) gl
CROSS JOIN pricequote.approval_logic(_tprice, _last_price_norm, _listprice_eff, _last_date, 1.0, 1.0, 1.0) al;
------------------------------------------------------------------ ------------------------------------------------------------------
-- Step 8: Build explanation JSON -- Step 8: Build explanation JSON
@ -433,8 +425,7 @@ BEGIN
'targets', 'targets',
jsonb_build_object( jsonb_build_object(
'target_price', _tprice, 'target_price', _tprice,
'target_math', _tmath, 'target_math', _tmath
'volume_range', _volume_range
), ),
'list', 'list',
jsonb_build_object( jsonb_build_object(
@ -444,9 +435,7 @@ BEGIN
'list_relevance', _list_relevance 'list_relevance', _list_relevance
), ),
'guidance_price', _guidance_price, 'guidance_price', _guidance_price,
'guidance_reason', _guidance_reason, 'guidance_reason', _guidance_reason
'approval_price', _approval_price,
'approval_reason', _approval_reason
); );
------------------------------------------------------------------ ------------------------------------------------------------------
@ -459,11 +448,9 @@ BEGIN
------------------------------------------ ------------------------------------------
jsonb_build_object( jsonb_build_object(
'label', 'History', 'label', 'History',
10, 'detailLevel',
'details', jsonb_build_array( 'details', jsonb_build_array(
jsonb_build_object( jsonb_build_object(
'label', CASE WHEN _last_price IS NOT NULL THEN 'Last Sale: ' || _last_date ELSE 'No Recent' END, 'label', CASE WHEN _last_price IS NOT NULL THEN 'Last Sale: ' || _last_date ELSE 'No Recent' END,
'detailLevel', 10,
'value', COALESCE(_last_price,0), 'value', COALESCE(_last_price,0),
'type', 'currency', 'type', 'currency',
'note', CASE WHEN _last_price IS NOT NULL THEN 'note', CASE WHEN _last_price IS NOT NULL THEN
@ -481,7 +468,6 @@ BEGIN
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN ||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object( COALESCE(jsonb_build_array(jsonb_build_object(
'label','Price Difference', 'label','Price Difference',
'detailLevel',10,
'value', _last_premium, 'value', _last_premium,
'type','percent', 'type','percent',
'note', _last_premium_method 'note', _last_premium_method
@ -492,7 +478,6 @@ BEGIN
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN ||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object( COALESCE(jsonb_build_array(jsonb_build_object(
'label','Adjusted Price', 'label','Adjusted Price',
'detailLevel',10,
'value', _last_price_norm, 'value', _last_price_norm,
'type','currency', 'type','currency',
'note','normalized to ' || _v1ds 'note','normalized to ' || _v1ds
@ -506,11 +491,9 @@ BEGIN
------------------------------------------ ------------------------------------------
jsonb_build_object( jsonb_build_object(
'label', 'List', 'label', 'List',
'detailLevel',10,
'details', jsonb_build_array( 'details', jsonb_build_array(
jsonb_build_object( jsonb_build_object(
'label', 'List:' || COALESCE(_list_code, ''), 'label', 'List:' || COALESCE(_list_code, ''),
'detailLevel',10,
'value', _list_price, 'value', _list_price,
'type', 'currency', 'type', 'currency',
'note', _list_relevance 'note', _list_relevance
@ -522,7 +505,6 @@ BEGIN
------------------------------------------ ------------------------------------------
jsonb_build_object( jsonb_build_object(
'label', 'Target Calculation', 'label', 'Target Calculation',
'detailLevel',10,
'details', 'details',
-- jsonb_build_array( -- jsonb_build_array(
( (
@ -531,8 +513,6 @@ BEGIN
jsonb_build_object( jsonb_build_object(
----------------------label------------------------------------------------ ----------------------label------------------------------------------------
'label',CASE WHEN value <> '' THEN RTRIM(SUBSTRING(value,1,18)) ELSE 'No Target' END, 'label',CASE WHEN value <> '' THEN RTRIM(SUBSTRING(value,1,18)) ELSE 'No Target' END,
----------------------detailLevel------------------------------------------
'detailLevel',10,
----------------------value------------------------------------------------ ----------------------value------------------------------------------------
'value',CASE WHEN value <> '' THEN 'value',CASE WHEN value <> '' THEN
SUBSTRING(value,23,7)::NUMERIC(20,5) + SUBSTRING(value,23,7)::NUMERIC(20,5) +
@ -566,11 +546,9 @@ BEGIN
------------------------------------------ ------------------------------------------
jsonb_build_object( jsonb_build_object(
'label', 'Guidance', 'label', 'Guidance',
'detailLevel',10,
'details', jsonb_build_array( 'details', jsonb_build_array(
jsonb_build_object( jsonb_build_object(
'label', 'Price', 'label', 'Price',
'detailLevel',10,
'value', COALESCE(_guidance_price,0), 'value', COALESCE(_guidance_price,0),
'type', 'currency', 'type', 'currency',
'note', COALESCE(_guidance_reason,'') 'note', COALESCE(_guidance_reason,'')
@ -594,7 +572,6 @@ BEGIN
_tprice, _tmath, _volume_range, _tprice, _tmath, _volume_range,
_list_price, _list_code, _listprice_eff, _list_relevance, _list_price, _list_code, _listprice_eff, _list_relevance,
_guidance_price, _guidance_reason, _guidance_price, _guidance_reason,
_approval_price, _approval_reason,
_expl, _ui_json; _expl, _ui_json;
END; END;
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;

View File

@ -105,9 +105,6 @@ lq AS MATERIALIZED (
-- ,jsonb_pretty(pricing) pricing -- ,jsonb_pretty(pricing) pricing
,p.guidance_price ,p.guidance_price
,p.guidance_reason ,p.guidance_reason
,p.approval_price
,p.approval_reason
,p.tprice guidance_target
,jsonb_pretty(p.ui_json->'data') expl ,jsonb_pretty(p.ui_json->'data') expl
FROM FROM
lq lq
@ -178,4 +175,4 @@ lq AS MATERIALIZED (
WHERE WHERE
COALESCE(g.bestprice,1) = 1 COALESCE(g.bestprice,1) = 1
) )
SELECT * FROM hist --LIMIT 1000--WHERE qid = 108655 SELECT * FROM hist --WHERE qid = 108655

View File

@ -1,275 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_lastprice
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most errors
BEGIN TRY
BEGIN TRAN; -- start transaction
--------------------------------------------------------------------------------
-- Reset target tables
--------------------------------------------------------------------------------
--DROP TABLE IF EXISTS pricing.lastpricedetail;
DELETE FROM pricing.lastpricedetail;
DROP TABLE IF EXISTS #flagged;
--------------------------------------------------------------------------------
-- Stage 1: Load cleaned input rows
-- Filters out irrelevant quotes/orders and calculates unit prices
--------------------------------------------------------------------------------
WITH base AS (
SELECT
o."Customer" AS customer,
o."Part Group" AS partgroup,
RTRIM(i.V1DS) AS dataseg,
o."Data Source" AS version,
o."Part Code" AS part,
o."Units" AS qty,
CASE
WHEN o."Units" = 0 THEN NULL
ELSE ROUND(o.[Value USD] / NULLIF(o."Units", 0), 5)
END AS price,
o.[Order Date] AS odate,
o.[Order Number] AS ordnum,
o.[Quote Number] AS quoten
FROM
fanalysis.rlarp.osm_stack_pretty o
INNER JOIN CMSInterfaceIn.[CMS.CUSLG].ITEMM i
ON i.item = o.[Part Code]
WHERE
o.[Data Source] IN ('Actual', 'Quotes')
AND o."Customer" IS NOT NULL
AND o."Financial Statement Line" = '41010'
AND o."Order Status" <> 'CANCELLED'
AND o."Units" > 0
AND o."Part Group" <> ''
-- Optional filter for testing
-- AND o."Customer" = 'ESBENSHADES GREENHOUSE'
),
--------------------------------------------------------------------------------
-- Stage 2: Rank each row based on recency and volume rules
-- Flags include:
-- - rn_mrs: most recent sale
-- - rn_mrq: most recent quote
-- - rn_lvs: largest sale in last year
-- - rn_lvq: largest quote in last year
-- - rn_dss: most recent sale per dataseg
-- - rn_dsq: most recent quote per dataseg
--------------------------------------------------------------------------------
ranked AS (
SELECT
b.*
-- Most recent sale (Actuals only)
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY b.odate DESC
)
END AS rn_mrs
-- Most recent quote (Quotes only)
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY b.odate DESC
)
END AS rn_mrq
-- Largest volume sale (Actuals only; last 12 months prioritized)
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY
CASE WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
b.qty DESC
)
END AS rn_lvs
-- Largest volume quote (Quotes only; last 12 months prioritized)
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY
CASE WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
b.qty DESC
)
END AS rn_lvq
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY b.odate DESC
)
END AS rn_dss
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY b.odate DESC
)
END AS rn_dsq
FROM base b
)
--------------------------------------------------------------------------------
-- Stage 2.5: Save only rows that meet any of the above criteria
-- and annotate each with global-level flag (mrs, mrq, lvs, lvq)
--------------------------------------------------------------------------------
SELECT
*,
CASE WHEN rn_mrs = 1 THEN 'mrs' END AS f1,
CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2,
CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3,
CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4,
CASE WHEN rn_dss = 1 AND version = 'Actual' THEN 'dss' END AS f5,
CASE WHEN rn_dsq = 1 AND version = 'Quotes' THEN 'dsq' END AS f6
INTO #flagged
FROM ranked
WHERE
rn_mrs = 1
OR rn_mrq = 1
OR rn_lvs = 1
OR rn_lvq = 1
OR (rn_dss = 1 AND version = 'Actual')
OR (rn_dsq = 1 AND version = 'Quotes');
CREATE NONCLUSTERED INDEX ix_flagged_lookup
ON #flagged(customer, partgroup, dataseg, version, part, qty, price, odate, ordnum, quoten);
--------------------------------------------------------------------------------
-- Stage 3: Build JSON from flagged rows
--------------------------------------------------------------------------------
-- Step 3.1: Explode all flags from the #flagged table
WITH exploded_flags AS (
SELECT
customer, partgroup, part, dataseg, version, qty, price, odate, ordnum, quoten,
flag
FROM #flagged
CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag)
WHERE flag IS NOT NULL
)
--SELECT * FROM exploded_flags
-- Step 3.2: Serialize each row into its JSON snippet
-- Carry odate and version for deduplication in seg_pieces
,serialized_flags AS (
SELECT
customer,
partgroup,
dataseg,
flag,
odate,
version,
CONCAT(
'"', flag, '":',
JSON_QUERY((
SELECT
version,
dataseg AS datasegment,
part,
qty,
price,
odate,
ordnum,
quoten,
flag
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
) AS json_piece
FROM exploded_flags
)
--SELECT * FROM serialized_flags
-- Step 3.3: Collect all global-level flags (mrs, mrq, lvs, lvq)
,flag_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM serialized_flags
WHERE flag IN ('mrs', 'mrq', 'lvs', 'lvq')
GROUP BY customer, partgroup
)
--SELECT * FROM flag_json
-- Step 3.4: Nest dss/dsq under each dataseg
-- Only keep the most recent dss/dsq per dataseg/version (prevents duplicate keys)
,seg_pieces AS (
SELECT
customer,
partgroup,
dataseg,
STRING_AGG(json_piece, ',') AS inner_json
FROM (
SELECT sf.*
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, flag
ORDER BY odate DESC,
CASE WHEN version = 'Actual' THEN 1 ELSE 0 END DESC
) AS rn
FROM serialized_flags
WHERE flag IN ('dss', 'dsq')
) sf
WHERE sf.rn = 1
) deduped
GROUP BY customer, partgroup, dataseg
)
--SELECT * FROM seg_pieces
-- Step 3.5: Wrap the inner_json under dataseg key
,wrapped_segs AS (
SELECT
customer,
partgroup,
CONCAT(
'"', dataseg, '": {', inner_json, '}'
) AS json_piece
FROM seg_pieces
)
-- Step 3.6: Aggregate all dataseg entries into one JSON block per customer/partgroup
,seg_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM wrapped_segs
GROUP BY customer, partgroup
)
--SELECT * FROM seg_json
--------------------------------------------------------------------------------
-- Stage 4: Merge flags and segment blocks into a single JSON object
-- Write final pricing history to pricing.lastpricedetail
--------------------------------------------------------------------------------
INSERT INTO
pricing.lastpricedetail
SELECT
COALESCE(f.customer, s.customer) AS customer,
COALESCE(f.partgroup, s.partgroup) AS partgroup,
CONCAT(
'{',
COALESCE(f.json_block, ''),
CASE
WHEN f.json_block IS NOT NULL AND s.json_block IS NOT NULL THEN ','
ELSE ''
END,
COALESCE(s.json_block, ''),
'}'
) AS part_stats
FROM flag_json f
FULL OUTER JOIN seg_json s
ON f.customer = s.customer AND f.partgroup = s.partgroup;
--------------------------------------------------------------------------------
-- Commit if everything succeeded
--------------------------------------------------------------------------------
COMMIT TRAN;
END TRY
BEGIN CATCH
-- Ensure transaction is rolled back
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRAN;
END
-- Optional: cleanup temp table (temp table persists for session until proc end,
-- but explicit drop is helpful if you reuse names or want to free resources)
IF OBJECT_ID('tempdb..#flagged') IS NOT NULL
DROP TABLE #flagged;
-- Rethrow the original error to the caller
THROW;
END CATCH;
END;

View File

@ -1 +0,0 @@
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;

View File

@ -1,6 +0,0 @@
EXEC pricing.pricing.rebuild_lastprice;
--2:45
EXEC pricing.pricing.rebuild_pricelist;
--14 secconds
EXEC pricing.pricing.rebuild_targets;
--12:49

View File

@ -1,6 +0,0 @@
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;
--37 seconds
CALL pricequote.rebuild_pricelist();
--32 seconds
CALL pricequote.refresh_target_prices_base();
--45 seconds

View File

@ -1,226 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_pricelist
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most errors
BEGIN TRY
BEGIN TRAN;
DROP TABLE pricing.pricelist_ranged;
CREATE TABLE pricing.pricelist_ranged (
jcplcd varchar(5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcpart varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcunit varchar(3) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcvoll numeric(12,5) NOT NULL,
jcpric numeric(12,5) NOT NULL,
vb_from float NULL,
vb_to float NULL,
price float NOT NULL
);
CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON PRICING.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----------------------------------------------------------traverse unit of measure graph-----------------------------------------------------------------------
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-------------setup table to hold target conversions---------------------
SELECT DISTINCT
jcpart partn
,jcunit fu
,'PC' tu
,cast(null as numeric) factor
INTO
#anchor
FROM
cmsinterfacein.lgdat.iprcc
WHERE
1=1;
--SELECT * FROM #anchor
-------pre-build punit stacked on itself with the columns flipped so you can go either direction per join---------
SELECT
*
INTO
#g
FROM
(
SELECT
IHPART IHPART,
rtrim(IHUNT1) IHUNT1,
rtrim(IHUNT2) IHUNT2,
IHCNV1 IHCNV1,
IHCNV2 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
UNION
SELECT
IHPART IHPART,
rtrim(IHUNT2) IHUNT1,
rtrim(IHUNT1) IHUNT2,
IHCNV2 IHCNV1,
IHCNV1 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
) x ;
CREATE INDEX g_idx on #g(ihpart,ihunt1);
WITH
--------do the expansion on all paths until the target uom is matched----------------------------------------------
--(complains about types not matching between anchor and recursion, explicitly just casting everything)
uom (partn, partx, lvl, mastf, mastt, xf, xt, factor, xfactor, xnum, xden, id, uom_list) AS
(
SELECT
cast(partn as varchar(20)) --partn
,cast(partn as varchar(20)) --partx
,cast(0 as int) --lvl
,fu --mastf
,tu --mastt
,cast(fu as varchar(3)) --xf
,cast(fu as varchar(3)) --xt
,CAST(1 AS FLOAT) --factor
,CAST(1 AS FLOAT) --xfactor
,CAST(1 AS FLOAT) --xnum
,CAST(1 AS FLOAT) --xden
,format(row_number() over (ORDER BY partn),'000000')
,cast(trim(fu) as varchar(max))
FROM
#anchor
UNION ALL
SELECT
cast(uom.partn as varchar(20)) --partn
,cast(ihpart as varchar(20)) --partx
,CAST(uom.lvl + 1 AS INT) --lvl
,uom.mastf --mastf
,uom.mastt --mastt
,cast(p.ihunt1 as varchar(3)) --xf
,cast(p.ihunt2 as varchar(3)) --xt
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) --factor
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) * uom.xfactor --xfactor
,p.ihcnv2 * uom.xnum --xnum
,p.ihcnv1 * uom.xden --xden
,uom.id + '.' + format(row_number() over (PARTITION BY uom.id ORDER BY partn),'00')
,uom.uom_list + '.' + trim(p.ihunt2)
FROM
uom
INNER JOIN #g p ON
p.ihpart IN (uom.partn,'&&GLOBAL')
AND p.ihunt1 = uom.xt
WHERE
1=1
--AND p.ihunt2 not in ('BD','BG','BU','BX','CA','CS','PA','PL','SL','C','K','DOZ','PR')
AND p.ihunt1 <> uom.mastt
--prevent recursion: newest joined UOM can't be in the history
AND charindex(p.ihunt2,uom.uom_list) = 0
)
--SELECT COUNT(*) FROM UOM
--------------uom is going to have multiple rows per requested conversion, need to use row_number to pick the best row------------------------------
,sorted AS (
SELECT
partn, mastf from_uom, xt to_uom, xfactor factor, lvl steps, row_number() OVER (PARTITION BY partn, mastf, mastt ORDER BY lvl ASC, factor ASC) rn
FROM
uom
WHERE
xt = mastt
)
SELECT * INTO #uom FROM sorted WHERE rn = 1;
--so far so good
drop table #anchor;
drop table #g;
TRUNCATE TABLE pricing.pricelist_ranged;
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.factor,
-- Normalize volume and price to PC
p.jcvoll * u.factor AS vol_pc,
p.jcpric / u.factor AS price_pc
FROM
cmsinterfacein.lgdat.iprcc p
INNER JOIN #uom u
ON u.partn = p.jcpart
AND u.from_uom = p.jcunit
AND u.to_uom = 'PC'
),
sorted AS (
SELECT
c.*,
ROW_NUMBER() OVER (PARTITION BY c.jcplcd, c.jcpart ORDER BY vol_pc ASC) AS rn
FROM conv c
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
INSERT INTO
pricing.pricelist_ranged
SELECT
RTRIM(jcplcd) jcplcd,
RTRIM(jcpart) jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price_pc AS price
FROM
ranged;
--CREATE INDEX pricelist_ranged_idx ON pricing.pricelist_ranged(jcpart, jcplcd, vb_from, vb_to);
COMMIT TRAN;
END TRY
BEGIN CATCH
-- Ensure transaction is rolled back
IF XACT_STATE() <> 0
BEGIN
ROLLBACK TRAN;
END
IF OBJECT_ID('tempdb..#anchor') IS NOT NULL DROP TABLE #anchor;
IF OBJECT_ID('tempdb..#g') IS NOT NULL DROP TABLE #g;
IF OBJECT_ID('tempdb..#uom') IS NOT NULL DROP TABLE #uom;
-- Re-throw original error
THROW;
END CATCH;
END;

View File

@ -1,108 +0,0 @@
CREATE OR REPLACE PROCEDURE pricequote.rebuild_pricelist()
LANGUAGE plpgsql
AS $$
BEGIN
DROP TABLE IF EXISTS uomc;
CREATE TEMP TABLE uomc AS (
WITH
uom AS (
SELECT
uom.p part
,uom.f fu
,uom.t tu
,uom.nm/uom.dm conv
FROM
(
SELECT
jsonb_agg(row_to_json(d)::jsonb) jdoc
FROM
(
select distinct
jcpart partn
, jcunit fu
, 'PC' tu
from
lgdat.iprcc
WHERE
jcpart <> ''
) d
) c
JOIN LATERAL rlarp.uom_array(c.jdoc) uom ON TRUE
)
SELECT * FROM uom
) WITH DATA;
CREATE INDEX uom_idx ON uomc (part, fu, tu);
-- Clear the output table
TRUNCATE TABLE pricequote.pricelist_ranged;
--DROP TABLE pricequote.pricelist_ranged;
-- Compute normalized volume/price and ranges
--CREATE TABLE pricequote.pricelist_ranged AS (
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.conv,
(p.jcvoll * u.conv) AS vol_pc,
(p.jcpric / NULLIF(u.conv, 0)) AS price_pc
FROM
lgdat.iprcc p
INNER JOIN uomc u
ON u.part = p.jcpart
AND u.fu = p.jcunit
AND u.tu = 'PC'
),
--SELECT * FROM conv LIMIT 1000
sorted AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY jcplcd, jcpart ORDER BY vol_pc ASC) AS rn
FROM conv
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc price,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
--SELECT * FROM ranged
INSERT INTO pricequote.pricelist_ranged (
jcplcd, jcpart, jcunit, jcvoll, jcpric, vb_from, vb_to, price
)
SELECT
jcplcd,
jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price
FROM ranged;
END;
$$;
--CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;

View File

@ -1,42 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.rebuild_targets
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- auto-rollback on most runtime errors
BEGIN TRY
BEGIN TRAN;
DELETE FROM pricing.target_prices;
INSERT INTO
pricing.target_prices
SELECT
stlc,
ds,
chan,
tier,
vol,
-- Extract lower bound: text between '[' and ','
TRY_CAST(SUBSTRING(vol, 2, CHARINDEX(',', vol) - 2) AS INT) AS lower_bound,
-- Extract upper bound: text between ',' and ')'
CASE
WHEN RIGHT(vol, 2) = ',)' THEN NULL
ELSE TRY_CAST(SUBSTRING(vol, CHARINDEX(',', vol) + 1, LEN(vol) - CHARINDEX(',', vol) - 1) AS INT)
END AS upper_bound,
price,
math
FROM
usmidsap02.ubm.pricequote.target_prices_view;
COMMIT TRAN;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0
ROLLBACK TRAN;
-- Rethrow original error
THROW;
END CATCH;
END

View File

@ -1,28 +0,0 @@
CREATE OR REPLACE PROCEDURE pricequote.refresh_target_prices_base()
LANGUAGE plpgsql
AS $$
BEGIN
DELETE FROM pricequote.target_prices_base;
WITH expand AS (
SELECT
c.compset,
c.stlc,
c.floor,
b.ds,
b.chan,
b.tier,
b.vol,
b.val,
b.price,
b.math AS math
FROM pricequote.core_target c
LEFT JOIN LATERAL pricequote.build_pricing_path_base(
c.options || jsonb_build_object('entity','Anchor','attr',c.stlc,'val',c.floor,'func','Price')
) AS b
ON b.lastflag
)
INSERT INTO pricequote.target_prices_base
SELECT * FROM expand;
END;
$$;

View File

@ -1,66 +0,0 @@
CREATE OR ALTER PROCEDURE pricing.sync_external
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON; -- causes runtime errors to auto-rollback
BEGIN TRY
BEGIN TRAN;
-- Refresh pricing.ffcret
TRUNCATE TABLE pricing.ffcret;
INSERT INTO pricing.ffcret
SELECT *
FROM fanalysis.rlarp.ffcret;
-- Refresh pricing.ffterr
TRUNCATE TABLE pricing.ffterr;
INSERT INTO pricing.ffterr
SELECT *
FROM fanalysis.rlarp.ffterr;
-- Refresh pricing.gld
TRUNCATE TABLE pricing.gld;
INSERT INTO pricing.gld
SELECT *
FROM fanalysis.rlarp.gld;
-- Refresh pricing.qrh
TRUNCATE TABLE pricing.qrh;
INSERT INTO pricing.qrh
SELECT *
FROM fanalysis.rlarp.qrh;
-- Refresh pricing.sach
TRUNCATE TABLE pricing.sach;
INSERT INTO pricing.sach
SELECT *
FROM fanalysis.lgdat.sach;
COMMIT TRAN;
-- rebuild last price
EXEC pricing.rebuild_lastprice;
-- rebuild ranged price list
EXEC pricing.rebuild_pricelist;
-- rebuild target prices
EXEC pricing.rebuild_targets;
END TRY
BEGIN CATCH
-- Rollback if any error
IF XACT_STATE() <> 0
ROLLBACK TRAN;
-- rethrow original error with context
DECLARE @ErrMsg NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @ErrSec NVARCHAR(200) = ERROR_SEVERITY();
DECLARE @ErrState INT = ERROR_STATE();
RAISERROR('refresh_pricing_tables failed: %s', 16, 1, @ErrMsg);
THROW; -- rethrow original error for callers to handle
END CATCH;
END

View File

@ -1,28 +0,0 @@
CREATE OR ALTER VIEW pricing.arcstx AS
SELECT
v6part PART,
v6plnt plnt,
v6stat stat,
v6rpln rpln,
v6unti unit,
COALESCE(cnsdat, cosdat, y3sdat) sdate,
COALESCE(cnstcs,costcs, y3stcs) std,
COALESCE(cnmats,costcs,y3smat + y3soc + y3sshc) mat,
COALESCE(cnlabs,y3slab,0) lab,
COALESCE(cnbrvs,y3svbr,0) var,
COALESCE(cnbrfs,y3sfbr,0) fix,
COALESCE(cnstoc,y3sotc,0) oth
FROM
CMSInterfaceIN.lgdat.stka
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstm_2510 ftcstm ON
cnpart = v6part
AND cnplnt = v6plnt
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstp_2510 ftcstp ON
copart = v6part
AND coplnt = v6plnt
LEFT OUTER JOIN CMSInterfaceIN.ARCHIVE.ftcstr_2510 ftcstr ON
y3part = v6part
AND y3plnt = v6plnt
WHERE
v6plnt IN ('152','154','155','112','113');

View File

@ -1,16 +0,0 @@
DROP TABLE IF EXISTS pricequote.core_target;
CREATE TABLE pricequote.core_target (
compset TEXT NOT NULL,
stlc TEXT NOT NULL,
floor NUMERIC NOT NULL,
options JSONB NOT NULL,
PRIMARY KEY (stlc)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON pricequote.target_prices TO PUBLIC;
DROP TABLE IF EXISTS import.core_target;
CREATE TABLE import.core_target AS (SELECT * FROM pricequote.core_target);

View File

@ -1,85 +0,0 @@
DROP TABLE pricing.cost_v1ds
DROP TABLE pricing.cost_v0ds
-- Final tables (one-time create)
CREATE TABLE pricing.cost_v1ds (
stlc varchar(50) NOT NULL,
v1ds varchar(50) NOT NULL,
curstdus decimal(19,6) NULL,
futstdus decimal(19,6) NULL,
CONSTRAINT PK_cost_v1ds PRIMARY KEY (stlc, v1ds)
);
CREATE INDEX IX_cost_v1ds_cur ON pricing.cost_v1ds(stlc, v1ds, curstdus);
CREATE TABLE pricing.cost_v0ds (
stlc varchar(50) NOT NULL,
v0ds varchar(50) NOT NULL,
curstdus decimal(19,6) NULL,
futstdus decimal(19,6) NULL,
CONSTRAINT PK_cost_v0ds PRIMARY KEY (stlc, v0ds)
);
CREATE INDEX IX_cost_v0ds_cur ON pricing.cost_v0ds(stlc, v0ds, curstdus);
CREATE OR ALTER PROCEDURE pricing.refresh_cost_rollups
AS
BEGIN
DELETE FROM pricing.cost_v1ds;
INSERT INTO
pricing.cost_v1ds
SELECT
trim(stlc) stlc
,trim(v1ds) v1ds
,avg(curstdus) curstdus
,avg(futstdus) futstdus
FROM
CMSInterfaceIN.[CMS.CUSLG].ITEMM i
LEFT OUTER JOIN pricing.arcstx a ON
a.part = i.item
AND a.plnt = i.dplt
LEFT OUTER JOIN pricing.plpr p ON
p.plnt = i.dplt
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = p.curr
AND x.tcur = 'US'
AND x.perd = p.ic
AND x.rtyp = 'ME'
WHERE
aplnt <> 'I'
AND stlc <> ''
AND substring(glec,1,1) <= '1'
GROUP BY
trim(stlc)
,trim(v1ds);
DELETE FROM pricing.cost_v0ds
INSERT INTO
pricing.cost_v0ds
SELECT
trim(stlc) stlc
,trim(colgrp)+trim(substring(branding,1,1)) v0ds
,avg(curstdus) curstdus
,avg(futstdus) futstdus
FROM
CMSInterfaceIN.[CMS.CUSLG].ITEMM i
LEFT OUTER JOIN pricing.arcstx a ON
a.part = i.item
AND a.plnt = i.dplt
LEFT OUTER JOIN pricing.plpr p ON
p.plnt = i.dplt
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = p.curr
AND x.tcur = 'US'
AND x.perd = p.ic
AND x.rtyp = 'ME'
WHERE
aplnt <> 'I'
AND stlc <> ''
AND substring(glec,1,1) <= '1'
GROUP BY
trim(stlc)
,trim(colgrp)+trim(substring(branding,1,1));
END

View File

@ -1,54 +0,0 @@
CREATE OR ALTER VIEW pricing.cust AS
SELECT
bvcust code,
bvbill default_billto,
bvname descr,
CASE WHEN bvadr6 = '' THEN bvname ELSE bvadr6 END dba,
bvctry country,
bvprcd province,
bvcity city,
bvcomp remit_to,
bvclas cclass,
bvstat status,
bvtype ctype,
RTRIM(bvschl) plevel,
s.bk7des3 folder,
-- pl.lists lists,
dr.repp default_rep,
rr.repp retail_rep,
gr.repp inside_rep,
nr.repp keyaccount_rep,
u.mfresp tier
FROM
cmsinterfacein.lgdat.cust c
LEFT OUTER JOIN cmsinterfacein.lgpgm.usrcust ON
cucust = bvcust
LEFT OUTER JOIN pricing.repc dr ON
dr.rcode = bvsalm
LEFT OUTER JOIN pricing.repc rr ON
rr.rcode = currep
LEFT OUTER JOIN pricing.repc gr ON
gr.rcode = cugrep
LEFT OUTER JOIN pricing.repc nr ON
nr.rcode = cunrep
LEFT OUTER JOIN pricing.sach s ON
s.bk7code = c.bvschl
LEFT OUTER JOIN pricing.ffterr t ON
t.prov = bvprcd
AND t.ctry = bvctry
LEFT OUTER JOIN pricing.ffcret x ON
x.fcur = c.bvcurr
AND x.tcur = 'US'
AND x.perd = (SELECT fspr FROM pricing.gld WHERE GETDATE() BETWEEN sdat AND edat)
AND x.rtyp = 'MA'
LEFT OUTER JOIN cmsinterfacein.lgdat.usrc u ON
u.mfsrce = 'MN'
AND u.mfent# = 12
AND u.mfkey2 = c.bvcust
-- LEFT OUTER JOIN (
-- SELECT jbplvl, STRING_AGG(JBPLCD, ', ') AS lists
-- FROM CMSinterfacein.[CMS.CUSLG].iprcbhc
-- GROUP BY jbplvl
-- ) AS pl ON
-- pl.jbplvl = c.bvschl
-- WHERE CASE WHEN bvadr6 = '' THEN bvname ELSE bvadr6 END = 'GRIFFIN'

View File

@ -1 +0,0 @@
SELECT * INTO pricing.ffcret FROM fanalysis.rlarp.ffcret

View File

@ -1 +0,0 @@
SELECT * INTO pricing.ffterr FROM fanalysis.rlarp.ffterr

View File

@ -1 +0,0 @@
SELECT * INTO pricing.gld FROM fanalysis.rlarp.gld

View File

@ -1,7 +1,237 @@
CREATE TABLE pricing.lastpricedetail ( --------------------------------------------------------------------------------
customer varchar(255), -- Reset target tables
partgroup varchar(10), --------------------------------------------------------------------------------
part_stats nvarchar(MAX) --DROP TABLE IF EXISTS pricing.lastpricedetail;
); DELETE FROM pricing.lastpricedetail;
DROP TABLE IF EXISTS #flagged;
CREATE UNIQUE NONCLUSTERED INDEX lastprice_cust_partgroup ON pricing.lastpricedetail ( customer ASC , partgroup ASC ); --------------------------------------------------------------------------------
-- Stage 1: Load cleaned input rows
-- Filters out irrelevant quotes/orders and calculates unit prices
--------------------------------------------------------------------------------
WITH base AS (
SELECT
o."Customer" AS customer,
o."Part Group" AS partgroup,
RTRIM(i.V1DS) AS dataseg,
o."Data Source" AS version,
o."Part Code" AS part,
o."Units" AS qty,
CASE
WHEN o."Units" = 0 THEN NULL
ELSE ROUND(o.[Value USD] / NULLIF(o."Units", 0), 5)
END AS price,
o.[Order Date] AS odate,
o.[Order Number] AS ordnum,
o.[Quote Number] AS quoten
FROM
rlarp.osm_stack_pretty o
INNER JOIN CMSInterfaceIn.[CMS.CUSLG].ITEMM i
ON i.item = o.[Part Code]
WHERE
o.[Data Source] IN ('Actual', 'Quotes')
AND o."Customer" IS NOT NULL
AND o."Financial Statement Line" = '41010'
AND o."Order Status" <> 'CANCELLED'
AND o."Units" > 0
AND o."Part Group" <> ''
-- Optional filter for testing
-- AND o."Customer" = 'ESBENSHADES GREENHOUSE'
),
--------------------------------------------------------------------------------
-- Stage 2: Rank each row based on recency and volume rules
-- Flags include:
-- - rn_mrs: most recent sale
-- - rn_mrq: most recent quote
-- - rn_lvs: largest sale in last year
-- - rn_lvq: largest quote in last year
-- - rn_dss: most recent sale per dataseg
-- - rn_dsq: most recent quote per dataseg
--------------------------------------------------------------------------------
ranked AS (
SELECT
b.*
-- Most recent sale
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC
) AS rn_mrs
-- Most recent quote
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC
) AS rn_mrq
-- Largest volume sale (last 12 months)
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE
WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE())
THEN b.qty ELSE NULL
END DESC
) AS rn_lvs
-- Largest volume quote (last 12 months)
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup
ORDER BY CASE
WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE())
THEN b.qty ELSE NULL
END DESC
) AS rn_lvq
-- Most recent sale per data segment
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC
) AS rn_dss
-- Most recent quote per data segment
,ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC
) AS rn_dsq
FROM base b
)
--------------------------------------------------------------------------------
-- Stage 2.5: Save only rows that meet any of the above criteria
-- and annotate each with global-level flag (mrs, mrq, lvs, lvq)
--------------------------------------------------------------------------------
SELECT
*,
CASE WHEN rn_mrs = 1 THEN 'mrs' END AS f1,
CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2,
CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3,
CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4,
CASE WHEN rn_dss = 1 AND version = 'Actual' THEN 'dss' END AS f5,
CASE WHEN rn_dsq = 1 AND version = 'Quotes' THEN 'dsq' END AS f6
INTO #flagged
FROM ranked
WHERE
rn_mrs = 1
OR rn_mrq = 1
OR rn_lvs = 1
OR rn_lvq = 1
OR (rn_dss = 1 AND version = 'Actual')
OR (rn_dsq = 1 AND version = 'Quotes');
CREATE NONCLUSTERED INDEX ix_flagged_lookup
ON #flagged(customer, partgroup, dataseg, version, part, qty, price, odate, ordnum, quoten);
--------------------------------------------------------------------------------
-- Stage 3: Build JSON from flagged rows
--------------------------------------------------------------------------------
-- Step 3.1: Explode all flags from the #flagged table
WITH exploded_flags AS (
SELECT
customer, partgroup, part, dataseg, version, part, qty, price, odate, ordnum, quoten,
flag
FROM #flagged
CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag)
WHERE flag IS NOT NULL
)
--SELECT * FROM exploded_flags
-- Step 3.2: Serialize each row into its JSON snippet
-- Carry odate and version for deduplication in seg_pieces
,serialized_flags AS (
SELECT
customer,
partgroup,
dataseg,
flag,
odate,
version,
CONCAT(
'"', flag, '":',
JSON_QUERY((
SELECT
version,
dataseg AS datasegment,
part,
qty,
price,
odate,
ordnum,
quoten,
flag
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
))
) AS json_piece
FROM exploded_flags
)
--SELECT * FROM serialized_flags
-- Step 3.3: Collect all global-level flags (mrs, mrq, lvs, lvq)
,flag_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM serialized_flags
WHERE flag IN ('mrs', 'mrq', 'lvs', 'lvq')
GROUP BY customer, partgroup
)
--SELECT * FROM flag_json
-- Step 3.4: Nest dss/dsq under each dataseg
-- Only keep the most recent dss/dsq per dataseg/version (prevents duplicate keys)
,seg_pieces AS (
SELECT
customer,
partgroup,
dataseg,
STRING_AGG(json_piece, ',') AS inner_json
FROM (
SELECT sf.*
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, flag
ORDER BY odate DESC,
CASE WHEN version = 'Actual' THEN 1 ELSE 0 END DESC
) AS rn
FROM serialized_flags
WHERE flag IN ('dss', 'dsq')
) sf
WHERE sf.rn = 1
) deduped
GROUP BY customer, partgroup, dataseg
)
--SELECT * FROM seg_pieces
-- Step 3.5: Wrap the inner_json under dataseg key
,wrapped_segs AS (
SELECT
customer,
partgroup,
CONCAT(
'"', dataseg, '": {', inner_json, '}'
) AS json_piece
FROM seg_pieces
)
-- Step 3.6: Aggregate all dataseg entries into one JSON block per customer/partgroup
,seg_json AS (
SELECT
customer,
partgroup,
STRING_AGG(json_piece, ',') AS json_block
FROM wrapped_segs
GROUP BY customer, partgroup
)
--SELECT * FROM seg_json
--------------------------------------------------------------------------------
-- Stage 4: Merge flags and segment blocks into a single JSON object
-- Write final pricing history to pricing.lastpricedetail
--------------------------------------------------------------------------------
INSERT INTO
pricing.lastpricedetail
SELECT
COALESCE(f.customer, s.customer) AS customer,
COALESCE(f.partgroup, s.partgroup) AS partgroup,
CONCAT(
'{',
COALESCE(f.json_block, ''),
CASE
WHEN f.json_block IS NOT NULL AND s.json_block IS NOT NULL THEN ','
ELSE ''
END,
COALESCE(s.json_block, ''),
'}'
) AS part_stats
FROM flag_json f
FULL OUTER JOIN seg_json s
ON f.customer = s.customer AND f.partgroup = s.partgroup;

View File

@ -1,6 +1,6 @@
-- REFRESH MATERIALIZED VIEW pricequote.lastpricedetail; REFRESH MATERIALIZED VIEW pricequote.lastpricedetail;
DROP MATERIALIZED VIEW pricequote.lastpricedetail; --DROP MATERIALIZED VIEW pricequote.lastpricedetail
CREATE MATERIALIZED VIEW pricequote.lastpricedetail AS CREATE MATERIALIZED VIEW pricequote.lastpricedetail AS
WITH base AS ( WITH base AS (
@ -28,38 +28,38 @@ WITH base AS (
ranked AS ( ranked AS (
SELECT b.*, SELECT b.*,
-- Most recent sale (Actuals first, newest date first) -- Most recent sale (Actuals first, newest date first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY (version = 'Actual') DESC, ORDER BY (version = 'Actual') DESC,
odate DESC NULLS LAST odate DESC NULLS LAST
) END AS rn_mrs, ) AS rn_mrs,
-- Most recent quote (Quotes first, newest date first) -- Most recent quote (Quotes first, newest date first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY (version = 'Quotes') DESC, ORDER BY (version = 'Quotes') DESC,
odate DESC NULLS LAST odate DESC NULLS LAST
) END AS rn_mrq, ) AS rn_mrq,
-- Largest volume sale in last year (those inside window first) -- Largest volume sale in last year (those inside window first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY (version = 'Actual' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC, ORDER BY (version = 'Actual' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
qty DESC NULLS LAST qty DESC NULLS LAST
) END AS rn_lvs, ) AS rn_lvs,
-- Largest volume quote in last year (those inside window first) -- Largest volume quote in last year (those inside window first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY (version = 'Quotes' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC, ORDER BY (version = 'Quotes' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
qty DESC NULLS LAST qty DESC NULLS LAST
) END AS rn_lvq, ) AS rn_lvq,
-- Per dataseg/version: most recent (version fixed in partition, so just date) -- Per dataseg/version: most recent (version fixed in partition, so just date)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version PARTITION BY customer, partgroup, dataseg, version
ORDER BY odate DESC NULLS LAST ORDER BY odate DESC NULLS LAST
) END AS rn_dss, ) AS rn_dss,
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version PARTITION BY customer, partgroup, dataseg, version
ORDER BY odate DESC NULLS LAST ORDER BY odate DESC NULLS LAST
) END AS rn_dsq ) AS rn_dsq
FROM base b FROM base b
), ),
flagged AS ( flagged AS (
@ -139,4 +139,4 @@ WITH DATA;
--SELECT * FROM pricequote.lastpricedetail; --SELECT * FROM pricequote.lastpricedetail;
CREATE INDEX lastpricedetail_idx ON pricequote.lastpricedetail(customer, partgroup); CREATE INDEX lastpricedetail_idx ON pricequote.lastpricedetail(customer, partgroup);

View File

@ -1,20 +0,0 @@
CREATE OR ALTER VIEW pricing.plpr AS
SELECT
yaplnt plnt,
LTRIM(RTRIM(a9)) AS comp,
a30 AS descr,
SUBSTRING(a249, 242, 2) curr,
SUBSTRING(a249, 32, 4) AS gl,
SUBSTRING(a249, 190, 4) AS ar,
SUBSTRING(a249, 182, 4) AS ap,
SUBSTRING(a249, 198, 4) AS fa,
SUBSTRING(a249, 238, 4) AS ic
FROM
CMSInterfaceIN.lgdat.plnt
INNER JOIN CMSInterfaceIN.lgdat.code ON
yacomp = LTRIM(RTRIM(a9))
LEFT OUTER JOIN CMSInterfaceIN.lgdat.name ON
'C0000' + LTRIM(RTRIM(a9)) = a7
WHERE
a2 = 'AA'
OR a2 IS NULL;

View File

@ -1,12 +1,199 @@
DROP TABLE pricing.pricelist_ranged;
CREATE TABLE pricing.pricelist_ranged ( CREATE TABLE pricing.pricelist_ranged (
jcplcd varchar(5) , jcplcd varchar(5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcpart varchar(20) , jcpart varchar(20) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcunit varchar(3) , jcunit varchar(3) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
jcvoll numeric(12,5) , jcvoll numeric(12,5) NOT NULL,
jcpric numeric(12,5) , jcpric numeric(12,5) NOT NULL,
vb_from float , vb_from float NULL,
vb_to float , vb_to float NULL,
price float price float NOT NULL
); );
CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON pricing.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ); CREATE NONCLUSTERED INDEX pricelist_ranged_idx ON FAnalysis.PRICING.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-----------------------------------------------------------traverse unit of measure graph-----------------------------------------------------------------------
--XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
-------------setup table to hold target conversions---------------------
SELECT DISTINCT
jcpart partn
,jcunit fu
,'PC' tu
,cast(null as numeric) factor
INTO
#anchor
FROM
cmsinterfacein.lgdat.iprcc
WHERE
1=1;
--SELECT * FROM #anchor
-------pre-build punit stacked on itself with the columns flipped so you can go either direction per join---------
SELECT
*
INTO
#g
FROM
(
SELECT
IHPART IHPART,
rtrim(IHUNT1) IHUNT1,
rtrim(IHUNT2) IHUNT2,
IHCNV1 IHCNV1,
IHCNV2 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
UNION
SELECT
IHPART IHPART,
rtrim(IHUNT2) IHUNT1,
rtrim(IHUNT1) IHUNT2,
IHCNV2 IHCNV1,
IHCNV1 IHCNV2
FROM
CMSInterfaceIN.LGDAT.PUNIT pu
--only deal with parts in the anchor table or the &&global parts
INNER JOIN (
SELECT DISTINCT partn FROM #anchor
) items ON
items.partn = pu.ihpart
OR pu.ihpart = '&&GLOBAL'
) x ;
CREATE INDEX g_idx on #g(ihpart,ihunt1);
WITH
--------do the expansion on all paths until the target uom is matched----------------------------------------------
--(complains about types not matching between anchor and recursion, explicitly just casting everything)
uom (partn, partx, lvl, mastf, mastt, xf, xt, factor, xfactor, xnum, xden, id, uom_list) AS
(
SELECT
cast(partn as varchar(20)) --partn
,cast(partn as varchar(20)) --partx
,cast(0 as int) --lvl
,fu --mastf
,tu --mastt
,cast(fu as varchar(3)) --xf
,cast(fu as varchar(3)) --xt
,CAST(1 AS FLOAT) --factor
,CAST(1 AS FLOAT) --xfactor
,CAST(1 AS FLOAT) --xnum
,CAST(1 AS FLOAT) --xden
,format(row_number() over (ORDER BY partn),'000000')
,cast(trim(fu) as varchar(max))
FROM
#anchor
UNION ALL
SELECT
cast(uom.partn as varchar(20)) --partn
,cast(ihpart as varchar(20)) --partx
,CAST(uom.lvl + 1 AS INT) --lvl
,uom.mastf --mastf
,uom.mastt --mastt
,cast(p.ihunt1 as varchar(3)) --xf
,cast(p.ihunt2 as varchar(3)) --xt
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) --factor
,CAST(p.ihcnv2/p.ihcnv1 AS FLOAT) * uom.xfactor --xfactor
,p.ihcnv2 * uom.xnum --xnum
,p.ihcnv1 * uom.xden --xden
,uom.id + '.' + format(row_number() over (PARTITION BY uom.id ORDER BY partn),'00')
,uom.uom_list + '.' + trim(p.ihunt2)
FROM
uom
INNER JOIN #g p ON
p.ihpart IN (uom.partn,'&&GLOBAL')
AND p.ihunt1 = uom.xt
WHERE
1=1
--AND p.ihunt2 not in ('BD','BG','BU','BX','CA','CS','PA','PL','SL','C','K','DOZ','PR')
AND p.ihunt1 <> uom.mastt
--prevent recursion: newest joined UOM can't be in the history
AND charindex(p.ihunt2,uom.uom_list) = 0
)
--SELECT COUNT(*) FROM UOM
--------------uom is going to have multiple rows per requested conversion, need to use row_number to pick the best row------------------------------
,sorted AS (
SELECT
partn, mastf from_uom, xt to_uom, xfactor factor, lvl steps, row_number() OVER (PARTITION BY partn, mastf, mastt ORDER BY lvl ASC, factor ASC) rn
FROM
uom
WHERE
xt = mastt
)
SELECT * INTO #uom FROM sorted WHERE rn = 1;
--so far so good
drop table #anchor;
drop table #g;
TRUNCATE TABLE pricing.pricelist_ranged;
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.factor,
-- Normalize volume and price to PC
p.jcvoll * u.factor AS vol_pc,
p.jcpric / u.factor AS price_pc
FROM
cmsinterfacein.lgdat.iprcc p
INNER JOIN #uom u
ON u.partn = p.jcpart
AND u.from_uom = p.jcunit
AND u.to_uom = 'PC'
),
sorted AS (
SELECT
c.*,
ROW_NUMBER() OVER (PARTITION BY c.jcplcd, c.jcpart ORDER BY vol_pc ASC) AS rn
FROM conv c
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
INSERT INTO
pricing.pricelist_ranged
SELECT
RTRIM(jcplcd) jcplcd,
RTRIM(jcpart) jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price_pc AS price
FROM
ranged;
--CREATE INDEX pricelist_ranged_idx ON pricing.pricelist_ranged(jcpart, jcplcd, vb_from, vb_to);

View File

@ -1,11 +1,101 @@
CREATE TABLE pricelist_ranged ( DROP TABLE IF EXISTS uomc;
jcplcd text NULL,
jcpart text NULL, CREATE TEMP TABLE uomc AS (
jcunit text NULL, WITH
jcvoll numeric(12, 5) NULL, uom AS (
jcpric numeric(12, 5) NULL, SELECT
vb_from numeric NULL, uom.p part
vb_to numeric NULL, ,uom.f fu
price numeric NULL ,uom.t tu
); ,uom.nm/uom.dm conv
CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged USING btree (jcpart, jcplcd, vb_from, vb_to); FROM
(
SELECT
jsonb_agg(row_to_json(d)::jsonb) jdoc
FROM
(
select distinct
jcpart partn
, jcunit fu
, 'PC' tu
from
lgdat.iprcc
WHERE
jcpart <> ''
) d
) c
JOIN LATERAL rlarp.uom_array(c.jdoc) uom ON TRUE
)
SELECT * FROM uom
) WITH DATA;
CREATE INDEX uom_idx ON uomc (part, fu, tu);
-- Clear the output table
TRUNCATE TABLE pricequote.pricelist_ranged;
--DROP TABLE pricequote.pricelist_ranged;
-- Compute normalized volume/price and ranges
--CREATE TABLE pricequote.pricelist_ranged AS (
WITH
conv AS (
SELECT
p.jcplcd,
p.jcpart,
p.jcunit,
p.jcvoll,
p.jcpric,
u.conv,
(p.jcvoll * u.conv) AS vol_pc,
(p.jcpric / NULLIF(u.conv, 0)) AS price_pc
FROM
lgdat.iprcc p
INNER JOIN uomc u
ON u.part = p.jcpart
AND u.fu = p.jcunit
AND u.tu = 'PC'
),
--SELECT * FROM conv LIMIT 1000
sorted AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY jcplcd, jcpart ORDER BY vol_pc ASC) AS rn
FROM conv
),
ranged AS (
SELECT
curr.jcplcd,
curr.jcpart,
curr.jcunit,
curr.jcvoll,
curr.jcpric,
curr.vol_pc,
curr.price_pc price,
curr.vol_pc AS vb_from,
COALESCE(next.vol_pc, 9999999.0) AS vb_to
FROM
sorted curr
LEFT JOIN sorted next
ON curr.jcplcd = next.jcplcd
AND curr.jcpart = next.jcpart
AND curr.rn + 1 = next.rn
)
--SELECT * FROM ranged
INSERT INTO pricequote.pricelist_ranged (
jcplcd, jcpart, jcunit, jcvoll, jcpric, vb_from, vb_to, price
)
SELECT
jcplcd,
jcpart,
jcunit,
jcvoll,
jcpric,
vb_from,
vb_to,
price
FROM ranged;
CREATE INDEX pricelist_ranged_idx ON pricequote.pricelist_ranged ( jcpart ASC , jcplcd ASC , vb_from ASC , vb_to ASC ) ;

View File

@ -1 +0,0 @@
SELECT * INTO pricing.pricing.qrh FROM fanalysis.RLARP.QRH q

View File

@ -1,18 +0,0 @@
CREATE OR ALTER VIEW pricing.repc AS
WITH
code AS (
SELECT
ltrim(rtrim(c.a9)) rcode
,(ltrim(rtrim(c.a9)) + ' - ') + c.a30 repp
FROM
CMSInterfaceIN.lgdat.code c
WHERE c.a2 = 'MM'
)
SELECT
COALESCE(c.rcode,q.qr) rcode
,COALESCE(c.repp,q.qr) repp
,COALESCE(q.dir,'Other') director
FROM
code c
FULL OUTER JOIN pricing.qrh q ON
q.qr = c.rcode

View File

@ -1 +0,0 @@
SELECT * INTO pricing.sach FROM fanalysis.lgdat.sach;

View File

@ -16,4 +16,26 @@ ALTER TABLE pricing.target_prices
ADD CONSTRAINT uq_target_prices_unique_combo ADD CONSTRAINT uq_target_prices_unique_combo
UNIQUE (stlc, ds, chan, tier, vol, lower_bound); UNIQUE (stlc, ds, chan, tier, vol, lower_bound);
DELETE FROM pricing.target_prices;
INSERT INTO
pricing.target_prices
SELECT
stlc,
ds,
chan,
tier,
vol,
-- Extract lower bound: text between '[' and ','
TRY_CAST(SUBSTRING(vol, 2, CHARINDEX(',', vol) - 2) AS INT) AS lower_bound,
-- Extract upper bound: text between ',' and ')'
CASE
WHEN RIGHT(vol, 2) = ',)' THEN NULL
ELSE TRY_CAST(SUBSTRING(vol, CHARINDEX(',', vol) + 1, LEN(vol) - CHARINDEX(',', vol) - 1) AS INT)
END AS upper_bound,
price,
math
FROM
usmidsap02.ubm.pricequote.target_prices_view;
--SELECT COUNT(*) FROM pricing.target_prices --SELECT COUNT(*) FROM pricing.target_prices

View File

@ -1,17 +0,0 @@
DROP TABLE pricequote.target_prices_base CASCADE;
CREATE TABLE pricequote.target_prices_base (
compset TEXT NOT NULL,
stlc TEXT NOT NULL,
floor NUMERIC NOT NULL,
ds TEXT NOT NULL,
chan TEXT NOT NULL,
tier TEXT NOT NULL,
vol INT4RANGE NOT NULL,
val NUMERIC NOT NULL,
price NUMERIC,
math TEXT[],
PRIMARY KEY (stlc, ds, chan, tier, vol)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON pricequote.target_prices_base TO PUBLIC;

View File

@ -8,4 +8,4 @@ SELECT
,price ,price
,to_jsonb(math)::text AS math ,to_jsonb(math)::text AS math
FROM FROM
pricequote.target_prices_base; pricequote.target_prices;

View File

@ -1,77 +1,75 @@
{ {
"details": [ "details": [
{
"label": "History",
"detailLevel": 10,
"details": [
{ {
"label": "Last Quote", "label": "Model Inputs",
"detailLevel": 10, "details": [
"value": 0.1012, {
"type": "currency", "label": "Base Cost",
"note": "XNS0T1G3G18B096 | Ord# 1008338 | 2025-06-12 | Qty 19,200" "value": 1.22446,
} "type": "currency"
] }
}, ]
{
"label": "List",
"detailLevel": 10,
"details": [
{
"label": "Code: GUAU",
"detailLevel": 10,
"value": 0.11,
"type": "currency",
"note": "List Min Qty: 9,600"
}
]
},
{
"label": "Target Calculation",
"detailLevel": 10,
"details": [
{
"label": "XNS0T1G3",
"detailLevel": 10,
"value": 0.08,
"type": "currency",
"note": "Base Floor"
}, },
{ {
"label": "Channel:WHS", "label": "Peer Target",
"detailLevel": 10, "details": [
"value": 0.2, {
"type": "Percent", "label": "Peer Median Margin",
"note": "Premium" "value": 36.873,
"type": "percent",
"note": "DIR|102 - HANGING POTS|110 - INJECTION|Tier 2"
}
]
}, },
{ {
"label": "Volume:1-8", "label": "Target Support",
"detailLevel": 10, "details": [
"value": 0.1, {
"type": "Percent", "label": "Tier 1 Truckload",
"note": "Premium" "value": 80,
"type": "currency",
"note": "reviewed floor price"
},
{
"label": "Warehouse",
"value": 1.2,
"type": "percent"
}
]
}, },
{ {
"label": "Target", "label": "Factor Adjustment",
"detailLevel": 10, "details": [
"value": 0.1056, {
"type": "currency", "label": "Package UOM",
"note": "Total" "value": -0.01,
"type": "percent"
},
{
"label": "# of Pallets",
"value": 0.02,
"type": "percent",
"note": "0.03"
},
{
"label": "Urgency",
"value": 0.03,
"type": "percent",
"note": "Top Priority"
},
{
"label": "State Adder/Subtractor",
"value": -0.02,
"type": "percent",
"note": "OH"
},
{
"label": "Branding",
"value": 0,
"type": "currency"
}
]
} }
] ]
},
{
"label": "Guidance",
"detailLevel": 10,
"details": [
{
"label": "Price",
"detailLevel": 10,
"value": 0.1012,
"type": "currency",
"note": "Using target price, capped to not exceed last price"
}
]
}
]
} }