Compare commits

..

67 Commits

Author SHA1 Message Date
619539eac9 update to hold at last price, no drop 2025-09-09 15:39:40 -04:00
5ec3f1d9c1 put target rebuild in a proc 2025-09-08 08:50:28 -04:00
581224dad8 wrap in transaction 2025-09-06 15:59:51 -04:00
af4c7c4853 change visibility level of target calculation 2025-09-05 08:48:42 -04:00
abac006970 work on filter before row number 2025-09-04 15:04:30 -04:00
a5a746c005 change visibility levels to hide target price details 2025-09-04 13:37:31 -04:00
fd930c9969 change guidance to limit to last price paid 2025-09-04 13:37:03 -04:00
7596038b54 remove price drop 2025-09-03 09:35:09 -04:00
1b7ec3635b testing 2025-08-30 08:56:15 -04:00
ca2e9ea3be add approval columns to quote review 2025-08-30 08:55:48 -04:00
a8d83cc042 revised run times 2025-08-28 09:32:32 -04:00
ad6d3d60f0 break our approval versus guidance logic in postgres 2025-08-28 09:08:51 -04:00
2cc7204c7f update guidance logic to be the shown price which is great of target or last price but capped at list 2025-08-28 00:19:27 -04:00
e047e7f39b mirror guidance logic 2025-08-27 23:06:59 -04:00
c5532cd2dd convert to factor and clarify the logical steps 2025-08-27 23:06:08 -04:00
a7945999a2 initial rework of last price so it is clearer to review 2025-08-27 22:41:39 -04:00
37fead59ff Merge branch 'new_targets' 2025-08-27 20:26:36 -04:00
42c5900efe huge increase in run times 2025-08-27 20:06:02 -04:00
c61729e017 point target source to new target tables 2025-08-27 15:08:50 -04:00
ce77ad7de3 flip the labels on last quote and last sale 2025-08-27 15:08:33 -04:00
9a410b82d1 break out base target table with proper index, and create separate refresh script 2025-08-27 15:07:57 -04:00
6bb0172c2d label parameters and adjust last to 5% 2025-08-27 10:45:55 -04:00
3c854ed0a4 repoint postgres targets to new targets 2025-08-27 10:31:37 -04:00
d653917c8b point view to new target math 2025-08-27 09:53:03 -04:00
993a476bea expand core targets and populate 2025-08-26 16:31:57 -04:00
59a28591bd example with detaillevel key 2025-08-26 16:31:31 -04:00
a79a6d973f change last price window to 18 months 2025-08-26 09:51:59 -04:00
11b1a3a2e4 only work off of actual sales, not quotes 2025-08-26 09:50:05 -04:00
3675b8541f introduce a new key-value pair in the ui json that indicates level of visibility to various roles 2025-08-25 21:05:13 -04:00
2a699e8f83 only use actual sales for last price info in the evaluation process 2025-08-25 17:35:19 -04:00
f19bd138e3 gate the row_number function so that only target rows are processes Actuals for sales and Quotes for quotes 2025-08-25 17:31:37 -04:00
8974095341 expand the new target definition into a complete listing 2025-08-25 17:05:15 -04:00
6e2e806f9b add sample build of new target logic 2025-08-21 15:35:20 -04:00
e9bd81f352 new table to store target price components 2025-08-21 15:30:08 -04:00
c77a6643b2 add commented out price build call 2025-08-21 12:01:08 -04:00
c02f7ef765 include base item so calculation works 2025-08-21 12:00:49 -04:00
1d2c2741b1 create function that does not compound pricing 2025-08-21 11:57:38 -04:00
0fdf2e9775 formatting 2025-08-21 11:08:06 -04:00
11e3f419f6 formatting 2025-08-21 11:07:14 -04:00
7067f3d1fa update to reflect shown detail level 2025-08-18 14:36:48 -04:00
7f5994a40d handle nulls 2025-08-18 14:36:33 -04:00
379e6a5160 change to 15% price reduction 2025-08-18 14:35:22 -04:00
b3635695e6 line ending or other non impact 2025-08-18 14:34:28 -04:00
d2f56fcafa examples 2025-08-18 14:33:38 -04:00
f1d4d8a60b cap at ceiling not working becuase _eff list was being set to null 2025-08-14 03:30:25 -04:00
ccf50036ca work on guidance logic output text and parameterize caps and floors 2025-08-14 02:32:01 -04:00
c46d4c25d7 link in cost, rounding, target for last price should be based on last price qty 2025-08-14 01:50:57 -04:00
23b3962313 join in cost and volume, fix last price cross join 2025-08-14 00:50:31 -04:00
3cc82d14c0 last price normalization and refactor 2025-08-13 23:23:35 -04:00
49748383ff return last part code; factor last qty into normalization, better notes on the last price sourcing 2025-08-13 22:23:08 -04:00
7f990ab611 commit: 2025-08-12 23:08:02 2025-08-12 23:08:02 -04:00
f2fe91f078 isolate version 2025-08-12 22:32:47 -04:00
9b2d1bef4d put base price in the note 2025-08-12 09:58:24 -04:00
59b616c2b7 truncate the descriptor on guidance logic 2025-08-12 09:57:48 -04:00
fb462fbcb1 commit: 2025-08-12 01:19:30 2025-08-12 01:19:30 -04:00
d77b042581 formatting on last price normalizarion display 2025-08-11 21:47:57 -04:00
f936b9d31d show bridge to normalize last price 2025-08-11 21:18:22 -04:00
a649924c86 combine panels 2025-08-11 20:47:08 -04:00
3f56f04f8b notes 2025-08-11 20:32:48 -04:00
eb7563f96c include last part code and add item for total target price 2025-08-11 20:24:12 -04:00
062b49e60c commit: 2025-08-11 17:42:43 2025-08-11 17:42:43 -04:00
8624add269 force output if null 2025-08-11 12:10:47 -04:00
82db9a4334 populate something even if there is no record 2025-08-11 12:00:08 -04:00
d46e71a170 shorten the note on last sale 2025-08-11 11:01:04 -04:00
d37aded2c9 history should be in an array 2025-08-11 11:00:40 -04:00
310f1dc8f9 change last sale price flagging 2025-08-11 09:50:37 -04:00
c423116b9d Merge branch 'master' of github.com:The-HC-Companies/price_api 2025-08-11 09:21:18 -04:00
21 changed files with 1751 additions and 711 deletions

View File

@ -0,0 +1,167 @@
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 Normal file
View File

