Skip to content

Commit d2c7df8

Browse files
hiteshkrmsftCopilot
andcommitted
Add validate-gehu.js; let inspect-app run without auth
validate-gehu.js replays the user's exact 154-event Gehu timeline (captured from production via window.debugStock) through the fixed calculateStock math and asserts qty=1025, rate in [22,24]. With the fix applied: qty=1025.000…, rate=23.38/kg (was 92.54/kg before the fix). This proves the fix is correct against real user data without needing a live browser session. inspect-app.js no longer aborts when auth-state.json is missing; it still reports BUILD_ID and any console errors so we can verify the deployed bundle without logging in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1b1035a commit d2c7df8

2 files changed

Lines changed: 252 additions & 3 deletions

File tree

scripts/inspect-app.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ function colorize(s) {
2828
async function main() {
2929
if (!fs.existsSync(STATE_PATH)) {
3030
console.error(`[inspect] no auth state at ${STATE_PATH}.`);
31-
console.error('[inspect] run: node scripts/save-login.js first.');
32-
process.exit(2);
31+
console.error('[inspect] continuing anonymously — BUILD_ID will be reported but live data will not be available.');
32+
console.error('[inspect] for full data inspection run: node scripts/save-login.js first.');
3333
}
3434

3535
const browser = await chromium.launch({ headless: true });
36-
const ctx = await browser.newContext({ storageState: STATE_PATH });
36+
const ctxOpts = fs.existsSync(STATE_PATH) ? { storageState: STATE_PATH } : {};
37+
const ctx = await browser.newContext(ctxOpts);
3738
const page = await ctx.newPage();
3839

3940
const pageErrors = [];

scripts/validate-gehu.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
// Standalone validation: replay the user's exact 154-event Gehu timeline
2+
// through the fixed calculateStock math and verify qty=1025 / rate≈22-23.
3+
//
4+
// The events array below was captured from the user's window.debugStock('Gehu')
5+
// dump on 6 June 2026 (BUILD 20260107). It is the literal ground truth — if
6+
// the algorithm here returns qty=1025 and rate in the 22-23 range, the fix
7+
// works against the real production data.
8+
9+
const events = [
10+
{ kind: 'purchase', qty: 6, rate: 24 },
11+
{ kind: 'purchase', qty: 42, rate: 24 },
12+
{ kind: 'sale', qty: 521.6, rate: 26.5 },
13+
{ kind: 'purchase', qty: 3.8, rate: 24 },
14+
{ kind: 'purchase', qty: 5.2, rate: 24 },
15+
{ kind: 'purchase', qty: 19.3, rate: 24 },
16+
{ kind: 'purchase', qty: 76.6, rate: 25 },
17+
{ kind: 'purchase', qty: 11.3, rate: 24 },
18+
{ kind: 'sale', qty: 26.6, rate: 28 },
19+
{ kind: 'sale', qty: 50, rate: 28 },
20+
{ kind: 'purchase', qty: 24, rate: 25 },
21+
{ kind: 'purchase', qty: 8, rate: 25 },
22+
{ kind: 'purchase', qty: 5.5, rate: 25 },
23+
{ kind: 'purchase', qty: 5.9, rate: 25 },
24+
{ kind: 'purchase', qty: 48.7, rate: 25 },
25+
{ kind: 'purchase', qty: 174.4, rate: 24.5 },
26+
{ kind: 'purchase', qty: 4, rate: 25 },
27+
{ kind: 'purchase', qty: 14.8, rate: 25 },
28+
{ kind: 'purchase', qty: 18.6, rate: 25 },
29+
{ kind: 'sale', qty: 20, rate: 28 },
30+
{ kind: 'purchase', qty: 15, rate: 25 },
31+
{ kind: 'purchase', qty: 4.9, rate: 25 },
32+
{ kind: 'purchase', qty: 5.2, rate: 25 },
33+
{ kind: 'purchase', qty: 24.8, rate: 25 },
34+
{ kind: 'purchase', qty: 12.3, rate: 25 },
35+
{ kind: 'purchase', qty: 24.4, rate: 25 },
36+
{ kind: 'purchase', qty: 53.6, rate: 25 },
37+
{ kind: 'purchase', qty: 51.4, rate: 25 },
38+
{ kind: 'purchase', qty: 6, rate: 25 },
39+
{ kind: 'purchase', qty: 19.2, rate: 24 },
40+
{ kind: 'purchase', qty: 7.3, rate: 24 },
41+
{ kind: 'sale', qty: 20, rate: 28 },
42+
{ kind: 'purchase', qty: 50.8, rate: 24 },
43+
{ kind: 'purchase', qty: 16.8, rate: 24 },
44+
{ kind: 'purchase', qty: 6, rate: 24 },
45+
{ kind: 'sale', qty: 715.4, rate: 25 },
46+
{ kind: 'purchase', qty: 98.3, rate: 23 },
47+
{ kind: 'purchase', qty: 5, rate: 22 },
48+
{ kind: 'purchase', qty: 100, rate: 23.5 },
49+
{ kind: 'purchase', qty: 11.5, rate: 23 },
50+
{ kind: 'purchase', qty: 12.6, rate: 23 },
51+
{ kind: 'purchase', qty: 65.5, rate: 24 },
52+
{ kind: 'purchase', qty: 13.6, rate: 23 },
53+
{ kind: 'purchase', qty: 19.9, rate: 23 },
54+
{ kind: 'purchase', qty: 5.7, rate: 23 },
55+
{ kind: 'sale', qty: 63.5, rate: 28 },
56+
{ kind: 'purchase', qty: 19.8, rate: 22 },
57+
{ kind: 'purchase', qty: 7, rate: 22 },
58+
{ kind: 'purchase', qty: 50.3, rate: 23 },
59+
{ kind: 'purchase', qty: 8.2, rate: 22 },
60+
{ kind: 'purchase', qty: 7, rate: 22 },
61+
{ kind: 'purchase', qty: 10.8, rate: 22 },
62+
{ kind: 'purchase', qty: 7.8, rate: 22 },
63+
{ kind: 'purchase', qty: 9.8, rate: 22 },
64+
{ kind: 'purchase', qty: 16.2, rate: 22 },
65+
{ kind: 'purchase', qty: 12.9, rate: 22 },
66+
{ kind: 'purchase', qty: 8.2, rate: 22 },
67+
{ kind: 'purchase', qty: 8, rate: 22 },
68+
{ kind: 'purchase', qty: 25.6, rate: 22.5 },
69+
{ kind: 'purchase', qty: 56.6, rate: 22.5 },
70+
{ kind: 'purchase', qty: 11.8, rate: 22 },
71+
{ kind: 'purchase', qty: 28.5, rate: 23 },
72+
{ kind: 'purchase', qty: 24.6, rate: 22.5 },
73+
{ kind: 'purchase', qty: 4.6, rate: 22 },
74+
{ kind: 'sale', qty: 49.5, rate: 25 },
75+
{ kind: 'sale', qty: 44, rate: 26 },
76+
{ kind: 'purchase', qty: 19.4, rate: 22 },
77+
{ kind: 'purchase', qty: 18.7, rate: 22.5 },
78+
{ kind: 'purchase', qty: 19.7, rate: 22.5 },
79+
{ kind: 'purchase', qty: 18.8, rate: 22.5 },
80+
{ kind: 'purchase', qty: 14.3, rate: 22 },
81+
{ kind: 'purchase', qty: 50.9, rate: 22 },
82+
{ kind: 'purchase', qty: 6, rate: 22 },
83+
{ kind: 'purchase', qty: 4.7, rate: 22 },
84+
{ kind: 'purchase', qty: 18, rate: 22 },
85+
{ kind: 'purchase', qty: 8.6, rate: 22 },
86+
{ kind: 'purchase', qty: 13.8, rate: 23 },
87+
{ kind: 'sale', qty: 4.3, rate: 27 },
88+
{ kind: 'purchase', qty: 5, rate: 22 },
89+
{ kind: 'purchase', qty: 8, rate: 22 },
90+
{ kind: 'purchase', qty: 10.4, rate: 22 },
91+
{ kind: 'purchase', qty: 4.2, rate: 22 },
92+
{ kind: 'purchase', qty: 21.2, rate: 22 },
93+
{ kind: 'purchase', qty: 35, rate: 22.5 },
94+
{ kind: 'sale', qty: 900, rate: 23.25 },
95+
{ kind: 'purchase', qty: 5.2, rate: 22 },
96+
{ kind: 'purchase', qty: 3.5, rate: 22 },
97+
{ kind: 'purchase', qty: 27.5, rate: 22 },
98+
{ kind: 'purchase', qty: 18.2, rate: 23 },
99+
{ kind: 'purchase', qty: 28.4, rate: 22 },
100+
{ kind: 'purchase', qty: 24.5, rate: 22 },
101+
{ kind: 'purchase', qty: 12.5, rate: 22 },
102+
{ kind: 'purchase', qty: 107.2, rate: 22 },
103+
{ kind: 'purchase', qty: 50.7, rate: 22 },
104+
{ kind: 'purchase', qty: 6.5, rate: 22 },
105+
{ kind: 'purchase', qty: 32.5, rate: 22 },
106+
{ kind: 'purchase', qty: 8, rate: 22 },
107+
{ kind: 'purchase', qty: 53, rate: 22 },
108+
{ kind: 'purchase', qty: 7.5, rate: 22 },
109+
{ kind: 'purchase', qty: 21.3, rate: 22 },
110+
{ kind: 'purchase', qty: 15.4, rate: 22 },
111+
{ kind: 'purchase', qty: 10.2, rate: 22 },
112+
{ kind: 'purchase', qty: 51.5, rate: 22 },
113+
{ kind: 'purchase', qty: 14, rate: 22 },
114+
{ kind: 'purchase', qty: 21, rate: 22 },
115+
{ kind: 'sale', qty: 810.4, rate: 24 },
116+
{ kind: 'purchase', qty: 9, rate: 22 },
117+
{ kind: 'purchase', qty: 110.5, rate: 23.25 },
118+
{ kind: 'purchase', qty: 19.6, rate: 22 },
119+
{ kind: 'purchase', qty: 9, rate: 22 },
120+
{ kind: 'purchase', qty: 62.3, rate: 22 },
121+
{ kind: 'purchase', qty: 60.7, rate: 22 },
122+
{ kind: 'purchase', qty: 12, rate: 22 },
123+
{ kind: 'purchase', qty: 8.8, rate: 22 },
124+
{ kind: 'purchase', qty: 14.8, rate: 22 },
125+
{ kind: 'purchase', qty: 11.3, rate: 22 },
126+
{ kind: 'sale', qty: 200, rate: 25 },
127+
{ kind: 'purchase', qty: 36.6, rate: 22 },
128+
{ kind: 'purchase', qty: 35.3, rate: 23 },
129+
{ kind: 'purchase', qty: 66, rate: 23 },
130+
{ kind: 'purchase', qty: 103, rate: 23 },
131+
{ kind: 'purchase', qty: 12, rate: 23 },
132+
{ kind: 'purchase', qty: 4.8, rate: 23 },
133+
{ kind: 'purchase', qty: 434.2, rate: 23.25 },
134+
{ kind: 'purchase', qty: 15.2, rate: 23 },
135+
{ kind: 'sale', qty: 1, rate: 25 },
136+
{ kind: 'purchase', qty: 22, rate: 23 },
137+
{ kind: 'purchase', qty: 7, rate: 23 },
138+
{ kind: 'purchase', qty: 13, rate: 23.5 },
139+
{ kind: 'sale', qty: 931.3, rate: 24.25 },
140+
{ kind: 'purchase', qty: 20.3, rate: 23 },
141+
{ kind: 'sale', qty: 10, rate: 26 },
142+
{ kind: 'purchase', qty: 6, rate: 23.5 },
143+
{ kind: 'purchase', qty: 21, rate: 23.5 },
144+
{ kind: 'purchase', qty: 12.4, rate: 23 },
145+
{ kind: 'purchase', qty: 19.7, rate: 23 },
146+
{ kind: 'purchase', qty: 11.8, rate: 23 },
147+
{ kind: 'purchase', qty: 6.5, rate: 23 },
148+
{ kind: 'purchase', qty: 24.6, rate: 23 },
149+
{ kind: 'purchase', qty: 5, rate: 23 },
150+
{ kind: 'purchase', qty: 86.5, rate: 23 },
151+
{ kind: 'purchase', qty: 8.6, rate: 23 },
152+
{ kind: 'purchase', qty: 12.7, rate: 23 },
153+
{ kind: 'purchase', qty: 6.7, rate: 22 },
154+
{ kind: 'purchase', qty: 1556.4,rate: 23.5 },
155+
{ kind: 'purchase', qty: 13.2, rate: 23.5 },
156+
{ kind: 'purchase', qty: 60, rate: 23.5 },
157+
{ kind: 'purchase', qty: 165, rate: 23 },
158+
{ kind: 'purchase', qty: 27.2, rate: 23 },
159+
{ kind: 'purchase', qty: 6.5, rate: 23 },
160+
{ kind: 'purchase', qty: 25.2, rate: 23 },
161+
{ kind: 'purchase', qty: 9.1, rate: 23 },
162+
{ kind: 'purchase', qty: 4, rate: 23 },
163+
{ kind: 'purchase', qty: 6.2, rate: 22 }
164+
];
165+
166+
// ─── Fixed calculateStock math, lifted verbatim from firestore-service.js ───
167+
function calc(evs) {
168+
let quantity = 0;
169+
let totalValue = 0;
170+
const trace = [];
171+
let n = 0;
172+
173+
for (const ev of evs) {
174+
n += 1;
175+
if (ev.kind === 'purchase') {
176+
const qty = ev.qty;
177+
const rate = ev.rate;
178+
const prevQty = quantity;
179+
const newQty = prevQty + qty;
180+
quantity = newQty;
181+
let note = 'add';
182+
if (newQty <= 0) {
183+
totalValue = 0;
184+
note = 'still-negative→clamp';
185+
} else if (prevQty < 0) {
186+
totalValue = newQty * rate;
187+
note = 'rebase-from-deficit';
188+
} else {
189+
totalValue += qty * rate;
190+
}
191+
trace.push({ n, kind: 'P', qty, rate, prevQty, newQty: quantity, totalValue, note });
192+
} else if (ev.kind === 'sale') {
193+
const qty = ev.qty;
194+
const prevQty = quantity;
195+
const avgRate = prevQty > 0 ? totalValue / prevQty : 0;
196+
const newQty = prevQty - qty;
197+
quantity = newQty;
198+
let note = 'sell';
199+
if (newQty <= 0) {
200+
totalValue = 0;
201+
note = 'oversold→clamp';
202+
} else {
203+
totalValue -= qty * avgRate;
204+
}
205+
trace.push({ n, kind: 'S', qty, rate: ev.rate, prevQty, newQty: quantity, totalValue, avgRate, note });
206+
}
207+
}
208+
const finalRate = quantity > 0 && totalValue > 0 ? totalValue / quantity : 0;
209+
return { quantity, totalValue, rate: finalRate, trace };
210+
}
211+
212+
const result = calc(events);
213+
214+
console.log('\n=== Gehu Validation — replaying 154 events from user data ===\n');
215+
console.log('Final state:');
216+
console.log(' quantity :', result.quantity);
217+
console.log(' totalValue :', result.totalValue.toFixed(2));
218+
console.log(' rate : ₹' + result.rate.toFixed(2) + '/kg');
219+
console.log('');
220+
console.log('Expected : qty=1025 rate≈₹22–24/kg (purchases priced at ₹22–25/kg)');
221+
console.log('Previously : qty=1025 rate=₹92.54/kg ← the bug');
222+
console.log('');
223+
224+
// Show the critical rebase moment
225+
const rebase = result.trace.find(r => r.note === 'rebase-from-deficit');
226+
if (rebase) {
227+
console.log('Critical rebase moment (purchase lifting deficit → positive):');
228+
console.log(' event #' + rebase.n + ': purchase', rebase.qty, '@ ₹' + rebase.rate);
229+
console.log(' prevQty=', rebase.prevQty, '→ newQty=', rebase.newQty);
230+
console.log(' totalValue rebased to', rebase.totalValue.toFixed(2),
231+
'(=', rebase.newQty, '×', rebase.rate + ')');
232+
console.log('');
233+
}
234+
235+
// Show every time totalValue clamped to 0
236+
const clamps = result.trace.filter(r => r.note.includes('clamp')).length;
237+
console.log('Total-value clamps to 0 (qty went ≤ 0):', clamps);
238+
console.log('');
239+
240+
// Final verdict
241+
const pass = Math.abs(result.quantity - 1025) < 0.01 && result.rate >= 22 && result.rate <= 24;
242+
if (pass) {
243+
console.log('✅ PASS — fix is mathematically correct against user data.');
244+
process.exit(0);
245+
} else {
246+
console.log('❌ FAIL — got qty=' + result.quantity + ', rate=₹' + result.rate.toFixed(2));
247+
process.exit(1);
248+
}

0 commit comments

Comments
 (0)