Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -249,135 +249,48 @@ def load_preset(name):
|
|
| 249 |
# ==========================================================================
|
| 250 |
|
| 251 |
def eval_intent_section_md():
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
)
|
| 260 |
-
per_class_table = "\n".join(per_class_rows)
|
| 261 |
-
return f"""
|
| 262 |
-
## 1. Intent Classifier (TF-IDF + Logistic Regression)
|
| 263 |
-
|
| 264 |
-
**What it does:** routes a free-text query (e.g. *"The conveyor belt in Zone C
|
| 265 |
-
is making noise"*) into one of 8 operational categories, used by both the
|
| 266 |
-
AI Assistant and Inventory & Order Query tabs to decide how to handle a
|
| 267 |
-
request.
|
| 268 |
-
|
| 269 |
-
**Training data:** {intent_eval.get('n_train', '?') } synthetically generated
|
| 270 |
-
example queries (see chart below), built from ~8 hand-written templates per
|
| 271 |
-
category with randomised SKU codes, zone names, order IDs, and equipment IDs
|
| 272 |
-
slotted in -- e.g. *"How many units of {{sku}} are in {{zone}}?"*. This keeps
|
| 273 |
-
the language varied while being fully reproducible (`src/data_generation.py`).
|
| 274 |
-
|
| 275 |
-
Evaluated on a **held-out stratified test split** of {intent_eval.get('n_test', '?')}
|
| 276 |
-
examples the model never saw during training.
|
| 277 |
-
|
| 278 |
-
| Metric | Score |
|
| 279 |
-
|---|---|
|
| 280 |
-
| **Accuracy** | **{intent_eval.get('accuracy', 0):.2%}** |
|
| 281 |
-
| **Macro F1** | **{intent_eval.get('macro_f1', 0):.2%}** |
|
| 282 |
-
|
| 283 |
-
**Per-class performance:**
|
| 284 |
-
|
| 285 |
-
| Intent | Precision | Recall | F1 | Support |
|
| 286 |
-
|---|---|---|---|---|
|
| 287 |
-
{per_class_table}
|
| 288 |
-
"""
|
| 289 |
|
| 290 |
|
| 291 |
def eval_anomaly_section_md():
|
| 292 |
-
return
|
| 293 |
-
## 2. Predictive Maintenance Anomaly Detector
|
| 294 |
-
|
| 295 |
-
**
|
| 296 |
-
(
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
**Training data:** {900 + 100} synthetic sensor readings (900 "normal"
|
| 300 |
-
+ 100 "anomaly" patterns), each with 4 features. Normal readings are drawn
|
| 301 |
-
from realistic operating ranges (e.g. ~55°C motor temp, ~2.2 mm/s vibration);
|
| 302 |
-
anomalies simulate bearing wear / misalignment / overload (elevated temp,
|
| 303 |
-
vibration, and current with reduced belt speed). See the distribution chart
|
| 304 |
-
below for exactly how these two classes differ.
|
| 305 |
-
|
| 306 |
-
The model itself is trained **unsupervised** (Isolation Forest never sees
|
| 307 |
-
the anomaly label during fitting) -- labels are used only to *evaluate* it
|
| 308 |
-
afterward, on a held-out test split of {anomaly_eval.get('n_test', '?')}
|
| 309 |
-
readings ({anomaly_eval.get('test_anomaly_rate', 0):.1%} true anomaly rate).
|
| 310 |
-
|
| 311 |
-
| Metric | Score |
|
| 312 |
-
|---|---|
|
| 313 |
-
| **Precision** | **{anomaly_eval.get('precision', 0):.2%}** |
|
| 314 |
-
| **Recall** | **{anomaly_eval.get('recall', 0):.2%}** |
|
| 315 |
-
| **F1 Score** | **{anomaly_eval.get('f1', 0):.2%}** |
|
| 316 |
-
| **ROC-AUC** | **{anomaly_eval.get('roc_auc', 0):.3f}** |
|
| 317 |
-
| Accuracy | {anomaly_eval.get('accuracy', 0):.2%} |
|
| 318 |
-
"""
|
| 319 |
|
| 320 |
|
| 321 |
def eval_retrieval_section_md():
|
| 322 |
-
|
| 323 |
-
f"
|
| 324 |
-
f"
|
| 325 |
-
|
|
|
|
| 326 |
)
|
| 327 |
-
return f"""
|
| 328 |
-
## 3. Retrieval (RAG) Evaluation
|
| 329 |
-
|
| 330 |
-
**What it does:** before the LLM answers a question, this component finds
|
| 331 |
-
the most relevant passages from a 10-article warehouse-operations knowledge
|
| 332 |
-
base (AS/RS, AGV/AMR, WMS, sortation, picking strategy, safety, etc. -- see
|
| 333 |
-
`src/knowledge_base.py`) using TF-IDF + cosine similarity, so the LLM answers
|
| 334 |
-
from real context rather than guessing.
|
| 335 |
-
|
| 336 |
-
**Evaluation data:** {retrieval_eval.get('n_queries', '?')} hand-labelled
|
| 337 |
-
(query, expected-article) pairs -- a small ground-truth set built by hand to
|
| 338 |
-
check the retriever finds the *right* article, not just *an* article.
|
| 339 |
-
|
| 340 |
-
| Metric | Score |
|
| 341 |
-
|---|---|
|
| 342 |
-
| **Hit Rate @ 1** | **{retrieval_eval.get('hit_rate_at_1', 0):.0%}** |
|
| 343 |
-
| **Hit Rate @ 2** | **{retrieval_eval.get('hit_rate_at_2', 0):.0%}** |
|
| 344 |
-
|
| 345 |
-
| Query | Expected Doc | Retrieved (top-1) | Hit | Score |
|
| 346 |
-
|---|---|---|---|---|
|
| 347 |
-
{retrieval_rows}
|
| 348 |
-
"""
|
| 349 |
|
| 350 |
|
| 351 |
def eval_latency_section_md():
|
| 352 |
-
return
|
| 353 |
-
## 4. Latency
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|---|---|
|
| 359 |
-
| Intent classification | {latency_eval.get('intent_classifier_ms', '?')} ms |
|
| 360 |
-
| Anomaly scoring | {latency_eval.get('anomaly_detector_ms', '?')} ms |
|
| 361 |
-
| KB retrieval (TF-IDF) | {latency_eval.get('kb_retrieval_ms', '?')} ms |
|
| 362 |
-
| LLM generation | Depends on the hosted Inference API (measured live per-request in the Assistant tab, not benchmarked here) |
|
| 363 |
-
"""
|
| 364 |
|
| 365 |
|
| 366 |
-
EVAL_METHODOLOGY_MD =
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
fixed seed, so results are fully reproducible via `python build_artifacts.py`.
|
| 372 |
-
- The intent classifier and anomaly detector are evaluated on a **held-out test
|
| 373 |
-
split** they never saw during training (stratified, 25% / 30% respectively).
|
| 374 |
-
- The anomaly detector itself is trained **unsupervised** (Isolation Forest never
|
| 375 |
-
sees the `label` column during `.fit()`); labels are used only to *evaluate* it,
|
| 376 |
-
mirroring how you'd validate an anomaly model against a small set of confirmed
|
| 377 |
-
historical incidents in production.
|
| 378 |
-
- In a production deployment, all three components would be continuously
|
| 379 |
-
re-evaluated against real WMS/WCS/sensor logs rather than synthetic data.
|
| 380 |
-
"""
|
| 381 |
|
| 382 |
|
| 383 |
|
|
@@ -483,8 +396,21 @@ linked repository. Feedback welcome.*
|
|
| 483 |
CUSTOM_CSS = """
|
| 484 |
#title-banner { text-align: center; margin-bottom: 0.5em; }
|
| 485 |
.gradio-container { max-width: 1150px !important; margin: auto; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
"""
|
| 487 |
|
|
|
|
|
|
|
|
|
|
| 488 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="Smart Warehouse AI Assistant") as demo:
|
| 489 |
gr.Markdown(
|
| 490 |
"<h1 id='title-banner'>🏭 Smart Warehouse AI Assistant</h1>"
|
|
@@ -500,25 +426,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="
|
|
| 500 |
)
|
| 501 |
|
| 502 |
with gr.Tab("💬 AI Assistant"):
|
| 503 |
-
gr.
|
| 504 |
-
"
|
| 505 |
-
"
|
| 506 |
-
"
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
gr.Markdown(
|
| 510 |
-
"- **Knowledge base (RAG source):** 10 original, hand-written articles "
|
| 511 |
-
"covering AS/RS, AGV/AMR, WMS, conveyor/sortation, picking strategy, "
|
| 512 |
-
"predictive maintenance, safety protocol, inventory accuracy, KPIs, and "
|
| 513 |
-
"energy efficiency (`src/knowledge_base.py`). Every answer's *Retrieved "
|
| 514 |
-
"context* line shows exactly which article(s) it drew on.\n"
|
| 515 |
-
"- **Intent classifier:** trained on ~480 synthetically generated example "
|
| 516 |
-
"queries across 8 categories (see the Model Evaluation tab for accuracy).\n"
|
| 517 |
-
"- **LLM:** a hosted instruct model called via the Hugging Face Inference "
|
| 518 |
-
"API — not run locally. If no `HF_TOKEN` is configured, or the API call "
|
| 519 |
-
"fails, this tab automatically falls back to showing the retrieved "
|
| 520 |
-
"knowledge-base passages directly, so it never just breaks."
|
| 521 |
-
)
|
| 522 |
gr.ChatInterface(
|
| 523 |
fn=chat_fn,
|
| 524 |
type="messages",
|
|
@@ -537,27 +450,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="
|
|
| 537 |
test_llm_btn.click(test_llm_fn, inputs=None, outputs=test_llm_output)
|
| 538 |
|
| 539 |
with gr.Tab("📦 Inventory & Order Query"):
|
| 540 |
-
gr.
|
| 541 |
-
"
|
| 542 |
-
"
|
| 543 |
-
"
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
gr.Markdown(
|
| 547 |
-
f"- **Inventory table:** {len(inventory_df)} synthetic SKUs across 5 "
|
| 548 |
-
"categories (Electronics, Apparel, Automotive Parts, Food & Beverage, "
|
| 549 |
-
"Household) and 4 warehouse zones, with randomised on-hand quantities, "
|
| 550 |
-
"reorder points, and unit costs.\n"
|
| 551 |
-
f"- **Orders table:** {len(orders_df)} synthetic orders with randomised "
|
| 552 |
-
"status (Received / Picking / Packed / Shipped / Delayed), line count, "
|
| 553 |
-
"priority, and zone.\n"
|
| 554 |
-
"- Both tables are generated by `src/data_generation.py` with a fixed "
|
| 555 |
-
"random seed, so they're reproducible but **not real operational data** "
|
| 556 |
-
"-- this is a stand-in for a live WMS/WCS query interface.\n"
|
| 557 |
-
"- Query parsing is regex-based slot extraction (SKU codes like `SKU-1042`, "
|
| 558 |
-
"order IDs like `#10007`, zone names) combined with the same intent "
|
| 559 |
-
"classifier used in the AI Assistant tab (`src/inventory_db.py`)."
|
| 560 |
-
)
|
| 561 |
with gr.Row():
|
| 562 |
inv_input = gr.Textbox(label="Query", placeholder="How many units of SKU-1042 are in Zone B?", scale=4)
|
| 563 |
inv_btn = gr.Button("Search", variant="primary", scale=1)
|
|
@@ -582,26 +480,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="
|
|
| 582 |
gr.Dataframe(orders_df, wrap=True)
|
| 583 |
|
| 584 |
with gr.Tab("⚠️ Predictive Maintenance"):
|
| 585 |
-
gr.
|
| 586 |
-
"
|
| 587 |
-
"
|
| 588 |
-
"
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
gr.Markdown(
|
| 592 |
-
"- **Training data:** 1,000 synthetic sensor readings (900 normal + 100 "
|
| 593 |
-
"anomalous) across 4 features -- motor temperature, vibration, motor "
|
| 594 |
-
"current, and belt speed -- generated by `src/data_generation.py`. "
|
| 595 |
-
"Anomalies simulate realistic failure signatures: elevated temperature, "
|
| 596 |
-
"vibration, and current combined with reduced/erratic belt speed (the "
|
| 597 |
-
"pattern of bearing wear, belt misalignment, or motor overload).\n"
|
| 598 |
-
"- **Model:** Isolation Forest (unsupervised) trained on scaled features "
|
| 599 |
-
"-- it never sees a 'this is an anomaly' label during training, only "
|
| 600 |
-
"learns what 'normal' looks like and flags deviations from it.\n"
|
| 601 |
-
"- See the **Model Evaluation** tab for the feature-distribution chart "
|
| 602 |
-
"showing exactly how normal vs. anomalous readings differ, plus "
|
| 603 |
-
"precision/recall/F1/ROC-AUC on held-out data."
|
| 604 |
-
)
|
| 605 |
preset_dropdown = gr.Dropdown(
|
| 606 |
choices=list(ANOMALY_PRESETS.keys()), label="Load a preset reading", value="Normal reading"
|
| 607 |
)
|
|
@@ -625,48 +509,47 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="
|
|
| 625 |
)
|
| 626 |
|
| 627 |
with gr.Tab("📊 Model Evaluation"):
|
| 628 |
-
gr.
|
| 629 |
-
"
|
| 630 |
-
"
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
gr.
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
gr.Markdown("**Confusion matrix:**")
|
| 640 |
gr.Image(os.path.join(ASSETS_DIR, "intent_confusion_matrix.png"), show_label=False, container=False)
|
| 641 |
|
| 642 |
-
gr.Markdown("-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
gr.Image(os.path.join(ASSETS_DIR, "sensor_distributions.png"), show_label=False, container=False)
|
| 646 |
with gr.Row():
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
gr.Image(os.path.join(ASSETS_DIR, "anomaly_metrics_bar.png"), show_label=False, container=False)
|
| 650 |
-
with gr.Column():
|
| 651 |
-
gr.Markdown("**Confusion matrix:**")
|
| 652 |
-
gr.Image(os.path.join(ASSETS_DIR, "anomaly_confusion_matrix.png"), show_label=False, container=False)
|
| 653 |
-
gr.Markdown("**ROC curve:**")
|
| 654 |
gr.Image(os.path.join(ASSETS_DIR, "anomaly_roc_curve.png"), show_label=False, container=False)
|
| 655 |
|
| 656 |
-
gr.Markdown("--
|
| 657 |
-
|
|
|
|
| 658 |
gr.Image(os.path.join(ASSETS_DIR, "retrieval_hitrate_bar.png"), show_label=False, container=False)
|
| 659 |
|
| 660 |
-
gr.Markdown("
|
| 661 |
-
gr.Markdown(eval_latency_section_md())
|
| 662 |
gr.Image(os.path.join(ASSETS_DIR, "latency_bar.png"), show_label=False, container=False)
|
| 663 |
|
| 664 |
-
gr.
|
| 665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
|
| 667 |
with gr.Tab("ℹ️ About"):
|
| 668 |
gr.Markdown(ABOUT_MD)
|
| 669 |
|
| 670 |
|
|
|
|
| 671 |
if __name__ == "__main__":
|
| 672 |
-
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
|
|
|
| 249 |
# ==========================================================================
|
| 250 |
|
| 251 |
def eval_intent_section_md():
|
| 252 |
+
return (
|
| 253 |
+
f"### 1. Intent Classifier "
|
| 254 |
+
f"🎯 **{intent_eval.get('accuracy', 0):.0%} accuracy** · "
|
| 255 |
+
f"**{intent_eval.get('macro_f1', 0):.0%} macro F1** · "
|
| 256 |
+
f"held-out test set, {intent_eval.get('n_test', '?')} examples, "
|
| 257 |
+
f"{intent_eval.get('n_classes', '?')} classes"
|
| 258 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
|
| 261 |
def eval_anomaly_section_md():
|
| 262 |
+
return (
|
| 263 |
+
f"### 2. Predictive Maintenance (Anomaly Detector) "
|
| 264 |
+
f"🎯 **{anomaly_eval.get('f1', 0):.0%} F1** · "
|
| 265 |
+
f"**{anomaly_eval.get('roc_auc', 0):.2f} ROC-AUC** · "
|
| 266 |
+
f"**{anomaly_eval.get('precision', 0):.0%} precision** · "
|
| 267 |
+
f"**{anomaly_eval.get('recall', 0):.0%} recall**"
|
| 268 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
|
| 271 |
def eval_retrieval_section_md():
|
| 272 |
+
return (
|
| 273 |
+
f"### 3. RAG Retrieval "
|
| 274 |
+
f"🎯 **{retrieval_eval.get('hit_rate_at_1', 0):.0%} hit-rate@1** · "
|
| 275 |
+
f"**{retrieval_eval.get('hit_rate_at_2', 0):.0%} hit-rate@2** · "
|
| 276 |
+
f"{retrieval_eval.get('n_queries', '?')} labelled test queries"
|
| 277 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
|
| 279 |
|
| 280 |
def eval_latency_section_md():
|
| 281 |
+
return (
|
| 282 |
+
f"### 4. Latency (CPU, avg of 50 runs) "
|
| 283 |
+
f"🎯 **{latency_eval.get('intent_classifier_ms', '?')} ms** intent · "
|
| 284 |
+
f"**{latency_eval.get('anomaly_detector_ms', '?')} ms** anomaly · "
|
| 285 |
+
f"**{latency_eval.get('kb_retrieval_ms', '?')} ms** retrieval"
|
| 286 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
|
| 289 |
+
EVAL_METHODOLOGY_MD = (
|
| 290 |
+
"*All metrics above are computed on held-out synthetic test data by "
|
| 291 |
+
"`build_artifacts.py` (fully reproducible) — see the **About** tab for "
|
| 292 |
+
"dataset details and how each model works.*"
|
| 293 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
|
| 296 |
|
|
|
|
| 396 |
CUSTOM_CSS = """
|
| 397 |
#title-banner { text-align: center; margin-bottom: 0.5em; }
|
| 398 |
.gradio-container { max-width: 1150px !important; margin: auto; }
|
| 399 |
+
.data-badge {
|
| 400 |
+
background: #eff6ff;
|
| 401 |
+
border-left: 4px solid #3b82f6;
|
| 402 |
+
border-radius: 6px;
|
| 403 |
+
padding: 10px 14px;
|
| 404 |
+
margin-bottom: 10px;
|
| 405 |
+
font-size: 0.92em;
|
| 406 |
+
line-height: 1.5;
|
| 407 |
+
}
|
| 408 |
+
.data-badge b { color: #1e3a8a; }
|
| 409 |
"""
|
| 410 |
|
| 411 |
+
def data_badge(html: str) -> str:
|
| 412 |
+
return f'<div class="data-badge">{html}</div>'
|
| 413 |
+
|
| 414 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), css=CUSTOM_CSS, title="Smart Warehouse AI Assistant") as demo:
|
| 415 |
gr.Markdown(
|
| 416 |
"<h1 id='title-banner'>🏭 Smart Warehouse AI Assistant</h1>"
|
|
|
|
| 426 |
)
|
| 427 |
|
| 428 |
with gr.Tab("💬 AI Assistant"):
|
| 429 |
+
gr.HTML(data_badge(
|
| 430 |
+
"🧪 <b>You are testing:</b> an LLM chatbot grounded in a 10-article synthetic "
|
| 431 |
+
"warehouse-ops knowledge base (RAG), routed by an intent classifier trained on "
|
| 432 |
+
"~480 synthetic queries. <b>Data type:</b> hand-written knowledge articles + "
|
| 433 |
+
"template-generated questions — not real Daifuku data."
|
| 434 |
+
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
gr.ChatInterface(
|
| 436 |
fn=chat_fn,
|
| 437 |
type="messages",
|
|
|
|
| 450 |
test_llm_btn.click(test_llm_fn, inputs=None, outputs=test_llm_output)
|
| 451 |
|
| 452 |
with gr.Tab("📦 Inventory & Order Query"):
|
| 453 |
+
gr.HTML(data_badge(
|
| 454 |
+
f"🧪 <b>You are testing:</b> NL→structured-query search over "
|
| 455 |
+
f"{len(inventory_df)} synthetic SKUs and {len(orders_df)} synthetic orders. "
|
| 456 |
+
"<b>Data type:</b> randomly generated inventory/order records (fixed seed, "
|
| 457 |
+
"reproducible) — a stand-in for a live WMS, not real warehouse data."
|
| 458 |
+
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
with gr.Row():
|
| 460 |
inv_input = gr.Textbox(label="Query", placeholder="How many units of SKU-1042 are in Zone B?", scale=4)
|
| 461 |
inv_btn = gr.Button("Search", variant="primary", scale=1)
|
|
|
|
| 480 |
gr.Dataframe(orders_df, wrap=True)
|
| 481 |
|
| 482 |
with gr.Tab("⚠️ Predictive Maintenance"):
|
| 483 |
+
gr.HTML(data_badge(
|
| 484 |
+
"🧪 <b>You are testing:</b> an Isolation Forest anomaly detector for conveyor/"
|
| 485 |
+
"crane motor sensors. <b>Data type:</b> 1,000 synthetic sensor readings (900 "
|
| 486 |
+
"normal + 100 simulated-fault) across temperature, vibration, current, and belt "
|
| 487 |
+
"speed — trained unsupervised, not on real Daifuku equipment telemetry."
|
| 488 |
+
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
preset_dropdown = gr.Dropdown(
|
| 490 |
choices=list(ANOMALY_PRESETS.keys()), label="Load a preset reading", value="Normal reading"
|
| 491 |
)
|
|
|
|
| 509 |
)
|
| 510 |
|
| 511 |
with gr.Tab("📊 Model Evaluation"):
|
| 512 |
+
gr.HTML(data_badge(
|
| 513 |
+
"🧪 All charts below are computed on <b>held-out test data</b> by "
|
| 514 |
+
"<code>build_artifacts.py</code> — fully reproducible, not cherry-picked."
|
| 515 |
+
))
|
| 516 |
+
|
| 517 |
+
gr.Markdown("### 1. Intent Classifier — accuracy: **{:.1%}** · macro F1: **{:.1%}**".format(
|
| 518 |
+
intent_eval.get("accuracy", 0), intent_eval.get("macro_f1", 0)
|
| 519 |
+
))
|
| 520 |
+
with gr.Row():
|
| 521 |
+
gr.Image(os.path.join(ASSETS_DIR, "intent_dataset_composition.png"), show_label=False, container=False)
|
| 522 |
+
gr.Image(os.path.join(ASSETS_DIR, "intent_per_class_bar.png"), show_label=False, container=False)
|
|
|
|
| 523 |
gr.Image(os.path.join(ASSETS_DIR, "intent_confusion_matrix.png"), show_label=False, container=False)
|
| 524 |
|
| 525 |
+
gr.Markdown("### 2. Predictive Maintenance — F1: **{:.1%}** · ROC-AUC: **{:.2f}**".format(
|
| 526 |
+
anomaly_eval.get("f1", 0), anomaly_eval.get("roc_auc", 0)
|
| 527 |
+
))
|
| 528 |
gr.Image(os.path.join(ASSETS_DIR, "sensor_distributions.png"), show_label=False, container=False)
|
| 529 |
with gr.Row():
|
| 530 |
+
gr.Image(os.path.join(ASSETS_DIR, "anomaly_metrics_bar.png"), show_label=False, container=False)
|
| 531 |
+
gr.Image(os.path.join(ASSETS_DIR, "anomaly_confusion_matrix.png"), show_label=False, container=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
gr.Image(os.path.join(ASSETS_DIR, "anomaly_roc_curve.png"), show_label=False, container=False)
|
| 533 |
|
| 534 |
+
gr.Markdown("### 3. RAG Retrieval — Hit-rate@1: **{:.0%}** · Hit-rate@2: **{:.0%}**".format(
|
| 535 |
+
retrieval_eval.get("hit_rate_at_1", 0), retrieval_eval.get("hit_rate_at_2", 0)
|
| 536 |
+
))
|
| 537 |
gr.Image(os.path.join(ASSETS_DIR, "retrieval_hitrate_bar.png"), show_label=False, container=False)
|
| 538 |
|
| 539 |
+
gr.Markdown("### 4. Latency Benchmark (CPU, local components)")
|
|
|
|
| 540 |
gr.Image(os.path.join(ASSETS_DIR, "latency_bar.png"), show_label=False, container=False)
|
| 541 |
|
| 542 |
+
with gr.Accordion("📋 Full metrics tables & methodology notes", open=False):
|
| 543 |
+
gr.Markdown(eval_intent_section_md())
|
| 544 |
+
gr.Markdown(eval_anomaly_section_md())
|
| 545 |
+
gr.Markdown(eval_retrieval_section_md())
|
| 546 |
+
gr.Markdown(eval_latency_section_md())
|
| 547 |
+
gr.Markdown(EVAL_METHODOLOGY_MD)
|
| 548 |
|
| 549 |
with gr.Tab("ℹ️ About"):
|
| 550 |
gr.Markdown(ABOUT_MD)
|
| 551 |
|
| 552 |
|
| 553 |
+
|
| 554 |
if __name__ == "__main__":
|
| 555 |
+
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|