@ -0,0 +1,184 @@
[
{
"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

@ -1,15 +1,38 @@
EXEC pricing.single_price_call
@bill = 'FARM0001',
@ship = 'KEYB0001',
@part = 'HZP3E100E21D050',
@v1ds = 'v1:T..BDL..',
@vol = 50000;
EXEC pricing.single_price_call EXEC pricing.single_price_call
@bill = 'GRIF0001', @bill = 'GRIF0001',
@ship = 'GRIF0001', @ship = 'GRIF0001',
@part = 'XNS0T1G3G18B96', @part = 'XNS0T1G3G18B096',
@v1ds = 'v1:B..PLT..',
@vol = 9600;
EXEC pricing.single_price_call
@bill = 'FARM0001',
@ship = 'KEYB0001',
@part = 'HCA10000B661100',
@v1ds = 'v1:T..CSE..D',
@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.pricelist_ranged pr WHERE pr.jcpart = 'XNS0T1G3G18B096' AND
SELECT * FROM CMSInterfaceIN.[CMS.CUSLG].
EXEC pricing.single_price_call
@bill = 'GRIF0001',
@ship = 'GRIF0001',
@part = 'XNS0T1G3G18B096',
@v1ds = 'v1:B..PLT..', @v1ds = 'v1:B..PLT..',
@vol = 9600; @vol = 9600;

View File

@ -1,10 +1,80 @@
SELECT SELECT
* ui_json->'data'
FROM pricequote.single_price_call( FROM pricequote.single_price_call(
'FARM0001', 'FARM0001',
'KEYB0001', 'KEYB0001',
'HZP3E100E21D050', 'HCA10000B661100',
'v1:T..BDL..', 'v1:T..CSE..D',
50000 50000
) f ) f;
SELECT
*, ui_json->'data'
FROM pricequote.single_price_call(
'BFGS0001',
'SPRI0019',
'INT12040G18C100',
'v1:B..CSE..',
7037
) f;
SELECT
*, ui_json->'data'
FROM pricequote.single_price_call(
'BELL0039',
'BELL0039',
'XNS0T1G3X19B096PZBND',
'v1:L.P.PLT..',
82420
) f;
SELECT * FROM rlarp.cust WHERE code = 'PACK0009'
-- 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
c.compset,
c.stlc,
c.floor,
b.ds,
b.chan,
b.tier,
b.vol,
b.val,
b.price,
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

@ -0,0 +1,81 @@
-- 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

@ -0,0 +1,72 @@
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,119 +1,61 @@
-- CREATE OR ALTER FUNCTION pricing.guidance_logic;
-- This function returns the least of two NUMERIC(20,5) values. CREATE OR ALTER FUNCTION pricing.guidance_logic(
CREATE OR ALTER FUNCTION dbo.LEAST_NUMERIC205( @target numeric(20,5),
@a NUMERIC(20,5), @last_norm numeric(20,5),
@b 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 NUMERIC(20,5) RETURNS @ret TABLE (
AS guidance_price numeric(20,5),
BEGIN guidance_reason nvarchar(4000)
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 @price NUMERIC(20,5); DECLARE
DECLARE @reason NVARCHAR(MAX) = ''; @base_price numeric(20,5), -- starting point (target if available, else last_norm, else list_eff)
DECLARE @floored NUMERIC(20,5); @after_floor numeric(20,5), -- base but limited to x% lower than last price
DECLARE @capped NUMERIC(20,5); @after_cap_last numeric(20,5), -- previous step but limited to x% higher than last price
DECLARE @use_last_price BIT = 0; @final_price numeric(20,5), -- previous step but limited to x% higher than list price
@reason nvarchar(4000) = N''; -- logic source of price
-- Determine if last price is recent (within last 2 years) -- Early exit if nothing to work with
IF @last_price IS NOT NULL AND @last_date IS NOT NULL AND @last_date > DATEADD(YEAR, -2, CAST(GETDATE() AS DATE)) IF @target IS NULL AND @last_norm IS NULL AND @list_eff IS NULL
SET @use_last_price = 1;
IF @target_price IS NOT NULL AND @use_last_price = 1
BEGIN BEGIN
SET @floored = dbo.GREATEST_NUMERIC205(@target_price, @last_price * 0.95); INSERT INTO @ret VALUES (NULL, N'No target, last, or list available');
SET @capped = dbo.LEAST_NUMERIC205(@floored, @last_price);
SET @price = dbo.LEAST_NUMERIC205(ISNULL(@list_price, 1e9), @capped);
IF @price = @last_price
BEGIN
SET @reason = 'Cap at last price';
END
ELSE
BEGIN
SET @reason = 'Using target price';
IF @target_price < @last_price * 0.95
SET @reason += ', floored to 5% below last price';
IF @target_price > @last_price
SET @reason += ', capped to not exceed last price';
IF @list_price IS NOT NULL AND @price = @list_price AND @target_price > @list_price
SET @reason += ', capped to not exceed list price';
END
END
ELSE IF @use_last_price = 1
BEGIN
SET @price = @last_price;
SET @reason = 'Last price - no target';
END
ELSE IF @target_price IS NOT NULL
BEGIN
SET @price = @target_price;
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
-- Pick starting base price
SET @base_price = COALESCE(@target, @last_norm, @list_eff);
-- 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;
-- Step 1: use base price less than last price, use last price
IF @base_price < @last_norm
BEGIN
SET @base_price = @last_norm;
SET @reason = N'Last price';
END
-- Step 2: use price from previous step but don't allow it to be x% above last price
IF @base_price > @list_eff
BEGIN
SET @base_price = @list_eff;
SET @reason = N'List price ceiling';
END
SET @final_price = @base_price;
INSERT INTO @ret VALUES (@final_price, @reason);
RETURN;
END;

View File

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

View File

@ -1,4 +1,3 @@
-- JSON-based helper function for last price selection -- JSON-based helper function for last price selection
CREATE OR ALTER FUNCTION pricing.pick_last_price_from_hist_json ( CREATE OR ALTER FUNCTION pricing.pick_last_price_from_hist_json (
@part_stats NVARCHAR(MAX), @part_stats NVARCHAR(MAX),
@ -11,11 +10,12 @@ RETURNS @result TABLE (
qty NUMERIC(20,5), qty NUMERIC(20,5),
dataseg NVARCHAR(100), dataseg NVARCHAR(100),
ord NVARCHAR(20), ord NVARCHAR(20),
quote NVARCHAR(20) quote NVARCHAR(20),
part NVARCHAR(100)
) )
AS AS
BEGIN BEGIN
DECLARE @age_threshold DATE = DATEADD(year, -1, GETDATE()); DECLARE @age_threshold DATE = DATEADD(month, -18, 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);
@ -27,63 +27,81 @@ BEGIN
WITH (mrq NVARCHAR(MAX) AS JSON, mrs NVARCHAR(MAX) AS JSON) flags; WITH (mrq NVARCHAR(MAX) AS JSON, mrs NVARCHAR(MAX) AS JSON) flags;
-- Helper to extract fields from a JSON object -- Helper to extract fields from a JSON object
DECLARE @dsq_price NUMERIC(20,5), @dsq_date DATE, @dsq_qty NUMERIC(20,5), @dsq_dataseg NVARCHAR(100), @dsq_ord NVARCHAR(20), @dsq_quote NVARCHAR(20); DECLARE @dsq_price NUMERIC(20,5), @dsq_date DATE, @dsq_qty NUMERIC(20,5), @dsq_dataseg NVARCHAR(100), @dsq_ord NVARCHAR(20), @dsq_quote NVARCHAR(20), @dsq_part NVARCHAR(100);
DECLARE @dss_price NUMERIC(20,5), @dss_date DATE, @dss_qty NUMERIC(20,5), @dss_dataseg NVARCHAR(100), @dss_ord NVARCHAR(20), @dss_quote NVARCHAR(20); DECLARE @dss_price NUMERIC(20,5), @dss_date DATE, @dss_qty NUMERIC(20,5), @dss_dataseg NVARCHAR(100), @dss_ord NVARCHAR(20), @dss_quote NVARCHAR(20), @dss_part NVARCHAR(100);
DECLARE @mrq_price NUMERIC(20,5), @mrq_date DATE, @mrq_qty NUMERIC(20,5), @mrq_dataseg NVARCHAR(100), @mrq_ord NVARCHAR(20), @mrq_quote NVARCHAR(20); DECLARE @mrq_price NUMERIC(20,5), @mrq_date DATE, @mrq_qty NUMERIC(20,5), @mrq_dataseg NVARCHAR(100), @mrq_ord NVARCHAR(20), @mrq_quote NVARCHAR(20), @mrq_part NVARCHAR(100);
DECLARE @mrs_price NUMERIC(20,5), @mrs_date DATE, @mrs_qty NUMERIC(20,5), @mrs_dataseg NVARCHAR(100), @mrs_ord NVARCHAR(20), @mrs_quote NVARCHAR(20); DECLARE @mrs_price NUMERIC(20,5), @mrs_date DATE, @mrs_qty NUMERIC(20,5), @mrs_dataseg NVARCHAR(100), @mrs_ord NVARCHAR(20), @mrs_quote NVARCHAR(20), @mrs_part NVARCHAR(100);
IF @dsq IS NOT NULL IF @dsq IS NOT NULL
SELECT @dsq_price = price, @dsq_date = odate, @dsq_qty = qty, @dsq_dataseg = datasegment, @dsq_ord = ordnum, @dsq_quote = quoten SELECT @dsq_price = price, @dsq_date = odate, @dsq_qty = qty, @dsq_dataseg = datasegment, @dsq_ord = ordnum, @dsq_quote = quoten, @dsq_part = part
FROM OPENJSON(@dsq) FROM OPENJSON(@dsq)
WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20)); WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20), part NVARCHAR(100));
IF @dss IS NOT NULL IF @dss IS NOT NULL
SELECT @dss_price = price, @dss_date = odate, @dss_qty = qty, @dss_dataseg = datasegment, @dss_ord = ordnum, @dss_quote = quoten SELECT @dss_price = price, @dss_date = odate, @dss_qty = qty, @dss_dataseg = datasegment, @dss_ord = ordnum, @dss_quote = quoten, @dss_part = part
FROM OPENJSON(@dss) FROM OPENJSON(@dss)
WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20)); WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20), part NVARCHAR(100));
IF @mrq IS NOT NULL IF @mrq IS NOT NULL
SELECT @mrq_price = price, @mrq_date = odate, @mrq_qty = qty, @mrq_dataseg = datasegment, @mrq_ord = ordnum, @mrq_quote = quoten SELECT @mrq_price = price, @mrq_date = odate, @mrq_qty = qty, @mrq_dataseg = datasegment, @mrq_ord = ordnum, @mrq_quote = quoten, @mrq_part = part
FROM OPENJSON(@mrq) FROM OPENJSON(@mrq)
WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20)); WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20), part NVARCHAR(100));
IF @mrs IS NOT NULL IF @mrs IS NOT NULL
SELECT @mrs_price = price, @mrs_date = odate, @mrs_qty = qty, @mrs_dataseg = datasegment, @mrs_ord = ordnum, @mrs_quote = quoten SELECT @mrs_price = price, @mrs_date = odate, @mrs_qty = qty, @mrs_dataseg = datasegment, @mrs_ord = ordnum, @mrs_quote = quoten, @mrs_part = part
FROM OPENJSON(@mrs) FROM OPENJSON(@mrs)
WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20)); WITH (price NUMERIC(20,5), qty NUMERIC(20,5), datasegment NVARCHAR(100), odate DATE, ordnum NVARCHAR(20), quoten NVARCHAR(20), part NVARCHAR(100));
-- 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 (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold) IF (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
BEGIN BEGIN
IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold INSERT INTO @result VALUES (@dss_price, 'dss', @dss_date, @dss_qty, @dss_dataseg, @dss_ord, @dss_quote, @dss_part);
INSERT INTO @result VALUES (@dsq_price, 'dsq', @dsq_date, @dsq_qty, @dsq_dataseg, @dsq_ord, @dsq_quote);
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);
RETURN; RETURN;
END END
-- IF (@dsq_date IS NOT NULL AND @dsq_date > @age_threshold)
-- OR (@dss_date IS NOT NULL AND @dss_date > @age_threshold)
-- BEGIN
-- IF @dsq_date IS NOT NULL AND (@dss_date IS NULL OR @dsq_date >= @dss_date) AND @dsq_date > @age_threshold
-- 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 (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL) IF @mrs_date IS NOT NULL AND @mrs_date > @age_threshold
BEGIN BEGIN
IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date) INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote, @mrs_part);
INSERT INTO @result VALUES (@mrq_price, 'mrq', @mrq_date, @mrq_qty, @mrq_dataseg, @mrq_ord, @mrq_quote);
ELSE IF @mrs_date IS NOT NULL
INSERT INTO @result VALUES (@mrs_price, 'mrs', @mrs_date, @mrs_qty, @mrs_dataseg, @mrs_ord, @mrs_quote);
RETURN; RETURN;
END END
-- IF (@mrq_date IS NOT NULL OR @mrs_date IS NOT NULL)
-- BEGIN
-- IF @mrq_date IS NOT NULL AND (@mrs_date IS NULL OR @mrq_date >= @mrs_date)
-- 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 @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; -- DECLARE
IF @dsq_date IS NOT NULL -- @best_price NUMERIC(20,5) = 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_source NVARCHAR(10) = NULL
IF @dss_date IS NOT NULL AND (@best_date IS NULL OR @dss_date > @best_date) -- ,@best_date DATE = 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_qty NUMERIC(20,5) = NULL
IF @mrq_date IS NOT NULL AND (@best_date IS NULL OR @mrq_date > @best_date) -- ,@best_dataseg NVARCHAR(100) = 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_ord NVARCHAR(20) = NULL
IF @mrs_date IS NOT NULL AND (@best_date IS NULL OR @mrs_date > @best_date) -- ,@best_quote NVARCHAR(20) = 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 NVARCHAR(100) = NULL;
-- 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); -- 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 '1 year'; age_threshold INTERVAL := INTERVAL '18 months';
dsq_date DATE := NULL; dsq_date DATE := NULL;
dss_date DATE := NULL; dss_date DATE := NULL;
mrq_date DATE := NULL; mrq_date DATE := NULL;
@ -20,6 +20,7 @@ BEGIN
best JSONB := NULL; best JSONB := NULL;
best_date DATE := NULL; best_date DATE := NULL;
BEGIN BEGIN
-- set dates
IF dsq IS NOT NULL AND (dsq->>'price') IS NOT NULL THEN IF dsq IS NOT NULL AND (dsq->>'price') IS NOT NULL THEN
dsq_date := (dsq->>'odate')::date; dsq_date := (dsq->>'odate')::date;
END IF; END IF;
@ -34,40 +35,31 @@ 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 (dsq_date IS NOT NULL AND dsq_date > (CURRENT_DATE - age_threshold)) IF dss_date IS NOT NULL AND dss_date > (CURRENT_DATE - age_threshold) THEN
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'); 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 (mrq_date IS NOT NULL OR mrs_date IS NOT NULL) THEN ELSIF mrs_date IS NOT NULL AND mrs_date > (CURRENT_DATE - age_threshold) THEN
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'); 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

