-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiagnostics.html
More file actions
199 lines (180 loc) · 7.43 KB
/
Copy pathdiagnostics.html
File metadata and controls
199 lines (180 loc) · 7.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Myra Diagnostics</title>
<link rel="stylesheet" href="./css/myra-app.css" />
</head>
<body>
<div class="wrap" style="max-width: 980px; margin: 60px auto 80px; padding: 0 22px;">
<div class="hero" style="text-align: center; margin-bottom: 26px;">
<h1 style="margin: 0; font-size: clamp(2.0rem, 5vw, 2.8rem); letter-spacing: -0.02em;">Myra Diagnostics</h1>
<p style="margin: 12px auto 0; max-width: 75ch;">
Inventory and data quality checks for the recommendation engine.
</p>
</div>
<div id="myra-app">
<div class="screen active" style="display:block;">
<div class="actions" style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:14px;">
<a class="btn" href="./index.html">Back to Myra</a>
<button class="btn btn-primary" id="btnRun" type="button">Run Checks</button>
</div>
<div id="status" style="color: rgba(245,245,255,0.7); min-height: 1.2em;" aria-live="polite"></div>
<div id="out" style="margin-top: 14px;"></div>
</div>
</div>
</div>
<script>
(function () {
"use strict";
const els = {
btnRun: document.getElementById("btnRun"),
status: document.getElementById("status"),
out: document.getElementById("out"),
};
function safeText(v) {
return String(v || "").trim();
}
function parseMoney(value) {
const raw = safeText(value);
if (!raw) return null;
const m = raw.match(/(\d+(\.\d+)?)/);
if (!m) return null;
const n = Number(m[1]);
return Number.isFinite(n) ? n : null;
}
function parseBeerAbv(v) {
const raw = safeText(v);
const m = raw.match(/(\d+(\.\d+)?)/);
if (!m) return null;
let n = Number(m[1]);
if (!Number.isFinite(n)) return null;
if (n > 0 && n <= 1) n = n * 100;
return n;
}
function card(title, html) {
const d = document.createElement("div");
d.className = "myra-result-card";
d.innerHTML = `
<div class="myra-result-header">
<h3 class="myra-result-title">${title}</h3>
</div>
<div class="myra-result-body">${html}</div>
`;
return d;
}
async function loadJson(path) {
const r = await fetch(path, { cache: "no-cache" });
if (!r.ok) throw new Error(`fetch_failed_${path}_${r.status}`);
return r.json();
}
function kvList(obj) {
const items = Object.entries(obj).map(([k, v]) => `<li><strong>${k}</strong>${v}</li>`).join("");
return `<ul class="myra-kv">${items}</ul>`;
}
function list(title, items) {
if (!items.length) return "";
const li = items.slice(0, 25).map(x => `<li>${x}</li>`).join("");
return `
<div class="myra-explain-box">
<div class="myra-explain-title">${title} (showing ${Math.min(items.length, 25)} of ${items.length})</div>
<ul class="myra-explain-list">${li}</ul>
</div>
`;
}
async function run() {
els.out.innerHTML = "";
els.status.textContent = "Loading…";
const [wine, beer, bar, images] = await Promise.all([
loadJson("./data/inventory.json"),
loadJson("./data/beerinventory2.json"),
loadJson("./data/hardbarinventory.json"),
loadJson("./data/myra-images.json").catch(() => ({ version: 1, by_sku: {}, by_name: {} })),
]);
els.status.textContent = "Running checks…";
// Wine checks
let wineIn = 0;
let wineOut = 0;
let wineLow = 0;
let wineMissingStock = 0;
const wineBadPrice = [];
const wineNoRm = [];
for (const w of wine) {
const rm = w.recommendation_matches;
if (!rm || typeof rm !== "object") {
wineNoRm.push(safeText(w.name) || "(missing name)");
continue;
}
const ss = safeText(rm.stock_status).toLowerCase();
if (!ss) wineMissingStock++;
if (ss === "in stock") wineIn++;
else if (ss === "out of stock") wineOut++;
else if (ss === "low in stock") wineLow++;
const p = parseMoney(w.price);
if (safeText(w.price) && p == null) wineBadPrice.push(`${safeText(w.name)} (${safeText(w.price)})`);
}
const wineCard = card(
"Wine Inventory",
kvList({
Total: wine.length,
"In stock": wineIn,
"Low in stock": wineLow,
"Out of stock": wineOut,
"Missing stock_status": wineMissingStock,
"Bad price parse": wineBadPrice.length,
"Missing recommendation_matches": wineNoRm.length,
"Images mapped (by SKU)": Object.keys(images.by_sku || {}).length,
"Images mapped (by name)": Object.keys(images.by_name || {}).length,
}) +
`<div class="myra-explain">${list("Bad price parse", wineBadPrice)}${list("Missing recommendation_matches", wineNoRm)}</div>`
);
// Beer checks
const beerBadAbv = [];
const beerNoImage = [];
for (const b of beer) {
const abv = parseBeerAbv(b.abv_percent);
if (safeText(b.abv_percent) && abv == null) beerBadAbv.push(`${safeText(b.name)} (${safeText(b.abv_percent)})`);
const img = safeText(b.image);
if (!img) beerNoImage.push(safeText(b.name));
}
const beerCard = card(
"Beer Inventory",
kvList({
Total: beer.length,
"Bad ABV parse": beerBadAbv.length,
"Missing image": beerNoImage.length,
}) + `<div class="myra-explain">${list("Bad ABV parse", beerBadAbv)}${list("Missing image", beerNoImage)}</div>`
);
// Spirits checks
const barMissingAbv = [];
const barMissingPrice = [];
for (const r of bar) {
const abv = Number(r.abv);
if (!Number.isFinite(abv)) barMissingAbv.push(safeText(r.name) || "(missing name)");
const price = r.price;
if (price == null || String(price).toLowerCase() === "none" || safeText(price) === "") barMissingPrice.push(safeText(r.name) || "(missing name)");
}
const barCard = card(
"Spirits Inventory",
kvList({
Total: bar.length,
"Missing ABV": barMissingAbv.length,
"Missing price": barMissingPrice.length,
}) + `<div class="myra-explain">${list("Missing ABV", barMissingAbv)}${list("Missing price", barMissingPrice)}</div>`
);
els.out.appendChild(wineCard);
els.out.appendChild(beerCard);
els.out.appendChild(barCard);
els.status.textContent = "Done.";
}
els.btnRun.addEventListener("click", () => {
run().catch(err => {
console.error(err);
els.status.textContent = `Failed: ${String(err && err.message ? err.message : err)}`;
});
});
})();
</script>
</body>
</html>