2023-11-08 12:54:46 -05:00
|
|
|
export function apply_guidance(doc: any) {
|
|
|
|
|
2023-12-01 13:53:23 -05:00
|
|
|
function sortObjectKeys(obj) {
|
|
|
|
// If the object is not an actual object or is an array, return it as is
|
|
|
|
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new object and sort the keys
|
|
|
|
const sortedObj = {};
|
|
|
|
Object.keys(obj).sort().forEach(key => {
|
|
|
|
// Recursively apply the function for nested objects
|
|
|
|
sortedObj[key] = sortObjectKeys(obj[key]);
|
|
|
|
});
|
|
|
|
|
|
|
|
return sortedObj;
|
|
|
|
}
|
|
|
|
|
2023-11-15 13:47:00 -05:00
|
|
|
function getAdjValue(number) {
|
|
|
|
const data = [
|
|
|
|
{f: 2.001, t: 1000, snap: 3, adj: 0 },
|
|
|
|
{f: 1.001, t: 2, snap: 2, adj: 0 },
|
|
|
|
{f: 0.1, t: 1, snap: 1, adj: 0 },
|
|
|
|
{f: 0, t: 0.1, snap: 0, adj: 0 },
|
|
|
|
{f: -1, t: -0.00001, snap: -1, adj: 0.05},
|
|
|
|
{f: -2, t: -0.999999, snap: -2, adj: 0.05},
|
|
|
|
{f: -1000, t: -2.001, snap: -3, adj: 0.10},
|
|
|
|
];
|
|
|
|
const match = data.find(row => number >= row.f && number <= row.t);
|
|
|
|
return match ? match.adj : null;
|
|
|
|
}
|
|
|
|
|
2023-12-11 11:29:16 -05:00
|
|
|
//-------------------------------set flor prices-------------------------------
|
|
|
|
function getFloor(stlc) {
|
|
|
|
switch (stlc) {
|
|
|
|
case "SDD10000":
|
|
|
|
return 0.045;
|
|
|
|
case "SDD10001":
|
|
|
|
return 0.045;
|
|
|
|
case "SDD12000":
|
|
|
|
return 0.065;
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-16 10:25:41 -05:00
|
|
|
function lowestPrice(priceObject) {
|
|
|
|
let Price = Infinity;
|
|
|
|
let Reason = '';
|
|
|
|
let Source = '';
|
|
|
|
let Snapped = Infinity;
|
|
|
|
|
2023-11-16 11:33:14 -05:00
|
|
|
//console.log(priceObject["targ"][0]);
|
2023-11-16 10:25:41 -05:00
|
|
|
// Iterate over each property in the object
|
|
|
|
for (let key in priceObject) {
|
2023-11-16 11:33:14 -05:00
|
|
|
// Ignore markPrice unless targPrice is null
|
|
|
|
if (key === "mark" && priceObject["targ"][0] !== null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (priceObject.hasOwnProperty(key)) {
|
2023-11-16 10:25:41 -05:00
|
|
|
let [cprice, creason, csource, csnap] = priceObject[key];
|
|
|
|
// Check if the current price is lower than the found so far
|
|
|
|
if (cprice && cprice < Price) {
|
|
|
|
Price = cprice;
|
|
|
|
Reason = creason;
|
|
|
|
Source = csource;
|
|
|
|
Snapped = csnap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {Reason, Price, Source, Snapped};
|
|
|
|
}
|
|
|
|
|
|
|
|
function ceiling(value, significance) {
|
|
|
|
return Math.ceil(value / significance) * significance;
|
|
|
|
}
|
|
|
|
|
2023-11-16 11:41:54 -05:00
|
|
|
function r5(value) {
|
|
|
|
return Number(value.toFixed(5));
|
|
|
|
}
|
|
|
|
|
2023-11-16 10:25:41 -05:00
|
|
|
function pp(value) {
|
2023-11-16 10:35:29 -05:00
|
|
|
// Multiplies by 1000 and rounds to the nearest 2 decimals
|
|
|
|
var result = Math.round(value * 1000 * 100) / 100;
|
|
|
|
|
|
|
|
// Converts the number to a string with commas for thousand separators
|
|
|
|
return result.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
|
2023-11-15 16:57:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// --------------------extract incoming data------------------------------------------------------
|
|
|
|
const targetPrice = doc.pricing?.v1tp ?? doc.pricing?.v0tp;
|
|
|
|
const priceBand = doc.pricing?.v1stdv ?? doc.pricing?.v0stdv;
|
|
|
|
const earlyCustPrice = doc.hist?.cust?.early_price;
|
|
|
|
const earlyCustSeason = doc.hist?.cust?.early_season;
|
2023-11-16 10:25:41 -05:00
|
|
|
const earlyMarkPrice = doc.hist?.market?.early_price;
|
|
|
|
const earlyMarkSeason = doc.hist?.market?.early_season;
|
2023-11-15 16:57:21 -05:00
|
|
|
const bridgePremium = doc.pricing?.bridgePremium;
|
|
|
|
const bridgedPrice = Number((earlyCustPrice * (bridgePremium ?? 1.00)).toFixed(5));
|
|
|
|
const altHist = doc.hist?.cust?.ds;
|
|
|
|
const iidx = doc.pricing?.iidx;
|
|
|
|
const curr = doc.customer?.curr;
|
|
|
|
const fxrate = doc.customer?.fxrate ?? 1.0;
|
|
|
|
const qty = doc.inputs?.qty;
|
|
|
|
const pltq = doc.product?.pltq;
|
|
|
|
const inflation = Math.max(...Object.keys(iidx).map(Number));
|
|
|
|
const inflationFactor = iidx[inflation];
|
|
|
|
const list = doc.pricing?.list && doc.product?.itemrel === "2" ? doc.pricing?.list : null;
|
2023-11-16 11:33:14 -05:00
|
|
|
const listUSD = list ? list * fxrate :null;
|
2023-12-01 13:53:23 -05:00
|
|
|
const stlc = doc.inputs.stlc;
|
2023-11-15 15:26:21 -05:00
|
|
|
|
2023-11-15 16:57:21 -05:00
|
|
|
// ------------------calculate price adders------------------------------------------------------
|
2023-12-01 13:53:23 -05:00
|
|
|
let ltp = stlc.includes("SDD") || stlc.includes("HZP") ? 0 : (qty < pltq ? 0.15 : null);
|
2023-11-15 16:57:21 -05:00
|
|
|
let anchor_sd = priceBand ? ((bridgedPrice - targetPrice) / priceBand).toFixed(2) : 0
|
|
|
|
let optimization = getAdjValue(anchor_sd);
|
|
|
|
let custAdder = (ltp ?? 0) + optimization + inflationFactor;
|
|
|
|
let markAdder = (ltp ?? 0) + inflationFactor;
|
2023-11-16 10:25:41 -05:00
|
|
|
let inflReason = (inflationFactor ?? 0) !== 0 ? ` + ${(inflationFactor * 100).toFixed(1)}% infl`: "";
|
|
|
|
let ltpReason = ltp ? ` + ${(ltp * 100).toFixed(1)}% ltp` : "";
|
|
|
|
let optReason = (optimization ?? 0) !== 0 ? ` + ${(optimization * 100).toFixed(1)}% opt`: "";
|
2023-11-15 16:57:21 -05:00
|
|
|
let custAddReason = `${inflReason}${ltpReason}${optReason}`;
|
|
|
|
let markAddReason = `${inflReason}${ltpReason}`;
|
2023-12-11 11:29:16 -05:00
|
|
|
let floor = getFloor(stlc);
|
2023-11-15 13:47:00 -05:00
|
|
|
|
2023-11-15 16:57:21 -05:00
|
|
|
// ------------------start building price options------------------------------------------------
|
|
|
|
|
2023-11-16 10:25:41 -05:00
|
|
|
let snap = .0005;
|
|
|
|
|
2023-11-16 11:41:54 -05:00
|
|
|
let custPrice = r5(bridgedPrice * (1 + custAdder));
|
2023-11-16 10:25:41 -05:00
|
|
|
let custSeason = earlyCustSeason;
|
2023-11-15 16:57:21 -05:00
|
|
|
let custReason = bridgePremium
|
2023-11-16 10:25:41 -05:00
|
|
|
? `${custSeason} (similar ${altHist} price ${pp(earlyCustPrice)} x ${bridgePremium} = ${pp(bridgedPrice)})${custAddReason}`
|
2023-11-16 11:33:14 -05:00
|
|
|
: `${custSeason} price ${pp(bridgedPrice)}${custAddReason}`;
|
2023-11-16 11:41:54 -05:00
|
|
|
let markPrice = r5(earlyMarkPrice * (1 + markAdder));
|
2023-11-16 10:35:29 -05:00
|
|
|
let markReason = `${earlyMarkSeason} ASP ${pp(earlyMarkPrice)}${markAddReason}`;
|
2023-11-16 11:41:54 -05:00
|
|
|
let targPrice = targetPrice ? r5(targetPrice * (1 + markAdder)) : null;
|
2023-11-16 10:25:41 -05:00
|
|
|
let targReason = `Target price ${pp(targetPrice)}${markAddReason}`;
|
|
|
|
let listPrice = listUSD;
|
2023-12-01 13:53:23 -05:00
|
|
|
let listReason = fxrate === 1 ? `list ${pp(list)}` : `list ${pp(list)} CAD ${pp(listUSD)} USD`;
|
2023-11-15 13:47:00 -05:00
|
|
|
|
2023-11-16 10:25:41 -05:00
|
|
|
let prices = {
|
2023-12-07 08:29:47 -05:00
|
|
|
cust: [custPrice, custReason, "cust", r5(ceiling(custPrice,snap))],
|
|
|
|
mark: [markPrice, markReason, "mark", r5(ceiling(markPrice,snap))],
|
|
|
|
targ: [targPrice, targReason, "targ", r5(ceiling(targPrice,snap))],
|
|
|
|
list: [listPrice, listReason, "list", r5(ceiling(listPrice,snap))]
|
2023-11-15 17:04:16 -05:00
|
|
|
}
|
|
|
|
|
2023-11-16 10:25:41 -05:00
|
|
|
let finalPrice = lowestPrice(prices);
|
2023-12-11 11:29:16 -05:00
|
|
|
if (floor && floor > finalPrice.Price) {
|
|
|
|
finalPrice.Price = floor;
|
|
|
|
finalPrice.Snapped = floor;
|
|
|
|
finalPrice.Reason = `${finalPrice.Reason} floor at ${floor}`;
|
|
|
|
}
|
2023-11-16 10:25:41 -05:00
|
|
|
let guidance = {
|
|
|
|
prices
|
|
|
|
,finalPrice
|
2023-11-20 15:56:18 -05:00
|
|
|
,targetPrice
|
|
|
|
,listUSD
|
|
|
|
,ltp
|
|
|
|
,inflationFactor
|
|
|
|
,optimization
|
2023-11-10 02:22:23 -05:00
|
|
|
}
|
2023-11-13 11:02:41 -05:00
|
|
|
doc.guidance = guidance;
|
2023-12-01 13:53:23 -05:00
|
|
|
//return doc;
|
|
|
|
return sortObjectKeys(doc);
|
2023-11-08 12:54:46 -05:00
|
|
|
}
|