@ -1,3 +1,4 @@
-- Recreate queue with columns matching single_price_call outputs
DROP TABLE IF EXISTS pricequote.queue; DROP TABLE IF EXISTS pricequote.queue;
CREATE TABLE pricequote.queue ( CREATE TABLE pricequote.queue (
@ -12,6 +13,22 @@ CREATE TABLE pricequote.queue (
tier TEXT, tier TEXT,
pltq NUMERIC, pltq NUMERIC,
plevel TEXT, plevel TEXT,
partgroup TEXT,
part_v1ds TEXT,
v0ds TEXT,
curstd_orig NUMERIC,
futstd_orig NUMERIC,
curstd NUMERIC,
futstd NUMERIC,
curstd_last NUMERIC,
futstd_last NUMERIC,
customized TEXT,
last_premium NUMERIC,
last_premium_method TEXT,
last_price_norm NUMERIC,
last_isdiff TEXT,
last_v0ds TEXT,
tprice_last NUMERIC,
last_price NUMERIC, last_price NUMERIC,
last_qty NUMERIC, last_qty NUMERIC,
last_dataseg TEXT, last_dataseg TEXT,
@ -19,227 +36,228 @@ CREATE TABLE pricequote.queue (
last_order TEXT, last_order TEXT,
last_quote TEXT, last_quote TEXT,
last_source TEXT, last_source TEXT,
hist JSONB,
tprice NUMERIC, tprice NUMERIC,
tmath JSONB, tmath JSONB,
volume_range TEXT, volume_range TEXT,
list_price NUMERIC, listprice NUMERIC,
list_code TEXT, listcode TEXT,
listprice_eff NUMERIC,
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
partgroup TEXT
); );
/* CREATE INDEX IF NOT EXISTS idx_osm_stack_merge
==================================================================================== ON rlarp.osm_stack (bill_cust, ship_cust, part, stlc, dataseg, qtyord);
Script: matrix_guidance.pg.sql
Purpose: Batch pricing logic for sales matrix (PostgreSQL)
-----------------------------------------------------------------------------------
Description:
- Seeds a queue table with distinct pricing scenarios from rlarp.osm_stack
- Enriches each scenario with customer, channel, tier, pack quantity, and price level
- Looks up and applies target price, price history, list price, and guidance logic
- Builds a JSON explanation for each scenario
- Merges results back into the main sales matrix table
Inputs:
- Source table: rlarp.osm_stack
- Pricing reference tables: pricequote.target_prices, pricequote.lastpricedetail, pricequote.pricelist_ranged
- Customer/item reference: rlarp.cust, CMS.CUSLG.itemm, CMS.CUSLG.IPRCBHC
Outputs:
- Updates rlarp.osm_stack.pricing with a JSON explanation for each scenario
- All intermediate results are stored in pricequote.queue
Key Business Logic:
- Channel/tier/customer resolution based on bill/ship codes
- Target price and math lookup by segment, channel, tier, and volume
- Price history precedence and extraction via helper function
- List price selection: lowest valid price for the scenario
- Guidance logic: computed from target, last, and list prices
Dependencies:
- pricequote.guidance_logic (function)
- pricequote.pick_last_price_from_hist (function)
Notes:
- Designed for batch/matrix pricing updates
- Assumes all referenced tables and functions exist
- See also: single_price_call.pg.sql for single-row logic
====================================================================================
*/
CREATE INDEX idx_osm_stack_merge
ON rlarp.osm_stack (
bill_cust,
ship_cust,
part,
stlc,
dataseg,
qtyord
);
CREATE INDEX idx_queue_merge
ON pricequote.queue (
bill,
ship,
part,
stlc,
v1ds,
vol
);
CREATE INDEX IF NOT EXISTS idx_queue_merge
ON pricequote.queue (bill, ship, part, stlc, v1ds, vol);
-- Batch procedure mirroring single_price_call logic (4-space indentation)
--DROP PROCEDURE IF EXISTS pricequote.process_queue; --DROP PROCEDURE IF EXISTS pricequote.process_queue;
CREATE OR REPLACE PROCEDURE pricequote.process_queue() CREATE OR REPLACE PROCEDURE pricequote.process_queue()
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
BEGIN BEGIN
--------------------------------------------------------------------
----------------------------------------------------------------------- -- 1) Seed queue from matrix
-- Step 1: Seed the queue table with distinct pricing scenarios --------------------------------------------------------------------
-----------------------------------------------------------------------
DELETE FROM pricequote.queue; DELETE FROM pricequote.queue;
-- 1:30
INSERT INTO pricequote.queue (bill, ship, part, stlc, v1ds, vol, expl) INSERT INTO pricequote.queue (bill, ship, part, stlc, v1ds, vol, expl, ui_json)
SELECT DISTINCT SELECT DISTINCT
o.bill_cust AS bill, o.bill_cust,
o.ship_cust AS ship, o.ship_cust,
o.part, o.part,
o.stlc, o.stlc,
o.dataseg AS v1ds, o.dataseg,
o.qtyord AS vol, o.qtyord,
'{}'::jsonb AS expl '{}'::jsonb,
'{}'::jsonb
FROM rlarp.osm_stack o FROM rlarp.osm_stack o
WHERE WHERE
o.fs_line = '41010' o.fs_line = '41010'
AND o.calc_status <> 'CANCELLED' AND o.calc_status <> 'CANCELLED'
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';
-- 44 seconds -- 2:12 0:38
--------------------------------------------------------------------
----------------------------------------------------------------------- -- 2) Enrich: chan, tier, cust, pltq, plevel, partgroup (+stlc fix)
-- Step 2: Enrich customer, tier, channel, pack quantity, and level --------------------------------------------------------------------
-----------------------------------------------------------------------
MERGE INTO pricequote.queue q MERGE INTO pricequote.queue q
USING ( USING (
SELECT SELECT
q.ctid, q.ctid,
-- Determine sales channel
CASE SUBSTRING(bc.cclass, 2, 3) CASE SUBSTRING(bc.cclass, 2, 3)
WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN 'WHS' ELSE 'DRP' END
WHEN 'DIS' THEN 'WHS'
ELSE 'DRP'
END
ELSE 'DIR' ELSE 'DIR'
END AS chan, END AS chan,
-- Determine pricing tier
CASE SUBSTRING(bc.cclass, 2, 3) CASE SUBSTRING(bc.cclass, 2, 3)
WHEN 'DIR' THEN bc.tier WHEN 'DIR' THEN bc.tier
ELSE COALESCE(sc.tier, bc.tier) ELSE COALESCE(sc.tier, bc.tier)
END AS tier, END AS tier,
-- Resolve customer DBA name
CASE SUBSTRING(bc.cclass, 2, 3) CASE SUBSTRING(bc.cclass, 2, 3)
WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN bc.dba ELSE sc.dba END
WHEN 'DIS' THEN bc.dba
ELSE sc.dba
END
ELSE bc.dba ELSE bc.dba
END AS cust, END AS cust,
-- Pack quantity
i.mpck AS pltq, i.mpck AS pltq,
-- Price level
CASE SUBSTRING(bc.cclass, 2, 3) CASE SUBSTRING(bc.cclass, 2, 3)
WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN CASE SUBSTRING(sc.cclass, 2, 3) WHEN 'DIS' THEN sc.plevel ELSE bc.plevel END
WHEN 'DIS' THEN sc.plevel
ELSE bc.plevel
END
ELSE bc.plevel ELSE bc.plevel
END AS plevel, END AS plevel,
i.partgroup i.partgroup AS partgroup,
SUBSTRING(q.part, 1, 8) AS stlc_fix
FROM pricequote.queue q FROM pricequote.queue q
JOIN rlarp.cust bc ON bc.code = q.bill JOIN rlarp.cust bc ON bc.code = q.bill
LEFT JOIN rlarp.cust sc ON sc.code = q.ship LEFT JOIN rlarp.cust sc ON sc.code = q.ship
LEFT JOIN "CMS.CUSLG".itemm i ON i.item = q.part LEFT JOIN "CMS.CUSLG".itemm i ON i.item = q.part
) src ) s
ON (q.ctid = src.ctid) ON (q.ctid = s.ctid)
WHEN MATCHED THEN WHEN MATCHED THEN UPDATE SET
UPDATE SET chan = s.chan,
chan = src.chan, tier = s.tier,
tier = src.tier, cust = s.cust,
cust = src.cust, pltq = s.pltq,
pltq = src.pltq, plevel = s.plevel,
plevel = src.plevel, partgroup = s.partgroup,
partgroup = src.partgroup; stlc = COALESCE(q.stlc, s.stlc_fix);
-- 17 seconds -- 4:51 0:17
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Step 3: Apply target prices and embed target metadata -- 3) Scenario fields from item master: part_v1ds, v0ds, orig costs
----------------------------------------------------------------------- -- + customized flag
--------------------------------------------------------------------
UPDATE pricequote.queue q
SET
part_v1ds = i0.v1ds,
v0ds = (CASE SUBSTRING(q.v1ds, 4, 1) WHEN 'B' THEN 'B' ELSE 'C' END)
|| (CASE SUBSTRING(q.v1ds, 6, 1) WHEN 'L' THEN 'L' WHEN 'P' THEN 'P' ELSE '' END),
curstd_orig = i0.curstdus,
futstd_orig = i0.futstdus,
customized = CASE
WHEN i0.v1ds IS NOT NULL AND q.v1ds IS NOT NULL AND i0.v1ds <> q.v1ds
THEN 'Customized'
ELSE ''
END
FROM "CMS.CUSLG".itemm i0
WHERE i0.item = q.part;
-- 3:21 0:20
--------------------------------------------------------------------
-- 4) History: store hist, extract last_* with precedence helper
--------------------------------------------------------------------
UPDATE pricequote.queue q
SET
hist = x.part_stats, -- from the correlated subquery
last_price = (j->>'price')::NUMERIC,
last_qty = (j->>'qty')::NUMERIC,
last_dataseg = j->>'datasegment',
last_date = (j->>'odate')::DATE,
last_order = j->>'ordnum',
last_quote = j->>'quoten',
last_source = j->>'source',
last_isdiff = CASE
WHEN (j->>'datasegment') IS NOT NULL
AND q.v1ds IS NOT NULL
AND (j->>'datasegment') <> q.v1ds
THEN 'Last Sale Diff Part'
END,
last_v0ds = (CASE SUBSTRING(j->>'datasegment', 4, 1)
WHEN 'B' THEN 'B' ELSE 'C' END)
|| (CASE SUBSTRING(j->>'datasegment', 6, 1)
WHEN 'L' THEN 'L'
WHEN 'P' THEN 'P'
ELSE '' END)
FROM (
SELECT
q2.ctid,
lp2.part_stats,
pricequote.pick_last_price_from_hist(lp2.part_stats, q2.v1ds) AS j
FROM pricequote.queue q2
JOIN pricequote.lastpricedetail lp2
ON lp2.customer = q2.cust
AND lp2.partgroup = q2.partgroup
) AS x
WHERE q.ctid = x.ctid;
-- 7:32 1:31
--------------------------------------------------------------------
-- 5) Target (requested v1ds): tprice, tmath, volume_range
--------------------------------------------------------------------
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
tprice = tp.price, tprice = tp.price,
expl = q.expl || jsonb_build_object( tmath = to_json(tp.math),
'target_price', tp.price, volume_range = tp.vol::TEXT
'calculated_pallets', FLOOR(q.vol / NULLIF(q.pltq, 0)), FROM pricequote.target_prices_base tp
'exact_pallets', ROUND(q.vol / NULLIF(q.pltq, 0), 5),
'volume range', tp.vol::TEXT,
'customer', q.cust,
'channel', q.chan,
'tier', TRIM(q.tier),
'target math', tp.math
)
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;
-- 20 seconds -- 2:51 0:15
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Step 4: Lookup price history and embed all relevant keys and precedence -- 6) Target for last_dataseg (tprice_last)
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Use the helper function to extract last price precedence and build JSON explanation as in single_price_call
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
last_price = (last_json->>'price')::numeric, tprice_last = tp2.price
last_qty = (last_json->>'qty')::numeric, FROM pricequote.target_prices_base tp2
last_dataseg = last_json->>'datasegment', WHERE
last_date = (last_json->>'odate')::date, q.last_dataseg IS NOT NULL
last_order = last_json->>'ordnum', AND tp2.stlc = q.stlc
last_quote = last_json->>'quoten', AND tp2.ds = q.last_dataseg
last_source = last_json->>'source', AND tp2.chan = q.chan
expl = q.expl || jsonb_build_object( AND tp2.tier = q.tier
'last_price', (last_json->>'price')::numeric, AND FLOOR(q.last_qty / NULLIF(q.pltq, 0))::INT <@ tp2.vol;
'last_qty', (last_json->>'qty')::numeric, -- 1:26 0:08
'last_dataseg', last_json->>'datasegment',
'last_source', last_json->>'source',
'last_date', (last_json->>'odate')::date,
'last_order', last_json->>'ordnum',
'last_quote', last_json->>'quoten'
)
FROM (
SELECT q.ctid, pricequote.pick_last_price_from_hist(lp.part_stats, q.v1ds) AS last_json
FROM pricequote.queue q
JOIN pricequote.lastpricedetail lp
ON lp.customer = q.cust AND lp.partgroup = q.partgroup
) sub
WHERE q.ctid = sub.ctid;
-- 2 minutes 36 seconds
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Step 5: Resolve best list price and insert it with list code -- 7) Cost data for requested v1ds and last_dataseg
----------------------------------------------------------------------- --------------------------------------------------------------------
UPDATE pricequote.queue q
SET
curstd = CASE WHEN COALESCE(q.customized,'') = '' THEN q.curstd_orig ELSE COALESCE(s.v1_cur, s.v0_cur) END,
futstd = CASE WHEN COALESCE(q.customized,'') = '' THEN q.futstd_orig ELSE COALESCE(s.v1_fut, s.v0_fut) END,
curstd_last = CASE WHEN COALESCE(q.last_isdiff,'') = '' THEN q.curstd_orig ELSE COALESCE(s.v1l_cur, s.v0l_cur) END,
futstd_last = CASE WHEN COALESCE(q.last_isdiff,'') = '' THEN q.futstd_orig ELSE COALESCE(s.v1l_fut, s.v0l_fut) END
FROM (
SELECT
q2.ctid,
v1.curstdus AS v1_cur,
v1.futstdus AS v1_fut,
v0.curstdus AS v0_cur,
v0.futstdus AS v0_fut,
v1l.curstdus AS v1l_cur,
v1l.futstdus AS v1l_fut,
v0l.curstdus AS v0l_cur,
v0l.futstdus AS v0l_fut
FROM pricequote.queue q2
LEFT JOIN rlarp.cost_v1ds v1
ON v1.stlc = q2.stlc AND v1.v1ds = q2.v1ds
LEFT JOIN rlarp.cost_v0ds v0
ON v0.stlc = q2.stlc AND v0.v0ds = q2.v0ds
LEFT JOIN rlarp.cost_v1ds v1l
ON v1l.stlc = q2.stlc AND v1l.v1ds = q2.last_dataseg
LEFT JOIN rlarp.cost_v0ds v0l
ON v0l.stlc = q2.stlc AND v0l.v0ds = q2.last_v0ds
) AS s
WHERE q.ctid = s.ctid;
-- 4:15 0:25
--------------------------------------------------------------------
-- 8) List price (lowest valid); allow open-ended ranges (vb_to IS NULL)
--------------------------------------------------------------------
WITH ranked_prices AS ( WITH ranked_prices AS (
SELECT SELECT
q.ctid, q.ctid,
@ -254,56 +272,260 @@ BEGIN
ON pr.jcplcd = TRIM(i.jbplcd) ON pr.jcplcd = TRIM(i.jbplcd)
AND pr.jcpart = q.part AND pr.jcpart = q.part
AND q.vol >= pr.vb_from AND q.vol >= pr.vb_from
AND q.vol < pr.vb_to AND (q.vol < pr.vb_to OR pr.vb_to IS NULL)
), ),
best_price AS ( best_price AS (
SELECT * FROM ranked_prices WHERE rn = 1 SELECT * FROM ranked_prices WHERE rn = 1
) )
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
list_price = p.price, listprice = p.price,
list_code = p.jcplcd, listcode = p.jcplcd
expl = q.expl || jsonb_build_object(
'list_price', p.price,
'list_code', p.jcplcd
)
FROM best_price p FROM best_price p
WHERE q.ctid = p.ctid; WHERE q.ctid = p.ctid;
-- 15 seconds -- 2:48 0:18
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Step 6: Compute guidance price using logic function -- 9) Normalize last (when last_dataseg != v1ds) + effective list flags
----------------------------------------------------------------------- --------------------------------------------------------------------
UPDATE pricequote.queue q UPDATE pricequote.queue q
SET SET
guidance_price = g.guidance_price, last_premium = CASE
guidance_reason = g.guidance_reason, WHEN q.last_isdiff IS NOT NULL
expl = q.expl || jsonb_build_object( AND q.tprice_last IS NOT NULL
'guidance_price', g.guidance_price, AND q.tprice IS NOT NULL
'guidance_reason', g.guidance_reason AND q.tprice_last <> 0
) THEN ROUND(q.tprice / q.tprice_last, 5)
WHEN q.last_isdiff IS NOT NULL
AND q.curstd_last IS NOT NULL
AND q.curstd IS NOT NULL
AND q.curstd_last <> 0
THEN ROUND(q.curstd / q.curstd_last,5)
END,
last_premium_method = CASE
WHEN q.last_isdiff IS NOT NULL
AND q.tprice_last IS NOT NULL
AND q.tprice IS NOT NULL
AND q.tprice_last <> 0
THEN 'Target Price Ratio'
WHEN q.last_isdiff IS NOT NULL
AND q.curstd_last IS NOT NULL
AND q.curstd IS NOT NULL
AND q.curstd_last <> 0
THEN 'Cost Ratio'
WHEN q.last_isdiff IS NOT NULL
THEN 'Unknown'
END,
last_price_norm = CASE
WHEN q.last_isdiff IS NOT NULL
AND q.tprice_last IS NOT NULL
AND q.tprice IS NOT NULL
AND q.tprice_last <> 0
THEN ROUND(q.last_price * (q.tprice / q.tprice_last), 5)
WHEN q.last_isdiff IS NOT NULL
AND q.curstd_last IS NOT NULL
AND q.curstd IS NOT NULL
AND q.curstd_last <> 0
THEN ROUND(q.last_price * (q.curstd / q.curstd_last), 5)
ELSE q.last_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;
-- 2:22 0:23
--------------------------------------------------------------------
-- 10) Guidance using normalized last + effective list
--------------------------------------------------------------------
UPDATE pricequote.queue q
SET
guidance_price = s.guidance_price,
guidance_reason = s.guidance_reason,
approval_price = s.approval_price,
approval_reason = s.approval_reason
FROM ( FROM (
SELECT SELECT
q.ctid, q2.ctid,
g.guidance_price, g.guidance_price,
g.guidance_reason g.guidance_reason,
FROM pricequote.queue q a.approval_price,
a.approval_reason
FROM
pricequote.queue q2
JOIN LATERAL pricequote.guidance_logic( JOIN LATERAL pricequote.guidance_logic(
q.tprice, q2.tprice,
q.last_price, q2.last_price_norm,
q.list_price, q2.listprice_eff,
q.last_date q2.last_date,
1.0, 1.0, 1.0
) g ON TRUE ) g ON TRUE
) g JOIN LATERAL pricequote.approval_logic(
WHERE q.ctid = g.ctid; q2.tprice,
-- 27 seconds q2.last_price_norm,
q2.listprice_eff,
q2.last_date,
1.0, 1.0, 1.0
) a ON TRUE
) s
WHERE q.ctid = s.ctid;
-- 4:33 0:39
--------------------------------------------------------------------
-- 11) Build expl and ui_json identical to single_price_call
--------------------------------------------------------------------
UPDATE pricequote.queue q
SET
expl = jsonb_build_object(
'last', jsonb_build_object(
'last_part', (pricequote.pick_last_price_from_hist(q.hist, q.v1ds)->>'part'),
'last_price', q.last_price,
'last_qty', q.last_qty,
'last_dataseg', q.last_dataseg,
'last_v0ds', q.last_v0ds,
'last_source', q.last_source,
'last_date', q.last_date,
'last_order', q.last_order,
'last_quote', q.last_quote,
'last_isdiff', q.last_isdiff,
'last_premium', q.last_premium,
'last_premium_method', q.last_premium_method,
'last_price_norm', q.last_price_norm,
'tprice_last', q.tprice_last
),
'scenario', jsonb_build_object(
'calculated_pallets', FLOOR(q.vol / NULLIF(q.pltq, 0)),
'exact_pallets', ROUND(q.vol / NULLIF(q.pltq, 0), 5),
'customer', q.cust,
'channel', q.chan,
'tier', TRIM(q.tier),
'v1ds', q.v1ds,
'v0ds', q.v0ds,
'part_v1ds', q.part_v1ds,
'customized', q.customized
),
'cost', jsonb_build_object(
'curstd_orig', q.curstd_orig,
'futstd_orig', q.futstd_orig,
'curstd_last', q.curstd_last,
'futstd_last', q.futstd_last,
'curstd', q.curstd,
'futstd', q.futstd
),
'targets', jsonb_build_object(
'target_price', q.tprice,
'target_math', q.tmath,
'volume_range', q.volume_range
),
'list', jsonb_build_object(
'listcode', q.listcode,
'listprice', q.listprice,
'listprice_eff', q.listprice_eff,
'list_relevance', q.list_relevance
),
'guidance_price', q.guidance_price,
'guidance_reason', q.guidance_reason,
'approval_price', q.approval_price,
'approval_reason', q.approval_reason
),
ui_json = jsonb_build_object(
'details', jsonb_build_array(
jsonb_build_object(
'label', 'History',
'details', jsonb_build_array(
jsonb_build_object(
'label', CASE WHEN q.last_price IS NOT NULL THEN 'Last Sale: ' || q.last_date ELSE 'No Recent' END,
'value', COALESCE(q.last_price, 0),
'type', 'currency',
'note', CASE WHEN q.last_price IS NOT NULL THEN
CASE q.last_source
WHEN 'mrq' THEN 'Recent similar ' || (pricequote.pick_last_price_from_hist(q.hist, q.v1ds)->>'part') || ' qty: ' || q.last_qty
WHEN 'mrs' THEN 'Recent similar ' || (pricequote.pick_last_price_from_hist(q.hist, q.v1ds)->>'part') || ' qty: ' || q.last_qty
WHEN 'dsq' THEN 'Last quote qty: ' || q.last_qty
WHEN 'dss' THEN 'Last sale qty: ' || q.last_qty
ELSE ''
END
|| CASE WHEN COALESCE(q.last_order, '0') = '0'
THEN ' Qt# ' || COALESCE(q.last_quote, '')
ELSE ' Ord# ' || COALESCE(q.last_order, '')
END
END
)
)
|| CASE WHEN COALESCE(q.last_premium, 1) <> 1 THEN
jsonb_build_array(
jsonb_build_object(
'label', 'Price Difference',
'value', q.last_premium,
'type', 'percent',
'note', q.last_premium_method
)
)
ELSE '[]'::jsonb END
|| CASE WHEN COALESCE(q.last_premium, 1) <> 1 THEN
jsonb_build_array(
jsonb_build_object(
'label', 'Adjusted Price',
'value', q.last_price_norm,
'type', 'currency',
'note', 'normalized to ' || q.v1ds
)
)
ELSE '[]'::jsonb END
),
jsonb_build_object(
'label', 'List',
'details', jsonb_build_array(
jsonb_build_object(
'label', 'List:' || COALESCE(q.listcode, ''),
'value', q.listprice,
'type', 'currency',
'note', q.list_relevance
)
)
),
jsonb_build_object(
'label', 'Target Calculation',
'details',
(
SELECT jsonb_agg(
jsonb_build_object(
'label', CASE WHEN v <> '' THEN RTRIM(SUBSTRING(v, 1, 18)) ELSE 'No Target' END,
'value', CASE WHEN v <> '' THEN SUBSTRING(v, 23, 7)::NUMERIC(20,5)
+ CASE SUBSTRING(v, 19, 1) WHEN '+' THEN 0 ELSE -1 END
ELSE 0 END,
'type', CASE WHEN v <> '' THEN CASE SUBSTRING(v, 19, 1) WHEN '+' THEN 'currency' ELSE 'Percent' END ELSE '' END,
'note', CASE WHEN v <> '' THEN CASE SUBSTRING(v, 19, 1) WHEN '+' THEN 'Price' ELSE 'Premium' END ELSE '' END
)
)
FROM jsonb_array_elements_text(COALESCE(q.tmath, '[""]'::jsonb)) AS t(v)
)
|| CASE WHEN q.tprice IS NULL THEN '[]'::jsonb
ELSE jsonb_build_object('label', 'Price', 'value', COALESCE(q.tprice, 0), 'type', 'currency', 'note', 'Total') END
),
jsonb_build_object(
'label', 'Guidance',
'details', jsonb_build_array(
jsonb_build_object(
'label', 'Price',
'value', COALESCE(q.guidance_price, 0),
'type', 'currency',
'note', COALESCE(q.guidance_reason, '')
)
)
)
),
'data', q.expl
);
-- 7:59 2:17
----------------------------------------------------------------------- --------------------------------------------------------------------
-- Step 7: merge the results back into sales matrix -- 12) Merge back into matrix (store both expl and ui)
----------------------------------------------------------------------- --------------------------------------------------------------------
UPDATE rlarp.osm_stack o UPDATE rlarp.osm_stack o
SET pricing = pricing || q.expl SET pricing = pricing
|| jsonb_build_object(
'expl', q.expl,
'ui', q.ui_json
)
FROM pricequote.queue q FROM pricequote.queue q
WHERE WHERE
o.bill_cust = q.bill o.bill_cust = q.bill
@ -316,12 +538,9 @@ BEGIN
AND o.calc_status <> 'CANCELLED' AND o.calc_status <> 'CANCELLED'
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';
-- 6 minutes 31 seconds -- 14:13 10:09
-----------------------------------------------------------------------
-- Done
-----------------------------------------------------------------------
RAISE NOTICE 'Queue processing complete.'; RAISE NOTICE 'Queue processing complete.';
END; END;
$$; $$;

View File

@ -113,6 +113,7 @@ BEGIN
last_order NVARCHAR(10), last_order NVARCHAR(10),
last_quote NVARCHAR(10), last_quote NVARCHAR(10),
last_isdiff NVARCHAR(100), last_isdiff NVARCHAR(100),
last_part NVARCHAR(100),
------------step 3 lookup target--------------- ------------step 3 lookup target---------------
tprice NUMERIC(20,5), tprice NUMERIC(20,5),
tprice_last NUMERIC(20,5), tprice_last NUMERIC(20,5),
@ -131,9 +132,12 @@ BEGIN
listprice NUMERIC(20,5), listprice NUMERIC(20,5),
listprice_eff NUMERIC(20,5), listprice_eff NUMERIC(20,5),
list_relevance NVARCHAR(100), list_relevance NVARCHAR(100),
list_from BIGINT,
------------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)
@ -171,7 +175,7 @@ BEGIN
WHEN 'DIS' THEN bc.dba WHEN 'DIS' THEN bc.dba
ELSE sc.dba ELSE sc.dba
END END
ELSE q.bill ELSE bc.dba
END, END,
pltq = i.mpck, pltq = i.mpck,
plevel = plevel =
@ -225,10 +229,11 @@ BEGIN
last_order = b.ord, last_order = b.ord,
last_quote = b.quote, last_quote = b.quote,
last_isdiff = CASE WHEN b.dataseg IS NOT NULL AND q.v1ds IS NOT NULL AND b.dataseg <> q.v1ds last_isdiff = CASE WHEN b.dataseg IS NOT NULL AND q.v1ds IS NOT NULL AND b.dataseg <> q.v1ds
THEN 'Last Sale Diff Part' ELSE '' END THEN 'Last Sale Diff Part' ELSE '' END,
last_part = b.part
FROM @queue q FROM @queue q
CROSS APPLY ( CROSS APPLY (
SELECT TOP 1 price, source, odate, qty, dataseg, ord, quote SELECT TOP 1 price, source, odate, qty, dataseg, ord, quote, part
FROM pricing.pick_last_price_from_hist_json(q.hist, q.v1ds) FROM pricing.pick_last_price_from_hist_json(q.hist, q.v1ds)
) b; ) b;
@ -257,9 +262,9 @@ BEGIN
AND q.last_dataseg = tpl.ds AND q.last_dataseg = tpl.ds
AND q.chan = tpl.chan AND q.chan = tpl.chan
AND q.tier = tpl.tier AND q.tier = tpl.tier
AND q.calculated_pallets >= tpl.lower_bound AND (q.last_qty/q.pltq) >= tpl.lower_bound
AND ( AND (
tpl.upper_bound IS NULL OR q.calculated_pallets < tpl.upper_bound tpl.upper_bound IS NULL OR (q.last_qty/q.pltq) < tpl.upper_bound
); );
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -336,7 +341,9 @@ BEGIN
ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY q.bill, q.ship, q.part, q.stlc, q.v1ds, q.vol PARTITION BY q.bill, q.ship, q.part, q.stlc, q.v1ds, q.vol
ORDER BY p.price ASC ORDER BY p.price ASC
) AS rn ) AS rn,
p.vb_from,
p.vb_to
FROM @queue q FROM @queue q
INNER JOIN CMSInterfaceIn."CMS.CUSLG".IPRCBHC i INNER JOIN CMSInterfaceIn."CMS.CUSLG".IPRCBHC i
ON TRIM(i.jbplvl) = TRIM(q.plevel) ON TRIM(i.jbplvl) = TRIM(q.plevel)
@ -351,8 +358,9 @@ BEGIN
SET SET
listcode = rp.jcplcd listcode = rp.jcplcd
,listprice = rp.price ,listprice = rp.price
,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE q.listprice END ,listprice_eff = CASE WHEN q.customized <> '' THEN NULL ELSE rp.price 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
FROM @queue q FROM @queue q
JOIN ranked_prices rp JOIN ranked_prices rp
ON q.bill = rp.bill ON q.bill = rp.bill
@ -371,13 +379,33 @@ 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),
) g; --allowable price drop percent
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.
@ -392,6 +420,7 @@ BEGIN
,q.last_source AS last_source ,q.last_source AS last_source
,FORMAT(q.last_date, 'yyyy-MM-dd') AS last_date ,FORMAT(q.last_date, 'yyyy-MM-dd') AS last_date
,q.last_isdiff AS last_isdiff ,q.last_isdiff AS last_isdiff
,q.last_part AS last_part
,q.tprice_last AS tprice_last ,q.tprice_last AS tprice_last
,q.tprice AS target_price ,q.tprice AS target_price
,JSON_QUERY(q.tmath) AS target_math ,JSON_QUERY(q.tmath) AS target_math
@ -423,6 +452,8 @@ 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
) )
@ -437,25 +468,63 @@ BEGIN
( (
SELECT SELECT
panel.label, panel.label,
panel.detailLevel,
JSON_QUERY(panel.details) AS details JSON_QUERY(panel.details) AS details
FROM ( FROM (
-- History Panel -- History Panel
SELECT SELECT
'History' AS label, 'History' AS label,
1 as detailLevel,
( (
SELECT SELECT
'Last Price' AS label, ----------------------label------------------------------------------------
q.last_price AS value, CASE
WHEN q.last_price IS NOT NULL
THEN
CASE ISNULL(q.last_source, '')
WHEN 'mrq' THEN 'Similar Quote'
WHEN 'mrs' THEN 'Similar Sale'
WHEN 'dsq' THEN 'Last Quote'
WHEN 'dss' THEN 'Last Sale'
ELSE ''
END
ELSE 'No Recent'
END AS label,
----------------------detail-----------------------------------------------
1 AS detailLevel,
----------------------value------------------------------------------------
ISNULL(q.last_price, 0) AS value,
----------------------type-------------------------------------------------
'currency' AS type, 'currency' AS type,
----------------------note-------------------------------------------------
CASE
WHEN q.last_price IS NOT NULL THEN
CONCAT( CONCAT(
'Source: ', ISNULL(q.last_source, 'N/A'), CASE ISNULL(q.last_source, '')
' | Date: ', ISNULL(CONVERT(varchar(10), q.last_date, 120), 'N/A'), WHEN 'mrq' THEN 'Similar - ' + last_part
' | Order: ', ISNULL(q.last_order, 'N/A'), WHEN 'mrs' THEN 'Similar - ' + last_part
' | Quote: ', ISNULL(q.last_quote, 'N/A'), WHEN 'dsq' THEN last_part
' | Dataseg: ', ISNULL(q.last_dataseg, 'N/A'), WHEN 'dss' THEN last_part
' | Qty: ', ISNULL(CAST(q.last_qty AS varchar(32)), 'N/A') ELSE ''
) AS note END,
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER CASE WHEN ISNULL(q.last_order, '0') = '0'
THEN ' | Qt# ' + ISNULL(q.last_quote, '')
ELSE ' | Ord# ' + ISNULL(q.last_order, '')
END,
ISNULL(' | ' + CONVERT(varchar(10), q.last_date, 120), ''),
' | Qty ' + format(q.last_qty,'#,###'),
CASE WHEN COALESCE(last_isdiff,'') <> ''
THEN
' | 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
''
END
AS note
FOR JSON PATH -- array with one object (no WITHOUT_ARRAY_WRAPPER)
) AS details ) AS details
UNION ALL UNION ALL
@ -463,29 +532,64 @@ BEGIN
-- List Panel -- List Panel
SELECT SELECT
'List' AS label, 'List' AS label,
1 AS detailLevel,
( (
SELECT SELECT
'List:' + q.listcode AS label, COALESCE('Code: ' + q.listcode,'No List') AS label,
q.listprice AS value, 1 AS detailLevel,
COALESCE(q.listprice,0) AS value,
'currency' AS type, 'currency' AS type,
q.list_relevance 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 Support' AS label, 'Target Calculation' AS label,
5 AS detailLevel,
( (
SELECT * FROM (
SELECT SELECT
RTRIM(SUBSTRING(value,1,18)) AS label, ----------------------label------------------------------------------------
CASE WHEN value <> '' THEN replace(RTRIM(SUBSTRING(value,1,18)),'Anchor:', '') ELSE 'No Target' END AS label,
----------------------detailLevel------------------------------------------
10 AS detailLevel,
----------------------value------------------------------------------------
CASE WHEN value <> '' THEN
TRY_CAST(SUBSTRING(value,23,7) AS NUMERIC(20,5)) TRY_CAST(SUBSTRING(value,23,7) AS NUMERIC(20,5))
+ CASE SUBSTRING(value,19,1) WHEN '+' THEN 0 ELSE -1 END AS value, + CASE SUBSTRING(value,19,1) WHEN '+' THEN 0 ELSE -1 END
CASE SUBSTRING(value,19,1) WHEN '+' THEN 'currency' ELSE 'Percent' END AS type, ELSE 0 END AS value,
CASE SUBSTRING(value,19,1) WHEN '+' THEN 'Price' ELSE 'Premium' END AS note ----------------------type-------------------------------------------------
FROM OPENJSON(q.expl, '$.target_math') CASE WHEN value <> '' THEN
CASE SUBSTRING(value,19,1) WHEN '+' THEN 'currency' ELSE 'Percent' END
ELSE '' END AS type,
----------------------note-------------------------------------------------
CASE WHEN value <> '' THEN
CASE WHEN CHARINDEX('Anchor',value) <> 0 THEN
'Base Floor'
ELSE
CASE SUBSTRING(value,19,1) WHEN '+' THEN 'Price' ELSE 'Premium' END
END
ELSE '' END AS note
FROM @queue q
OUTER APPLY OPENJSON(q.expl, '$.target_math')
WITH (value NVARCHAR(MAX) '$') WITH (value NVARCHAR(MAX) '$')
UNION ALL
SELECT
----------------------label------------------------------------------------
'Target' AS label,
----------------------detailLevel------------------------------------------
5 AS detailLevel,
----------------------value------------------------------------------------
tprice AS value,
----------------------type-------------------------------------------------
'currency' AS type,
----------------------note-------------------------------------------------
'Total' AS note
FROM @queue q
) x
FOR JSON PATH FOR JSON PATH
) AS details ) AS details
@ -494,10 +598,12 @@ BEGIN
-- Guidance Panel -- Guidance Panel
SELECT SELECT
'Guidance' AS label, 'Guidance' AS label,
1 AS detailLevel,
( (
SELECT SELECT
'Price' AS label, 'Price' AS label,
q.guidance_price AS value, 1 AS detailLevel,
COALESCE(q.guidance_price,0) AS value,
'currency' AS type, 'currency' AS type,
q.guidance_reason AS note q.guidance_reason AS note
FOR JSON PATH FOR JSON PATH

View File

@ -35,7 +35,7 @@
==================================================================================== ====================================================================================
*/ */
DROP FUNCTION pricequote.single_price_call(text,text,text,text,numeric); -- 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,
@ -79,6 +79,7 @@ RETURNS TABLE (
last_order TEXT, last_order TEXT,
last_quote TEXT, last_quote TEXT,
last_source TEXT, last_source TEXT,
hist JSONB,
tprice NUMERIC, tprice NUMERIC,
tmath JSONB, tmath JSONB,
volume_range TEXT, volume_range TEXT,
@ -88,6 +89,8 @@ 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 $$
@ -127,10 +130,10 @@ DECLARE
_last_isdiff TEXT; _last_isdiff TEXT;
_last_part TEXT; _last_part TEXT;
------------step 3 lookup target--------------- ------------step 3 lookup target---------------
_tprice NUMERIC; _tprice NUMERIC(20,5);
_tmath JSONB; _tmath JSONB;
_volume_range TEXT; _volume_range TEXT;
_tprice_last NUMERIC; _tprice_last NUMERIC(20,5);
------------step 4 normalize last price-------- ------------step 4 normalize last price--------
_curstd NUMERIC; _curstd NUMERIC;
_futstd NUMERIC; _futstd NUMERIC;
@ -147,6 +150,8 @@ 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;
@ -264,7 +269,7 @@ BEGIN
,_tmath ,_tmath
,_volume_range ,_volume_range
FROM FROM
pricequote.target_prices tp pricequote.target_prices_base tp
WHERE WHERE
tp.stlc = _stlc tp.stlc = _stlc
AND tp.ds = _v1ds AND tp.ds = _v1ds
@ -282,38 +287,36 @@ BEGIN
INTO INTO
_tprice_last _tprice_last
FROM FROM
pricequote.target_prices tp pricequote.target_prices_base 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(_vol / NULLIF(_pltq, 0))::int <@ tp.vol; AND FLOOR(_last_qty / 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;
-- Current/future standard for last_dataseg
SELECT SELECT
curstdus, futstdus ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _curstd_orig ELSE COALESCE(v1.curstdus, v0.curstdus) END,5) AS curstd,
ROUND(CASE WHEN COALESCE(_customized,'') = '' THEN _futstd_orig ELSE COALESCE(v1.futstdus, v0.futstdus) END,5) AS futstd,
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _curstd_orig ELSE COALESCE(v1l.curstdus, v0l.curstdus) END,5) AS curstd_last,
ROUND(CASE WHEN COALESCE(_last_isdiff,'') = '' THEN _futstd_orig ELSE COALESCE(v1l.futstdus, v0l.futstdus) END,5) AS futstd_last
INTO INTO
_curstd_last, _futstd_last _curstd, _futstd, _curstd_last, _futstd_last
FROM FROM (VALUES (1)) AS x(dummy)
"CMS.CUSLG".itemm i LEFT JOIN rlarp.cost_v1ds v1
WHERE ON v1.stlc = _stlc AND v1.v1ds = _v1ds
i.item = _part LEFT JOIN rlarp.cost_v0ds v0
AND i.v1ds = _last_dataseg; 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
@ -324,7 +327,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 := _curstd / _curstd_last; _last_premium := ROUND(_curstd / _curstd_last, 5);
_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
@ -373,10 +376,16 @@ 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
FROM pricequote.guidance_logic(_tprice, _last_price_norm, _listprice_eff, _last_date) gl; ,_approval_price
,_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
@ -424,7 +433,8 @@ 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(
@ -434,7 +444,9 @@ 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
); );
------------------------------------------------------------------ ------------------------------------------------------------------
@ -442,42 +454,126 @@ BEGIN
------------------------------------------------------------------ ------------------------------------------------------------------
_ui_json := jsonb_build_object( _ui_json := jsonb_build_object(
'details', jsonb_build_array( 'details', jsonb_build_array(
------------------------------------------
-- history
------------------------------------------
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 _last_source || 'note', CASE WHEN _last_price IS NOT NULL THEN
CASE _last_source
WHEN 'mrq' THEN 'Recent similar ' || _last_part || ' ' || 'qty: ' || _last_qty
WHEN 'mrs' THEN 'Recent similar ' || _last_part || ' ' || 'qty: ' || _last_qty
WHEN 'dsq' THEN 'Last quote ' || 'qty: ' || _last_qty
WHEN 'dss' THEN 'Last sale ' || 'qty: ' || _last_qty
ELSE ''
END ||
CASE WHEN COALESCE(_last_order, '0') = '0' THEN ' Qt# ' || COALESCE(_last_quote, '') ELSE ' Ord# ' || COALESCE(_last_order, '') END CASE WHEN COALESCE(_last_order, '0') = '0' THEN ' Qt# ' || COALESCE(_last_quote, '') ELSE ' Ord# ' || COALESCE(_last_order, '') END
ELSE NULL END ELSE NULL END
) )
) )
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object(
'label','Price Difference',
'detailLevel',10,
'value', _last_premium,
'type','percent',
'note', _last_premium_method
)),'[]'::jsonb)
ELSE
'[]'::jsonb
END
||CASE WHEN COALESCE(_last_premium,1) <> 1 THEN
COALESCE(jsonb_build_array(jsonb_build_object(
'label','Adjusted Price',
'detailLevel',10,
'value', _last_price_norm,
'type','currency',
'note','normalized to ' || _v1ds
)),'[]'::jsonb)
ELSE
'[]'::jsonb
END
), ),
------------------------------------------
-- price list
------------------------------------------
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
) )
) )
), ),
------------------------------------------
-- history
------------------------------------------
jsonb_build_object( jsonb_build_object(
'label', 'Target Support', 'label', 'Target Calculation',
'details', _tmath 'detailLevel',10,
'details',
-- jsonb_build_array(
(
SELECT
jsonb_agg(
jsonb_build_object(
----------------------label------------------------------------------------
'label',CASE WHEN value <> '' THEN RTRIM(SUBSTRING(value,1,18)) ELSE 'No Target' END,
----------------------detailLevel------------------------------------------
'detailLevel',10,
----------------------value------------------------------------------------
'value',CASE WHEN value <> '' THEN
SUBSTRING(value,23,7)::NUMERIC(20,5) +
CASE SUBSTRING(value,19,1) WHEN '+' THEN 0 ELSE -1 END
ELSE
0
END,
----------------------type-------------------------------------------------
'type', CASE WHEN value <> '' THEN
CASE SUBSTRING(value,19,1)
WHEN '+' THEN 'currency'
ELSE 'Percent'
END
ELSE '' END,
----------------------note-------------------------------------------------
'note',CASE WHEN value <> '' THEN
CASE SUBSTRING(value,19,1)
WHEN '+' THEN 'Price'
ELSE 'Premium'
END
ELSE '' END
)
)
FROM jsonb_array_elements_text(COALESCE(_tmath,'[""]'::jsonb)) ae
)
||CASE WHEN _tprice IS NULL THEN '[]'::jsonb ELSE jsonb_build_object('label','Price','value',COALESCE(_tprice,0),'type','currency','note','Total') END
-- )
), ),
------------------------------------------
-- history
------------------------------------------
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',
'value', _guidance_price, 'detailLevel',10,
'value', COALESCE(_guidance_price,0),
'type', 'currency', 'type', 'currency',
'note', _guidance_reason 'note', COALESCE(_guidance_reason,'')
) )
) )
) )
@ -494,10 +590,11 @@ BEGIN
_chan, _cust, _tier, _pltq, _plevel, _partgroup, _part_v1ds, _v0ds, _chan, _cust, _tier, _pltq, _plevel, _partgroup, _part_v1ds, _v0ds,
_curstd_orig, _futstd_orig, _curstd, _futstd, _curstd_last, _futstd_last, _curstd_orig, _futstd_orig, _curstd, _futstd, _curstd_last, _futstd_last,
_customized, _last_premium, _last_premium_method, _last_price_norm, _last_isdiff, _last_v0ds, _tprice_last, _customized, _last_premium, _last_premium_method, _last_price_norm, _last_isdiff, _last_v0ds, _tprice_last,
_last_price, _last_qty, _last_dataseg, _last_date, _last_order, _last_quote, _last_source, _last_price, _last_qty, _last_dataseg, _last_date, _last_order, _last_quote, _last_source, _hist,
_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

@ -1,7 +1,7 @@
-- set work_mem TO '4GB'; -- set work_mem TO '4GB';
-- --
DROP VIEW IF EXISTS rlarp.quote_review; DROP VIEW IF EXISTS rlarp.quote_review;
CREATE VIEW rlarp.quote_review AS CREATE OR REPLACE VIEW rlarp.quote_review AS
WITH WITH
---------------------get quote lines from SQL Server--------------------- ---------------------get quote lines from SQL Server---------------------
lq AS MATERIALIZED ( lq AS MATERIALIZED (
@ -105,7 +105,10 @@ lq AS MATERIALIZED (
-- ,jsonb_pretty(pricing) pricing -- ,jsonb_pretty(pricing) pricing
,p.guidance_price ,p.guidance_price
,p.guidance_reason ,p.guidance_reason
,jsonb_pretty(p.expl) expl ,p.approval_price
,p.approval_reason
,p.tprice guidance_target
,jsonb_pretty(p.ui_json->'data') expl
FROM FROM
lq lq
LEFT OUTER JOIN "CMS.CUSLG".itemm i ON LEFT OUTER JOIN "CMS.CUSLG".itemm i ON
@ -131,7 +134,6 @@ lq AS MATERIALIZED (
lq.billto lq.billto
,lq.shipto ,lq.shipto
,lq.part ,lq.part
,substring(lq.part,1,8)
,lq.v1ds ,lq.v1ds
,lq.units_each ,lq.units_each
) p ON TRUE ) p ON TRUE
@ -176,4 +178,4 @@ lq AS MATERIALIZED (
WHERE WHERE
COALESCE(g.bestprice,1) = 1 COALESCE(g.bestprice,1) = 1
) )
SELECT * FROM hist --WHERE qid = 108655 SELECT * FROM hist --LIMIT 1000--WHERE qid = 108655

16
tables/core_target.pg.sql Normal file
View File

@ -0,0 +1,16 @@
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

@ -51,42 +51,50 @@ WITH base AS (
ranked AS ( ranked AS (
SELECT SELECT
b.* b.*
-- Most recent sale -- Most recent sale (Actuals only)
,ROW_NUMBER() OVER ( ,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC ORDER BY b.odate DESC
) AS rn_mrs )
-- Most recent quote END AS rn_mrs
,ROW_NUMBER() OVER ( -- Most recent quote (Quotes only)
,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup PARTITION BY b.customer, b.partgroup
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC ORDER BY b.odate DESC
) AS rn_mrq )
-- Largest volume sale (last 12 months) END AS rn_mrq
,ROW_NUMBER() OVER ( -- Largest volume sale (Actuals only; last 12 months prioritized)
,CASE WHEN b.version = 'Actual' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup PARTITION BY b.customer, b.partgroup
ORDER BY CASE ORDER BY
WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) CASE WHEN b.version = 'Actual' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
THEN b.qty ELSE NULL b.qty DESC
END DESC )
) AS rn_lvs END AS rn_lvs
-- Largest volume quote (last 12 months) -- Largest volume quote (Quotes only; last 12 months prioritized)
,ROW_NUMBER() OVER ( ,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup PARTITION BY b.customer, b.partgroup
ORDER BY CASE ORDER BY
WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) CASE WHEN b.version = 'Quotes' AND b.odate >= DATEADD(YEAR, -1, GETDATE()) THEN 1 ELSE 0 END DESC,
THEN b.qty ELSE NULL b.qty DESC
END DESC )
) AS rn_lvq END AS rn_lvq
-- Most recent sale per data segment ,CASE WHEN b.version = 'Actual' THEN
,ROW_NUMBER() OVER ( ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Actual' THEN b.odate ELSE NULL END DESC ORDER BY b.odate DESC
) AS rn_dss )
-- Most recent quote per data segment END AS rn_dss
,ROW_NUMBER() OVER ( ,CASE WHEN b.version = 'Quotes' THEN
ROW_NUMBER() OVER (
PARTITION BY b.customer, b.partgroup, b.dataseg, b.version PARTITION BY b.customer, b.partgroup, b.dataseg, b.version
ORDER BY CASE WHEN b.version = 'Quotes' THEN b.odate ELSE NULL END DESC ORDER BY b.odate DESC
) AS rn_dsq )
END AS rn_dsq
FROM base b FROM base b
) )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -121,7 +129,7 @@ ON #flagged(customer, partgroup, dataseg, version, part, qty, price, odate, ordn
-- Step 3.1: Explode all flags from the #flagged table -- Step 3.1: Explode all flags from the #flagged table
WITH exploded_flags AS ( WITH exploded_flags AS (
SELECT SELECT
customer, partgroup, dataseg, version, part, qty, price, odate, ordnum, quoten, customer, partgroup, part, dataseg, version, qty, price, odate, ordnum, quoten,
flag flag
FROM #flagged FROM #flagged
CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag) CROSS APPLY (VALUES (f1), (f2), (f3), (f4), (f5), (f6)) AS f(flag)

View File

@ -1,10 +1,13 @@
REFRESH MATERIALIZED VIEW pricequote.lastpricedetail; -- REFRESH 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 (
SELECT SELECT
customer, customer,
partgroup, partgroup,
part,
dataseg, dataseg,
version, version,
qtyord AS qty, qtyord AS qty,
@ -23,32 +26,41 @@ WITH base AS (
AND version IN ('Actual', 'Quotes') AND version IN ('Actual', 'Quotes')
), ),
ranked AS ( ranked AS (
SELECT *, SELECT b.*,
ROW_NUMBER() OVER ( -- Most recent sale (Actuals first, newest date first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY CASE WHEN version = 'Actual' THEN odate END DESC ORDER BY (version = 'Actual') DESC,
) AS rn_mrs, odate DESC NULLS LAST
ROW_NUMBER() OVER ( ) END AS rn_mrs,
-- Most recent quote (Quotes first, newest date first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY CASE WHEN version = 'Quotes' THEN odate END DESC ORDER BY (version = 'Quotes') DESC,
) AS rn_mrq, odate DESC NULLS LAST
ROW_NUMBER() OVER ( ) END AS rn_mrq,
-- Largest volume sale in last year (those inside window first)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY CASE WHEN version = 'Actual' AND odate >= (CURRENT_DATE - INTERVAL '1 year') THEN qty END DESC ORDER BY (version = 'Actual' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
) AS rn_lvs, qty DESC NULLS LAST
ROW_NUMBER() OVER ( ) END AS rn_lvs,
-- Largest volume quote in last year (those inside window first)
CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup PARTITION BY customer, partgroup
ORDER BY CASE WHEN version = 'Quotes' AND odate >= (CURRENT_DATE - INTERVAL '1 year') THEN qty END DESC ORDER BY (version = 'Quotes' AND odate >= CURRENT_DATE - INTERVAL '1 year') DESC,
) AS rn_lvq, qty DESC NULLS LAST
ROW_NUMBER() OVER ( ) END AS rn_lvq,
-- Per dataseg/version: most recent (version fixed in partition, so just date)
CASE WHEN version = 'Actual' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version PARTITION BY customer, partgroup, dataseg, version
ORDER BY CASE WHEN version = 'Actual' THEN odate END DESC ORDER BY odate DESC NULLS LAST
) AS rn_dss, ) END AS rn_dss,
ROW_NUMBER() OVER ( CASE WHEN version = 'Quotes' THEN ROW_NUMBER() OVER (
PARTITION BY customer, partgroup, dataseg, version PARTITION BY customer, partgroup, dataseg, version
ORDER BY CASE WHEN version = 'Quotes' THEN odate END DESC ORDER BY odate DESC NULLS LAST
) AS rn_dsq ) END AS rn_dsq
FROM base FROM base b
), ),
flagged AS ( flagged AS (
SELECT *, SELECT *,
@ -56,15 +68,16 @@ flagged AS (
CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2, CASE WHEN rn_mrq = 1 THEN 'mrq' END AS f2,
CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3, CASE WHEN rn_lvs = 1 THEN 'lvs' END AS f3,
CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4, CASE WHEN rn_lvq = 1 THEN 'lvq' END AS f4,
CASE WHEN rn_dss = 1 THEN 'dss' END AS f5, CASE WHEN rn_dss = 1 AND VERSION = 'Actual' THEN 'dss' END AS f5,
CASE WHEN rn_dsq = 1 THEN 'dsq' END AS f6 CASE WHEN rn_dsq = 1 AND VERSION = 'Quotes' THEN 'dsq' END AS f6
FROM ranked FROM ranked
WHERE WHERE
rn_mrs = 1 OR rn_mrq = 1 OR rn_lvs = 1 OR rn_lvq = 1 OR rn_dss = 1 OR rn_dsq = 1 rn_mrs = 1 OR rn_mrq = 1 OR rn_lvs = 1 OR rn_lvq = 1 OR rn_dss = 1 OR rn_dsq = 1
), )
exploded_flags AS ( --SELECT * FROM flagged WHERE customer = 'HYBELS' AND partgroup = 'HZP3E100'
,exploded_flags AS (
SELECT SELECT
customer, partgroup, dataseg, version, qty, price, odate, ordnum, quoten, customer, partgroup, part, dataseg, version, qty, price, odate, ordnum, quoten,
unnest(ARRAY[f1, f2, f3, f4, f5, f6]) AS flag unnest(ARRAY[f1, f2, f3, f4, f5, f6]) AS flag
FROM flagged FROM flagged
), ),
@ -77,6 +90,7 @@ serialized_flags AS (
jsonb_build_object( jsonb_build_object(
'version', version, 'version', version,
'datasegment', dataseg, 'datasegment', dataseg,
'part', part,
'qty', qty, 'qty', qty,
'price', price, 'price', price,
'odate', odate, 'odate', odate,

View File

@ -0,0 +1,28 @@
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

@ -0,0 +1,17 @@
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; pricequote.target_prices_base;

View File

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