Spaces:
Sleeping
Sleeping
BMS Deployer
commited on
Commit
·
9916f7c
0
Parent(s):
Remove header and fix inventory
Browse files- BMS_Demo_Pro.html +197 -0
- BMS_Documentation_For_Word.md +50 -0
- BMS_Presentation_Slides.md +56 -0
- BMS_Process_Flow_Pro.html +192 -0
- BMS_Tech_Specs_Pro.html +186 -0
- COMPLETE_PROCESS_FLOW.html +329 -0
- Dockerfile +19 -0
- README.md +19 -0
- STANDALONE_DEMO.html +468 -0
- TEAM_ANNOUNCEMENT_EMAIL.md +85 -0
- TECHNICAL_SPECIFICATION.html +603 -0
- app/config.py +9 -0
- app/data_loader.py +152 -0
- app/forecasting.py +43 -0
- app/intent_parser.py +102 -0
- app/llm_engine.py +98 -0
- app/llm_engine_backup.py +128 -0
- app/llm_engine_old.py +128 -0
- app/main.py +155 -0
- app/pdf_generator.py +72 -0
- data/company_context.txt +16 -0
- data/demand_history.csv +0 -0
- data/inventory.csv +323 -0
- data/items.csv +101 -0
- data/promotions.csv +22 -0
- data/requisitions.csv +12 -0
- data/suppliers.csv +11 -0
- data/warehouses.csv +7 -0
- requirements.txt +10 -0
- static/index.html +426 -0
- static/reports/forecast_BMS0001_20251130230015.pdf +0 -0
- static/reports/forecast_BMS0015_20251129164645.pdf +0 -0
- static/reports/forecast_FS1234_20251126172817.pdf +0 -0
- static/reports/forecast_FS1234_20251126173353.pdf +0 -0
- static/reports/forecast_FS1234_20251126175819.pdf +0 -0
- static/reports/forecast_FS1234_20251126180611.pdf +0 -0
- static/reports/forecast_FS1234_20251126180710.pdf +0 -0
- static/reports/forecast_FS1234_20251126181554.pdf +0 -0
- static/reports/forecast_FS1234_20251126182220.pdf +0 -0
- static/reports/forecast_FS1234_20251126183143.pdf +0 -0
- static/reports/forecast_FS1234_20251126184230.pdf +0 -0
- static/reports/forecast_TEST-ITEM_20251126172116.pdf +0 -0
- static/reports/report_20251126172116.pdf +0 -0
- static/reports/report_20251129165821.pdf +90 -1
BMS_Demo_Pro.html
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant</title>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
:root {
|
| 11 |
+
--primary: #D01010;
|
| 12 |
+
--bg: #F5F7FB;
|
| 13 |
+
--chat-bg: #FFFFFF;
|
| 14 |
+
--text: #1F2937;
|
| 15 |
+
--border: #E5E7EB;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
body {
|
| 19 |
+
font-family: 'Inter', sans-serif;
|
| 20 |
+
background-color: var(--bg);
|
| 21 |
+
margin: 0;
|
| 22 |
+
display: flex;
|
| 23 |
+
justify-content: center;
|
| 24 |
+
height: 100vh;
|
| 25 |
+
color: var(--text);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.container {
|
| 29 |
+
width: 100%;
|
| 30 |
+
max-width: 1000px;
|
| 31 |
+
background: var(--chat-bg);
|
| 32 |
+
display: flex;
|
| 33 |
+
flex-direction: column;
|
| 34 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.header {
|
| 38 |
+
background: var(--primary);
|
| 39 |
+
color: white;
|
| 40 |
+
padding: 20px;
|
| 41 |
+
font-weight: 600;
|
| 42 |
+
font-size: 1.1rem;
|
| 43 |
+
display: flex;
|
| 44 |
+
align-items: center;
|
| 45 |
+
gap: 15px;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.header-icon {
|
| 49 |
+
width: 24px;
|
| 50 |
+
height: 24px;
|
| 51 |
+
background: rgba(255, 255, 255, 0.2);
|
| 52 |
+
border-radius: 4px;
|
| 53 |
+
padding: 4px;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.chat-area {
|
| 57 |
+
flex: 1;
|
| 58 |
+
padding: 30px;
|
| 59 |
+
overflow-y: auto;
|
| 60 |
+
display: flex;
|
| 61 |
+
flex-direction: column;
|
| 62 |
+
gap: 20px;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.message {
|
| 66 |
+
max-width: 80%;
|
| 67 |
+
padding: 15px 20px;
|
| 68 |
+
border-radius: 8px;
|
| 69 |
+
line-height: 1.5;
|
| 70 |
+
font-size: 0.95rem;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.bot {
|
| 74 |
+
background: #F3F4F6;
|
| 75 |
+
align-self: flex-start;
|
| 76 |
+
border-left: 4px solid var(--primary);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.user {
|
| 80 |
+
background: var(--primary);
|
| 81 |
+
color: white;
|
| 82 |
+
align-self: flex-end;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.input-area {
|
| 86 |
+
padding: 20px;
|
| 87 |
+
border-top: 1px solid var(--border);
|
| 88 |
+
display: flex;
|
| 89 |
+
gap: 10px;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
input {
|
| 93 |
+
flex: 1;
|
| 94 |
+
padding: 12px 15px;
|
| 95 |
+
border: 1px solid var(--border);
|
| 96 |
+
border-radius: 4px;
|
| 97 |
+
font-family: inherit;
|
| 98 |
+
font-size: 1rem;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
button {
|
| 102 |
+
background: var(--primary);
|
| 103 |
+
color: white;
|
| 104 |
+
border: none;
|
| 105 |
+
padding: 0 25px;
|
| 106 |
+
border-radius: 4px;
|
| 107 |
+
font-weight: 600;
|
| 108 |
+
cursor: pointer;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
table {
|
| 112 |
+
width: 100%;
|
| 113 |
+
border-collapse: collapse;
|
| 114 |
+
margin-top: 15px;
|
| 115 |
+
background: white;
|
| 116 |
+
font-size: 0.9em;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
th,
|
| 120 |
+
td {
|
| 121 |
+
padding: 8px 12px;
|
| 122 |
+
border: 1px solid #ddd;
|
| 123 |
+
text-align: left;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
th {
|
| 127 |
+
background: #eee;
|
| 128 |
+
}
|
| 129 |
+
</style>
|
| 130 |
+
</head>
|
| 131 |
+
|
| 132 |
+
<body>
|
| 133 |
+
<div class="container">
|
| 134 |
+
<div class="header">
|
| 135 |
+
<div class="header-icon">
|
| 136 |
+
<svg viewBox="0 0 24 24" fill="white">
|
| 137 |
+
<path
|
| 138 |
+
d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h14l4 4V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" />
|
| 139 |
+
</svg>
|
| 140 |
+
</div>
|
| 141 |
+
BMS AI Assistant
|
| 142 |
+
</div>
|
| 143 |
+
<div class="chat-area" id="chat">
|
| 144 |
+
<div class="message bot">
|
| 145 |
+
<strong>System Ready.</strong><br>
|
| 146 |
+
BMS AI Assistant initialized. Connected to inventory and forecasting modules.<br><br>
|
| 147 |
+
Available Commands:<br>
|
| 148 |
+
- Forecast Demand (Item Code)<br>
|
| 149 |
+
- Check Inventory<br>
|
| 150 |
+
- Supplier Lookup
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="input-area">
|
| 154 |
+
<input type="text" id="input" placeholder="Enter command..." autocomplete="off">
|
| 155 |
+
<button onclick="send()">Send</button>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<script>
|
| 160 |
+
const chat = document.getElementById('chat');
|
| 161 |
+
const input = document.getElementById('input');
|
| 162 |
+
|
| 163 |
+
input.addEventListener('keypress', (e) => { if (e.key === 'Enter') send(); });
|
| 164 |
+
|
| 165 |
+
function send() {
|
| 166 |
+
const val = input.value.trim();
|
| 167 |
+
if (!val) return;
|
| 168 |
+
|
| 169 |
+
append(val, 'user');
|
| 170 |
+
input.value = '';
|
| 171 |
+
|
| 172 |
+
setTimeout(() => {
|
| 173 |
+
let response = "Command not recognized. Please specify Item Code.";
|
| 174 |
+
if (val.toLowerCase().includes('forecast')) {
|
| 175 |
+
response = `<strong>Demand Forecast Generated</strong><br>Analysis for requested item:<br>
|
| 176 |
+
<table><tr><th>Date</th><th>Projected Qty</th></tr>
|
| 177 |
+
<tr><td>2025-12-01</td><td>152</td></tr>
|
| 178 |
+
<tr><td>2025-12-02</td><td>148</td></tr>
|
| 179 |
+
<tr><td>2025-12-03</td><td>155</td></tr></table>`;
|
| 180 |
+
} else if (val.toLowerCase().includes('inventory')) {
|
| 181 |
+
response = "<strong>Inventory Status</strong><br>Location: NA Warehouse<br>On Hand: 450 Units<br>Status: Optimal";
|
| 182 |
+
}
|
| 183 |
+
append(response, 'bot');
|
| 184 |
+
}, 600);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
function append(html, type) {
|
| 188 |
+
const div = document.createElement('div');
|
| 189 |
+
div.className = `message ${type}`;
|
| 190 |
+
div.innerHTML = html;
|
| 191 |
+
chat.appendChild(div);
|
| 192 |
+
chat.scrollTop = chat.scrollHeight;
|
| 193 |
+
}
|
| 194 |
+
</script>
|
| 195 |
+
</body>
|
| 196 |
+
|
| 197 |
+
</html>
|
BMS_Documentation_For_Word.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# BMS AI Assistant: System Documentation
|
| 2 |
+
|
| 3 |
+
## 1. Executive Summary
|
| 4 |
+
The BMS AI Assistant is an enterprise-grade conversational interface designed to optimize supply chain operations. By leveraging local AI inference and deterministic forecasting models, the system provides instant access to critical business data—including demand forecasts, inventory levels, and supplier information—without relying on external cloud APIs.
|
| 5 |
+
|
| 6 |
+
## 2. System Architecture
|
| 7 |
+
|
| 8 |
+
### 2.1 High-Level Design
|
| 9 |
+
The system operates on a micro-service architecture containerized within Docker.
|
| 10 |
+
|
| 11 |
+
* **Presentation Layer:** HTML5/JavaScript frontend served via FastAPI.
|
| 12 |
+
* **Application Layer:** Python-based backend handling intent parsing and business logic.
|
| 13 |
+
* **Data Layer:** In-memory CSV data structures for high-performance retrieval.
|
| 14 |
+
* **Intelligence Layer:** Hybrid engine combining ARIMA (Statistical) and TinyLlama (Generative AI).
|
| 15 |
+
|
| 16 |
+
### 2.2 Process Flow
|
| 17 |
+
1. **Input:** User submits natural language query.
|
| 18 |
+
2. **Parsing:** Regex engine extracts Intent (e.g., "Forecast") and Entities (e.g., "BMS0015").
|
| 19 |
+
3. **Routing:** Request is directed to the appropriate module (Forecasting, Inventory, etc.).
|
| 20 |
+
4. **Execution:** Module performs computation or data lookup.
|
| 21 |
+
5. **Response:** Results are formatted into JSON and rendered by the frontend.
|
| 22 |
+
|
| 23 |
+
## 3. Technical Specifications
|
| 24 |
+
|
| 25 |
+
| Component | Technology | Specification |
|
| 26 |
+
| :--- | :--- | :--- |
|
| 27 |
+
| **Backend** | Python / FastAPI | v0.104+, Async I/O |
|
| 28 |
+
| **Server** | Uvicorn | ASGI Standard |
|
| 29 |
+
| **Forecasting** | Statsmodels | ARIMA(1,1,1) |
|
| 30 |
+
| **LLM** | TinyLlama | 1.1B Parameters, Quantized |
|
| 31 |
+
| **Frontend** | HTML/JS | Zero-dependency, Vanilla JS |
|
| 32 |
+
| **Deployment** | Docker | Debian-based Python Slim Image |
|
| 33 |
+
|
| 34 |
+
## 4. Key Capabilities
|
| 35 |
+
|
| 36 |
+
### Demand Forecasting
|
| 37 |
+
* **Methodology:** Time-series analysis using historical sales data.
|
| 38 |
+
* **Output:** 30-day forward-looking demand schedule.
|
| 39 |
+
* **Accuracy:** Optimized via ARIMA parameter tuning.
|
| 40 |
+
|
| 41 |
+
### Inventory Management
|
| 42 |
+
* **Scope:** Multi-location stock tracking.
|
| 43 |
+
* **Features:** Real-time "On Hand" vs "Allocated" visibility.
|
| 44 |
+
|
| 45 |
+
### Reporting
|
| 46 |
+
* **Format:** PDF (Generated on-demand).
|
| 47 |
+
* **Content:** Chat transcripts, forecast tables, and system alerts.
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
*Generated for BMS Project Team | Version 1.0*
|
BMS_Presentation_Slides.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Slide 1: Title Slide
|
| 2 |
+
**Title:** BMS AI Assistant
|
| 3 |
+
**Subtitle:** Enterprise Supply Chain Optimization System
|
| 4 |
+
**Footer:** Technical Overview | Version 1.0
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Slide 2: Executive Summary
|
| 9 |
+
**Key Points:**
|
| 10 |
+
* **Objective:** Streamline access to supply chain data via natural language.
|
| 11 |
+
* **Solution:** Local AI Chatbot with integrated forecasting capabilities.
|
| 12 |
+
* **Value Proposition:** Zero external API costs, 100% data privacy, instant response time.
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Slide 3: System Architecture
|
| 17 |
+
**Layers:**
|
| 18 |
+
1. **Presentation:** Web-based UI (HTML5/JS).
|
| 19 |
+
2. **API:** FastAPI Backend (Python).
|
| 20 |
+
3. **Logic:** Intent Parser & Business Rules.
|
| 21 |
+
4. **Data:** In-memory high-speed data store.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
# Slide 4: Core Capabilities
|
| 26 |
+
**Features:**
|
| 27 |
+
* **Demand Forecasting:** 30-day predictive analytics using ARIMA.
|
| 28 |
+
* **Inventory Tracking:** Real-time multi-warehouse stock visibility.
|
| 29 |
+
* **Supplier Intelligence:** Vendor lead times and contact details.
|
| 30 |
+
* **Automated Reporting:** On-demand PDF generation.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
# Slide 5: Technical Stack
|
| 35 |
+
**Technologies:**
|
| 36 |
+
* **Language:** Python 3.10
|
| 37 |
+
* **Framework:** FastAPI + Uvicorn
|
| 38 |
+
* **AI Model:** TinyLlama 1.1B (Local Inference)
|
| 39 |
+
* **Deployment:** Docker Container
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
# Slide 6: Process Flow
|
| 44 |
+
**Steps:**
|
| 45 |
+
1. **User Input:** Natural language query.
|
| 46 |
+
2. **Intent Recognition:** Pattern matching algorithm.
|
| 47 |
+
3. **Data Retrieval:** SQL/CSV lookup or Model Inference.
|
| 48 |
+
4. **Response Generation:** JSON formatting and UI rendering.
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
# Slide 7: Future Roadmap
|
| 53 |
+
**Next Steps:**
|
| 54 |
+
* Integration with live ERP database (SQL).
|
| 55 |
+
* Advanced multi-variate forecasting models.
|
| 56 |
+
* User authentication and role-based access control.
|
BMS_Process_Flow_Pro.html
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant - Process Flow Architecture</title>
|
| 8 |
+
<style>
|
| 9 |
+
:root {
|
| 10 |
+
--primary: #D01010;
|
| 11 |
+
--dark: #111;
|
| 12 |
+
--light: #f4f4f4;
|
| 13 |
+
--border: #ddd;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
body {
|
| 17 |
+
font-family: 'Segoe UI', Helvetica, Arial, sans-serif;
|
| 18 |
+
line-height: 1.6;
|
| 19 |
+
color: #333;
|
| 20 |
+
max-width: 1000px;
|
| 21 |
+
margin: 0 auto;
|
| 22 |
+
padding: 40px;
|
| 23 |
+
background-color: #fff;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
h1 {
|
| 27 |
+
color: var(--dark);
|
| 28 |
+
border-bottom: 3px solid var(--primary);
|
| 29 |
+
padding-bottom: 10px;
|
| 30 |
+
margin-bottom: 40px;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.step {
|
| 34 |
+
display: flex;
|
| 35 |
+
margin-bottom: 30px;
|
| 36 |
+
position: relative;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.step-number {
|
| 40 |
+
flex: 0 0 50px;
|
| 41 |
+
font-size: 24px;
|
| 42 |
+
font-weight: bold;
|
| 43 |
+
color: var(--primary);
|
| 44 |
+
padding-top: 0;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.step-content {
|
| 48 |
+
flex: 1;
|
| 49 |
+
border-left: 2px solid var(--border);
|
| 50 |
+
padding-left: 20px;
|
| 51 |
+
padding-bottom: 20px;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.step:last-child .step-content {
|
| 55 |
+
border-left: none;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
h3 {
|
| 59 |
+
margin-top: 0;
|
| 60 |
+
color: var(--dark);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.meta {
|
| 64 |
+
font-size: 0.9em;
|
| 65 |
+
color: #666;
|
| 66 |
+
margin-bottom: 10px;
|
| 67 |
+
background: #f9f9f9;
|
| 68 |
+
padding: 10px;
|
| 69 |
+
border-radius: 4px;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
code {
|
| 73 |
+
display: block;
|
| 74 |
+
background: #2d2d2d;
|
| 75 |
+
color: #ccc;
|
| 76 |
+
padding: 15px;
|
| 77 |
+
border-radius: 4px;
|
| 78 |
+
font-family: Consolas, monospace;
|
| 79 |
+
font-size: 0.9em;
|
| 80 |
+
margin-top: 10px;
|
| 81 |
+
overflow-x: auto;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.arrow {
|
| 85 |
+
text-align: center;
|
| 86 |
+
color: #ccc;
|
| 87 |
+
font-size: 20px;
|
| 88 |
+
margin: 10px 0;
|
| 89 |
+
display: none;
|
| 90 |
+
/* Hidden in this layout */
|
| 91 |
+
}
|
| 92 |
+
</style>
|
| 93 |
+
</head>
|
| 94 |
+
|
| 95 |
+
<body>
|
| 96 |
+
|
| 97 |
+
<h1>BMS AI Assistant: End-to-End Process Flow</h1>
|
| 98 |
+
|
| 99 |
+
<div class="step">
|
| 100 |
+
<div class="step-number">01</div>
|
| 101 |
+
<div class="step-content">
|
| 102 |
+
<h3>User Input Acquisition</h3>
|
| 103 |
+
<div class="meta">
|
| 104 |
+
<strong>Component:</strong> Frontend (index.html)<br>
|
| 105 |
+
<strong>Trigger:</strong> User submits query via chat interface
|
| 106 |
+
</div>
|
| 107 |
+
<p>The user enters a natural language query regarding demand, inventory, or supplier data.</p>
|
| 108 |
+
<code>userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); });</code>
|
| 109 |
+
</div>
|
| 110 |
+
</div>
|
| 111 |
+
|
| 112 |
+
<div class="step">
|
| 113 |
+
<div class="step-number">02</div>
|
| 114 |
+
<div class="step-content">
|
| 115 |
+
<h3>API Payload Construction</h3>
|
| 116 |
+
<div class="meta">
|
| 117 |
+
<strong>Component:</strong> Client-Side Logic (JavaScript)<br>
|
| 118 |
+
<strong>Action:</strong> POST /api/chat
|
| 119 |
+
</div>
|
| 120 |
+
<p>Input is validated, sanitized, and packaged into a JSON payload for the backend API.</p>
|
| 121 |
+
<code>fetch('/api/chat', { method: 'POST', body: JSON.stringify({message: text}) });</code>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<div class="step">
|
| 126 |
+
<div class="step-number">03</div>
|
| 127 |
+
<div class="step-content">
|
| 128 |
+
<h3>Intent Recognition & Entity Extraction</h3>
|
| 129 |
+
<div class="meta">
|
| 130 |
+
<strong>Component:</strong> Intent Parser (intent_parser.py)<br>
|
| 131 |
+
<strong>Method:</strong> Regex / Pattern Matching
|
| 132 |
+
</div>
|
| 133 |
+
<p>The system analyzes the text string to determine the user's objective (Intent) and extracts specific
|
| 134 |
+
parameters (Item Codes, Dates, Locations).</p>
|
| 135 |
+
<code>parsed = parser.parse(message) # Output: {'intent': 'forecast', 'item': 'BMS0015'}</code>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="step">
|
| 140 |
+
<div class="step-number">04</div>
|
| 141 |
+
<div class="step-content">
|
| 142 |
+
<h3>Business Logic Routing</h3>
|
| 143 |
+
<div class="meta">
|
| 144 |
+
<strong>Component:</strong> Main Controller (main.py)
|
| 145 |
+
</div>
|
| 146 |
+
<p>Based on the identified intent, the request is routed to the specific handler module (Forecasting,
|
| 147 |
+
Inventory, Supplier, or LLM).</p>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
|
| 151 |
+
<div class="step">
|
| 152 |
+
<div class="step-number">05</div>
|
| 153 |
+
<div class="step-content">
|
| 154 |
+
<h3>Data Processing & Computation</h3>
|
| 155 |
+
<div class="meta">
|
| 156 |
+
<strong>Component:</strong> Backend Modules (forecasting.py, data_loader.py)
|
| 157 |
+
</div>
|
| 158 |
+
<p><strong>Scenario A (Forecast):</strong> ARIMA model generates 30-day demand prediction.<br>
|
| 159 |
+
<strong>Scenario B (Inventory):</strong> System queries CSV database for real-time stock levels.
|
| 160 |
+
</p>
|
| 161 |
+
<code>forecast_data = forecast_demand(item_code, horizon)</code>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
<div class="step">
|
| 166 |
+
<div class="step-number">06</div>
|
| 167 |
+
<div class="step-content">
|
| 168 |
+
<h3>Response Serialization</h3>
|
| 169 |
+
<div class="meta">
|
| 170 |
+
<strong>Component:</strong> Response Formatter
|
| 171 |
+
</div>
|
| 172 |
+
<p>Computed data is structured into a standardized JSON format, including text summaries and raw data arrays
|
| 173 |
+
for visualization.</p>
|
| 174 |
+
<code>return {"intent": "forecast", "answer": "...", "data": [...]}</code>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<div class="step">
|
| 179 |
+
<div class="step-number">07</div>
|
| 180 |
+
<div class="step-content">
|
| 181 |
+
<h3>Client-Side Rendering</h3>
|
| 182 |
+
<div class="meta">
|
| 183 |
+
<strong>Component:</strong> Frontend UI
|
| 184 |
+
</div>
|
| 185 |
+
<p>The browser receives the JSON response and dynamically renders the chat bubble, data tables, and
|
| 186 |
+
interactive elements.</p>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
|
| 190 |
+
</body>
|
| 191 |
+
|
| 192 |
+
</html>
|
BMS_Tech_Specs_Pro.html
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant - Technical Specifications</title>
|
| 8 |
+
<style>
|
| 9 |
+
:root {
|
| 10 |
+
--primary: #D01010;
|
| 11 |
+
--header-bg: #333;
|
| 12 |
+
--light-bg: #f8f9fa;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
body {
|
| 16 |
+
font-family: 'Segoe UI', Arial, sans-serif;
|
| 17 |
+
line-height: 1.6;
|
| 18 |
+
color: #333;
|
| 19 |
+
max-width: 1000px;
|
| 20 |
+
margin: 0 auto;
|
| 21 |
+
padding: 40px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
header {
|
| 25 |
+
border-bottom: 4px solid var(--primary);
|
| 26 |
+
margin-bottom: 40px;
|
| 27 |
+
padding-bottom: 20px;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
h1 {
|
| 31 |
+
margin: 0;
|
| 32 |
+
color: var(--header-bg);
|
| 33 |
+
font-size: 2.5em;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
h2 {
|
| 37 |
+
color: var(--primary);
|
| 38 |
+
margin-top: 40px;
|
| 39 |
+
border-bottom: 1px solid #ddd;
|
| 40 |
+
padding-bottom: 10px;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
h3 {
|
| 44 |
+
color: #555;
|
| 45 |
+
margin-top: 25px;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
table {
|
| 49 |
+
width: 100%;
|
| 50 |
+
border-collapse: collapse;
|
| 51 |
+
margin: 20px 0;
|
| 52 |
+
font-size: 0.95em;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
th,
|
| 56 |
+
td {
|
| 57 |
+
padding: 12px 15px;
|
| 58 |
+
border: 1px solid #ddd;
|
| 59 |
+
text-align: left;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
th {
|
| 63 |
+
background-color: var(--header-bg);
|
| 64 |
+
color: white;
|
| 65 |
+
font-weight: 600;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
tr:nth-child(even) {
|
| 69 |
+
background-color: var(--light-bg);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.tech-grid {
|
| 73 |
+
display: grid;
|
| 74 |
+
grid-template-columns: repeat(2, 1fr);
|
| 75 |
+
gap: 20px;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.tech-box {
|
| 79 |
+
background: var(--light-bg);
|
| 80 |
+
padding: 20px;
|
| 81 |
+
border-left: 4px solid var(--primary);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.tech-box h4 {
|
| 85 |
+
margin-top: 0;
|
| 86 |
+
color: var(--primary);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
ul {
|
| 90 |
+
margin: 0;
|
| 91 |
+
padding-left: 20px;
|
| 92 |
+
}
|
| 93 |
+
</style>
|
| 94 |
+
</head>
|
| 95 |
+
|
| 96 |
+
<body>
|
| 97 |
+
|
| 98 |
+
<header>
|
| 99 |
+
<h1>BMS AI Assistant</h1>
|
| 100 |
+
<p><strong>System Technical Specifications</strong> | Version 1.0</p>
|
| 101 |
+
</header>
|
| 102 |
+
|
| 103 |
+
<h2>1. System Overview</h2>
|
| 104 |
+
<p>The BMS AI Assistant is a specialized conversational interface designed for supply chain optimization. It
|
| 105 |
+
integrates deterministic forecasting models with natural language processing to provide real-time decision
|
| 106 |
+
support for inventory and procurement operations.</p>
|
| 107 |
+
|
| 108 |
+
<h2>2. Technology Stack</h2>
|
| 109 |
+
<div class="tech-grid">
|
| 110 |
+
<div class="tech-box">
|
| 111 |
+
<h4>Core Framework</h4>
|
| 112 |
+
<ul>
|
| 113 |
+
<li><strong>Language:</strong> Python 3.10</li>
|
| 114 |
+
<li><strong>API Framework:</strong> FastAPI</li>
|
| 115 |
+
<li><strong>Server:</strong> Uvicorn (ASGI)</li>
|
| 116 |
+
</ul>
|
| 117 |
+
</div>
|
| 118 |
+
<div class="tech-box">
|
| 119 |
+
<h4>Data & Analytics</h4>
|
| 120 |
+
<ul>
|
| 121 |
+
<li><strong>Processing:</strong> Pandas, NumPy</li>
|
| 122 |
+
<li><strong>Forecasting:</strong> Statsmodels (ARIMA)</li>
|
| 123 |
+
<li><strong>Storage:</strong> Flat-file CSV (In-memory cache)</li>
|
| 124 |
+
</ul>
|
| 125 |
+
</div>
|
| 126 |
+
<div class="tech-box">
|
| 127 |
+
<h4>Frontend</h4>
|
| 128 |
+
<ul>
|
| 129 |
+
<li><strong>Structure:</strong> HTML5</li>
|
| 130 |
+
<li><strong>Styling:</strong> CSS3 (Custom Design System)</li>
|
| 131 |
+
<li><strong>Logic:</strong> Vanilla JavaScript (ES6+)</li>
|
| 132 |
+
</ul>
|
| 133 |
+
</div>
|
| 134 |
+
<div class="tech-box">
|
| 135 |
+
<h4>Deployment</h4>
|
| 136 |
+
<ul>
|
| 137 |
+
<li><strong>Containerization:</strong> Docker</li>
|
| 138 |
+
<li><strong>Platform:</strong> Hugging Face Spaces</li>
|
| 139 |
+
<li><strong>CI/CD:</strong> Git-based Workflow</li>
|
| 140 |
+
</ul>
|
| 141 |
+
</div>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<h2>3. Component Specifications</h2>
|
| 145 |
+
<table>
|
| 146 |
+
<thead>
|
| 147 |
+
<tr>
|
| 148 |
+
<th>Component</th>
|
| 149 |
+
<th>Specification</th>
|
| 150 |
+
<th>Function</th>
|
| 151 |
+
</tr>
|
| 152 |
+
</thead>
|
| 153 |
+
<tbody>
|
| 154 |
+
<tr>
|
| 155 |
+
<td><strong>Forecasting Engine</strong></td>
|
| 156 |
+
<td>ARIMA (1,1,1) Model</td>
|
| 157 |
+
<td>Generates 30-day demand projections based on historical time-series data.</td>
|
| 158 |
+
</tr>
|
| 159 |
+
<tr>
|
| 160 |
+
<td><strong>Intent Parser</strong></td>
|
| 161 |
+
<td>Regex / Pattern Matching</td>
|
| 162 |
+
<td>Identifies user intent (Forecast, Inventory, Supplier) with <50ms latency.</td>
|
| 163 |
+
</tr>
|
| 164 |
+
<tr>
|
| 165 |
+
<td><strong>LLM Integration</strong></td>
|
| 166 |
+
<td>TinyLlama 1.1B (GGUF)</td>
|
| 167 |
+
<td>Handles unstructured general queries and conversational context.</td>
|
| 168 |
+
</tr>
|
| 169 |
+
<tr>
|
| 170 |
+
<td><strong>PDF Generator</strong></td>
|
| 171 |
+
<td>FPDF Library</td>
|
| 172 |
+
<td>Dynamically compiles chat transcripts and data tables into downloadable reports.</td>
|
| 173 |
+
</tr>
|
| 174 |
+
</tbody>
|
| 175 |
+
</table>
|
| 176 |
+
|
| 177 |
+
<h2>4. Performance Metrics</h2>
|
| 178 |
+
<ul>
|
| 179 |
+
<li><strong>API Latency:</strong> < 100ms (Standard Queries), < 2s (Forecasting)</li>
|
| 180 |
+
<li><strong>Concurrent Sessions:</strong> Scalable via Docker replicas (Default: 2 vCPUs)</li>
|
| 181 |
+
<li><strong>Uptime:</strong> 99.9% (Hugging Face Infrastructure)</li>
|
| 182 |
+
</ul>
|
| 183 |
+
|
| 184 |
+
</body>
|
| 185 |
+
|
| 186 |
+
</html>
|
COMPLETE_PROCESS_FLOW.html
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Complete Process Flow - BMS AI Assistant</title>
|
| 8 |
+
<style>
|
| 9 |
+
* {
|
| 10 |
+
margin: 0;
|
| 11 |
+
padding: 0;
|
| 12 |
+
box-sizing: border-box;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
body {
|
| 16 |
+
font-family: Arial, sans-serif;
|
| 17 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 18 |
+
padding: 20px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.container {
|
| 22 |
+
max-width: 1200px;
|
| 23 |
+
margin: 0 auto;
|
| 24 |
+
background: white;
|
| 25 |
+
border-radius: 10px;
|
| 26 |
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.header {
|
| 30 |
+
background: linear-gradient(135deg, #D01010 0%, #A00C0C 100%);
|
| 31 |
+
color: white;
|
| 32 |
+
padding: 30px;
|
| 33 |
+
text-align: center;
|
| 34 |
+
border-radius: 10px 10px 0 0;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.header h1 {
|
| 38 |
+
font-size: 2em;
|
| 39 |
+
margin-bottom: 10px;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.content {
|
| 43 |
+
padding: 30px;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.flow-step {
|
| 47 |
+
background: #f8f9fa;
|
| 48 |
+
border-left: 5px solid #D01010;
|
| 49 |
+
padding: 20px;
|
| 50 |
+
margin: 15px 0;
|
| 51 |
+
border-radius: 5px;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.flow-step h3 {
|
| 55 |
+
color: #D01010;
|
| 56 |
+
margin-bottom: 10px;
|
| 57 |
+
display: flex;
|
| 58 |
+
align-items: center;
|
| 59 |
+
gap: 10px;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.step-num {
|
| 63 |
+
background: #D01010;
|
| 64 |
+
color: white;
|
| 65 |
+
width: 35px;
|
| 66 |
+
height: 35px;
|
| 67 |
+
border-radius: 50%;
|
| 68 |
+
display: inline-flex;
|
| 69 |
+
align-items: center;
|
| 70 |
+
justify-content: center;
|
| 71 |
+
font-weight: bold;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.arrow {
|
| 75 |
+
text-align: center;
|
| 76 |
+
font-size: 2em;
|
| 77 |
+
color: #D01010;
|
| 78 |
+
margin: 10px 0;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.code {
|
| 82 |
+
background: #2d2d2d;
|
| 83 |
+
color: #f8f8f2;
|
| 84 |
+
padding: 15px;
|
| 85 |
+
border-radius: 5px;
|
| 86 |
+
margin: 10px 0;
|
| 87 |
+
font-family: monospace;
|
| 88 |
+
font-size: 0.9em;
|
| 89 |
+
overflow-x: auto;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
.branch {
|
| 93 |
+
display: grid;
|
| 94 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 95 |
+
gap: 10px;
|
| 96 |
+
margin: 15px 0;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.branch-item {
|
| 100 |
+
background: white;
|
| 101 |
+
border: 2px solid #D01010;
|
| 102 |
+
padding: 15px;
|
| 103 |
+
text-align: center;
|
| 104 |
+
border-radius: 5px;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.branch-item h4 {
|
| 108 |
+
color: #D01010;
|
| 109 |
+
margin-bottom: 5px;
|
| 110 |
+
}
|
| 111 |
+
</style>
|
| 112 |
+
</head>
|
| 113 |
+
|
| 114 |
+
<body>
|
| 115 |
+
<div class="container">
|
| 116 |
+
<div class="header">
|
| 117 |
+
<h1>🔄 Complete Technical Process Flow</h1>
|
| 118 |
+
<p>BMS AI Assistant - User to System to User</p>
|
| 119 |
+
</div>
|
| 120 |
+
<div class="content">
|
| 121 |
+
|
| 122 |
+
<div class="flow-step">
|
| 123 |
+
<h3><span class="step-num">1</span> User Input</h3>
|
| 124 |
+
<p><strong>Component:</strong> Frontend UI (index.html)</p>
|
| 125 |
+
<p><strong>Action:</strong> User types query in chat interface</p>
|
| 126 |
+
<p><strong>Example:</strong> "What is the forecast for BMS0015 next month?"</p>
|
| 127 |
+
<div class="code">userInput.addEventListener('keypress', (e) => {
|
| 128 |
+
if (e.key === 'Enter') sendMessage();
|
| 129 |
+
});</div>
|
| 130 |
+
</div>
|
| 131 |
+
|
| 132 |
+
<div class="arrow">↓</div>
|
| 133 |
+
|
| 134 |
+
<div class="flow-step">
|
| 135 |
+
<h3><span class="step-num">2</span> Client Processing</h3>
|
| 136 |
+
<p><strong>Component:</strong> JavaScript</p>
|
| 137 |
+
<p><strong>Action:</strong> Validate input, display user message, prepare API call</p>
|
| 138 |
+
<div class="code">const text = userInput.value.trim();
|
| 139 |
+
addMessage(text, 'user');</div>
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<div class="arrow">↓</div>
|
| 143 |
+
|
| 144 |
+
<div class="flow-step">
|
| 145 |
+
<h3><span class="step-num">3</span> HTTP POST Request</h3>
|
| 146 |
+
<p><strong>Endpoint:</strong> /api/chat</p>
|
| 147 |
+
<p><strong>Method:</strong> POST</p>
|
| 148 |
+
<p><strong>Payload:</strong> {"message": "user query"}</p>
|
| 149 |
+
<div class="code">fetch('/api/chat', {
|
| 150 |
+
method: 'POST',
|
| 151 |
+
headers: {'Content-Type': 'application/json'},
|
| 152 |
+
body: JSON.stringify({message: text})
|
| 153 |
+
});</div>
|
| 154 |
+
</div>
|
| 155 |
+
|
| 156 |
+
<div class="arrow">↓</div>
|
| 157 |
+
|
| 158 |
+
<div class="flow-step">
|
| 159 |
+
<h3><span class="step-num">4</span> Server Receives Request</h3>
|
| 160 |
+
<p><strong>Component:</strong> FastAPI (main.py)</p>
|
| 161 |
+
<p><strong>Server:</strong> Uvicorn ASGI</p>
|
| 162 |
+
<div class="code">@app.post("/api/chat")
|
| 163 |
+
async def chat(request: ChatRequest):
|
| 164 |
+
message = request.message</div>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
<div class="arrow">↓</div>
|
| 168 |
+
|
| 169 |
+
<div class="flow-step">
|
| 170 |
+
<h3><span class="step-num">5</span> Intent Parsing</h3>
|
| 171 |
+
<p><strong>Component:</strong> intent_parser.py</p>
|
| 172 |
+
<p><strong>Method:</strong> Regex pattern matching</p>
|
| 173 |
+
<p><strong>Extracts:</strong> Intent, Item code, Quantity, Location, Horizon</p>
|
| 174 |
+
<div class="code">parsed = parser.parse(message)
|
| 175 |
+
# Returns: {
|
| 176 |
+
# "intent": "demand_forecast",
|
| 177 |
+
# "item_code": "BMS0015",
|
| 178 |
+
# "horizon_days": 30
|
| 179 |
+
# }</div>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
<div class="arrow">↓</div>
|
| 183 |
+
|
| 184 |
+
<div class="flow-step">
|
| 185 |
+
<h3><span class="step-num">6</span> Intent Routing</h3>
|
| 186 |
+
<p><strong>Decision Point:</strong> Route to appropriate handler</p>
|
| 187 |
+
<div class="branch">
|
| 188 |
+
<div class="branch-item">
|
| 189 |
+
<h4>Forecast</h4>
|
| 190 |
+
<p>forecasting.py</p>
|
| 191 |
+
</div>
|
| 192 |
+
<div class="branch-item">
|
| 193 |
+
<h4>Item Details</h4>
|
| 194 |
+
<p>data_loader.py</p>
|
| 195 |
+
</div>
|
| 196 |
+
<div class="branch-item">
|
| 197 |
+
<h4>Inventory</h4>
|
| 198 |
+
<p>data_loader.py</p>
|
| 199 |
+
</div>
|
| 200 |
+
<div class="branch-item">
|
| 201 |
+
<h4>Supplier</h4>
|
| 202 |
+
<p>data_loader.py</p>
|
| 203 |
+
</div>
|
| 204 |
+
<div class="branch-item">
|
| 205 |
+
<h4>Requisition</h4>
|
| 206 |
+
<p>data_loader.py</p>
|
| 207 |
+
</div>
|
| 208 |
+
<div class="branch-item">
|
| 209 |
+
<h4>Status</h4>
|
| 210 |
+
<p>data_loader.py</p>
|
| 211 |
+
</div>
|
| 212 |
+
<div class="branch-item">
|
| 213 |
+
<h4>PDF</h4>
|
| 214 |
+
<p>pdf_generator.py</p>
|
| 215 |
+
</div>
|
| 216 |
+
<div class="branch-item">
|
| 217 |
+
<h4>Chat</h4>
|
| 218 |
+
<p>llm_engine.py</p>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
<div class="arrow">↓</div>
|
| 224 |
+
|
| 225 |
+
<div class="flow-step">
|
| 226 |
+
<h3><span class="step-num">7</span> Business Logic Execution</h3>
|
| 227 |
+
<p><strong>Example:</strong> Demand Forecast</p>
|
| 228 |
+
<p>1. Load demand_history.csv<br>
|
| 229 |
+
2. Filter by item_code<br>
|
| 230 |
+
3. Fit ARIMA model<br>
|
| 231 |
+
4. Generate forecast<br>
|
| 232 |
+
5. Format as JSON</p>
|
| 233 |
+
<div class="code">forecast_data = forecast_demand(item_code, horizon)
|
| 234 |
+
# Returns: [{"date": "2025-01-01", "qty": 150}, ...]</div>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
<div class="arrow">↓</div>
|
| 238 |
+
|
| 239 |
+
<div class="flow-step">
|
| 240 |
+
<h3><span class="step-num">8</span> Data Retrieval</h3>
|
| 241 |
+
<p><strong>Component:</strong> data_loader.py</p>
|
| 242 |
+
<p><strong>Sources:</strong> items.csv, inventory.csv, suppliers.csv, etc.</p>
|
| 243 |
+
<p><strong>Technology:</strong> Pandas DataFrames</p>
|
| 244 |
+
<div class="code">item = loader.items_df[loader.items_df['item_code'] == 'BMS0015']</div>
|
| 245 |
+
</div>
|
| 246 |
+
|
| 247 |
+
<div class="arrow">↓</div>
|
| 248 |
+
|
| 249 |
+
<div class="flow-step">
|
| 250 |
+
<h3><span class="step-num">9</span> LLM Processing (if needed)</h3>
|
| 251 |
+
<p><strong>Component:</strong> llm_engine.py</p>
|
| 252 |
+
<p><strong>Trigger:</strong> Only for general_chat intent</p>
|
| 253 |
+
<p><strong>Model:</strong> TinyLlama 1.1B</p>
|
| 254 |
+
<p><strong>Time:</strong> 5-15 seconds</p>
|
| 255 |
+
<div class="code">response = llm.generate_response(query)
|
| 256 |
+
# Uses company_context.txt for RAG</div>
|
| 257 |
+
</div>
|
| 258 |
+
|
| 259 |
+
<div class="arrow">↓</div>
|
| 260 |
+
|
| 261 |
+
<div class="flow-step">
|
| 262 |
+
<h3><span class="step-num">10</span> Response Formatting</h3>
|
| 263 |
+
<p><strong>Component:</strong> main.py</p>
|
| 264 |
+
<p><strong>Format:</strong> JSON</p>
|
| 265 |
+
<div class="code">return {
|
| 266 |
+
"intent": "demand_forecast",
|
| 267 |
+
"answer": "Forecast for BMS0015...",
|
| 268 |
+
"forecast": [{"date": "2025-01-01", "qty": 150}]
|
| 269 |
+
}</div>
|
| 270 |
+
</div>
|
| 271 |
+
|
| 272 |
+
<div class="arrow">↓</div>
|
| 273 |
+
|
| 274 |
+
<div class="flow-step">
|
| 275 |
+
<h3><span class="step-num">11</span> HTTP Response</h3>
|
| 276 |
+
<p><strong>Status:</strong> 200 OK</p>
|
| 277 |
+
<p><strong>Content-Type:</strong> application/json</p>
|
| 278 |
+
<p><strong>Sent to:</strong> Client browser</p>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div class="arrow">↓</div>
|
| 282 |
+
|
| 283 |
+
<div class="flow-step">
|
| 284 |
+
<h3><span class="step-num">12</span> Client Receives Response</h3>
|
| 285 |
+
<p><strong>Component:</strong> JavaScript Fetch API</p>
|
| 286 |
+
<p><strong>Action:</strong> Parse JSON response</p>
|
| 287 |
+
<div class="code">const data = await response.json();
|
| 288 |
+
let botHtml = data.answer;</div>
|
| 289 |
+
</div>
|
| 290 |
+
|
| 291 |
+
<div class="arrow">↓</div>
|
| 292 |
+
|
| 293 |
+
<div class="flow-step">
|
| 294 |
+
<h3><span class="step-num">13</span> UI Rendering</h3>
|
| 295 |
+
<p><strong>Component:</strong> JavaScript DOM manipulation</p>
|
| 296 |
+
<p><strong>Actions:</strong></p>
|
| 297 |
+
<p>1. Create message bubble<br>
|
| 298 |
+
2. Add bot icon<br>
|
| 299 |
+
3. Render forecast table (if present)<br>
|
| 300 |
+
4. Add download button (if PDF)<br>
|
| 301 |
+
5. Scroll to bottom</p>
|
| 302 |
+
<div class="code">addMessage(botHtml, 'bot', true);
|
| 303 |
+
if (data.forecast) {
|
| 304 |
+
botHtml += renderForecastTable(data.forecast);
|
| 305 |
+
}</div>
|
| 306 |
+
</div>
|
| 307 |
+
|
| 308 |
+
<div class="arrow">↓</div>
|
| 309 |
+
|
| 310 |
+
<div class="flow-step">
|
| 311 |
+
<h3><span class="step-num">14</span> User Sees Response</h3>
|
| 312 |
+
<p><strong>Display:</strong> Chat bubble with bot icon</p>
|
| 313 |
+
<p><strong>Features:</strong> Formatted text, tables, download links</p>
|
| 314 |
+
<p><strong>Animation:</strong> Fade-in effect</p>
|
| 315 |
+
<p><strong>Interaction:</strong> User can copy text, download PDFs, ask follow-up</p>
|
| 316 |
+
</div>
|
| 317 |
+
|
| 318 |
+
<div style="background: #e8f5e9; padding: 20px; border-radius: 5px; margin-top: 30px;">
|
| 319 |
+
<h3 style="color: #2e7d32; margin-bottom: 10px;">✅ Flow Complete</h3>
|
| 320 |
+
<p><strong>Total Steps:</strong> 14</p>
|
| 321 |
+
<p><strong>Average Time:</strong> 2-15 seconds (depending on intent)</p>
|
| 322 |
+
<p><strong>Technologies Used:</strong> HTML/JS, FastAPI, Pandas, ARIMA, TinyLlama</p>
|
| 323 |
+
</div>
|
| 324 |
+
|
| 325 |
+
</div>
|
| 326 |
+
</div>
|
| 327 |
+
</body>
|
| 328 |
+
|
| 329 |
+
</html>
|
Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y gcc g++ && rm -rf /var/lib/apt/lists/*
|
| 6 |
+
|
| 7 |
+
COPY requirements.txt .
|
| 8 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 9 |
+
|
| 10 |
+
COPY app/ ./app/
|
| 11 |
+
COPY data/ ./data/
|
| 12 |
+
COPY static/ ./static/
|
| 13 |
+
|
| 14 |
+
RUN mkdir -p static/reports
|
| 15 |
+
|
| 16 |
+
EXPOSE 7860
|
| 17 |
+
ENV PYTHONUNBUFFERED=1
|
| 18 |
+
|
| 19 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: BMS AI Assistant
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# BMS AI Assistant
|
| 12 |
+
|
| 13 |
+
An intelligent chatbot for Business Management Systems.
|
| 14 |
+
|
| 15 |
+
## Features
|
| 16 |
+
- Demand Forecasting
|
| 17 |
+
- Inventory Management
|
| 18 |
+
- Supplier Information
|
| 19 |
+
- PDF Reporting
|
STANDALONE_DEMO.html
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant - Standalone Demo</title>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
:root {
|
| 11 |
+
--primary-color: #D01010;
|
| 12 |
+
--primary-dark: #A00C0C;
|
| 13 |
+
--bg-color: #F3F4F6;
|
| 14 |
+
--chat-bg: #FFFFFF;
|
| 15 |
+
--user-msg-bg: #D01010;
|
| 16 |
+
--user-msg-text: #FFFFFF;
|
| 17 |
+
--bot-msg-bg: #F3F4F6;
|
| 18 |
+
--bot-msg-text: #1F2937;
|
| 19 |
+
--border-color: #E5E7EB;
|
| 20 |
+
--text-color: #1F2937;
|
| 21 |
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
| 22 |
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
body {
|
| 26 |
+
font-family: 'Inter', sans-serif;
|
| 27 |
+
background-color: var(--bg-color);
|
| 28 |
+
margin: 0;
|
| 29 |
+
display: flex;
|
| 30 |
+
justify-content: center;
|
| 31 |
+
height: 100vh;
|
| 32 |
+
color: var(--text-color);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.chat-container {
|
| 36 |
+
width: 100%;
|
| 37 |
+
max-width: 900px;
|
| 38 |
+
background-color: var(--chat-bg);
|
| 39 |
+
box-shadow: var(--shadow-md);
|
| 40 |
+
display: flex;
|
| 41 |
+
flex-direction: column;
|
| 42 |
+
height: 100%;
|
| 43 |
+
overflow: hidden;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.header {
|
| 47 |
+
background-color: var(--primary-color);
|
| 48 |
+
color: white;
|
| 49 |
+
padding: 20px;
|
| 50 |
+
text-align: center;
|
| 51 |
+
font-size: 1.25rem;
|
| 52 |
+
font-weight: 600;
|
| 53 |
+
box-shadow: var(--shadow-sm);
|
| 54 |
+
z-index: 10;
|
| 55 |
+
display: flex;
|
| 56 |
+
align-items: center;
|
| 57 |
+
justify-content: center;
|
| 58 |
+
gap: 10px;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.header svg {
|
| 62 |
+
width: 24px;
|
| 63 |
+
height: 24px;
|
| 64 |
+
fill: currentColor;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.demo-badge {
|
| 68 |
+
background: #FFA500;
|
| 69 |
+
padding: 5px 15px;
|
| 70 |
+
border-radius: 20px;
|
| 71 |
+
font-size: 0.8em;
|
| 72 |
+
margin-left: 10px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.chat-window {
|
| 76 |
+
flex: 1;
|
| 77 |
+
padding: 20px;
|
| 78 |
+
overflow-y: auto;
|
| 79 |
+
display: flex;
|
| 80 |
+
flex-direction: column;
|
| 81 |
+
gap: 16px;
|
| 82 |
+
scroll-behavior: smooth;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.message {
|
| 86 |
+
max-width: 80%;
|
| 87 |
+
padding: 14px 18px;
|
| 88 |
+
border-radius: 16px;
|
| 89 |
+
line-height: 1.5;
|
| 90 |
+
position: relative;
|
| 91 |
+
animation: fadeIn 0.3s ease-out;
|
| 92 |
+
display: flex;
|
| 93 |
+
align-items: flex-start;
|
| 94 |
+
gap: 12px;
|
| 95 |
+
font-size: 0.95rem;
|
| 96 |
+
box-shadow: var(--shadow-sm);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
@keyframes fadeIn {
|
| 100 |
+
from {
|
| 101 |
+
opacity: 0;
|
| 102 |
+
transform: translateY(10px);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
to {
|
| 106 |
+
opacity: 1;
|
| 107 |
+
transform: translateY(0);
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.message.user {
|
| 112 |
+
align-self: flex-end;
|
| 113 |
+
background-color: var(--user-msg-bg);
|
| 114 |
+
color: var(--user-msg-text);
|
| 115 |
+
border-bottom-right-radius: 4px;
|
| 116 |
+
flex-direction: row-reverse;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.message.bot {
|
| 120 |
+
align-self: flex-start;
|
| 121 |
+
background-color: var(--bot-msg-bg);
|
| 122 |
+
color: var(--bot-msg-text);
|
| 123 |
+
border-bottom-left-radius: 4px;
|
| 124 |
+
border: 1px solid var(--border-color);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.icon-container {
|
| 128 |
+
width: 36px;
|
| 129 |
+
height: 36px;
|
| 130 |
+
border-radius: 50%;
|
| 131 |
+
display: flex;
|
| 132 |
+
align-items: center;
|
| 133 |
+
justify-content: center;
|
| 134 |
+
flex-shrink: 0;
|
| 135 |
+
background-color: white;
|
| 136 |
+
box-shadow: var(--shadow-sm);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.message.user .icon-container {
|
| 140 |
+
color: var(--primary-color);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.message.bot .icon-container {
|
| 144 |
+
background-color: var(--primary-color);
|
| 145 |
+
color: white;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.icon-container svg {
|
| 149 |
+
width: 20px;
|
| 150 |
+
height: 20px;
|
| 151 |
+
fill: currentColor;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.message-content {
|
| 155 |
+
flex: 1;
|
| 156 |
+
word-wrap: break-word;
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.input-area {
|
| 160 |
+
padding: 20px;
|
| 161 |
+
border-top: 1px solid var(--border-color);
|
| 162 |
+
display: flex;
|
| 163 |
+
gap: 12px;
|
| 164 |
+
background-color: #fff;
|
| 165 |
+
align-items: center;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
input[type="text"] {
|
| 169 |
+
flex: 1;
|
| 170 |
+
padding: 14px 20px;
|
| 171 |
+
border: 1px solid var(--border-color);
|
| 172 |
+
border-radius: 30px;
|
| 173 |
+
outline: none;
|
| 174 |
+
font-size: 1rem;
|
| 175 |
+
transition: all 0.2s;
|
| 176 |
+
font-family: inherit;
|
| 177 |
+
background-color: #F9FAFB;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
input[type="text"]:focus {
|
| 181 |
+
border-color: var(--primary-color);
|
| 182 |
+
background-color: #fff;
|
| 183 |
+
box-shadow: 0 0 0 3px rgba(208, 16, 16, 0.1);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
button {
|
| 187 |
+
background-color: var(--primary-color);
|
| 188 |
+
color: white;
|
| 189 |
+
border: none;
|
| 190 |
+
padding: 14px 28px;
|
| 191 |
+
border-radius: 30px;
|
| 192 |
+
cursor: pointer;
|
| 193 |
+
font-weight: 600;
|
| 194 |
+
transition: background-color 0.2s, transform 0.1s;
|
| 195 |
+
font-family: inherit;
|
| 196 |
+
display: flex;
|
| 197 |
+
align-items: center;
|
| 198 |
+
gap: 8px;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
button:hover {
|
| 202 |
+
background-color: var(--primary-dark);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
button:active {
|
| 206 |
+
transform: scale(0.98);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.chart-container {
|
| 210 |
+
margin-top: 12px;
|
| 211 |
+
background: #fff;
|
| 212 |
+
border-radius: 8px;
|
| 213 |
+
border: 1px solid var(--border-color);
|
| 214 |
+
overflow: hidden;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
table {
|
| 218 |
+
width: 100%;
|
| 219 |
+
border-collapse: collapse;
|
| 220 |
+
font-size: 0.9rem;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
th,
|
| 224 |
+
td {
|
| 225 |
+
padding: 10px 14px;
|
| 226 |
+
text-align: left;
|
| 227 |
+
border-bottom: 1px solid var(--border-color);
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
th {
|
| 231 |
+
background-color: #F9FAFB;
|
| 232 |
+
font-weight: 600;
|
| 233 |
+
color: var(--text-color);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
tr:last-child td {
|
| 237 |
+
border-bottom: none;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.download-btn {
|
| 241 |
+
display: inline-flex;
|
| 242 |
+
align-items: center;
|
| 243 |
+
gap: 8px;
|
| 244 |
+
background-color: var(--primary-color);
|
| 245 |
+
color: white;
|
| 246 |
+
padding: 10px 20px;
|
| 247 |
+
text-decoration: none;
|
| 248 |
+
border-radius: 25px;
|
| 249 |
+
font-weight: 600;
|
| 250 |
+
margin-top: 12px;
|
| 251 |
+
transition: background-color 0.2s;
|
| 252 |
+
font-size: 0.9rem;
|
| 253 |
+
cursor: pointer;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
.download-btn:hover {
|
| 257 |
+
background-color: var(--primary-dark);
|
| 258 |
+
}
|
| 259 |
+
</style>
|
| 260 |
+
</head>
|
| 261 |
+
|
| 262 |
+
<body>
|
| 263 |
+
|
| 264 |
+
<div class="chat-container">
|
| 265 |
+
<div class="header">
|
| 266 |
+
<svg viewBox="0 0 24 24">
|
| 267 |
+
<path
|
| 268 |
+
d="M12 2L2 7l10 5 10-5-10-5zm0 9l2.5-1.25L12 8.5l-2.5 1.25L12 11zm0 2.5l-5-2.5-5 2.5L12 22l10-8.5-5-2.5-5 2.5z" />
|
| 269 |
+
</svg>
|
| 270 |
+
BMS AI Assistant
|
| 271 |
+
<span class="demo-badge">DEMO MODE</span>
|
| 272 |
+
</div>
|
| 273 |
+
<div class="chat-window" id="chat-window">
|
| 274 |
+
<div class="message bot">
|
| 275 |
+
<div class="icon-container">
|
| 276 |
+
<svg viewBox="0 0 24 24">
|
| 277 |
+
<path
|
| 278 |
+
d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7V5.73C9.4 5.39 9 4.74 9 4a2 2 0 0 1 2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18A2.5 2.5 0 0 0 10 15.5A2.5 2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M12 8a6 6 0 0 0-6 6h12a6 6 0 0 0-6-6z" />
|
| 279 |
+
</svg>
|
| 280 |
+
</div>
|
| 281 |
+
<div class="message-content">
|
| 282 |
+
<strong>Hello! I am your BMS AI Assistant.</strong><br><br>
|
| 283 |
+
I can help you with:<br>
|
| 284 |
+
• Demand Forecasting (e.g., "Forecast for BMS0015")<br>
|
| 285 |
+
• Inventory Checks (e.g., "Stock for BMS0015")<br>
|
| 286 |
+
• Supplier Information<br>
|
| 287 |
+
• Order Requisitions<br>
|
| 288 |
+
• General Inquiries<br><br>
|
| 289 |
+
How can I assist you today?
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
<div class="input-area">
|
| 294 |
+
<input type="text" id="user-input" placeholder="Type your query..." autocomplete="off">
|
| 295 |
+
<button onclick="sendMessage()">
|
| 296 |
+
Send
|
| 297 |
+
<svg viewBox="0 0 24 24" style="width: 16px; height: 16px;">
|
| 298 |
+
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
| 299 |
+
</svg>
|
| 300 |
+
</button>
|
| 301 |
+
</div>
|
| 302 |
+
</div>
|
| 303 |
+
|
| 304 |
+
<script>
|
| 305 |
+
const chatWindow = document.getElementById('chat-window');
|
| 306 |
+
const userInput = document.getElementById('user-input');
|
| 307 |
+
|
| 308 |
+
// Mock data
|
| 309 |
+
const mockData = {
|
| 310 |
+
forecast_bms0015: [
|
| 311 |
+
{ date: '2025-12-01', qty: 152 },
|
| 312 |
+
{ date: '2025-12-02', qty: 148 },
|
| 313 |
+
{ date: '2025-12-03', qty: 155 },
|
| 314 |
+
{ date: '2025-12-04', qty: 151 },
|
| 315 |
+
{ date: '2025-12-05', qty: 149 },
|
| 316 |
+
{ date: '2025-12-06', qty: 153 },
|
| 317 |
+
{ date: '2025-12-07', qty: 150 }
|
| 318 |
+
],
|
| 319 |
+
forecast_bms0020: [
|
| 320 |
+
{ date: '2025-12-01', qty: 89 },
|
| 321 |
+
{ date: '2025-12-02', qty: 92 },
|
| 322 |
+
{ date: '2025-12-03', qty: 87 },
|
| 323 |
+
{ date: '2025-12-04', qty: 90 },
|
| 324 |
+
{ date: '2025-12-05', qty: 88 },
|
| 325 |
+
{ date: '2025-12-06', qty: 91 },
|
| 326 |
+
{ date: '2025-12-07', qty: 89 }
|
| 327 |
+
]
|
| 328 |
+
};
|
| 329 |
+
|
| 330 |
+
userInput.addEventListener('keypress', function (e) {
|
| 331 |
+
if (e.key === 'Enter') {
|
| 332 |
+
sendMessage();
|
| 333 |
+
}
|
| 334 |
+
});
|
| 335 |
+
|
| 336 |
+
function sendMessage() {
|
| 337 |
+
const text = userInput.value.trim();
|
| 338 |
+
if (!text) return;
|
| 339 |
+
|
| 340 |
+
addMessage(text, 'user');
|
| 341 |
+
userInput.value = '';
|
| 342 |
+
|
| 343 |
+
// Simulate processing delay
|
| 344 |
+
setTimeout(() => {
|
| 345 |
+
const response = processQuery(text.toLowerCase());
|
| 346 |
+
addMessage(response.answer, 'bot', true, response.forecast);
|
| 347 |
+
}, 500);
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
function processQuery(query) {
|
| 351 |
+
// Greeting
|
| 352 |
+
if (query.match(/^(hi|hello|hey|greetings)$/)) {
|
| 353 |
+
return {
|
| 354 |
+
answer: "Hello! I am the BMS AI Assistant. How can I help you today?"
|
| 355 |
+
};
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
// Forecast
|
| 359 |
+
if (query.includes('forecast') || query.includes('predict')) {
|
| 360 |
+
if (query.includes('bms0015')) {
|
| 361 |
+
return {
|
| 362 |
+
answer: "📊 <strong>Demand Forecast for BMS0015</strong> (Next 30 days)<br><br>Based on historical data analysis using ARIMA model:",
|
| 363 |
+
forecast: mockData.forecast_bms0015
|
| 364 |
+
};
|
| 365 |
+
} else if (query.includes('bms0020')) {
|
| 366 |
+
return {
|
| 367 |
+
answer: "📊 <strong>Demand Forecast for BMS0020</strong> (Next 30 days)<br><br>Based on historical data analysis using ARIMA model:",
|
| 368 |
+
forecast: mockData.forecast_bms0020
|
| 369 |
+
};
|
| 370 |
+
} else {
|
| 371 |
+
return { answer: "Please specify an item code (e.g., BMS0015 or BMS0020)." };
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
// Item details
|
| 376 |
+
if (query.includes('details') || query.includes('about') || query.includes('info')) {
|
| 377 |
+
if (query.includes('bms0015')) {
|
| 378 |
+
return { answer: "<strong>Item Details:</strong><br>• Code: BMS0015<br>• Description: Fuel Water Separator<br>• UOM: Each<br>• List Price: $45.99" };
|
| 379 |
+
} else if (query.includes('bms0020')) {
|
| 380 |
+
return { answer: "<strong>Item Details:</strong><br>• Code: BMS0020<br>• Description: Lube Oil Filter<br>• UOM: Each<br>• List Price: $32.50" };
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
// Inventory
|
| 385 |
+
if (query.includes('inventory') || query.includes('stock') || query.includes('available')) {
|
| 386 |
+
if (query.includes('bms0015')) {
|
| 387 |
+
return { answer: "📦 <strong>Inventory Status for BMS0015:</strong><br>• On Hand: 450 units<br>• Location: NA Warehouse<br>• Last Updated: 2025-11-29" };
|
| 388 |
+
} else if (query.includes('bms0020')) {
|
| 389 |
+
return { answer: "📦 <strong>Inventory Status for BMS0020:</strong><br>• On Hand: 320 units<br>• Location: NA Warehouse<br>• Last Updated: 2025-11-29" };
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// Supplier
|
| 394 |
+
if (query.includes('supplier') || query.includes('vendor') || query.includes('who supplies')) {
|
| 395 |
+
return { answer: "🏭 <strong>Supplier Information:</strong><br>• Supplier: Acme Filters Inc.<br>• Lead Time: 14 days<br>• Contact: [email protected]" };
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
// Order/Requisition
|
| 399 |
+
if (query.includes('order') || query.includes('buy') || query.includes('purchase') || query.includes('requisition')) {
|
| 400 |
+
const reqId = 'REQ' + Math.floor(Math.random() * 9000 + 1000);
|
| 401 |
+
return { answer: `✅ <strong>Requisition Created Successfully!</strong><br>• Requisition ID: ${reqId}<br>• Item: BMS0015<br>• Quantity: 50<br>• Status: Pending` };
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
// System status
|
| 405 |
+
if (query.includes('status') || query.includes('alerts') || query.includes('dashboard')) {
|
| 406 |
+
return { answer: "⚠️ <strong>System Alerts:</strong><br>• BMS0015: Low stock warning (below reorder point)<br>• AB9999: Supplier lead time increased<br><br>✅ All other systems normal." };
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
// PDF
|
| 410 |
+
if (query.includes('pdf') || query.includes('download') || query.includes('report')) {
|
| 411 |
+
return { answer: '📄 <strong>Report Generation</strong><br><br><a href="#" class="download-btn" onclick="alert(\'In production, this would download a PDF report.\'); return false;">📥 Download Forecast Report</a>' };
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
// General about BMS
|
| 415 |
+
if (query.includes('bms') || query.includes('what do') || query.includes('company')) {
|
| 416 |
+
return { answer: "BMS Inc. is a global power leader that designs, manufactures, distributes, and services a broad portfolio of power solutions. Our products range from diesel, natural gas, electric and hybrid powertrains to components including filtration, aftertreatment, and turbochargers." };
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
// Default
|
| 420 |
+
return { answer: "I can help you with demand forecasting, inventory checks, supplier information, and order requisitions. Try asking about item BMS0015 or BMS0020!" };
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
function addMessage(text, sender, isHtml = false, forecast = null) {
|
| 424 |
+
const div = document.createElement('div');
|
| 425 |
+
div.className = `message ${sender}`;
|
| 426 |
+
|
| 427 |
+
const iconDiv = document.createElement('div');
|
| 428 |
+
iconDiv.className = 'icon-container';
|
| 429 |
+
|
| 430 |
+
if (sender === 'user') {
|
| 431 |
+
iconDiv.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>`;
|
| 432 |
+
} else {
|
| 433 |
+
iconDiv.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7V5.73C9.4 5.39 9 4.74 9 4a2 2 0 0 1 2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18A2.5 2.5 0 0 0 10 15.5A2.5 2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M12 8a6 6 0 0 0-6 6h12a6 6 0 0 0-6-6z"/></svg>`;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
const contentDiv = document.createElement('div');
|
| 437 |
+
contentDiv.className = 'message-content';
|
| 438 |
+
|
| 439 |
+
if (isHtml) {
|
| 440 |
+
contentDiv.innerHTML = text;
|
| 441 |
+
if (forecast) {
|
| 442 |
+
contentDiv.innerHTML += renderForecastTable(forecast);
|
| 443 |
+
}
|
| 444 |
+
} else {
|
| 445 |
+
contentDiv.textContent = text;
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
div.appendChild(iconDiv);
|
| 449 |
+
div.appendChild(contentDiv);
|
| 450 |
+
|
| 451 |
+
chatWindow.appendChild(div);
|
| 452 |
+
chatWindow.scrollTop = chatWindow.scrollHeight;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
function renderForecastTable(forecastData) {
|
| 456 |
+
let html = '<div class="chart-container"><table><thead><tr><th>Date</th><th>Quantity</th></tr></thead><tbody>';
|
| 457 |
+
|
| 458 |
+
forecastData.forEach(row => {
|
| 459 |
+
html += `<tr><td>${row.date}</td><td>${row.qty}</td></tr>`;
|
| 460 |
+
});
|
| 461 |
+
|
| 462 |
+
html += '</tbody></table></div>';
|
| 463 |
+
return html;
|
| 464 |
+
}
|
| 465 |
+
</script>
|
| 466 |
+
</body>
|
| 467 |
+
|
| 468 |
+
</html>
|
TEAM_ANNOUNCEMENT_EMAIL.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Team Announcement Email Drafts
|
| 2 |
+
|
| 3 |
+
Here are three versions of the email you can send to your team. Choose the one that best fits your team's culture.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Option 1: The "Vibe Coding" Approach (Recommended)
|
| 8 |
+
*Best for highlighting the innovative/experimental nature of the project.*
|
| 9 |
+
|
| 10 |
+
**Subject:** POC: BMS AI Assistant - Built with Gemini & Open Source Tools
|
| 11 |
+
|
| 12 |
+
Hi Team,
|
| 13 |
+
|
| 14 |
+
I did some "vibe coding" this weekend using Gemini to build a Proof of Concept (POC) for our supply chain operations. I wanted to see how quickly we could spin up a functional AI assistant using 100% open-source tools without any licensing costs.
|
| 15 |
+
|
| 16 |
+
**What it is:**
|
| 17 |
+
A chatbot that can forecast demand, check inventory, and answer supplier questions in real-time.
|
| 18 |
+
|
| 19 |
+
**The Tech Stack:**
|
| 20 |
+
It’s running entirely on open-source technology:
|
| 21 |
+
* **Backend:** FastAPI (Python)
|
| 22 |
+
* **AI Model:** TinyLlama (Local inference, no API costs)
|
| 23 |
+
* **Forecasting:** ARIMA (Statistical modeling)
|
| 24 |
+
* **Hosting:** Hugging Face Spaces (Dockerized)
|
| 25 |
+
|
| 26 |
+
**Try it out here:**
|
| 27 |
+
👉 **[Link to BMS AI Assistant](https://huggingface.co/spaces/bmsuser/BMS-AI-BOT)**
|
| 28 |
+
*(Note: It might take a minute to wake up if it's been idle)*
|
| 29 |
+
|
| 30 |
+
**Documentation:**
|
| 31 |
+
I've attached the **Technical Specification** and **Process Flow** documents if you want to dive into the architecture.
|
| 32 |
+
|
| 33 |
+
Please play around with it and let me know what you think! Since this is a POC, I'm looking for suggestions on features or use cases we might have missed.
|
| 34 |
+
|
| 35 |
+
Best,
|
| 36 |
+
|
| 37 |
+
[Your Name]
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Option 2: Professional & Direct
|
| 42 |
+
*Best for a formal corporate environment.*
|
| 43 |
+
|
| 44 |
+
**Subject:** For Review: BMS AI Assistant Prototype (Open Source POC)
|
| 45 |
+
|
| 46 |
+
Team,
|
| 47 |
+
|
| 48 |
+
I have deployed a prototype of the **BMS AI Assistant**, a conversational tool designed to streamline our access to demand and inventory data.
|
| 49 |
+
|
| 50 |
+
This POC was built to demonstrate the feasibility of using purely open-source, cost-effective technologies for enterprise AI. It leverages local LLMs and statistical forecasting models to provide instant answers without external API dependencies.
|
| 51 |
+
|
| 52 |
+
**Access the Application:**
|
| 53 |
+
https://huggingface.co/spaces/bmsuser/BMS-AI-BOT
|
| 54 |
+
|
| 55 |
+
**Key Features to Test:**
|
| 56 |
+
* **Demand Forecasting:** Ask for forecasts on specific items (e.g., "Forecast for BMS0015").
|
| 57 |
+
* **Inventory Checks:** Query real-time stock levels.
|
| 58 |
+
* **Supplier Info:** Retrieve vendor details.
|
| 59 |
+
|
| 60 |
+
I have attached the full **Technical Specifications** for your review. Please test the system and provide your feedback on its potential utility for our workflows.
|
| 61 |
+
|
| 62 |
+
Regards,
|
| 63 |
+
|
| 64 |
+
[Your Name]
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## Option 3: Short & Casual
|
| 69 |
+
*Best for Slack/Teams or quick updates.*
|
| 70 |
+
|
| 71 |
+
**Subject:** Check this out: AI Bot for BMS (POC)
|
| 72 |
+
|
| 73 |
+
Hey everyone,
|
| 74 |
+
|
| 75 |
+
I hacked together a quick POC for an AI Assistant using Gemini and some open-source tools (FastAPI, Llama, etc.). It’s actually working pretty well for forecasting and inventory checks.
|
| 76 |
+
|
| 77 |
+
**Live Demo:** https://huggingface.co/spaces/bmsuser/BMS-AI-BOT
|
| 78 |
+
|
| 79 |
+
I've attached the specs if you're curious about how it works under the hood.
|
| 80 |
+
|
| 81 |
+
Give it a spin and let me know if this vibe works for us!
|
| 82 |
+
|
| 83 |
+
Cheers,
|
| 84 |
+
|
| 85 |
+
[Your Name]
|
TECHNICAL_SPECIFICATION.html
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant - Technical Specification</title>
|
| 8 |
+
<style>
|
| 9 |
+
* {
|
| 10 |
+
margin: 0;
|
| 11 |
+
padding: 0;
|
| 12 |
+
box-sizing: border-box;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
body {
|
| 16 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 17 |
+
line-height: 1.6;
|
| 18 |
+
color: #333;
|
| 19 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 20 |
+
padding: 20px;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.container {
|
| 24 |
+
max-width: 1200px;
|
| 25 |
+
margin: 0 auto;
|
| 26 |
+
background: white;
|
| 27 |
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
| 28 |
+
border-radius: 10px;
|
| 29 |
+
overflow: hidden;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.header {
|
| 33 |
+
background: linear-gradient(135deg, #D01010 0%, #A00C0C 100%);
|
| 34 |
+
color: white;
|
| 35 |
+
padding: 40px;
|
| 36 |
+
text-align: center;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.header h1 {
|
| 40 |
+
font-size: 2.5em;
|
| 41 |
+
margin-bottom: 10px;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.header p {
|
| 45 |
+
font-size: 1.2em;
|
| 46 |
+
opacity: 0.9;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.content {
|
| 50 |
+
padding: 40px;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.section {
|
| 54 |
+
margin-bottom: 40px;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.section h2 {
|
| 58 |
+
color: #D01010;
|
| 59 |
+
font-size: 2em;
|
| 60 |
+
margin-bottom: 20px;
|
| 61 |
+
padding-bottom: 10px;
|
| 62 |
+
border-bottom: 3px solid #D01010;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
.section h3 {
|
| 66 |
+
color: #333;
|
| 67 |
+
font-size: 1.5em;
|
| 68 |
+
margin: 20px 0 10px 0;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.tech-stack {
|
| 72 |
+
display: grid;
|
| 73 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 74 |
+
gap: 20px;
|
| 75 |
+
margin: 20px 0;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.tech-card {
|
| 79 |
+
background: #f8f9fa;
|
| 80 |
+
padding: 20px;
|
| 81 |
+
border-radius: 8px;
|
| 82 |
+
border-left: 4px solid #D01010;
|
| 83 |
+
transition: transform 0.3s;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.tech-card:hover {
|
| 87 |
+
transform: translateY(-5px);
|
| 88 |
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.tech-card h4 {
|
| 92 |
+
color: #D01010;
|
| 93 |
+
margin-bottom: 10px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.tech-card ul {
|
| 97 |
+
list-style: none;
|
| 98 |
+
padding-left: 0;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
.tech-card li:before {
|
| 102 |
+
content: "✓ ";
|
| 103 |
+
color: #D01010;
|
| 104 |
+
font-weight: bold;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.flow-diagram {
|
| 108 |
+
background: #f8f9fa;
|
| 109 |
+
padding: 30px;
|
| 110 |
+
border-radius: 8px;
|
| 111 |
+
margin: 20px 0;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.flow-step {
|
| 115 |
+
background: white;
|
| 116 |
+
padding: 20px;
|
| 117 |
+
margin: 15px 0;
|
| 118 |
+
border-radius: 8px;
|
| 119 |
+
border-left: 4px solid #D01010;
|
| 120 |
+
position: relative;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.flow-step:before {
|
| 124 |
+
content: attr(data-step);
|
| 125 |
+
position: absolute;
|
| 126 |
+
left: -15px;
|
| 127 |
+
top: 50%;
|
| 128 |
+
transform: translateY(-50%);
|
| 129 |
+
background: #D01010;
|
| 130 |
+
color: white;
|
| 131 |
+
width: 30px;
|
| 132 |
+
height: 30px;
|
| 133 |
+
border-radius: 50%;
|
| 134 |
+
display: flex;
|
| 135 |
+
align-items: center;
|
| 136 |
+
justify-content: center;
|
| 137 |
+
font-weight: bold;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.flow-step h4 {
|
| 141 |
+
color: #D01010;
|
| 142 |
+
margin-bottom: 10px;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.code-block {
|
| 146 |
+
background: #2d2d2d;
|
| 147 |
+
color: #f8f8f2;
|
| 148 |
+
padding: 20px;
|
| 149 |
+
border-radius: 8px;
|
| 150 |
+
overflow-x: auto;
|
| 151 |
+
margin: 15px 0;
|
| 152 |
+
font-family: 'Courier New', monospace;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.specs-table {
|
| 156 |
+
width: 100%;
|
| 157 |
+
border-collapse: collapse;
|
| 158 |
+
margin: 20px 0;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.specs-table th,
|
| 162 |
+
.specs-table td {
|
| 163 |
+
padding: 15px;
|
| 164 |
+
text-align: left;
|
| 165 |
+
border-bottom: 1px solid #ddd;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.specs-table th {
|
| 169 |
+
background: #D01010;
|
| 170 |
+
color: white;
|
| 171 |
+
font-weight: bold;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.specs-table tr:hover {
|
| 175 |
+
background: #f8f9fa;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.badge {
|
| 179 |
+
display: inline-block;
|
| 180 |
+
padding: 5px 10px;
|
| 181 |
+
border-radius: 20px;
|
| 182 |
+
font-size: 0.85em;
|
| 183 |
+
font-weight: bold;
|
| 184 |
+
margin: 5px;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
.badge-primary {
|
| 188 |
+
background: #D01010;
|
| 189 |
+
color: white;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.badge-success {
|
| 193 |
+
background: #28a745;
|
| 194 |
+
color: white;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.badge-info {
|
| 198 |
+
background: #17a2b8;
|
| 199 |
+
color: white;
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
.architecture-diagram {
|
| 203 |
+
background: white;
|
| 204 |
+
padding: 30px;
|
| 205 |
+
border-radius: 8px;
|
| 206 |
+
text-align: center;
|
| 207 |
+
margin: 20px 0;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.layer {
|
| 211 |
+
background: #f8f9fa;
|
| 212 |
+
padding: 20px;
|
| 213 |
+
margin: 10px 0;
|
| 214 |
+
border-radius: 8px;
|
| 215 |
+
border: 2px solid #D01010;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.layer h4 {
|
| 219 |
+
color: #D01010;
|
| 220 |
+
margin-bottom: 10px;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.component {
|
| 224 |
+
display: inline-block;
|
| 225 |
+
background: white;
|
| 226 |
+
padding: 10px 20px;
|
| 227 |
+
margin: 5px;
|
| 228 |
+
border-radius: 5px;
|
| 229 |
+
border: 1px solid #ddd;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
@media print {
|
| 233 |
+
body {
|
| 234 |
+
background: white;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.container {
|
| 238 |
+
box-shadow: none;
|
| 239 |
+
}
|
| 240 |
+
}
|
| 241 |
+
</style>
|
| 242 |
+
</head>
|
| 243 |
+
|
| 244 |
+
<body>
|
| 245 |
+
<div class="container">
|
| 246 |
+
<div class="header">
|
| 247 |
+
<h1>🏭 BMS AI Assistant</h1>
|
| 248 |
+
<p>Technical Specification & Process Flow Documentation</p>
|
| 249 |
+
<p style="font-size: 0.9em; margin-top: 10px;">Version 1.0 | Enterprise Demand Forecasting System</p>
|
| 250 |
+
</div>
|
| 251 |
+
|
| 252 |
+
<div class="content">
|
| 253 |
+
<!-- Executive Summary -->
|
| 254 |
+
<div class="section">
|
| 255 |
+
<h2>📋 Executive Summary</h2>
|
| 256 |
+
<p>The BMS AI Assistant is an enterprise-grade, zero-cost chatbot system designed for demand
|
| 257 |
+
forecasting, inventory management, and ERP integration. Built with open-source technologies, it
|
| 258 |
+
combines rule-based intent parsing with local AI capabilities to provide intelligent, context-aware
|
| 259 |
+
responses without external API dependencies.</p>
|
| 260 |
+
|
| 261 |
+
<div style="margin-top: 20px;">
|
| 262 |
+
<span class="badge badge-primary">Zero Cost</span>
|
| 263 |
+
<span class="badge badge-success">100% Open Source</span>
|
| 264 |
+
<span class="badge badge-info">Local AI</span>
|
| 265 |
+
<span class="badge badge-primary">Enterprise Ready</span>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
|
| 269 |
+
<!-- System Architecture -->
|
| 270 |
+
<div class="section">
|
| 271 |
+
<h2>🏗️ System Architecture</h2>
|
| 272 |
+
|
| 273 |
+
<div class="architecture-diagram">
|
| 274 |
+
<div class="layer">
|
| 275 |
+
<h4>Presentation Layer</h4>
|
| 276 |
+
<div class="component">HTML5 + CSS3</div>
|
| 277 |
+
<div class="component">JavaScript (Vanilla)</div>
|
| 278 |
+
<div class="component">SVG Icons</div>
|
| 279 |
+
<div class="component">Hugging Face Spaces</div>
|
| 280 |
+
</div>
|
| 281 |
+
|
| 282 |
+
<div class="layer">
|
| 283 |
+
<h4>API Layer</h4>
|
| 284 |
+
<div class="component">FastAPI</div>
|
| 285 |
+
<div class="component">Uvicorn (ASGI)</div>
|
| 286 |
+
<div class="component">REST Endpoints</div>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<div class="layer">
|
| 290 |
+
<h4>Business Logic Layer</h4>
|
| 291 |
+
<div class="component">Intent Parser</div>
|
| 292 |
+
<div class="component">ARIMA Forecasting</div>
|
| 293 |
+
<div class="component">LLM Engine</div>
|
| 294 |
+
<div class="component">PDF Generator</div>
|
| 295 |
+
</div>
|
| 296 |
+
|
| 297 |
+
<div class="layer">
|
| 298 |
+
<h4>Data Layer</h4>
|
| 299 |
+
<div class="component">CSV Files</div>
|
| 300 |
+
<div class="component">Pandas DataFrames</div>
|
| 301 |
+
<div class="component">In-Memory Cache</div>
|
| 302 |
+
</div>
|
| 303 |
+
|
| 304 |
+
<div class="layer">
|
| 305 |
+
<h4>AI/ML Layer</h4>
|
| 306 |
+
<div class="component">TinyLlama 1.1B</div>
|
| 307 |
+
<div class="component">ctransformers</div>
|
| 308 |
+
<div class="component">Statsmodels (ARIMA)</div>
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
|
| 313 |
+
<!-- Technology Stack -->
|
| 314 |
+
<div class="section">
|
| 315 |
+
<h2>💻 Technology Stack</h2>
|
| 316 |
+
|
| 317 |
+
<div class="tech-stack">
|
| 318 |
+
<div class="tech-card">
|
| 319 |
+
<h4>Backend Framework</h4>
|
| 320 |
+
<ul>
|
| 321 |
+
<li>FastAPI 0.104+</li>
|
| 322 |
+
<li>Uvicorn (ASGI Server)</li>
|
| 323 |
+
<li>Python 3.10+</li>
|
| 324 |
+
<li>Async/Await Support</li>
|
| 325 |
+
</ul>
|
| 326 |
+
</div>
|
| 327 |
+
|
| 328 |
+
<div class="tech-card">
|
| 329 |
+
<h4>AI/ML Stack</h4>
|
| 330 |
+
<ul>
|
| 331 |
+
<li>TinyLlama 1.1B (GGUF)</li>
|
| 332 |
+
<li>ctransformers 0.2.27+</li>
|
| 333 |
+
<li>Hugging Face Hub</li>
|
| 334 |
+
<li>Statsmodels (ARIMA)</li>
|
| 335 |
+
</ul>
|
| 336 |
+
</div>
|
| 337 |
+
|
| 338 |
+
<div class="tech-card">
|
| 339 |
+
<h4>Data Processing</h4>
|
| 340 |
+
<ul>
|
| 341 |
+
<li>Pandas 2.0+</li>
|
| 342 |
+
<li>NumPy 1.24+</li>
|
| 343 |
+
<li>CSV-based Storage</li>
|
| 344 |
+
<li>In-Memory Caching</li>
|
| 345 |
+
</ul>
|
| 346 |
+
</div>
|
| 347 |
+
|
| 348 |
+
<div class="tech-card">
|
| 349 |
+
<h4>Frontend</h4>
|
| 350 |
+
<ul>
|
| 351 |
+
<li>HTML5 + CSS3</li>
|
| 352 |
+
<li>Vanilla JavaScript</li>
|
| 353 |
+
<li>Inter Font Family</li>
|
| 354 |
+
<li>SVG Graphics</li>
|
| 355 |
+
</ul>
|
| 356 |
+
</div>
|
| 357 |
+
|
| 358 |
+
<div class="tech-card">
|
| 359 |
+
<h4>Cloud Deployment</h4>
|
| 360 |
+
<ul>
|
| 361 |
+
<li>Hugging Face Spaces</li>
|
| 362 |
+
<li>Docker Container</li>
|
| 363 |
+
<li>Git Version Control</li>
|
| 364 |
+
</ul>
|
| 365 |
+
</div>
|
| 366 |
+
|
| 367 |
+
<div class="tech-card">
|
| 368 |
+
<h4>Testing & QA</h4>
|
| 369 |
+
<ul>
|
| 370 |
+
<li>Python Requests</li>
|
| 371 |
+
<li>Custom Test Suite</li>
|
| 372 |
+
<li>Load Testing Scripts</li>
|
| 373 |
+
<li>Concurrent Testing</li>
|
| 374 |
+
</ul>
|
| 375 |
+
</div>
|
| 376 |
+
</div>
|
| 377 |
+
</div>
|
| 378 |
+
|
| 379 |
+
<!-- Technical Specifications -->
|
| 380 |
+
<div class="section">
|
| 381 |
+
<h2>📊 Technical Specifications</h2>
|
| 382 |
+
|
| 383 |
+
<table class="specs-table">
|
| 384 |
+
<thead>
|
| 385 |
+
<tr>
|
| 386 |
+
<th>Component</th>
|
| 387 |
+
<th>Specification</th>
|
| 388 |
+
<th>Purpose</th>
|
| 389 |
+
</tr>
|
| 390 |
+
</thead>
|
| 391 |
+
<tbody>
|
| 392 |
+
<tr>
|
| 393 |
+
<td><strong>LLM Model</strong></td>
|
| 394 |
+
<td>TinyLlama 1.1B (Q4_K_M quantized)</td>
|
| 395 |
+
<td>General chat, context-aware responses</td>
|
| 396 |
+
</tr>
|
| 397 |
+
<tr>
|
| 398 |
+
<td><strong>Model Size</strong></td>
|
| 399 |
+
<td>~500 MB (GGUF format)</td>
|
| 400 |
+
<td>Optimized for CPU inference</td>
|
| 401 |
+
</tr>
|
| 402 |
+
<tr>
|
| 403 |
+
<td><strong>Forecasting Algorithm</strong></td>
|
| 404 |
+
<td>ARIMA (AutoRegressive Integrated Moving Average)</td>
|
| 405 |
+
<td>Time-series demand prediction</td>
|
| 406 |
+
</tr>
|
| 407 |
+
<tr>
|
| 408 |
+
<td><strong>Intent Recognition</strong></td>
|
| 409 |
+
<td>Rule-based pattern matching + Regex</td>
|
| 410 |
+
<td>Fast, deterministic intent parsing</td>
|
| 411 |
+
</tr>
|
| 412 |
+
<tr>
|
| 413 |
+
<td><strong>API Protocol</strong></td>
|
| 414 |
+
<td>REST (JSON)</td>
|
| 415 |
+
<td>Standard HTTP communication</td>
|
| 416 |
+
</tr>
|
| 417 |
+
<tr>
|
| 418 |
+
<td><strong>Data Format</strong></td>
|
| 419 |
+
<td>CSV (UTF-8)</td>
|
| 420 |
+
<td>Simple, portable data storage</td>
|
| 421 |
+
</tr>
|
| 422 |
+
<tr>
|
| 423 |
+
<td><strong>PDF Generation</strong></td>
|
| 424 |
+
<td>FPDF Library</td>
|
| 425 |
+
<td>Report generation</td>
|
| 426 |
+
</tr>
|
| 427 |
+
<tr>
|
| 428 |
+
<td><strong>Response Time</strong></td>
|
| 429 |
+
<td><2s (rule-based), <15s (LLM)</td>
|
| 430 |
+
<td>User experience optimization</td>
|
| 431 |
+
</tr>
|
| 432 |
+
<tr>
|
| 433 |
+
<td><strong>Concurrent Users</strong></td>
|
| 434 |
+
<td>5-10 (free tier), 50+ (paid)</td>
|
| 435 |
+
<td>Scalability</td>
|
| 436 |
+
</tr>
|
| 437 |
+
<tr>
|
| 438 |
+
<td><strong>Memory Requirement</strong></td>
|
| 439 |
+
<td>~2 GB RAM (minimum)</td>
|
| 440 |
+
<td>Model + application overhead</td>
|
| 441 |
+
</tr>
|
| 442 |
+
</tbody>
|
| 443 |
+
</table>
|
| 444 |
+
</div>
|
| 445 |
+
|
| 446 |
+
<!-- Process Flow -->
|
| 447 |
+
<div class="section">
|
| 448 |
+
<h2>🔄 Complete Process Flow</h2>
|
| 449 |
+
|
| 450 |
+
<div class="flow-diagram">
|
| 451 |
+
<div class="flow-step" data-step="1">
|
| 452 |
+
<h4>User Input</h4>
|
| 453 |
+
<p><strong>Component:</strong> index.html (Frontend)</p>
|
| 454 |
+
<p><strong>Action:</strong> User types query in chat interface</p>
|
| 455 |
+
<p><strong>Technology:</strong> JavaScript event listener (keypress/click)</p>
|
| 456 |
+
<div class="code-block">
|
| 457 |
+
userInput.addEventListener('keypress', function (e) {
|
| 458 |
+
if (e.key === 'Enter') sendMessage();
|
| 459 |
+
});</div>
|
| 460 |
+
</div>
|
| 461 |
+
|
| 462 |
+
<div class="flow-step" data-step="2">
|
| 463 |
+
<h4>API Request</h4>
|
| 464 |
+
<p><strong>Component:</strong> JavaScript Fetch API</p>
|
| 465 |
+
<p><strong>Action:</strong> POST request to /api/chat endpoint</p>
|
| 466 |
+
<p><strong>Payload:</strong> JSON with user message</p>
|
| 467 |
+
<div class="code-block">
|
| 468 |
+
fetch('/api/chat', {
|
| 469 |
+
method: 'POST',
|
| 470 |
+
headers: {'Content-Type': 'application/json'},
|
| 471 |
+
body: JSON.stringify({message: text})
|
| 472 |
+
});</div>
|
| 473 |
+
</div>
|
| 474 |
+
|
| 475 |
+
<div class="flow-step" data-step="3">
|
| 476 |
+
<h4>Intent Parsing</h4>
|
| 477 |
+
<p><strong>Component:</strong> intent_parser.py</p>
|
| 478 |
+
<p><strong>Action:</strong> Analyze query using regex patterns</p>
|
| 479 |
+
<p><strong>Output:</strong> Intent type + extracted entities (item_code, quantity, etc.)</p>
|
| 480 |
+
<div class="code-block">
|
| 481 |
+
parsed = parser.parse(message)
|
| 482 |
+
# Returns: {
|
| 483 |
+
# "intent": "demand_forecast",
|
| 484 |
+
# "item_code": "BMS0015",
|
| 485 |
+
# "horizon_days": 30
|
| 486 |
+
# }</div>
|
| 487 |
+
</div>
|
| 488 |
+
|
| 489 |
+
<div class="flow-step" data-step="4">
|
| 490 |
+
<h4>Intent Routing</h4>
|
| 491 |
+
<p><strong>Component:</strong> main.py (FastAPI)</p>
|
| 492 |
+
<p><strong>Action:</strong> Route to appropriate handler based on intent</p>
|
| 493 |
+
<p><strong>Intents:</strong> demand_forecast, item_details, check_inventory, supplier_info,
|
| 494 |
+
create_requisition, system_status, generate_report, general_chat</p>
|
| 495 |
+
</div>
|
| 496 |
+
|
| 497 |
+
<div class="flow-step" data-step="5">
|
| 498 |
+
<h4>Business Logic Execution</h4>
|
| 499 |
+
<p><strong>Components:</strong> Multiple modules based on intent</p>
|
| 500 |
+
<p><strong>Actions:</strong></p>
|
| 501 |
+
<ul style="margin-left: 20px; margin-top: 10px;">
|
| 502 |
+
<li><strong>Forecasting:</strong> forecasting.py → ARIMA model → Predict demand</li>
|
| 503 |
+
<li><strong>Data Retrieval:</strong> data_loader.py → Load CSV → Query data</li>
|
| 504 |
+
<li><strong>LLM Response:</strong> llm_engine.py → TinyLlama → Generate text</li>
|
| 505 |
+
<li><strong>PDF Generation:</strong> pdf_generator.py → FPDF → Create report</li>
|
| 506 |
+
</ul>
|
| 507 |
+
</div>
|
| 508 |
+
|
| 509 |
+
<div class="flow-step" data-step="6">
|
| 510 |
+
<h4>Response Formatting</h4>
|
| 511 |
+
<p><strong>Component:</strong> main.py</p>
|
| 512 |
+
<p><strong>Action:</strong> Format response as JSON</p>
|
| 513 |
+
<p><strong>Structure:</strong> {intent, answer, forecast (optional), pdf_link (optional)}</p>
|
| 514 |
+
<div class="code-block">
|
| 515 |
+
return {
|
| 516 |
+
"intent": "demand_forecast",
|
| 517 |
+
"answer": "Forecast for BMS0015...",
|
| 518 |
+
"forecast": [{date: "2025-01-01", qty: 150}, ...]
|
| 519 |
+
}</div>
|
| 520 |
+
</div>
|
| 521 |
+
|
| 522 |
+
<div class="flow-step" data-step="7">
|
| 523 |
+
<h4>Frontend Rendering</h4>
|
| 524 |
+
<p><strong>Component:</strong> index.html (JavaScript)</p>
|
| 525 |
+
<p><strong>Action:</strong> Parse JSON, render message bubble, display tables/charts</p>
|
| 526 |
+
<p><strong>Features:</strong> HTML sanitization, table rendering, download buttons</p>
|
| 527 |
+
<div class="code-block">
|
| 528 |
+
addMessage(botHtml, 'bot', true);
|
| 529 |
+
if (data.forecast) {
|
| 530 |
+
botHtml += renderForecastTable(data.forecast);
|
| 531 |
+
}</div>
|
| 532 |
+
</div>
|
| 533 |
+
|
| 534 |
+
<div class="flow-step" data-step="8">
|
| 535 |
+
<h4>User Interaction</h4>
|
| 536 |
+
<p><strong>Component:</strong> Frontend UI</p>
|
| 537 |
+
<p><strong>Action:</strong> Display response with icons, animations, and interactive elements
|
| 538 |
+
</p>
|
| 539 |
+
<p><strong>Features:</strong> Smooth scrolling, copy text, download PDFs, click links</p>
|
| 540 |
+
</div>
|
| 541 |
+
</div>
|
| 542 |
+
</div>
|
| 543 |
+
|
| 544 |
+
<!-- Detailed Component Breakdown -->
|
| 545 |
+
<div class="section">
|
| 546 |
+
<h2>🔧 Component Breakdown</h2>
|
| 547 |
+
|
| 548 |
+
<h3>1. Intent Parser (intent_parser.py)</h3>
|
| 549 |
+
<div class="code-block">
|
| 550 |
+
# Pattern Matching Examples:
|
| 551 |
+
- "forecast" → demand_forecast
|
| 552 |
+
- "inventory|stock" → check_inventory
|
| 553 |
+
- "supplier|vendor" → supplier_info
|
| 554 |
+
- "order|buy|purchase" → create_requisition
|
| 555 |
+
- Item Code Extraction: r'\b([a-z]{2}\d{4})\b'
|
| 556 |
+
- Quantity Extraction: r'\b(\d+)\s*(?:units|pcs)?'
|
| 557 |
+
- Horizon Parsing: "next month" → 30 days</div>
|
| 558 |
+
|
| 559 |
+
<h3>2. Data Loader (data_loader.py)</h3>
|
| 560 |
+
<div class="code-block">
|
| 561 |
+
# Data Sources:
|
| 562 |
+
- items.csv: Product catalog (item_code, description, price)
|
| 563 |
+
- demand_history.csv: Historical sales data
|
| 564 |
+
- inventory.csv: Stock levels by location
|
| 565 |
+
- suppliers.csv: Supplier information
|
| 566 |
+
- requisitions.csv: Order tracking
|
| 567 |
+
|
| 568 |
+
# Methods:
|
| 569 |
+
- load_data(): Initialize all DataFrames
|
| 570 |
+
- get_item_details(item_code): Retrieve product info
|
| 571 |
+
- get_inventory(item_code): Check stock levels
|
| 572 |
+
- create_requisition(item_code, qty): Generate order</div>
|
| 573 |
+
|
| 574 |
+
<h3>3. Forecasting Engine (forecasting.py)</h3>
|
| 575 |
+
<div class="code-block">
|
| 576 |
+
# ARIMA Process:
|
| 577 |
+
1. Load historical demand data
|
| 578 |
+
2. Aggregate by date
|
| 579 |
+
3. Fit ARIMA model (order=(1,1,1))
|
| 580 |
+
4. Generate forecast for N days
|
| 581 |
+
5. Return predictions with dates
|
| 582 |
+
|
| 583 |
+
# Output Format:
|
| 584 |
+
[
|
| 585 |
+
{"date": "2025-01-01", "qty": 150},
|
| 586 |
+
{"date": "2025-01-02", "qty": 148},
|
| 587 |
+
...
|
| 588 |
+
]</div>
|
| 589 |
+
|
| 590 |
+
<h3>4. LLM Engine (llm_engine.py)</h3>
|
| 591 |
+
<div class="code-block">
|
| 592 |
+
# Model Configuration:
|
| 593 |
+
- Model: TinyLlama-1.1B-Chat-v1.0 (GGUF)
|
| 594 |
+
- Context Length: 2048 tokens
|
| 595 |
+
- Temperature: 0.1 (deterministic)
|
| 596 |
+
- Max Tokens: 150
|
| 597 |
+
- Stop Tokens: ["</s>", "User:"]</div>
|
| 598 |
+
</div>
|
| 599 |
+
</div>
|
| 600 |
+
</div>
|
| 601 |
+
</body>
|
| 602 |
+
|
| 603 |
+
</html>
|
app/config.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 4 |
+
DATA_DIR = os.path.join(BASE_DIR, "data")
|
| 5 |
+
ITEMS_FILE = os.path.join(DATA_DIR, "items.csv")
|
| 6 |
+
DEMAND_FILE = os.path.join(DATA_DIR, "demand_history.csv")
|
| 7 |
+
|
| 8 |
+
DEFAULT_HORIZON = 30
|
| 9 |
+
DEFAULT_LOCATION = "NA"
|
app/data_loader.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app.config import ITEMS_FILE, DEMAND_FILE
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
|
| 5 |
+
INVENTORY_FILE = os.path.join(os.path.dirname(ITEMS_FILE), "inventory.csv")
|
| 6 |
+
SUPPLIERS_FILE = os.path.join(os.path.dirname(ITEMS_FILE), "suppliers.csv")
|
| 7 |
+
REQUISITIONS_FILE = os.path.join(os.path.dirname(ITEMS_FILE), "requisitions.csv")
|
| 8 |
+
|
| 9 |
+
class DataLoader:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.items_df = None
|
| 12 |
+
self.demand_df = None
|
| 13 |
+
self.inventory_df = None
|
| 14 |
+
self.suppliers_df = None
|
| 15 |
+
self.requisitions_df = None
|
| 16 |
+
self.load_data()
|
| 17 |
+
|
| 18 |
+
def load_data(self):
|
| 19 |
+
if os.path.exists(ITEMS_FILE):
|
| 20 |
+
self.items_df = pd.read_csv(ITEMS_FILE)
|
| 21 |
+
self.items_df['item_code'] = self.items_df['item_code'].str.upper()
|
| 22 |
+
else:
|
| 23 |
+
self.items_df = pd.DataFrame(columns=['item_code', 'description', 'uom', 'list_price'])
|
| 24 |
+
|
| 25 |
+
if os.path.exists(DEMAND_FILE):
|
| 26 |
+
self.demand_df = pd.read_csv(DEMAND_FILE, keep_default_na=False)
|
| 27 |
+
self.demand_df['date'] = pd.to_datetime(self.demand_df['date'])
|
| 28 |
+
self.demand_df['item_code'] = self.demand_df['item_code'].str.upper()
|
| 29 |
+
if 'region' in self.demand_df.columns:
|
| 30 |
+
self.demand_df['region'] = self.demand_df['region'].str.upper()
|
| 31 |
+
else:
|
| 32 |
+
self.demand_df = pd.DataFrame(columns=['item_code', 'date', 'quantity', 'region'])
|
| 33 |
+
|
| 34 |
+
if os.path.exists(INVENTORY_FILE):
|
| 35 |
+
self.inventory_df = pd.read_csv(INVENTORY_FILE)
|
| 36 |
+
self.inventory_df.rename(columns={'on_hand': 'qty_on_hand'}, inplace=True)
|
| 37 |
+
self.inventory_df['item_code'] = self.inventory_df['item_code'].str.upper()
|
| 38 |
+
if 'region' in self.inventory_df.columns:
|
| 39 |
+
self.inventory_df['region'] = self.inventory_df['region'].str.upper()
|
| 40 |
+
|
| 41 |
+
# Calculate Status
|
| 42 |
+
def get_status(row):
|
| 43 |
+
if row['available'] < 50: return 'Critical'
|
| 44 |
+
elif row['available'] < 100: return 'Low Stock'
|
| 45 |
+
else: return 'In Stock'
|
| 46 |
+
|
| 47 |
+
if 'available' in self.inventory_df.columns:
|
| 48 |
+
self.inventory_df['status'] = self.inventory_df.apply(get_status, axis=1)
|
| 49 |
+
else:
|
| 50 |
+
self.inventory_df['status'] = 'Unknown'
|
| 51 |
+
else:
|
| 52 |
+
self.inventory_df = pd.DataFrame(columns=['item_code', 'region', 'qty_on_hand', 'reorder_point', 'status'])
|
| 53 |
+
|
| 54 |
+
if os.path.exists(SUPPLIERS_FILE):
|
| 55 |
+
self.suppliers_df = pd.read_csv(SUPPLIERS_FILE)
|
| 56 |
+
# self.suppliers_df['item_code'] = self.suppliers_df['item_code'].str.upper() # Removed as item_code is not in suppliers.csv
|
| 57 |
+
else:
|
| 58 |
+
self.suppliers_df = pd.DataFrame(columns=['id', 'name', 'lead_time', 'email'])
|
| 59 |
+
|
| 60 |
+
if os.path.exists(REQUISITIONS_FILE):
|
| 61 |
+
self.requisitions_df = pd.read_csv(REQUISITIONS_FILE)
|
| 62 |
+
else:
|
| 63 |
+
self.requisitions_df = pd.DataFrame(columns=['req_id', 'item_code', 'qty', 'date', 'status'])
|
| 64 |
+
|
| 65 |
+
def get_item(self, item_code):
|
| 66 |
+
if self.items_df is None or item_code is None:
|
| 67 |
+
return None
|
| 68 |
+
item = self.items_df[self.items_df['item_code'] == item_code.upper()]
|
| 69 |
+
if not item.empty:
|
| 70 |
+
return item.iloc[0].to_dict()
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
+
def get_time_series(self, item_code, location=None):
|
| 74 |
+
if self.demand_df is None:
|
| 75 |
+
return pd.DataFrame()
|
| 76 |
+
|
| 77 |
+
mask = (self.demand_df['item_code'] == item_code.upper())
|
| 78 |
+
if location:
|
| 79 |
+
mask &= (self.demand_df['region'] == location.upper())
|
| 80 |
+
|
| 81 |
+
df_filtered = self.demand_df[mask].copy()
|
| 82 |
+
|
| 83 |
+
if df_filtered.empty:
|
| 84 |
+
return pd.DataFrame()
|
| 85 |
+
|
| 86 |
+
df_grouped = df_filtered.groupby('date')['quantity'].sum().reset_index()
|
| 87 |
+
df_grouped = df_grouped.sort_values('date')
|
| 88 |
+
df_grouped.set_index('date', inplace=True)
|
| 89 |
+
df_grouped = df_grouped.asfreq('D', fill_value=0)
|
| 90 |
+
return df_grouped
|
| 91 |
+
|
| 92 |
+
def get_inventory(self, item_code, location=None):
|
| 93 |
+
if self.inventory_df is None: return []
|
| 94 |
+
mask = (self.inventory_df['item_code'] == item_code.upper())
|
| 95 |
+
if location:
|
| 96 |
+
mask &= (self.inventory_df['region'] == location.upper())
|
| 97 |
+
return self.inventory_df[mask].to_dict('records')
|
| 98 |
+
|
| 99 |
+
def get_supplier(self, item_code):
|
| 100 |
+
if self.suppliers_df is None or self.items_df is None: return []
|
| 101 |
+
|
| 102 |
+
# Find supplier_id from items
|
| 103 |
+
item_row = self.items_df[self.items_df['item_code'] == item_code.upper()]
|
| 104 |
+
if item_row.empty: return []
|
| 105 |
+
|
| 106 |
+
supplier_id = item_row.iloc[0]['supplier_id']
|
| 107 |
+
|
| 108 |
+
# Find supplier details
|
| 109 |
+
mask = (self.suppliers_df['id'] == supplier_id)
|
| 110 |
+
suppliers = self.suppliers_df[mask].copy()
|
| 111 |
+
|
| 112 |
+
# Rename columns to match what main.py expects
|
| 113 |
+
suppliers.rename(columns={
|
| 114 |
+
'name': 'supplier_name',
|
| 115 |
+
'lead_time': 'lead_time_days',
|
| 116 |
+
'email': 'contact_email'
|
| 117 |
+
}, inplace=True)
|
| 118 |
+
|
| 119 |
+
return suppliers.to_dict('records')
|
| 120 |
+
|
| 121 |
+
def create_requisition(self, item_code, qty):
|
| 122 |
+
import datetime
|
| 123 |
+
import random
|
| 124 |
+
|
| 125 |
+
req_id = f"REQ{random.randint(1000, 9999)}"
|
| 126 |
+
new_row = {
|
| 127 |
+
"req_id": req_id,
|
| 128 |
+
"item_code": item_code.upper(),
|
| 129 |
+
"qty": qty,
|
| 130 |
+
"date": datetime.date.today().strftime("%Y-%m-%d"),
|
| 131 |
+
"status": "Pending"
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
# Append to DataFrame
|
| 135 |
+
self.requisitions_df = pd.concat([self.requisitions_df, pd.DataFrame([new_row])], ignore_index=True)
|
| 136 |
+
|
| 137 |
+
# Save to CSV
|
| 138 |
+
self.requisitions_df.to_csv(REQUISITIONS_FILE, index=False)
|
| 139 |
+
return req_id
|
| 140 |
+
|
| 141 |
+
def get_alerts(self):
|
| 142 |
+
if self.inventory_df is None: return []
|
| 143 |
+
# Filter for Critical or Low Stock
|
| 144 |
+
mask = self.inventory_df['status'].isin(['Critical', 'Low Stock'])
|
| 145 |
+
return self.inventory_df[mask].to_dict('records')
|
| 146 |
+
|
| 147 |
+
def get_items(self):
|
| 148 |
+
if self.items_df is None: return []
|
| 149 |
+
return self.items_df[['item_code', 'description']].to_dict('records')
|
| 150 |
+
|
| 151 |
+
# Global instance
|
| 152 |
+
loader = DataLoader()
|
app/forecasting.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
from statsmodels.tsa.arima.model import ARIMA
|
| 4 |
+
from app.data_loader import loader
|
| 5 |
+
import warnings
|
| 6 |
+
|
| 7 |
+
# Suppress statsmodels warnings for cleaner output
|
| 8 |
+
warnings.filterwarnings("ignore")
|
| 9 |
+
|
| 10 |
+
def forecast_demand(item_code, horizon_days, location=None):
|
| 11 |
+
"""
|
| 12 |
+
Generates a demand forecast using ARIMA.
|
| 13 |
+
Returns a list of dicts: [{"date": "YYYY-MM-DD", "qty": 123}, ...]
|
| 14 |
+
"""
|
| 15 |
+
ts_data = loader.get_time_series(item_code, location)
|
| 16 |
+
|
| 17 |
+
if ts_data.empty or len(ts_data) < 10:
|
| 18 |
+
return [] # Not enough data to forecast
|
| 19 |
+
|
| 20 |
+
try:
|
| 21 |
+
# Simple ARIMA model (1,1,1) is often a good baseline for non-stationary data
|
| 22 |
+
# For a robust system, we would use auto_arima or grid search, but this is a POC.
|
| 23 |
+
model = ARIMA(ts_data['quantity'], order=(5,1,0))
|
| 24 |
+
model_fit = model.fit()
|
| 25 |
+
|
| 26 |
+
forecast_result = model_fit.forecast(steps=horizon_days)
|
| 27 |
+
|
| 28 |
+
forecast_list = []
|
| 29 |
+
start_date = ts_data.index[-1] + pd.Timedelta(days=1)
|
| 30 |
+
|
| 31 |
+
for i, val in enumerate(forecast_result):
|
| 32 |
+
date = start_date + pd.Timedelta(days=i)
|
| 33 |
+
# Ensure non-negative forecast
|
| 34 |
+
qty = int(max(0, round(val)))
|
| 35 |
+
forecast_list.append({
|
| 36 |
+
"date": date.strftime('%Y-%m-%d'),
|
| 37 |
+
"qty": qty
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
return forecast_list
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"Forecasting error for {item_code}: {e}")
|
| 43 |
+
return []
|
app/intent_parser.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
from app.data_loader import loader
|
| 3 |
+
|
| 4 |
+
class IntentParser:
|
| 5 |
+
def __init__(self):
|
| 6 |
+
pass
|
| 7 |
+
|
| 8 |
+
def parse(self, text):
|
| 9 |
+
text = text.lower()
|
| 10 |
+
|
| 11 |
+
response = {
|
| 12 |
+
"intent": "general_chat", # Default to LLM
|
| 13 |
+
"item_code": None,
|
| 14 |
+
"location": None,
|
| 15 |
+
"horizon_days": None,
|
| 16 |
+
"extra": {}
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
# 1. Detect Intent (order matters - more specific checks first)
|
| 20 |
+
if any(x in text for x in ["list items", "show catalog", "what items", "available items", "list available", "show items"]):
|
| 21 |
+
response["intent"] = "list_items"
|
| 22 |
+
elif any(x in text for x in ["forecast", "demand", "predict", "future", "sales"]):
|
| 23 |
+
response["intent"] = "demand_forecast"
|
| 24 |
+
elif any(x in text for x in ["details", "info", "about", "price", "description"]):
|
| 25 |
+
response["intent"] = "item_details"
|
| 26 |
+
elif any(x in text for x in ["inventory", "stock", "how many", "available", "on hand"]):
|
| 27 |
+
response["intent"] = "check_inventory"
|
| 28 |
+
elif any(x in text for x in ["supplier", "vendor", "who supplies", "lead time"]):
|
| 29 |
+
response["intent"] = "supplier_info"
|
| 30 |
+
elif any(x in text for x in ["buy", "order", "purchase", "requisition"]):
|
| 31 |
+
response["intent"] = "create_requisition"
|
| 32 |
+
elif any(x in text for x in ["status", "alerts", "dashboard", "overview"]):
|
| 33 |
+
response["intent"] = "system_status"
|
| 34 |
+
elif any(x in text for x in ["pdf", "download", "report", "file"]):
|
| 35 |
+
response["intent"] = "generate_report"
|
| 36 |
+
|
| 37 |
+
# 2. Extract Item Code
|
| 38 |
+
# Look for patterns like FS1234, BMS0001, etc. (2-3 letters + 4 digits)
|
| 39 |
+
item_match = re.search(r'\b([a-z]{2,3}\d{4})\b', text)
|
| 40 |
+
if item_match:
|
| 41 |
+
response["item_code"] = item_match.group(1).upper()
|
| 42 |
+
else:
|
| 43 |
+
# Fallback: check against known items in DB
|
| 44 |
+
if loader.items_df is not None:
|
| 45 |
+
known_items = loader.items_df['item_code'].tolist()
|
| 46 |
+
for item in known_items:
|
| 47 |
+
if item.lower() in text:
|
| 48 |
+
response["item_code"] = item
|
| 49 |
+
break
|
| 50 |
+
|
| 51 |
+
# 2.5 Extract Quantity (for orders)
|
| 52 |
+
qty_match = re.search(r'\b(\d+)\s*(?:units|pcs|pieces)?\b', text)
|
| 53 |
+
if qty_match:
|
| 54 |
+
# Avoid confusing horizon days with quantity if possible
|
| 55 |
+
# If "days" is not next to it, assume qty
|
| 56 |
+
if "day" not in text[qty_match.end():qty_match.end()+5]:
|
| 57 |
+
response["extra"]["qty"] = int(qty_match.group(1))
|
| 58 |
+
|
| 59 |
+
# 3. Extract Location
|
| 60 |
+
if re.search(r'\b(na|north america|usa|us)\b', text):
|
| 61 |
+
response["location"] = "NA"
|
| 62 |
+
elif re.search(r'\b(eu|europe|uk|germany)\b', text):
|
| 63 |
+
response["location"] = "EU"
|
| 64 |
+
elif re.search(r'\b(apac|asia|china|india)\b', text):
|
| 65 |
+
response["location"] = "APAC"
|
| 66 |
+
|
| 67 |
+
# 4. Extract Horizon
|
| 68 |
+
response["horizon_days"] = self._parse_horizon(text)
|
| 69 |
+
|
| 70 |
+
return response
|
| 71 |
+
|
| 72 |
+
def _parse_horizon(self, text):
|
| 73 |
+
days = None
|
| 74 |
+
|
| 75 |
+
# Explicit days
|
| 76 |
+
days_match = re.search(r'(\d+)\s*days?', text)
|
| 77 |
+
if days_match:
|
| 78 |
+
return int(days_match.group(1))
|
| 79 |
+
|
| 80 |
+
# Weeks
|
| 81 |
+
weeks_match = re.search(r'(\d+)\s*weeks?', text)
|
| 82 |
+
if weeks_match:
|
| 83 |
+
return int(weeks_match.group(1)) * 7
|
| 84 |
+
|
| 85 |
+
# Months
|
| 86 |
+
months_match = re.search(r'(\d+)\s*months?', text)
|
| 87 |
+
if months_match:
|
| 88 |
+
return int(months_match.group(1)) * 30
|
| 89 |
+
|
| 90 |
+
# Keywords
|
| 91 |
+
if "next week" in text:
|
| 92 |
+
return 7
|
| 93 |
+
if "next month" in text:
|
| 94 |
+
return 30
|
| 95 |
+
if "next year" in text:
|
| 96 |
+
return 365
|
| 97 |
+
if "tomorrow" in text:
|
| 98 |
+
return 1
|
| 99 |
+
|
| 100 |
+
return days
|
| 101 |
+
|
| 102 |
+
parser = IntentParser()
|
app/llm_engine.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
from ctransformers import AutoModelForCausalLM
|
| 4 |
+
|
| 5 |
+
MODEL_REPO = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF"
|
| 6 |
+
MODEL_FILE = "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf"
|
| 7 |
+
MODEL_TYPE = "llama"
|
| 8 |
+
|
| 9 |
+
PROFANITY_LIST = ['damn', 'hell', 'crap', 'stupid', 'idiot', 'dumb', 'suck']
|
| 10 |
+
OFF_TOPIC_KEYWORDS = ['weather', 'sports', 'politics', 'religion', 'movie', 'game', 'recipe', 'joke', 'story', 'music', 'celebrity']
|
| 11 |
+
|
| 12 |
+
class LLMEngine:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
self.model = None
|
| 15 |
+
self.context = ""
|
| 16 |
+
self.load_context()
|
| 17 |
+
|
| 18 |
+
def load_context(self):
|
| 19 |
+
try:
|
| 20 |
+
context_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "company_context.txt")
|
| 21 |
+
if os.path.exists(context_path):
|
| 22 |
+
with open(context_path, "r", encoding="utf-8") as f:
|
| 23 |
+
self.context = f.read()
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"Error loading context: {e}")
|
| 26 |
+
|
| 27 |
+
def load_model(self):
|
| 28 |
+
if self.model is None:
|
| 29 |
+
print("Loading LLM...")
|
| 30 |
+
try:
|
| 31 |
+
self.model = AutoModelForCausalLM.from_pretrained(MODEL_REPO, model_file=MODEL_FILE, model_type=MODEL_TYPE, context_length=2048, gpu_layers=0)
|
| 32 |
+
print("LLM Loaded.")
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"Failed to load LLM: {e}")
|
| 35 |
+
|
| 36 |
+
def check_profanity(self, text):
|
| 37 |
+
for word in PROFANITY_LIST:
|
| 38 |
+
if re.search(r'\b' + word + r'\b', text.lower()):
|
| 39 |
+
return True
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
def check_off_topic(self, text):
|
| 43 |
+
for keyword in OFF_TOPIC_KEYWORDS:
|
| 44 |
+
if keyword in text.lower():
|
| 45 |
+
return True
|
| 46 |
+
return False
|
| 47 |
+
|
| 48 |
+
def generate_response(self, user_query):
|
| 49 |
+
if self.check_profanity(user_query):
|
| 50 |
+
return "I'm here to assist with business operations. Please keep our conversation professional."
|
| 51 |
+
if self.check_off_topic(user_query):
|
| 52 |
+
return "I specialize in demand forecasting, inventory management, and order processing. How can I help you with these?"
|
| 53 |
+
|
| 54 |
+
greetings = ["hi", "hello", "hey", "greetings", "good morning", "good afternoon", "good evening"]
|
| 55 |
+
if user_query.lower().strip().strip("!.,?") in greetings:
|
| 56 |
+
return "Hello! I'm BMS AI Assistant. I can help you with:\n• Demand Forecasting\n• Inventory Checks\n• Supplier Information\n• Order Requisitions\n• PDF Reports\n\nWhat would you like to know?"
|
| 57 |
+
|
| 58 |
+
if any(w in user_query.lower() for w in ['capabilities', 'what can you do', 'help me', 'functions']):
|
| 59 |
+
return "I can assist you with:\n1. Demand Forecasting\n2. Inventory Management\n3. Supplier Information\n4. Order Processing\n5. PDF Reports\n\nTry asking: 'Forecast for BMS0015'"
|
| 60 |
+
|
| 61 |
+
if any(w in user_query.lower() for w in ['who made you', 'who developed', 'who created', 'who built']):
|
| 62 |
+
return "I'm BMS AI Assistant, developed to help you manage inventory and forecast demand efficiently."
|
| 63 |
+
|
| 64 |
+
if any(p in user_query.lower() for p in ['how are you', 'how do you do', 'how is it going']):
|
| 65 |
+
return "I'm functioning well and ready to assist you! How can I help with your inventory or forecasting needs today?"
|
| 66 |
+
|
| 67 |
+
if self.model is None:
|
| 68 |
+
self.load_model()
|
| 69 |
+
if self.model is None:
|
| 70 |
+
return "I'm sorry, I couldn't load my language model. Please try asking about specific items."
|
| 71 |
+
|
| 72 |
+
system_prompt = (
|
| 73 |
+
"You are BMS AI Assistant, a helpful and professional AI for Business Management Systems. "
|
| 74 |
+
"Your goal is to assist users with demand forecasting, inventory management, and supplier information. "
|
| 75 |
+
"Answer questions based ONLY on the provided context. If the answer is not in the context, politely state that you don't have that information. "
|
| 76 |
+
"Be concise but friendly."
|
| 77 |
+
)
|
| 78 |
+
full_prompt = f"<|system|>\n{system_prompt}\n\nContext:\n{self.context}</s>\n<|user|>\n{user_query}</s>\n<|assistant|>"
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
response = self.model(
|
| 82 |
+
full_prompt,
|
| 83 |
+
max_new_tokens=150,
|
| 84 |
+
temperature=0.1,
|
| 85 |
+
repetition_penalty=1.1,
|
| 86 |
+
top_k=40,
|
| 87 |
+
top_p=0.9,
|
| 88 |
+
stop=["</s>", "<|user|>", "<|system|>"]
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
if "<|system|>" in response:
|
| 92 |
+
response = response.split("<|system|>")[0]
|
| 93 |
+
|
| 94 |
+
return response.strip()
|
| 95 |
+
except Exception as e:
|
| 96 |
+
return f"Error generating response: {e}"
|
| 97 |
+
|
| 98 |
+
llm = LLMEngine()
|
app/llm_engine_backup.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
from ctransformers import AutoModelForCausalLM
|
| 4 |
+
from huggingface_hub import hf_hub_download
|
| 5 |
+
|
| 6 |
+
# Configuration
|
| 7 |
+
MODEL_REPO = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF"
|
| 8 |
+
MODEL_FILE = "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf"
|
| 9 |
+
MODEL_TYPE = "llama"
|
| 10 |
+
|
| 11 |
+
# Content Moderation Lists
|
| 12 |
+
PROFANITY_LIST = [
|
| 13 |
+
'damn', 'hell', 'crap', 'stupid', 'idiot', 'dumb', 'suck',
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
OFF_TOPIC_KEYWORDS = [
|
| 17 |
+
'weather', 'sports', 'politics', 'religion', 'movie', 'game',
|
| 18 |
+
'recipe', 'joke', 'story', 'music', 'celebrity'
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
class LLMEngine:
|
| 22 |
+
def __init__(self):
|
| 23 |
+
self.model = None
|
| 24 |
+
self.context = ""
|
| 25 |
+
self.load_context()
|
| 26 |
+
|
| 27 |
+
def load_context(self):
|
| 28 |
+
try:
|
| 29 |
+
context_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "company_context.txt")
|
| 30 |
+
if os.path.exists(context_path):
|
| 31 |
+
with open(context_path, "r", encoding="utf-8") as f:
|
| 32 |
+
self.context = f.read()
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"Error loading context: {e}")
|
| 35 |
+
|
| 36 |
+
def load_model(self):
|
| 37 |
+
if self.model is None:
|
| 38 |
+
print("Loading LLM... this may take a moment.")
|
| 39 |
+
try:
|
| 40 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 41 |
+
MODEL_REPO,
|
| 42 |
+
model_file=MODEL_FILE,
|
| 43 |
+
model_type=MODEL_TYPE,
|
| 44 |
+
context_length=2048,
|
| 45 |
+
gpu_layers=0
|
| 46 |
+
)
|
| 47 |
+
print("LLM Loaded successfully.")
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Failed to load LLM: {e}")
|
| 50 |
+
|
| 51 |
+
def check_profanity(self, text):
|
| 52 |
+
"""Check for inappropriate language"""
|
| 53 |
+
text_lower = text.lower()
|
| 54 |
+
for word in PROFANITY_LIST:
|
| 55 |
+
if re.search(r'\b' + word + r'\b', text_lower):
|
| 56 |
+
return True
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
def check_off_topic(self, text):
|
| 60 |
+
"""Check if query is off-topic"""
|
| 61 |
+
text_lower = text.lower()
|
| 62 |
+
for keyword in OFF_TOPIC_KEYWORDS:
|
| 63 |
+
if keyword in text_lower:
|
| 64 |
+
return True
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
def generate_response(self, user_query):
|
| 68 |
+
"""Generate response with content moderation"""
|
| 69 |
+
|
| 70 |
+
# Content Moderation
|
| 71 |
+
if self.check_profanity(user_query):
|
| 72 |
+
return ("I'm here to assist with business operations. "
|
| 73 |
+
"Please keep our conversation professional and focused on "
|
| 74 |
+
"demand forecasting, inventory management, or order processing.")
|
| 75 |
+
|
| 76 |
+
# Off-topic detection
|
| 77 |
+
if self.check_off_topic(user_query):
|
| 78 |
+
return ("I specialize in demand forecasting, inventory management, and order processing. "
|
| 79 |
+
"How can I help you with these business functions?")
|
| 80 |
+
|
| 81 |
+
# Greetings - Enhanced
|
| 82 |
+
greetings = ["hi", "hello", "hey", "greetings", "good morning", "good afternoon", "good evening"]
|
| 83 |
+
query_clean = user_query.lower().strip().strip("!.,?")
|
| 84 |
+
if query_clean in greetings:
|
| 85 |
+
return ("Hello! I'm BMS AI Assistant. I can help you with:\n"
|
| 86 |
+
"• Demand Forecasting\n"
|
| 87 |
+
"• Inventory Checks\n"
|
| 88 |
+
"• Supplier Information\n"
|
| 89 |
+
"• Order Requisitions\n"
|
| 90 |
+
"• PDF Reports\n\n"
|
| 91 |
+
"What would you like to know?")
|
| 92 |
+
|
| 93 |
+
# Capabilities query
|
| 94 |
+
if any(word in user_query.lower() for word in ['capabilities', 'what can you do', 'help me', 'functions']):
|
| 95 |
+
return ("I can assist you with:\n\n"
|
| 96 |
+
"1. Demand Forecasting - Predict future demand for items\n"
|
| 97 |
+
"2. Inventory Management - Check stock levels across warehouses\n"
|
| 98 |
+
"3. Supplier Information - Get supplier details and lead times\n"
|
| 99 |
+
"4. Order Processing - Create purchase requisitions\n"
|
| 100 |
+
"5. PDF Reports - Download detailed reports\n\n"
|
| 101 |
+
"Try asking: 'Forecast for BMS0015' or 'Check inventory for BMS0042'")
|
| 102 |
+
|
| 103 |
+
# Who developed you
|
| 104 |
+
if any(word in user_query.lower() for word in ['who made you', 'who developed', 'who created', 'who built']):
|
| 105 |
+
return "I'm BMS AI Assistant, developed to help you manage inventory and forecast demand efficiently."
|
| 106 |
+
|
| 107 |
+
# How are you
|
| 108 |
+
if any(phrase in user_query.lower() for phrase in ['how are you', 'how do you do', 'how is it going']):
|
| 109 |
+
return "I'm functioning well and ready to assist you! How can I help with your inventory or forecasting needs today?"
|
| 110 |
+
|
| 111 |
+
# Load model if needed
|
| 112 |
+
if self.model is None:
|
| 113 |
+
self.load_model()
|
| 114 |
+
|
| 115 |
+
if self.model is None:
|
| 116 |
+
return "I'm sorry, I couldn't load my language model. Please try asking about specific items or inventory."
|
| 117 |
+
|
| 118 |
+
# Construct Prompt
|
| 119 |
+
system_prompt = (
|
| 120 |
+
"You are BMS AI Assistant for business operations. "
|
| 121 |
+
"Answer questions using ONLY the provided context. "
|
| 122 |
+
"If the answer is not in the context, say you don't know. "
|
| 123 |
+
"Do not make up facts. "
|
| 124 |
+
"Keep answers concise (under 3 sentences). "
|
| 125 |
+
"Be professional and helpful."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
full_prompt = f"<|system|>\n{system_prompt}\n\nContext:\n{self.context}</s>\n
|
app/llm_engine_old.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
from ctransformers import AutoModelForCausalLM
|
| 4 |
+
from huggingface_hub import hf_hub_download
|
| 5 |
+
|
| 6 |
+
# Configuration
|
| 7 |
+
MODEL_REPO = "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF"
|
| 8 |
+
MODEL_FILE = "tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf"
|
| 9 |
+
MODEL_TYPE = "llama"
|
| 10 |
+
|
| 11 |
+
# Content Moderation Lists
|
| 12 |
+
PROFANITY_LIST = [
|
| 13 |
+
'damn', 'hell', 'crap', 'stupid', 'idiot', 'dumb', 'suck',
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
OFF_TOPIC_KEYWORDS = [
|
| 17 |
+
'weather', 'sports', 'politics', 'religion', 'movie', 'game',
|
| 18 |
+
'recipe', 'joke', 'story', 'music', 'celebrity'
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
class LLMEngine:
|
| 22 |
+
def __init__(self):
|
| 23 |
+
self.model = None
|
| 24 |
+
self.context = ""
|
| 25 |
+
self.load_context()
|
| 26 |
+
|
| 27 |
+
def load_context(self):
|
| 28 |
+
try:
|
| 29 |
+
context_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "company_context.txt")
|
| 30 |
+
if os.path.exists(context_path):
|
| 31 |
+
with open(context_path, "r", encoding="utf-8") as f:
|
| 32 |
+
self.context = f.read()
|
| 33 |
+
except Exception as e:
|
| 34 |
+
print(f"Error loading context: {e}")
|
| 35 |
+
|
| 36 |
+
def load_model(self):
|
| 37 |
+
if self.model is None:
|
| 38 |
+
print("Loading LLM... this may take a moment.")
|
| 39 |
+
try:
|
| 40 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 41 |
+
MODEL_REPO,
|
| 42 |
+
model_file=MODEL_FILE,
|
| 43 |
+
model_type=MODEL_TYPE,
|
| 44 |
+
context_length=2048,
|
| 45 |
+
gpu_layers=0
|
| 46 |
+
)
|
| 47 |
+
print("LLM Loaded successfully.")
|
| 48 |
+
except Exception as e:
|
| 49 |
+
print(f"Failed to load LLM: {e}")
|
| 50 |
+
|
| 51 |
+
def check_profanity(self, text):
|
| 52 |
+
"""Check for inappropriate language"""
|
| 53 |
+
text_lower = text.lower()
|
| 54 |
+
for word in PROFANITY_LIST:
|
| 55 |
+
if re.search(r'\b' + word + r'\b', text_lower):
|
| 56 |
+
return True
|
| 57 |
+
return False
|
| 58 |
+
|
| 59 |
+
def check_off_topic(self, text):
|
| 60 |
+
"""Check if query is off-topic"""
|
| 61 |
+
text_lower = text.lower()
|
| 62 |
+
for keyword in OFF_TOPIC_KEYWORDS:
|
| 63 |
+
if keyword in text_lower:
|
| 64 |
+
return True
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
def generate_response(self, user_query):
|
| 68 |
+
"""Generate response with content moderation"""
|
| 69 |
+
|
| 70 |
+
# Content Moderation
|
| 71 |
+
if self.check_profanity(user_query):
|
| 72 |
+
return ("I'm here to assist with business operations. "
|
| 73 |
+
"Please keep our conversation professional and focused on "
|
| 74 |
+
"demand forecasting, inventory management, or order processing.")
|
| 75 |
+
|
| 76 |
+
# Off-topic detection
|
| 77 |
+
if self.check_off_topic(user_query):
|
| 78 |
+
return ("I specialize in demand forecasting, inventory management, and order processing. "
|
| 79 |
+
"How can I help you with these business functions?")
|
| 80 |
+
|
| 81 |
+
# Greetings - Enhanced
|
| 82 |
+
greetings = ["hi", "hello", "hey", "greetings", "good morning", "good afternoon", "good evening"]
|
| 83 |
+
query_clean = user_query.lower().strip().strip("!.,?")
|
| 84 |
+
if query_clean in greetings:
|
| 85 |
+
return ("Hello! I'm BMS AI Assistant. I can help you with:\n"
|
| 86 |
+
"• Demand Forecasting\n"
|
| 87 |
+
"• Inventory Checks\n"
|
| 88 |
+
"• Supplier Information\n"
|
| 89 |
+
"• Order Requisitions\n"
|
| 90 |
+
"• PDF Reports\n\n"
|
| 91 |
+
"What would you like to know?")
|
| 92 |
+
|
| 93 |
+
# Capabilities query
|
| 94 |
+
if any(word in user_query.lower() for word in ['capabilities', 'what can you do', 'help me', 'functions']):
|
| 95 |
+
return ("I can assist you with:\n\n"
|
| 96 |
+
"1. Demand Forecasting - Predict future demand for items\n"
|
| 97 |
+
"2. Inventory Management - Check stock levels across warehouses\n"
|
| 98 |
+
"3. Supplier Information - Get supplier details and lead times\n"
|
| 99 |
+
"4. Order Processing - Create purchase requisitions\n"
|
| 100 |
+
"5. PDF Reports - Download detailed reports\n\n"
|
| 101 |
+
"Try asking: 'Forecast for BMS0015' or 'Check inventory for BMS0042'")
|
| 102 |
+
|
| 103 |
+
# Who developed you
|
| 104 |
+
if any(word in user_query.lower() for word in ['who made you', 'who developed', 'who created', 'who built']):
|
| 105 |
+
return "I'm BMS AI Assistant, developed to help you manage inventory and forecast demand efficiently."
|
| 106 |
+
|
| 107 |
+
# How are you
|
| 108 |
+
if any(phrase in user_query.lower() for phrase in ['how are you', 'how do you do', 'how is it going']):
|
| 109 |
+
return "I'm functioning well and ready to assist you! How can I help with your inventory or forecasting needs today?"
|
| 110 |
+
|
| 111 |
+
# Load model if needed
|
| 112 |
+
if self.model is None:
|
| 113 |
+
self.load_model()
|
| 114 |
+
|
| 115 |
+
if self.model is None:
|
| 116 |
+
return "I'm sorry, I couldn't load my language model. Please try asking about specific items or inventory."
|
| 117 |
+
|
| 118 |
+
# Construct Prompt
|
| 119 |
+
system_prompt = (
|
| 120 |
+
"You are BMS AI Assistant for business operations. "
|
| 121 |
+
"Answer questions using ONLY the provided context. "
|
| 122 |
+
"If the answer is not in the context, say you don't know. "
|
| 123 |
+
"Do not make up facts. "
|
| 124 |
+
"Keep answers concise (under 3 sentences). "
|
| 125 |
+
"Be professional and helpful."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
full_prompt = f"<|system|>\n{system_prompt}\n\nContext:\n{self.context}</s>\n
|
app/main.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.staticfiles import StaticFiles
|
| 3 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 4 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
import os
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
# Configure logging
|
| 10 |
+
logging.basicConfig(level=logging.INFO)
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
from app.intent_parser import parser
|
| 14 |
+
from app.forecasting import forecast_demand
|
| 15 |
+
from app.data_loader import loader
|
| 16 |
+
from app.config import DEFAULT_HORIZON
|
| 17 |
+
from app.llm_engine import llm
|
| 18 |
+
from app.pdf_generator import generate_forecast_pdf, generate_general_pdf
|
| 19 |
+
|
| 20 |
+
app = FastAPI(title="BMS AI Assistant")
|
| 21 |
+
|
| 22 |
+
# CORS
|
| 23 |
+
app.add_middleware(
|
| 24 |
+
CORSMiddleware,
|
| 25 |
+
allow_origins=["*"],
|
| 26 |
+
allow_credentials=True,
|
| 27 |
+
allow_methods=["*"],
|
| 28 |
+
allow_headers=["*"],
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# Mount Static Files
|
| 32 |
+
# We mount this to serve assets like CSS/JS if they are referenced in the HTML
|
| 33 |
+
static_dir = "/app/static"
|
| 34 |
+
if not os.path.exists(static_dir):
|
| 35 |
+
# Fallback for local testing
|
| 36 |
+
static_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static")
|
| 37 |
+
|
| 38 |
+
if os.path.exists(static_dir):
|
| 39 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
| 40 |
+
logger.info(f"Mounted static files from: {static_dir}")
|
| 41 |
+
else:
|
| 42 |
+
logger.warning(f"Static directory not found at {static_dir}")
|
| 43 |
+
|
| 44 |
+
class ChatRequest(BaseModel):
|
| 45 |
+
message: str
|
| 46 |
+
|
| 47 |
+
# Root Endpoint
|
| 48 |
+
@app.get("/", response_class=HTMLResponse)
|
| 49 |
+
async def read_root():
|
| 50 |
+
# Logic: Read the file content and return it.
|
| 51 |
+
possible_paths = [
|
| 52 |
+
"/app/static/index.html", # Docker absolute path
|
| 53 |
+
os.path.join(os.path.dirname(os.path.dirname(__file__)), "static", "index.html"), # Local relative
|
| 54 |
+
"static/index.html" # Simple relative
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
for path in possible_paths:
|
| 58 |
+
if os.path.exists(path):
|
| 59 |
+
logger.info(f"Serving HTML from: {path}")
|
| 60 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 61 |
+
return HTMLResponse(content=f.read())
|
| 62 |
+
|
| 63 |
+
logger.error("Could not find index.html in any known location.")
|
| 64 |
+
return HTMLResponse(content="<h1>Error: index.html not found</h1>", status_code=500)
|
| 65 |
+
|
| 66 |
+
@app.get("/api/health")
|
| 67 |
+
async def health_check():
|
| 68 |
+
return {"status": "ok", "message": "BMS AI Assistant is running"}
|
| 69 |
+
|
| 70 |
+
# Chat Endpoint
|
| 71 |
+
chat_context = {
|
| 72 |
+
"last_forecast": None,
|
| 73 |
+
"last_answer": None
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
@app.post("/api/chat")
|
| 77 |
+
async def chat_endpoint(request: ChatRequest):
|
| 78 |
+
user_text = request.message
|
| 79 |
+
try:
|
| 80 |
+
parsed = parser.parse(user_text)
|
| 81 |
+
intent = parsed["intent"]
|
| 82 |
+
item_code = parsed["item_code"]
|
| 83 |
+
location = parsed["location"]
|
| 84 |
+
horizon = parsed["horizon_days"]
|
| 85 |
+
|
| 86 |
+
response_data = {
|
| 87 |
+
"intent": intent,
|
| 88 |
+
"answer": "",
|
| 89 |
+
"forecast": []
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
if intent == "demand_forecast":
|
| 93 |
+
if not item_code:
|
| 94 |
+
response_data["answer"] = "I can help with forecasting, but I need to know which item you are interested in."
|
| 95 |
+
else:
|
| 96 |
+
if not horizon: horizon = DEFAULT_HORIZON
|
| 97 |
+
forecast = forecast_demand(item_code, horizon, location)
|
| 98 |
+
response_data["forecast"] = forecast
|
| 99 |
+
|
| 100 |
+
chat_context["last_forecast"] = {"item_code": item_code, "forecast": forecast, "location": location}
|
| 101 |
+
|
| 102 |
+
total = sum(d['qty'] for d in forecast) if forecast else 0
|
| 103 |
+
response_data["answer"] = f"Forecast for {item_code} over next {horizon} days is {total} units."
|
| 104 |
+
|
| 105 |
+
elif intent == "list_items":
|
| 106 |
+
items = loader.get_items()
|
| 107 |
+
msg = "**Available Items:**\n"
|
| 108 |
+
for item in items[:10]:
|
| 109 |
+
msg += f"- {item['item_code']}: {item['description']}\n"
|
| 110 |
+
response_data["answer"] = msg
|
| 111 |
+
|
| 112 |
+
elif intent == "check_inventory":
|
| 113 |
+
if not item_code:
|
| 114 |
+
response_data["answer"] = "Please specify an item code to check inventory for."
|
| 115 |
+
else:
|
| 116 |
+
inv_data = loader.get_inventory(item_code, location)
|
| 117 |
+
if not inv_data:
|
| 118 |
+
response_data["answer"] = f"No inventory data found for {item_code}."
|
| 119 |
+
else:
|
| 120 |
+
msg = f"**Inventory for {item_code}:**\n"
|
| 121 |
+
for record in inv_data:
|
| 122 |
+
loc = record.get('region', 'Unknown')
|
| 123 |
+
qty = record.get('qty_on_hand', 0)
|
| 124 |
+
status = record.get('status', 'Unknown')
|
| 125 |
+
msg += f"- Location: {loc}, On Hand: {qty}, Status: {status}\n"
|
| 126 |
+
response_data["answer"] = msg
|
| 127 |
+
# Update context for potential report generation
|
| 128 |
+
chat_context["last_forecast"] = {"item_code": item_code, "forecast": [], "location": location}
|
| 129 |
+
|
| 130 |
+
elif intent == "generate_report":
|
| 131 |
+
# Check if we have a valid context
|
| 132 |
+
if chat_context.get("last_forecast") and chat_context["last_forecast"].get("item_code"):
|
| 133 |
+
lf = chat_context["last_forecast"]
|
| 134 |
+
# Use the item code from the last interaction
|
| 135 |
+
target_item = lf["item_code"]
|
| 136 |
+
|
| 137 |
+
# If the user explicitly mentioned a different item in this request, use that instead
|
| 138 |
+
if item_code and item_code != target_item:
|
| 139 |
+
target_item = item_code
|
| 140 |
+
# If we don't have forecast data for this new item, we might need to generate it or just produce a generic report
|
| 141 |
+
# For now, let's just warn or proceed with what we have
|
| 142 |
+
|
| 143 |
+
filename = generate_forecast_pdf(target_item, lf.get("forecast", []), lf.get("location"))
|
| 144 |
+
response_data["answer"] = f"Report generated for {target_item}: <a href='/static/reports/{filename}' target='_blank'>Download PDF</a>"
|
| 145 |
+
else:
|
| 146 |
+
response_data["answer"] = "I don't have enough context to generate a report. Please ask for a forecast or inventory check first."
|
| 147 |
+
# Fallback to LLM
|
| 148 |
+
response_data["answer"] = llm.generate_response(user_text)
|
| 149 |
+
chat_context["last_answer"] = response_data["answer"]
|
| 150 |
+
|
| 151 |
+
return JSONResponse(content=response_data)
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
logger.error(f"Error in chat endpoint: {e}")
|
| 155 |
+
return JSONResponse(content={"answer": f"Error processing request: {str(e)}"}, status_code=500)
|
app/pdf_generator.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fpdf import FPDF
|
| 2 |
+
import os
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
|
| 5 |
+
REPORT_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static", "reports")
|
| 6 |
+
if not os.path.exists(REPORT_DIR):
|
| 7 |
+
os.makedirs(REPORT_DIR)
|
| 8 |
+
|
| 9 |
+
class PDFReport(FPDF):
|
| 10 |
+
def header(self):
|
| 11 |
+
self.set_font('Arial', 'B', 15)
|
| 12 |
+
self.cell(0, 10, 'Cummins Demand Forecasting Report', 0, 1, 'C')
|
| 13 |
+
self.ln(10)
|
| 14 |
+
|
| 15 |
+
def footer(self):
|
| 16 |
+
self.set_y(-15)
|
| 17 |
+
self.set_font('Arial', 'I', 8)
|
| 18 |
+
self.cell(0, 10, 'Page ' + str(self.page_no()), 0, 0, 'C')
|
| 19 |
+
|
| 20 |
+
def generate_forecast_pdf(item_code, forecast_data, location=None):
|
| 21 |
+
pdf = PDFReport()
|
| 22 |
+
pdf.add_page()
|
| 23 |
+
|
| 24 |
+
# Title Info
|
| 25 |
+
pdf.set_font('Arial', 'B', 12)
|
| 26 |
+
pdf.cell(0, 10, f'Item: {item_code}', 0, 1)
|
| 27 |
+
pdf.cell(0, 10, f'Location: {location if location else "All Locations"}', 0, 1)
|
| 28 |
+
pdf.cell(0, 10, f'Date Generated: {datetime.now().strftime("%Y-%m-%d %H:%M")}', 0, 1)
|
| 29 |
+
pdf.ln(10)
|
| 30 |
+
|
| 31 |
+
# Table Header
|
| 32 |
+
pdf.set_font('Arial', 'B', 10)
|
| 33 |
+
pdf.cell(60, 10, 'Date', 1)
|
| 34 |
+
pdf.cell(40, 10, 'Quantity', 1)
|
| 35 |
+
pdf.ln()
|
| 36 |
+
|
| 37 |
+
# Table Data
|
| 38 |
+
pdf.set_font('Arial', '', 10)
|
| 39 |
+
total_qty = 0
|
| 40 |
+
for row in forecast_data:
|
| 41 |
+
pdf.cell(60, 10, str(row['date']), 1)
|
| 42 |
+
pdf.cell(40, 10, str(row['qty']), 1)
|
| 43 |
+
pdf.ln()
|
| 44 |
+
total_qty += row['qty']
|
| 45 |
+
|
| 46 |
+
pdf.ln(5)
|
| 47 |
+
pdf.set_font('Arial', 'B', 10)
|
| 48 |
+
pdf.cell(60, 10, 'Total Forecasted Demand:', 0)
|
| 49 |
+
pdf.cell(40, 10, str(total_qty), 0)
|
| 50 |
+
|
| 51 |
+
filename = f"forecast_{item_code}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
| 52 |
+
filepath = os.path.join(REPORT_DIR, filename)
|
| 53 |
+
pdf.output(filepath)
|
| 54 |
+
|
| 55 |
+
return filename
|
| 56 |
+
|
| 57 |
+
def generate_general_pdf(title, content):
|
| 58 |
+
pdf = PDFReport()
|
| 59 |
+
pdf.add_page()
|
| 60 |
+
|
| 61 |
+
pdf.set_font('Arial', 'B', 12)
|
| 62 |
+
pdf.cell(0, 10, title, 0, 1)
|
| 63 |
+
pdf.ln(5)
|
| 64 |
+
|
| 65 |
+
pdf.set_font('Arial', '', 10)
|
| 66 |
+
pdf.multi_cell(0, 10, content)
|
| 67 |
+
|
| 68 |
+
filename = f"report_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
| 69 |
+
filepath = os.path.join(REPORT_DIR, filename)
|
| 70 |
+
pdf.output(filepath)
|
| 71 |
+
|
| 72 |
+
return filename
|
data/company_context.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Cummins Inc. is a global power leader that designs, manufactures, distributes, and services a broad portfolio of power solutions.
|
| 2 |
+
The company’s products range from diesel, natural gas, electric and hybrid powertrains and powertrain-related components including filtration, aftertreatment, turbochargers, fuel systems, controls systems, air handling systems, automated transmissions, electric power generation systems, batteries, electrified power systems, hydrogen generation and fuel cell products.
|
| 3 |
+
|
| 4 |
+
Key Business Segments:
|
| 5 |
+
1. Engine Segment: Manufactures and markets a complete line of diesel and natural gas-powered engines for on-highway and off-highway use.
|
| 6 |
+
2. Distribution Segment: Provides parts, service, and support to customers globally.
|
| 7 |
+
3. Components Segment: Supplies filtration products (Fleetguard), turbochargers (Holset), and aftertreatment systems.
|
| 8 |
+
4. Power Systems Segment: Provides power generation systems, standby generators, and prime power solutions.
|
| 9 |
+
5. Accelera (New Power): Focuses on zero-emissions technologies like hydrogen fuel cells and battery electric systems.
|
| 10 |
+
|
| 11 |
+
Our goal is to power a more prosperous world through three global priorities:
|
| 12 |
+
- Addressing climate change and air emissions.
|
| 13 |
+
- Using natural resources sustainably.
|
| 14 |
+
- Improving communities.
|
| 15 |
+
|
| 16 |
+
This chatbot is the BMS AI Assistant, designed to assist with Demand Forecasting for the Components Segment, specifically for filtration products like BMS0015 and BMS0042.
|
data/demand_history.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/inventory.csv
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
item_code,warehouse_code,warehouse_name,region,on_hand,allocated,available,last_updated
|
| 2 |
+
BMS0001,EU-01,Europe - Daventry,EU,161,4,120,2025-11-29
|
| 3 |
+
BMS0001,APAC-02,Asia Pacific - Shanghai,APAC,301,93,237,2025-11-29
|
| 4 |
+
BMS0001,NA-02,North America - Memphis,NA,742,57,720,2025-11-29
|
| 5 |
+
BMS0002,NA-01,North America - Columbus,NA,244,31,161,2025-11-29
|
| 6 |
+
BMS0002,NA-02,North America - Memphis,NA,374,86,287,2025-11-29
|
| 7 |
+
BMS0002,EU-02,Europe - Rotterdam,EU,241,58,238,2025-11-29
|
| 8 |
+
BMS0002,APAC-01,Asia Pacific - Singapore,APAC,647,54,552,2025-11-29
|
| 9 |
+
BMS0003,NA-02,North America - Memphis,NA,52,0,12,2025-11-29
|
| 10 |
+
BMS0003,APAC-02,Asia Pacific - Shanghai,APAC,76,56,66,2025-11-29
|
| 11 |
+
BMS0004,APAC-02,Asia Pacific - Shanghai,APAC,440,24,424,2025-11-29
|
| 12 |
+
BMS0004,EU-01,Europe - Daventry,EU,609,47,526,2025-11-29
|
| 13 |
+
BMS0004,APAC-01,Asia Pacific - Singapore,APAC,122,50,70,2025-11-29
|
| 14 |
+
BMS0004,NA-02,North America - Memphis,NA,129,9,85,2025-11-29
|
| 15 |
+
BMS0005,EU-02,Europe - Rotterdam,EU,756,48,730,2025-11-29
|
| 16 |
+
BMS0005,APAC-02,Asia Pacific - Shanghai,APAC,508,36,446,2025-11-29
|
| 17 |
+
BMS0005,EU-01,Europe - Daventry,EU,52,15,11,2025-11-29
|
| 18 |
+
BMS0005,APAC-01,Asia Pacific - Singapore,APAC,16,6,3,2025-11-29
|
| 19 |
+
BMS0006,NA-02,North America - Memphis,NA,683,90,677,2025-11-29
|
| 20 |
+
BMS0006,APAC-01,Asia Pacific - Singapore,APAC,130,49,50,2025-11-29
|
| 21 |
+
BMS0006,EU-02,Europe - Rotterdam,EU,523,58,509,2025-11-29
|
| 22 |
+
BMS0007,EU-02,Europe - Rotterdam,EU,341,28,244,2025-11-29
|
| 23 |
+
BMS0007,EU-01,Europe - Daventry,EU,68,68,16,2025-11-29
|
| 24 |
+
BMS0007,APAC-01,Asia Pacific - Singapore,APAC,339,69,298,2025-11-29
|
| 25 |
+
BMS0007,NA-01,North America - Columbus,NA,186,23,122,2025-11-29
|
| 26 |
+
BMS0008,APAC-02,Asia Pacific - Shanghai,APAC,126,57,106,2025-11-29
|
| 27 |
+
BMS0008,EU-02,Europe - Rotterdam,EU,473,34,420,2025-11-29
|
| 28 |
+
BMS0008,APAC-01,Asia Pacific - Singapore,APAC,59,0,44,2025-11-29
|
| 29 |
+
BMS0008,EU-01,Europe - Daventry,EU,301,11,301,2025-11-29
|
| 30 |
+
BMS0009,APAC-01,Asia Pacific - Singapore,APAC,540,57,537,2025-11-29
|
| 31 |
+
BMS0009,APAC-02,Asia Pacific - Shanghai,APAC,241,47,230,2025-11-29
|
| 32 |
+
BMS0009,NA-02,North America - Memphis,NA,488,11,413,2025-11-29
|
| 33 |
+
BMS0010,EU-01,Europe - Daventry,EU,346,1,344,2025-11-29
|
| 34 |
+
BMS0010,APAC-01,Asia Pacific - Singapore,APAC,618,61,612,2025-11-29
|
| 35 |
+
BMS0010,EU-02,Europe - Rotterdam,EU,437,34,353,2025-11-29
|
| 36 |
+
BMS0010,NA-02,North America - Memphis,NA,264,6,176,2025-11-29
|
| 37 |
+
BMS0011,APAC-02,Asia Pacific - Shanghai,APAC,696,41,667,2025-11-29
|
| 38 |
+
BMS0011,NA-01,North America - Columbus,NA,294,53,281,2025-11-29
|
| 39 |
+
BMS0011,NA-02,North America - Memphis,NA,99,41,57,2025-11-29
|
| 40 |
+
BMS0012,EU-01,Europe - Daventry,EU,30,25,26,2025-11-29
|
| 41 |
+
BMS0012,NA-02,North America - Memphis,NA,449,71,363,2025-11-29
|
| 42 |
+
BMS0012,APAC-02,Asia Pacific - Shanghai,APAC,744,23,738,2025-11-29
|
| 43 |
+
BMS0012,EU-02,Europe - Rotterdam,EU,116,46,46,2025-11-29
|
| 44 |
+
BMS0013,NA-01,North America - Columbus,NA,30,28,14,2025-11-29
|
| 45 |
+
BMS0013,NA-02,North America - Memphis,NA,667,29,591,2025-11-29
|
| 46 |
+
BMS0014,NA-02,North America - Memphis,NA,786,55,705,2025-11-29
|
| 47 |
+
BMS0014,NA-01,North America - Columbus,NA,635,41,554,2025-11-29
|
| 48 |
+
BMS0014,APAC-01,Asia Pacific - Singapore,APAC,703,15,658,2025-11-29
|
| 49 |
+
BMS0015,NA-02,North America - Memphis,NA,269,41,171,2025-11-29
|
| 50 |
+
BMS0015,APAC-02,Asia Pacific - Shanghai,APAC,56,53,4,2025-11-29
|
| 51 |
+
BMS0016,APAC-01,Asia Pacific - Singapore,APAC,491,100,482,2025-11-29
|
| 52 |
+
BMS0016,EU-02,Europe - Rotterdam,EU,759,27,697,2025-11-29
|
| 53 |
+
BMS0016,NA-01,North America - Columbus,NA,785,49,736,2025-11-29
|
| 54 |
+
BMS0016,EU-01,Europe - Daventry,EU,478,1,394,2025-11-29
|
| 55 |
+
BMS0017,NA-01,North America - Columbus,NA,54,21,9,2025-11-29
|
| 56 |
+
BMS0017,EU-02,Europe - Rotterdam,EU,61,45,35,2025-11-29
|
| 57 |
+
BMS0017,APAC-02,Asia Pacific - Shanghai,APAC,296,3,251,2025-11-29
|
| 58 |
+
BMS0017,NA-02,North America - Memphis,NA,97,11,28,2025-11-29
|
| 59 |
+
BMS0018,NA-02,North America - Memphis,NA,747,35,745,2025-11-29
|
| 60 |
+
BMS0018,APAC-01,Asia Pacific - Singapore,APAC,714,95,702,2025-11-29
|
| 61 |
+
BMS0019,EU-01,Europe - Daventry,EU,635,2,601,2025-11-29
|
| 62 |
+
BMS0019,APAC-02,Asia Pacific - Shanghai,APAC,206,66,134,2025-11-29
|
| 63 |
+
BMS0019,NA-01,North America - Columbus,NA,772,24,731,2025-11-29
|
| 64 |
+
BMS0019,NA-02,North America - Memphis,NA,230,12,172,2025-11-29
|
| 65 |
+
BMS0020,APAC-02,Asia Pacific - Shanghai,APAC,297,55,284,2025-11-29
|
| 66 |
+
BMS0020,NA-02,North America - Memphis,NA,359,85,314,2025-11-29
|
| 67 |
+
BMS0021,NA-02,North America - Memphis,NA,666,90,656,2025-11-29
|
| 68 |
+
BMS0021,EU-01,Europe - Daventry,EU,68,31,35,2025-11-29
|
| 69 |
+
BMS0022,EU-01,Europe - Daventry,EU,106,17,53,2025-11-29
|
| 70 |
+
BMS0022,APAC-01,Asia Pacific - Singapore,APAC,749,70,677,2025-11-29
|
| 71 |
+
BMS0023,EU-02,Europe - Rotterdam,EU,552,89,524,2025-11-29
|
| 72 |
+
BMS0023,EU-01,Europe - Daventry,EU,335,81,313,2025-11-29
|
| 73 |
+
BMS0024,NA-02,North America - Memphis,NA,743,83,665,2025-11-29
|
| 74 |
+
BMS0024,APAC-01,Asia Pacific - Singapore,APAC,419,100,355,2025-11-29
|
| 75 |
+
BMS0024,APAC-02,Asia Pacific - Shanghai,APAC,559,41,507,2025-11-29
|
| 76 |
+
BMS0025,APAC-02,Asia Pacific - Shanghai,APAC,625,34,584,2025-11-29
|
| 77 |
+
BMS0025,EU-02,Europe - Rotterdam,EU,773,51,708,2025-11-29
|
| 78 |
+
BMS0026,EU-01,Europe - Daventry,EU,220,10,141,2025-11-29
|
| 79 |
+
BMS0026,EU-02,Europe - Rotterdam,EU,383,62,311,2025-11-29
|
| 80 |
+
BMS0026,NA-01,North America - Columbus,NA,681,75,627,2025-11-29
|
| 81 |
+
BMS0026,APAC-02,Asia Pacific - Shanghai,APAC,338,72,325,2025-11-29
|
| 82 |
+
BMS0027,APAC-02,Asia Pacific - Shanghai,APAC,608,95,576,2025-11-29
|
| 83 |
+
BMS0027,NA-02,North America - Memphis,NA,262,82,163,2025-11-29
|
| 84 |
+
BMS0028,EU-01,Europe - Daventry,EU,474,83,386,2025-11-29
|
| 85 |
+
BMS0028,APAC-01,Asia Pacific - Singapore,APAC,566,39,524,2025-11-29
|
| 86 |
+
BMS0028,NA-02,North America - Memphis,NA,465,64,432,2025-11-29
|
| 87 |
+
BMS0028,NA-01,North America - Columbus,NA,256,76,173,2025-11-29
|
| 88 |
+
BMS0029,NA-01,North America - Columbus,NA,491,60,477,2025-11-29
|
| 89 |
+
BMS0029,NA-02,North America - Memphis,NA,317,83,268,2025-11-29
|
| 90 |
+
BMS0029,APAC-01,Asia Pacific - Singapore,APAC,337,87,333,2025-11-29
|
| 91 |
+
BMS0029,APAC-02,Asia Pacific - Shanghai,APAC,669,86,650,2025-11-29
|
| 92 |
+
BMS0030,EU-02,Europe - Rotterdam,EU,519,31,463,2025-11-29
|
| 93 |
+
BMS0030,NA-01,North America - Columbus,NA,418,78,410,2025-11-29
|
| 94 |
+
BMS0030,APAC-01,Asia Pacific - Singapore,APAC,32,32,28,2025-11-29
|
| 95 |
+
BMS0030,APAC-02,Asia Pacific - Shanghai,APAC,559,91,483,2025-11-29
|
| 96 |
+
BMS0031,NA-01,North America - Columbus,NA,56,7,49,2025-11-29
|
| 97 |
+
BMS0031,NA-02,North America - Memphis,NA,45,18,16,2025-11-29
|
| 98 |
+
BMS0031,EU-02,Europe - Rotterdam,EU,3,2,1,2025-11-29
|
| 99 |
+
BMS0031,EU-01,Europe - Daventry,EU,714,100,682,2025-11-29
|
| 100 |
+
BMS0032,EU-01,Europe - Daventry,EU,357,51,297,2025-11-29
|
| 101 |
+
BMS0032,NA-02,North America - Memphis,NA,137,11,49,2025-11-29
|
| 102 |
+
BMS0032,APAC-01,Asia Pacific - Singapore,APAC,65,42,56,2025-11-29
|
| 103 |
+
BMS0033,NA-01,North America - Columbus,NA,279,46,187,2025-11-29
|
| 104 |
+
BMS0033,APAC-01,Asia Pacific - Singapore,APAC,702,86,602,2025-11-29
|
| 105 |
+
BMS0034,EU-02,Europe - Rotterdam,EU,774,67,693,2025-11-29
|
| 106 |
+
BMS0034,EU-01,Europe - Daventry,EU,135,9,83,2025-11-29
|
| 107 |
+
BMS0035,EU-01,Europe - Daventry,EU,444,22,357,2025-11-29
|
| 108 |
+
BMS0035,NA-01,North America - Columbus,NA,11,0,11,2025-11-29
|
| 109 |
+
BMS0035,APAC-02,Asia Pacific - Shanghai,APAC,545,65,476,2025-11-29
|
| 110 |
+
BMS0036,APAC-01,Asia Pacific - Singapore,APAC,762,100,749,2025-11-29
|
| 111 |
+
BMS0036,EU-02,Europe - Rotterdam,EU,315,30,257,2025-11-29
|
| 112 |
+
BMS0036,APAC-02,Asia Pacific - Shanghai,APAC,455,21,435,2025-11-29
|
| 113 |
+
BMS0036,NA-02,North America - Memphis,NA,183,64,136,2025-11-29
|
| 114 |
+
BMS0037,EU-02,Europe - Rotterdam,EU,80,35,8,2025-11-29
|
| 115 |
+
BMS0037,APAC-01,Asia Pacific - Singapore,APAC,237,45,201,2025-11-29
|
| 116 |
+
BMS0037,NA-02,North America - Memphis,NA,549,71,540,2025-11-29
|
| 117 |
+
BMS0038,NA-01,North America - Columbus,NA,474,53,383,2025-11-29
|
| 118 |
+
BMS0038,EU-02,Europe - Rotterdam,EU,73,5,43,2025-11-29
|
| 119 |
+
BMS0038,NA-02,North America - Memphis,NA,458,60,401,2025-11-29
|
| 120 |
+
BMS0038,APAC-02,Asia Pacific - Shanghai,APAC,252,11,240,2025-11-29
|
| 121 |
+
BMS0039,NA-02,North America - Memphis,NA,706,24,606,2025-11-29
|
| 122 |
+
BMS0039,EU-01,Europe - Daventry,EU,465,84,425,2025-11-29
|
| 123 |
+
BMS0040,NA-02,North America - Memphis,NA,321,14,233,2025-11-29
|
| 124 |
+
BMS0040,APAC-02,Asia Pacific - Shanghai,APAC,355,85,347,2025-11-29
|
| 125 |
+
BMS0040,EU-01,Europe - Daventry,EU,640,37,567,2025-11-29
|
| 126 |
+
BMS0040,EU-02,Europe - Rotterdam,EU,280,5,195,2025-11-29
|
| 127 |
+
BMS0041,EU-02,Europe - Rotterdam,EU,441,60,424,2025-11-29
|
| 128 |
+
BMS0041,EU-01,Europe - Daventry,EU,232,75,174,2025-11-29
|
| 129 |
+
BMS0042,APAC-01,Asia Pacific - Singapore,APAC,537,64,519,2025-11-29
|
| 130 |
+
BMS0042,APAC-02,Asia Pacific - Shanghai,APAC,98,96,3,2025-11-29
|
| 131 |
+
BMS0042,NA-01,North America - Columbus,NA,768,6,712,2025-11-29
|
| 132 |
+
BMS0042,EU-02,Europe - Rotterdam,EU,386,98,289,2025-11-29
|
| 133 |
+
BMS0043,NA-01,North America - Columbus,NA,548,84,467,2025-11-29
|
| 134 |
+
BMS0043,EU-01,Europe - Daventry,EU,703,87,644,2025-11-29
|
| 135 |
+
BMS0043,APAC-01,Asia Pacific - Singapore,APAC,788,5,756,2025-11-29
|
| 136 |
+
BMS0043,NA-02,North America - Memphis,NA,147,47,119,2025-11-29
|
| 137 |
+
BMS0044,EU-02,Europe - Rotterdam,EU,733,52,672,2025-11-29
|
| 138 |
+
BMS0044,APAC-02,Asia Pacific - Shanghai,APAC,732,31,716,2025-11-29
|
| 139 |
+
BMS0044,APAC-01,Asia Pacific - Singapore,APAC,382,34,307,2025-11-29
|
| 140 |
+
BMS0044,EU-01,Europe - Daventry,EU,401,48,398,2025-11-29
|
| 141 |
+
BMS0045,NA-01,North America - Columbus,NA,219,96,156,2025-11-29
|
| 142 |
+
BMS0045,NA-02,North America - Memphis,NA,130,26,60,2025-11-29
|
| 143 |
+
BMS0045,EU-02,Europe - Rotterdam,EU,29,11,15,2025-11-29
|
| 144 |
+
BMS0046,NA-02,North America - Memphis,NA,412,94,357,2025-11-29
|
| 145 |
+
BMS0046,NA-01,North America - Columbus,NA,184,77,135,2025-11-29
|
| 146 |
+
BMS0047,EU-02,Europe - Rotterdam,EU,234,33,213,2025-11-29
|
| 147 |
+
BMS0047,APAC-01,Asia Pacific - Singapore,APAC,536,72,529,2025-11-29
|
| 148 |
+
BMS0047,NA-02,North America - Memphis,NA,669,33,648,2025-11-29
|
| 149 |
+
BMS0047,APAC-02,Asia Pacific - Shanghai,APAC,406,69,365,2025-11-29
|
| 150 |
+
BMS0048,APAC-02,Asia Pacific - Shanghai,APAC,3,3,1,2025-11-29
|
| 151 |
+
BMS0048,EU-01,Europe - Daventry,EU,518,77,441,2025-11-29
|
| 152 |
+
BMS0048,APAC-01,Asia Pacific - Singapore,APAC,244,18,235,2025-11-29
|
| 153 |
+
BMS0048,NA-02,North America - Memphis,NA,638,9,553,2025-11-29
|
| 154 |
+
BMS0049,NA-02,North America - Memphis,NA,304,16,292,2025-11-29
|
| 155 |
+
BMS0049,APAC-02,Asia Pacific - Shanghai,APAC,489,77,448,2025-11-29
|
| 156 |
+
BMS0049,NA-01,North America - Columbus,NA,215,38,127,2025-11-29
|
| 157 |
+
BMS0049,APAC-01,Asia Pacific - Singapore,APAC,544,99,500,2025-11-29
|
| 158 |
+
BMS0050,EU-01,Europe - Daventry,EU,290,39,237,2025-11-29
|
| 159 |
+
BMS0050,APAC-02,Asia Pacific - Shanghai,APAC,172,33,169,2025-11-29
|
| 160 |
+
BMS0050,NA-01,North America - Columbus,NA,794,39,707,2025-11-29
|
| 161 |
+
BMS0051,EU-01,Europe - Daventry,EU,93,54,1,2025-11-29
|
| 162 |
+
BMS0051,NA-01,North America - Columbus,NA,675,91,583,2025-11-29
|
| 163 |
+
BMS0051,EU-02,Europe - Rotterdam,EU,769,49,671,2025-11-29
|
| 164 |
+
BMS0051,APAC-01,Asia Pacific - Singapore,APAC,771,36,759,2025-11-29
|
| 165 |
+
BMS0052,NA-01,North America - Columbus,NA,226,28,170,2025-11-29
|
| 166 |
+
BMS0052,APAC-02,Asia Pacific - Shanghai,APAC,600,2,568,2025-11-29
|
| 167 |
+
BMS0052,APAC-01,Asia Pacific - Singapore,APAC,451,34,435,2025-11-29
|
| 168 |
+
BMS0053,NA-02,North America - Memphis,NA,435,86,385,2025-11-29
|
| 169 |
+
BMS0053,NA-01,North America - Columbus,NA,21,11,0,2025-11-29
|
| 170 |
+
BMS0054,NA-01,North America - Columbus,NA,607,0,511,2025-11-29
|
| 171 |
+
BMS0054,APAC-01,Asia Pacific - Singapore,APAC,134,92,63,2025-11-29
|
| 172 |
+
BMS0054,EU-01,Europe - Daventry,EU,682,74,670,2025-11-29
|
| 173 |
+
BMS0054,NA-02,North America - Memphis,NA,642,63,636,2025-11-29
|
| 174 |
+
BMS0055,APAC-01,Asia Pacific - Singapore,APAC,144,35,125,2025-11-29
|
| 175 |
+
BMS0055,APAC-02,Asia Pacific - Shanghai,APAC,63,61,21,2025-11-29
|
| 176 |
+
BMS0055,NA-01,North America - Columbus,NA,240,2,144,2025-11-29
|
| 177 |
+
BMS0055,NA-02,North America - Memphis,NA,574,88,518,2025-11-29
|
| 178 |
+
BMS0056,NA-02,North America - Memphis,NA,612,54,513,2025-11-29
|
| 179 |
+
BMS0056,APAC-02,Asia Pacific - Shanghai,APAC,400,56,372,2025-11-29
|
| 180 |
+
BMS0056,EU-02,Europe - Rotterdam,EU,285,79,245,2025-11-29
|
| 181 |
+
BMS0057,APAC-02,Asia Pacific - Shanghai,APAC,754,41,663,2025-11-29
|
| 182 |
+
BMS0057,EU-02,Europe - Rotterdam,EU,36,2,3,2025-11-29
|
| 183 |
+
BMS0057,NA-01,North America - Columbus,NA,380,100,309,2025-11-29
|
| 184 |
+
BMS0057,APAC-01,Asia Pacific - Singapore,APAC,330,97,296,2025-11-29
|
| 185 |
+
BMS0058,NA-01,North America - Columbus,NA,69,55,6,2025-11-29
|
| 186 |
+
BMS0058,EU-01,Europe - Daventry,EU,598,21,584,2025-11-29
|
| 187 |
+
BMS0058,EU-02,Europe - Rotterdam,EU,146,56,138,2025-11-29
|
| 188 |
+
BMS0058,NA-02,North America - Memphis,NA,736,77,696,2025-11-29
|
| 189 |
+
BMS0059,EU-02,Europe - Rotterdam,EU,494,15,429,2025-11-29
|
| 190 |
+
BMS0059,NA-01,North America - Columbus,NA,429,60,422,2025-11-29
|
| 191 |
+
BMS0060,APAC-01,Asia Pacific - Singapore,APAC,730,95,670,2025-11-29
|
| 192 |
+
BMS0060,EU-02,Europe - Rotterdam,EU,143,3,135,2025-11-29
|
| 193 |
+
BMS0060,NA-02,North America - Memphis,NA,790,55,758,2025-11-29
|
| 194 |
+
BMS0060,EU-01,Europe - Daventry,EU,554,83,514,2025-11-29
|
| 195 |
+
BMS0061,EU-02,Europe - Rotterdam,EU,224,17,139,2025-11-29
|
| 196 |
+
BMS0061,NA-01,North America - Columbus,NA,333,70,322,2025-11-29
|
| 197 |
+
BMS0061,NA-02,North America - Memphis,NA,717,74,627,2025-11-29
|
| 198 |
+
BMS0062,NA-02,North America - Memphis,NA,233,2,229,2025-11-29
|
| 199 |
+
BMS0062,EU-01,Europe - Daventry,EU,310,87,280,2025-11-29
|
| 200 |
+
BMS0062,NA-01,North America - Columbus,NA,393,18,346,2025-11-29
|
| 201 |
+
BMS0063,EU-02,Europe - Rotterdam,EU,341,31,246,2025-11-29
|
| 202 |
+
BMS0063,APAC-01,Asia Pacific - Singapore,APAC,658,93,581,2025-11-29
|
| 203 |
+
BMS0063,NA-01,North America - Columbus,NA,692,30,692,2025-11-29
|
| 204 |
+
BMS0063,EU-01,Europe - Daventry,EU,351,71,251,2025-11-29
|
| 205 |
+
BMS0064,APAC-01,Asia Pacific - Singapore,APAC,709,48,688,2025-11-29
|
| 206 |
+
BMS0064,EU-02,Europe - Rotterdam,EU,288,0,253,2025-11-29
|
| 207 |
+
BMS0065,EU-02,Europe - Rotterdam,EU,65,4,10,2025-11-29
|
| 208 |
+
BMS0065,NA-01,North America - Columbus,NA,702,89,694,2025-11-29
|
| 209 |
+
BMS0065,APAC-01,Asia Pacific - Singapore,APAC,586,44,491,2025-11-29
|
| 210 |
+
BMS0066,NA-01,North America - Columbus,NA,627,25,536,2025-11-29
|
| 211 |
+
BMS0066,EU-02,Europe - Rotterdam,EU,152,73,63,2025-11-29
|
| 212 |
+
BMS0066,APAC-02,Asia Pacific - Shanghai,APAC,554,55,513,2025-11-29
|
| 213 |
+
BMS0066,EU-01,Europe - Daventry,EU,197,54,148,2025-11-29
|
| 214 |
+
BMS0067,EU-01,Europe - Daventry,EU,17,2,0,2025-11-29
|
| 215 |
+
BMS0067,APAC-02,Asia Pacific - Shanghai,APAC,73,71,12,2025-11-29
|
| 216 |
+
BMS0068,EU-01,Europe - Daventry,EU,308,96,217,2025-11-29
|
| 217 |
+
BMS0068,NA-01,North America - Columbus,NA,207,49,125,2025-11-29
|
| 218 |
+
BMS0068,NA-02,North America - Memphis,NA,76,22,33,2025-11-29
|
| 219 |
+
BMS0068,EU-02,Europe - Rotterdam,EU,67,36,46,2025-11-29
|
| 220 |
+
BMS0069,APAC-02,Asia Pacific - Shanghai,APAC,617,86,607,2025-11-29
|
| 221 |
+
BMS0069,EU-02,Europe - Rotterdam,EU,133,39,125,2025-11-29
|
| 222 |
+
BMS0069,NA-01,North America - Columbus,NA,335,91,315,2025-11-29
|
| 223 |
+
BMS0069,EU-01,Europe - Daventry,EU,68,19,58,2025-11-29
|
| 224 |
+
BMS0070,APAC-02,Asia Pacific - Shanghai,APAC,559,58,533,2025-11-29
|
| 225 |
+
BMS0070,APAC-01,Asia Pacific - Singapore,APAC,17,1,5,2025-11-29
|
| 226 |
+
BMS0070,NA-02,North America - Memphis,NA,39,15,29,2025-11-29
|
| 227 |
+
BMS0070,NA-01,North America - Columbus,NA,517,75,461,2025-11-29
|
| 228 |
+
BMS0071,APAC-02,Asia Pacific - Shanghai,APAC,479,95,420,2025-11-29
|
| 229 |
+
BMS0071,EU-01,Europe - Daventry,EU,376,55,282,2025-11-29
|
| 230 |
+
BMS0071,EU-02,Europe - Rotterdam,EU,761,88,681,2025-11-29
|
| 231 |
+
BMS0071,NA-01,North America - Columbus,NA,303,82,290,2025-11-29
|
| 232 |
+
BMS0072,APAC-01,Asia Pacific - Singapore,APAC,315,47,298,2025-11-29
|
| 233 |
+
BMS0072,APAC-02,Asia Pacific - Shanghai,APAC,32,30,24,2025-11-29
|
| 234 |
+
BMS0073,APAC-01,Asia Pacific - Singapore,APAC,197,63,148,2025-11-29
|
| 235 |
+
BMS0073,EU-01,Europe - Daventry,EU,268,74,230,2025-11-29
|
| 236 |
+
BMS0073,NA-01,North America - Columbus,NA,710,10,686,2025-11-29
|
| 237 |
+
BMS0074,NA-01,North America - Columbus,NA,524,32,431,2025-11-29
|
| 238 |
+
BMS0074,APAC-01,Asia Pacific - Singapore,APAC,327,46,308,2025-11-29
|
| 239 |
+
BMS0075,NA-02,North America - Memphis,NA,636,90,604,2025-11-29
|
| 240 |
+
BMS0075,EU-01,Europe - Daventry,EU,128,21,63,2025-11-29
|
| 241 |
+
BMS0075,APAC-01,Asia Pacific - Singapore,APAC,349,9,316,2025-11-29
|
| 242 |
+
BMS0075,APAC-02,Asia Pacific - Shanghai,APAC,93,30,43,2025-11-29
|
| 243 |
+
BMS0076,NA-02,North America - Memphis,NA,540,99,485,2025-11-29
|
| 244 |
+
BMS0076,APAC-02,Asia Pacific - Shanghai,APAC,111,30,84,2025-11-29
|
| 245 |
+
BMS0076,EU-01,Europe - Daventry,EU,752,24,669,2025-11-29
|
| 246 |
+
BMS0076,APAC-01,Asia Pacific - Singapore,APAC,218,1,174,2025-11-29
|
| 247 |
+
BMS0077,NA-01,North America - Columbus,NA,79,15,48,2025-11-29
|
| 248 |
+
BMS0077,NA-02,North America - Memphis,NA,504,17,478,2025-11-29
|
| 249 |
+
BMS0077,APAC-02,Asia Pacific - Shanghai,APAC,713,53,653,2025-11-29
|
| 250 |
+
BMS0078,APAC-02,Asia Pacific - Shanghai,APAC,184,95,169,2025-11-29
|
| 251 |
+
BMS0078,APAC-01,Asia Pacific - Singapore,APAC,216,94,169,2025-11-29
|
| 252 |
+
BMS0078,NA-01,North America - Columbus,NA,743,80,721,2025-11-29
|
| 253 |
+
BMS0078,EU-01,Europe - Daventry,EU,757,19,748,2025-11-29
|
| 254 |
+
BMS0079,NA-01,North America - Columbus,NA,528,74,525,2025-11-29
|
| 255 |
+
BMS0079,APAC-02,Asia Pacific - Shanghai,APAC,586,70,559,2025-11-29
|
| 256 |
+
BMS0080,APAC-02,Asia Pacific - Shanghai,APAC,583,27,555,2025-11-29
|
| 257 |
+
BMS0080,NA-01,North America - Columbus,NA,211,5,135,2025-11-29
|
| 258 |
+
BMS0080,EU-02,Europe - Rotterdam,EU,668,8,644,2025-11-29
|
| 259 |
+
BMS0080,EU-01,Europe - Daventry,EU,528,83,451,2025-11-29
|
| 260 |
+
BMS0081,APAC-02,Asia Pacific - Shanghai,APAC,575,54,573,2025-11-29
|
| 261 |
+
BMS0081,APAC-01,Asia Pacific - Singapore,APAC,165,2,68,2025-11-29
|
| 262 |
+
BMS0082,NA-02,North America - Memphis,NA,679,93,615,2025-11-29
|
| 263 |
+
BMS0082,APAC-01,Asia Pacific - Singapore,APAC,651,15,582,2025-11-29
|
| 264 |
+
BMS0082,EU-02,Europe - Rotterdam,EU,122,34,92,2025-11-29
|
| 265 |
+
BMS0082,EU-01,Europe - Daventry,EU,208,97,168,2025-11-29
|
| 266 |
+
BMS0083,APAC-01,Asia Pacific - Singapore,APAC,254,47,190,2025-11-29
|
| 267 |
+
BMS0083,EU-02,Europe - Rotterdam,EU,735,71,734,2025-11-29
|
| 268 |
+
BMS0083,NA-02,North America - Memphis,NA,371,69,304,2025-11-29
|
| 269 |
+
BMS0083,EU-01,Europe - Daventry,EU,792,57,730,2025-11-29
|
| 270 |
+
BMS0084,APAC-01,Asia Pacific - Singapore,APAC,417,71,354,2025-11-29
|
| 271 |
+
BMS0084,EU-02,Europe - Rotterdam,EU,582,6,560,2025-11-29
|
| 272 |
+
BMS0084,NA-01,North America - Columbus,NA,286,36,233,2025-11-29
|
| 273 |
+
BMS0084,NA-02,North America - Memphis,NA,267,34,247,2025-11-29
|
| 274 |
+
BMS0085,APAC-02,Asia Pacific - Shanghai,APAC,588,67,496,2025-11-29
|
| 275 |
+
BMS0085,EU-01,Europe - Daventry,EU,648,92,600,2025-11-29
|
| 276 |
+
BMS0086,APAC-01,Asia Pacific - Singapore,APAC,707,92,608,2025-11-29
|
| 277 |
+
BMS0086,EU-01,Europe - Daventry,EU,597,23,510,2025-11-29
|
| 278 |
+
BMS0086,NA-01,North America - Columbus,NA,791,60,788,2025-11-29
|
| 279 |
+
BMS0087,APAC-01,Asia Pacific - Singapore,APAC,496,47,464,2025-11-29
|
| 280 |
+
BMS0087,NA-01,North America - Columbus,NA,148,19,67,2025-11-29
|
| 281 |
+
BMS0088,APAC-01,Asia Pacific - Singapore,APAC,92,38,33,2025-11-29
|
| 282 |
+
BMS0088,APAC-02,Asia Pacific - Shanghai,APAC,396,69,323,2025-11-29
|
| 283 |
+
BMS0088,NA-01,North America - Columbus,NA,563,99,537,2025-11-29
|
| 284 |
+
BMS0089,NA-02,North America - Memphis,NA,343,79,293,2025-11-29
|
| 285 |
+
BMS0089,APAC-01,Asia Pacific - Singapore,APAC,365,97,300,2025-11-29
|
| 286 |
+
BMS0089,NA-01,North America - Columbus,NA,384,53,298,2025-11-29
|
| 287 |
+
BMS0090,APAC-02,Asia Pacific - Shanghai,APAC,798,31,764,2025-11-29
|
| 288 |
+
BMS0090,NA-01,North America - Columbus,NA,551,32,508,2025-11-29
|
| 289 |
+
BMS0090,EU-01,Europe - Daventry,EU,283,41,253,2025-11-29
|
| 290 |
+
BMS0090,APAC-01,Asia Pacific - Singapore,APAC,431,58,359,2025-11-29
|
| 291 |
+
BMS0091,APAC-02,Asia Pacific - Shanghai,APAC,426,24,399,2025-11-29
|
| 292 |
+
BMS0091,NA-02,North America - Memphis,NA,266,44,179,2025-11-29
|
| 293 |
+
BMS0092,NA-01,North America - Columbus,NA,73,60,28,2025-11-29
|
| 294 |
+
BMS0092,APAC-01,Asia Pacific - Singapore,APAC,607,48,555,2025-11-29
|
| 295 |
+
BMS0092,APAC-02,Asia Pacific - Shanghai,APAC,88,37,49,2025-11-29
|
| 296 |
+
BMS0093,EU-02,Europe - Rotterdam,EU,683,34,609,2025-11-29
|
| 297 |
+
BMS0093,APAC-02,Asia Pacific - Shanghai,APAC,60,33,16,2025-11-29
|
| 298 |
+
BMS0093,EU-01,Europe - Daventry,EU,621,91,577,2025-11-29
|
| 299 |
+
BMS0093,APAC-01,Asia Pacific - Singapore,APAC,481,15,475,2025-11-29
|
| 300 |
+
BMS0094,NA-01,North America - Columbus,NA,98,32,97,2025-11-29
|
| 301 |
+
BMS0094,EU-02,Europe - Rotterdam,EU,180,21,96,2025-11-29
|
| 302 |
+
BMS0094,APAC-02,Asia Pacific - Shanghai,APAC,219,81,176,2025-11-29
|
| 303 |
+
BMS0095,EU-02,Europe - Rotterdam,EU,533,42,488,2025-11-29
|
| 304 |
+
BMS0095,NA-01,North America - Columbus,NA,177,22,80,2025-11-29
|
| 305 |
+
BMS0095,APAC-02,Asia Pacific - Shanghai,APAC,601,100,555,2025-11-29
|
| 306 |
+
BMS0095,NA-02,North America - Memphis,NA,23,19,2,2025-11-29
|
| 307 |
+
BMS0096,EU-01,Europe - Daventry,EU,19,8,2,2025-11-29
|
| 308 |
+
BMS0096,NA-02,North America - Memphis,NA,526,36,458,2025-11-29
|
| 309 |
+
BMS0096,EU-02,Europe - Rotterdam,EU,673,89,582,2025-11-29
|
| 310 |
+
BMS0097,EU-02,Europe - Rotterdam,EU,291,41,235,2025-11-29
|
| 311 |
+
BMS0097,NA-01,North America - Columbus,NA,765,40,709,2025-11-29
|
| 312 |
+
BMS0097,APAC-01,Asia Pacific - Singapore,APAC,412,66,396,2025-11-29
|
| 313 |
+
BMS0097,NA-02,North America - Memphis,NA,442,83,415,2025-11-29
|
| 314 |
+
BMS0098,APAC-01,Asia Pacific - Singapore,APAC,519,74,435,2025-11-29
|
| 315 |
+
BMS0098,NA-01,North America - Columbus,NA,487,29,405,2025-11-29
|
| 316 |
+
BMS0098,APAC-02,Asia Pacific - Shanghai,APAC,48,14,21,2025-11-29
|
| 317 |
+
BMS0099,APAC-01,Asia Pacific - Singapore,APAC,479,35,425,2025-11-29
|
| 318 |
+
BMS0099,NA-01,North America - Columbus,NA,448,32,421,2025-11-29
|
| 319 |
+
BMS0099,EU-02,Europe - Rotterdam,EU,199,38,199,2025-11-29
|
| 320 |
+
BMS0100,APAC-02,Asia Pacific - Shanghai,APAC,346,43,343,2025-11-29
|
| 321 |
+
BMS0100,EU-02,Europe - Rotterdam,EU,112,88,26,2025-11-29
|
| 322 |
+
BMS0100,NA-01,North America - Columbus,NA,204,26,180,2025-11-29
|
| 323 |
+
BMS0100,NA-02,North America - Memphis,NA,793,37,764,2025-11-29
|
data/items.csv
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
item_code,description,category,uom,list_price,cost_price,reorder_point,reorder_qty,supplier_id,active,created_date
|
| 2 |
+
BMS0001,Belt - Type A - Model 859,Belts,Each,51.13,30.4,76,479,SUP002,Y,2024-01-01
|
| 3 |
+
BMS0002,Switche - Type D - Model 132,Switches,Each,22.09,23.15,179,213,SUP009,Y,2024-01-01
|
| 4 |
+
BMS0003,Gasket - Type D - Model 325,Gaskets,Each,24.62,12.23,51,281,SUP007,Y,2024-01-01
|
| 5 |
+
BMS0004,Seal - Type C - Model 259,Seals,Each,22.34,33.77,76,247,SUP007,Y,2024-01-01
|
| 6 |
+
BMS0005,Belt - Type C - Model 967,Belts,Each,57.68,33.25,61,435,SUP009,Y,2024-01-01
|
| 7 |
+
BMS0006,Belt - Type D - Model 180,Belts,Each,77.44,72.23,142,495,SUP004,Y,2024-01-01
|
| 8 |
+
BMS0007,Belt - Type A - Model 777,Belts,Each,46.65,34.97,70,319,SUP002,Y,2024-01-01
|
| 9 |
+
BMS0008,Sensor - Type C - Model 564,Sensors,Each,143.53,68.23,144,381,SUP004,Y,2024-01-01
|
| 10 |
+
BMS0009,Bearing - Type A - Model 723,Bearings,Each,106.2,64.47,112,283,SUP008,Y,2024-01-01
|
| 11 |
+
BMS0010,Sensor - Type C - Model 755,Sensors,Each,151.67,51.82,133,228,SUP004,Y,2024-01-01
|
| 12 |
+
BMS0011,Filter - Type C - Model 510,Filters,Each,33.74,19.65,195,361,SUP004,Y,2024-01-01
|
| 13 |
+
BMS0012,Valve - Type D - Model 758,Valves,Each,101.53,48.81,113,487,SUP009,Y,2024-01-01
|
| 14 |
+
BMS0013,Bearing - Type D - Model 697,Bearings,Each,77.93,37.08,85,460,SUP008,Y,2024-01-01
|
| 15 |
+
BMS0014,Belt - Type A - Model 981,Belts,Each,35.42,58.29,158,232,SUP007,Y,2024-01-01
|
| 16 |
+
BMS0015,Sensor - Type D - Model 641,Sensors,Each,83.97,89.51,52,258,SUP009,Y,2024-01-01
|
| 17 |
+
BMS0016,Bearing - Type C - Model 214,Bearings,Each,65.22,31.76,50,334,SUP009,Y,2024-01-01
|
| 18 |
+
BMS0017,Hose - Type A - Model 991,Hoses,Each,66.15,57.68,179,301,SUP003,Y,2024-01-01
|
| 19 |
+
BMS0018,Seal - Type B - Model 652,Seals,Each,57.77,39.3,50,365,SUP008,Y,2024-01-01
|
| 20 |
+
BMS0019,Filter - Type A - Model 471,Filters,Each,76.51,51.0,128,322,SUP001,Y,2024-01-01
|
| 21 |
+
BMS0020,Gasket - Type A - Model 187,Gaskets,Each,35.08,26.59,186,264,SUP003,Y,2024-01-01
|
| 22 |
+
BMS0021,Valve - Type B - Model 371,Valves,Each,111.51,84.69,104,476,SUP004,Y,2024-01-01
|
| 23 |
+
BMS0022,Bearing - Type D - Model 787,Bearings,Each,107.99,56.11,182,431,SUP002,Y,2024-01-01
|
| 24 |
+
BMS0023,Gasket - Type B - Model 165,Gaskets,Each,20.51,20.51,108,312,SUP001,Y,2024-01-01
|
| 25 |
+
BMS0024,Belt - Type A - Model 334,Belts,Each,31.4,17.17,134,236,SUP009,Y,2024-01-01
|
| 26 |
+
BMS0025,Gasket - Type C - Model 785,Gaskets,Each,25.96,19.2,196,495,SUP008,Y,2024-01-01
|
| 27 |
+
BMS0026,Gasket - Type D - Model 926,Gaskets,Each,23.06,7.32,160,381,SUP007,Y,2024-01-01
|
| 28 |
+
BMS0027,Sensor - Type D - Model 984,Sensors,Each,158.01,103.09,75,231,SUP007,Y,2024-01-01
|
| 29 |
+
BMS0028,Seal - Type A - Model 354,Seals,Each,21.2,25.86,85,416,SUP003,Y,2024-01-01
|
| 30 |
+
BMS0029,Bearing - Type D - Model 355,Bearings,Each,134.94,24.56,190,250,SUP001,Y,2024-01-01
|
| 31 |
+
BMS0030,Pump - Type A - Model 195,Pumps,Each,472.02,307.94,92,408,SUP008,Y,2024-01-01
|
| 32 |
+
BMS0031,Valve - Type B - Model 985,Valves,Each,93.15,27.16,147,201,SUP007,Y,2024-01-01
|
| 33 |
+
BMS0032,Bearing - Type D - Model 392,Bearings,Each,80.76,101.29,192,449,SUP003,Y,2024-01-01
|
| 34 |
+
BMS0033,Gasket - Type C - Model 322,Gaskets,Each,43.84,20.26,188,231,SUP006,Y,2024-01-01
|
| 35 |
+
BMS0034,Filter - Type A - Model 698,Filters,Each,48.38,55.43,185,280,SUP001,Y,2024-01-01
|
| 36 |
+
BMS0035,Pump - Type A - Model 971,Pumps,Each,190.61,237.42,110,406,SUP002,Y,2024-01-01
|
| 37 |
+
BMS0036,Switche - Type B - Model 692,Switches,Each,61.62,43.59,157,498,SUP010,Y,2024-01-01
|
| 38 |
+
BMS0037,Pump - Type C - Model 367,Pumps,Each,197.62,271.1,111,335,SUP007,Y,2024-01-01
|
| 39 |
+
BMS0038,Hose - Type C - Model 568,Hoses,Each,42.35,52.68,68,204,SUP008,Y,2024-01-01
|
| 40 |
+
BMS0039,Switche - Type A - Model 175,Switches,Each,57.63,37.8,83,378,SUP002,Y,2024-01-01
|
| 41 |
+
BMS0040,Gasket - Type C - Model 391,Gaskets,Each,13.84,27.06,127,470,SUP001,Y,2024-01-01
|
| 42 |
+
BMS0041,Pump - Type C - Model 779,Pumps,Each,159.36,316.12,117,259,SUP002,Y,2024-01-01
|
| 43 |
+
BMS0042,Pump - Type B - Model 378,Pumps,Each,227.07,130.56,137,304,SUP005,Y,2024-01-01
|
| 44 |
+
BMS0043,Pump - Type D - Model 357,Pumps,Each,464.03,307.22,73,416,SUP005,Y,2024-01-01
|
| 45 |
+
BMS0044,Filter - Type A - Model 441,Filters,Each,68.98,41.17,117,282,SUP008,Y,2024-01-01
|
| 46 |
+
BMS0045,Pump - Type D - Model 674,Pumps,Each,123.67,92.92,88,479,SUP001,Y,2024-01-01
|
| 47 |
+
BMS0046,Seal - Type B - Model 540,Seals,Each,18.12,17.93,60,383,SUP004,Y,2024-01-01
|
| 48 |
+
BMS0047,Gasket - Type A - Model 462,Gaskets,Each,36.86,28.41,154,279,SUP004,Y,2024-01-01
|
| 49 |
+
BMS0048,Hose - Type B - Model 522,Hoses,Each,19.91,51.83,135,410,SUP004,Y,2024-01-01
|
| 50 |
+
BMS0049,Bearing - Type B - Model 906,Bearings,Each,114.15,51.28,59,440,SUP004,Y,2024-01-01
|
| 51 |
+
BMS0050,Gasket - Type D - Model 458,Gaskets,Each,19.29,26.04,108,314,SUP001,Y,2024-01-01
|
| 52 |
+
BMS0051,Gasket - Type D - Model 436,Gaskets,Each,18.31,6.65,121,379,SUP009,Y,2024-01-01
|
| 53 |
+
BMS0052,Sensor - Type C - Model 128,Sensors,Each,62.88,136.66,95,497,SUP005,Y,2024-01-01
|
| 54 |
+
BMS0053,Filter - Type A - Model 710,Filters,Each,45.42,45.79,130,423,SUP010,Y,2024-01-01
|
| 55 |
+
BMS0054,Pump - Type A - Model 494,Pumps,Each,461.81,124.84,61,423,SUP001,Y,2024-01-01
|
| 56 |
+
BMS0055,Pump - Type B - Model 472,Pumps,Each,283.89,335.79,134,360,SUP002,Y,2024-01-01
|
| 57 |
+
BMS0056,Bearing - Type C - Model 782,Bearings,Each,79.01,53.01,125,483,SUP003,Y,2024-01-01
|
| 58 |
+
BMS0057,Gasket - Type D - Model 780,Gaskets,Each,42.79,22.88,94,491,SUP005,Y,2024-01-01
|
| 59 |
+
BMS0058,Sensor - Type A - Model 411,Sensors,Each,89.47,75.58,198,364,SUP008,Y,2024-01-01
|
| 60 |
+
BMS0059,Valve - Type D - Model 791,Valves,Each,65.99,70.68,93,243,SUP005,Y,2024-01-01
|
| 61 |
+
BMS0060,Pump - Type C - Model 195,Pumps,Each,431.0,280.82,129,315,SUP004,Y,2024-01-01
|
| 62 |
+
BMS0061,Hose - Type A - Model 147,Hoses,Each,36.85,37.27,68,433,SUP007,Y,2024-01-01
|
| 63 |
+
BMS0062,Switche - Type B - Model 835,Switches,Each,68.74,37.21,112,275,SUP001,Y,2024-01-01
|
| 64 |
+
BMS0063,Belt - Type D - Model 324,Belts,Each,41.71,81.1,182,437,SUP001,Y,2024-01-01
|
| 65 |
+
BMS0064,Pump - Type B - Model 968,Pumps,Each,166.12,109.07,168,471,SUP009,Y,2024-01-01
|
| 66 |
+
BMS0065,Switche - Type C - Model 873,Switches,Each,82.4,43.25,179,418,SUP009,Y,2024-01-01
|
| 67 |
+
BMS0066,Valve - Type B - Model 861,Valves,Each,159.92,68.26,113,341,SUP009,Y,2024-01-01
|
| 68 |
+
BMS0067,Valve - Type B - Model 381,Valves,Each,98.78,95.92,110,339,SUP006,Y,2024-01-01
|
| 69 |
+
BMS0068,Seal - Type A - Model 241,Seals,Each,19.24,20.53,89,309,SUP002,Y,2024-01-01
|
| 70 |
+
BMS0069,Sensor - Type D - Model 438,Sensors,Each,129.1,73.98,102,415,SUP007,Y,2024-01-01
|
| 71 |
+
BMS0070,Switche - Type A - Model 977,Switches,Each,81.65,41.36,172,203,SUP006,Y,2024-01-01
|
| 72 |
+
BMS0071,Bearing - Type D - Model 973,Bearings,Each,137.0,90.71,187,479,SUP010,Y,2024-01-01
|
| 73 |
+
BMS0072,Gasket - Type D - Model 324,Gaskets,Each,18.1,17.77,149,372,SUP007,Y,2024-01-01
|
| 74 |
+
BMS0073,Hose - Type D - Model 230,Hoses,Each,93.52,40.55,150,488,SUP001,Y,2024-01-01
|
| 75 |
+
BMS0074,Belt - Type D - Model 238,Belts,Each,107.35,27.54,116,394,SUP006,Y,2024-01-01
|
| 76 |
+
BMS0075,Gasket - Type D - Model 434,Gaskets,Each,20.49,28.29,121,415,SUP005,Y,2024-01-01
|
| 77 |
+
BMS0076,Belt - Type D - Model 119,Belts,Each,96.16,18.59,139,314,SUP002,Y,2024-01-01
|
| 78 |
+
BMS0077,Filter - Type A - Model 353,Filters,Each,28.96,10.03,89,322,SUP003,Y,2024-01-01
|
| 79 |
+
BMS0078,Valve - Type A - Model 677,Valves,Each,172.42,69.83,115,388,SUP003,Y,2024-01-01
|
| 80 |
+
BMS0079,Switche - Type A - Model 896,Switches,Each,77.35,61.2,77,496,SUP001,Y,2024-01-01
|
| 81 |
+
BMS0080,Bearing - Type D - Model 506,Bearings,Each,142.98,35.25,112,252,SUP005,Y,2024-01-01
|
| 82 |
+
BMS0081,Switche - Type A - Model 915,Switches,Each,88.87,51.91,138,472,SUP007,Y,2024-01-01
|
| 83 |
+
BMS0082,Seal - Type A - Model 618,Seals,Each,43.08,7.64,157,450,SUP002,Y,2024-01-01
|
| 84 |
+
BMS0083,Sensor - Type C - Model 750,Sensors,Each,183.19,78.95,89,422,SUP003,Y,2024-01-01
|
| 85 |
+
BMS0084,Pump - Type C - Model 730,Pumps,Each,427.12,221.61,173,438,SUP007,Y,2024-01-01
|
| 86 |
+
BMS0085,Switche - Type C - Model 430,Switches,Each,79.62,54.37,72,342,SUP008,Y,2024-01-01
|
| 87 |
+
BMS0086,Gasket - Type D - Model 683,Gaskets,Each,30.58,14.92,57,453,SUP006,Y,2024-01-01
|
| 88 |
+
BMS0087,Hose - Type D - Model 317,Hoses,Each,45.32,25.19,121,341,SUP009,Y,2024-01-01
|
| 89 |
+
BMS0088,Filter - Type B - Model 187,Filters,Each,31.89,29.52,192,323,SUP008,Y,2024-01-01
|
| 90 |
+
BMS0089,Valve - Type D - Model 911,Valves,Each,37.5,51.89,153,324,SUP005,Y,2024-01-01
|
| 91 |
+
BMS0090,Switche - Type C - Model 584,Switches,Each,58.74,29.53,190,369,SUP006,Y,2024-01-01
|
| 92 |
+
BMS0091,Valve - Type C - Model 413,Valves,Each,71.45,33.67,99,361,SUP002,Y,2024-01-01
|
| 93 |
+
BMS0092,Pump - Type B - Model 296,Pumps,Each,202.23,206.61,200,468,SUP010,Y,2024-01-01
|
| 94 |
+
BMS0093,Bearing - Type A - Model 952,Bearings,Each,53.29,37.79,95,354,SUP001,Y,2024-01-01
|
| 95 |
+
BMS0094,Pump - Type B - Model 380,Pumps,Each,137.3,87.16,124,264,SUP008,Y,2024-01-01
|
| 96 |
+
BMS0095,Belt - Type A - Model 687,Belts,Each,52.01,48.03,137,294,SUP001,Y,2024-01-01
|
| 97 |
+
BMS0096,Bearing - Type D - Model 216,Bearings,Each,128.65,52.86,68,495,SUP001,Y,2024-01-01
|
| 98 |
+
BMS0097,Hose - Type B - Model 930,Hoses,Each,61.34,27.72,113,260,SUP009,Y,2024-01-01
|
| 99 |
+
BMS0098,Sensor - Type B - Model 894,Sensors,Each,126.0,77.91,163,352,SUP010,Y,2024-01-01
|
| 100 |
+
BMS0099,Sensor - Type C - Model 682,Sensors,Each,141.26,95.89,75,306,SUP004,Y,2024-01-01
|
| 101 |
+
BMS0100,Bearing - Type A - Model 260,Bearings,Each,58.78,66.02,90,201,SUP007,Y,2024-01-01
|
data/promotions.csv
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
promo_id,item_code,start_date,end_date,discount_pct,promo_price,description
|
| 2 |
+
PROMO0001,BMS0084,2025-03-04,2025-04-03,15,363.05,15% Off - Holiday Special
|
| 3 |
+
PROMO0002,BMS0080,2025-06-09,2025-07-05,25,107.23,25% Off - Clearance
|
| 4 |
+
PROMO0003,BMS0049,2025-09-18,2025-10-11,30,79.9,30% Off - Flash Sale
|
| 5 |
+
PROMO0004,BMS0099,2025-07-02,2025-08-01,20,113.01,20% Off - Seasonal Sale
|
| 6 |
+
PROMO0005,BMS0095,2025-08-28,2025-09-16,10,46.81,10% Off - Seasonal Sale
|
| 7 |
+
PROMO0006,BMS0059,2025-09-11,2025-09-28,30,46.19,30% Off - Flash Sale
|
| 8 |
+
PROMO0007,BMS0079,2025-08-25,2025-09-13,15,65.75,15% Off - Clearance
|
| 9 |
+
PROMO0008,BMS0081,2025-08-06,2025-08-15,10,79.98,10% Off - Holiday Special
|
| 10 |
+
PROMO0009,BMS0093,2025-02-07,2025-03-08,20,42.63,20% Off - Holiday Special
|
| 11 |
+
PROMO0010,BMS0018,2025-03-09,2025-04-01,30,40.44,30% Off - Flash Sale
|
| 12 |
+
PROMO0011,BMS0020,2025-04-18,2025-04-29,10,31.57,10% Off - Flash Sale
|
| 13 |
+
PROMO0012,BMS0010,2025-06-23,2025-07-04,10,136.5,10% Off - Seasonal Sale
|
| 14 |
+
PROMO0013,BMS0025,2025-09-21,2025-10-14,25,19.47,25% Off - Holiday Special
|
| 15 |
+
PROMO0014,BMS0013,2025-03-01,2025-03-13,25,58.45,25% Off - Clearance
|
| 16 |
+
PROMO0015,BMS0080,2025-04-25,2025-05-09,20,114.38,20% Off - Seasonal Sale
|
| 17 |
+
PROMO0016,BMS0031,2025-09-27,2025-10-20,30,65.2,30% Off - Seasonal Sale
|
| 18 |
+
PROMO0017,BMS0061,2025-06-13,2025-07-08,25,27.64,25% Off - Holiday Special
|
| 19 |
+
PROMO0018,BMS0028,2025-07-11,2025-07-21,30,14.84,30% Off - Seasonal Sale
|
| 20 |
+
PROMO0019,BMS0011,2025-02-16,2025-03-17,15,28.68,15% Off - Holiday Special
|
| 21 |
+
PROMO0020,BMS0025,2025-09-23,2025-10-20,20,20.77,20% Off - Seasonal Sale
|
| 22 |
+
PROMO0021,BMS0040,2025-08-12,2025-08-22,30,9.69,30% Off - Flash Sale
|
data/requisitions.csv
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
req_id,item_code,quantity,req_date,status,qty,date
|
| 2 |
+
REQ9613,BMS0022,200.0,2025-11-12,Pending,,
|
| 3 |
+
REQ8649,BMS0054,50.0,2025-11-21,Pending,,
|
| 4 |
+
REQ5311,BMS0056,50.0,2025-11-18,Approved,,
|
| 5 |
+
REQ2710,BMS0069,200.0,2025-11-17,Ordered,,
|
| 6 |
+
REQ9804,BMS0098,50.0,2025-11-19,Approved,,
|
| 7 |
+
REQ8741,BMS0030,150.0,2025-11-20,Approved,,
|
| 8 |
+
REQ8550,BMS0015,100.0,2025-11-03,Ordered,,
|
| 9 |
+
REQ8326,BMS0024,150.0,2025-11-21,Ordered,,
|
| 10 |
+
REQ8459,BMS0077,50.0,2025-11-26,Ordered,,
|
| 11 |
+
REQ9846,BMS0004,150.0,2025-11-27,Pending,,
|
| 12 |
+
REQ3445,BMS0015,,,Pending,50.0,2025-11-29
|
data/suppliers.csv
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
id,name,lead_time,email
|
| 2 |
+
SUP001,Acme Filters Inc.,14,[email protected]
|
| 3 |
+
SUP002,Global Parts Co.,10,[email protected]
|
| 4 |
+
SUP003,Premium Components Ltd.,21,[email protected]
|
| 5 |
+
SUP004,Industrial Supply Corp.,7,[email protected]
|
| 6 |
+
SUP005,Quality Parts Group,12,[email protected]
|
| 7 |
+
SUP006,Precision Manufacturing,18,[email protected]
|
| 8 |
+
SUP007,Reliable Components,9,[email protected]
|
| 9 |
+
SUP008,Advanced Parts Solutions,15,[email protected]
|
| 10 |
+
SUP009,Express Supply Chain,5,[email protected]
|
| 11 |
+
SUP010,International Parts Hub,20,[email protected]
|
data/warehouses.csv
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
code,name,region
|
| 2 |
+
NA-01,North America - Columbus,NA
|
| 3 |
+
NA-02,North America - Memphis,NA
|
| 4 |
+
EU-01,Europe - Daventry,EU
|
| 5 |
+
EU-02,Europe - Rotterdam,EU
|
| 6 |
+
APAC-01,Asia Pacific - Singapore,APAC
|
| 7 |
+
APAC-02,Asia Pacific - Shanghai,APAC
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
pandas==2.1.3
|
| 4 |
+
numpy==1.26.2
|
| 5 |
+
scikit-learn==1.3.2
|
| 6 |
+
statsmodels==0.14.0
|
| 7 |
+
ctransformers==0.2.27
|
| 8 |
+
fpdf==1.7.2
|
| 9 |
+
pydantic==2.5.0
|
| 10 |
+
python-multipart==0.0.6
|
static/index.html
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>BMS AI Assistant</title>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
:root {
|
| 11 |
+
--primary-color: #D01010;
|
| 12 |
+
/* Cummins Red */
|
| 13 |
+
--primary-dark: #A00C0C;
|
| 14 |
+
--bg-color: #F3F4F6;
|
| 15 |
+
--chat-bg: #FFFFFF;
|
| 16 |
+
--user-msg-bg: #D01010;
|
| 17 |
+
--user-msg-text: #FFFFFF;
|
| 18 |
+
--bot-msg-bg: #F3F4F6;
|
| 19 |
+
--bot-msg-text: #1F2937;
|
| 20 |
+
--border-color: #E5E7EB;
|
| 21 |
+
--text-color: #1F2937;
|
| 22 |
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
| 23 |
+
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
body {
|
| 27 |
+
font-family: 'Inter', sans-serif;
|
| 28 |
+
background-color: var(--bg-color);
|
| 29 |
+
margin: 0;
|
| 30 |
+
display: flex;
|
| 31 |
+
justify-content: center;
|
| 32 |
+
height: 100vh;
|
| 33 |
+
color: var(--text-color);
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.chat-container {
|
| 37 |
+
width: 100%;
|
| 38 |
+
max-width: 900px;
|
| 39 |
+
background-color: var(--chat-bg);
|
| 40 |
+
box-shadow: var(--shadow-md);
|
| 41 |
+
overflow-y: auto;
|
| 42 |
+
display: flex;
|
| 43 |
+
flex-direction: column;
|
| 44 |
+
gap: 16px;
|
| 45 |
+
scroll-behavior: smooth;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.header {
|
| 49 |
+
display: none;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.message {
|
| 53 |
+
max-width: 80%;
|
| 54 |
+
padding: 14px 18px;
|
| 55 |
+
border-radius: 16px;
|
| 56 |
+
line-height: 1.5;
|
| 57 |
+
position: relative;
|
| 58 |
+
animation: fadeIn 0.3s ease-out;
|
| 59 |
+
display: flex;
|
| 60 |
+
align-items: flex-start;
|
| 61 |
+
gap: 12px;
|
| 62 |
+
font-size: 0.95rem;
|
| 63 |
+
box-shadow: var(--shadow-sm);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
@keyframes fadeIn {
|
| 67 |
+
from {
|
| 68 |
+
opacity: 0;
|
| 69 |
+
transform: translateY(10px);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
to {
|
| 73 |
+
opacity: 1;
|
| 74 |
+
transform: translateY(0);
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.message.user {
|
| 79 |
+
align-self: flex-end;
|
| 80 |
+
background-color: var(--user-msg-bg);
|
| 81 |
+
color: var(--user-msg-text);
|
| 82 |
+
border-bottom-right-radius: 4px;
|
| 83 |
+
flex-direction: row-reverse;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.message.bot {
|
| 87 |
+
align-self: flex-start;
|
| 88 |
+
background-color: var(--bot-msg-bg);
|
| 89 |
+
color: var(--bot-msg-text);
|
| 90 |
+
border-bottom-left-radius: 4px;
|
| 91 |
+
border: 1px solid var(--border-color);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.icon-container {
|
| 95 |
+
width: 36px;
|
| 96 |
+
height: 36px;
|
| 97 |
+
border-radius: 50%;
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
justify-content: center;
|
| 101 |
+
flex-shrink: 0;
|
| 102 |
+
background-color: white;
|
| 103 |
+
box-shadow: var(--shadow-sm);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.message.user .icon-container {
|
| 107 |
+
color: var(--primary-color);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.message.bot .icon-container {
|
| 111 |
+
background-color: var(--primary-color);
|
| 112 |
+
color: white;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.icon-container svg {
|
| 116 |
+
width: 20px;
|
| 117 |
+
height: 20px;
|
| 118 |
+
fill: currentColor;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
.message-content {
|
| 122 |
+
flex: 1;
|
| 123 |
+
word-wrap: break-word;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.input-area {
|
| 127 |
+
padding: 20px;
|
| 128 |
+
border-top: 1px solid var(--border-color);
|
| 129 |
+
display: flex;
|
| 130 |
+
gap: 12px;
|
| 131 |
+
background-color: #fff;
|
| 132 |
+
align-items: center;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
input[type="text"] {
|
| 136 |
+
flex: 1;
|
| 137 |
+
padding: 14px 20px;
|
| 138 |
+
border: 1px solid var(--border-color);
|
| 139 |
+
border-radius: 30px;
|
| 140 |
+
outline: none;
|
| 141 |
+
font-size: 1rem;
|
| 142 |
+
transition: all 0.2s;
|
| 143 |
+
font-family: inherit;
|
| 144 |
+
background-color: #F9FAFB;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
input[type="text"]:focus {
|
| 148 |
+
border-color: var(--primary-color);
|
| 149 |
+
background-color: #fff;
|
| 150 |
+
box-shadow: 0 0 0 3px rgba(208, 16, 16, 0.1);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
button {
|
| 154 |
+
background-color: var(--primary-color);
|
| 155 |
+
color: white;
|
| 156 |
+
border: none;
|
| 157 |
+
padding: 14px 28px;
|
| 158 |
+
border-radius: 30px;
|
| 159 |
+
cursor: pointer;
|
| 160 |
+
font-weight: 600;
|
| 161 |
+
transition: background-color 0.2s, transform 0.1s;
|
| 162 |
+
font-family: inherit;
|
| 163 |
+
display: flex;
|
| 164 |
+
align-items: center;
|
| 165 |
+
gap: 8px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
button:hover {
|
| 169 |
+
background-color: var(--primary-dark);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
button:active {
|
| 173 |
+
transform: scale(0.98);
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/* Table Styling */
|
| 177 |
+
.chart-container {
|
| 178 |
+
margin-top: 12px;
|
| 179 |
+
background: #fff;
|
| 180 |
+
border-radius: 8px;
|
| 181 |
+
border: 1px solid var(--border-color);
|
| 182 |
+
overflow: hidden;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
table {
|
| 186 |
+
width: 100%;
|
| 187 |
+
border-collapse: collapse;
|
| 188 |
+
font-size: 0.9rem;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
th,
|
| 192 |
+
td {
|
| 193 |
+
padding: 10px 14px;
|
| 194 |
+
text-align: left;
|
| 195 |
+
border-bottom: 1px solid var(--border-color);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
th {
|
| 199 |
+
background-color: #F9FAFB;
|
| 200 |
+
font-weight: 600;
|
| 201 |
+
color: var(--text-color);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
tr:last-child td {
|
| 205 |
+
border-bottom: none;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.download-btn {
|
| 209 |
+
display: inline-flex;
|
| 210 |
+
align-items: center;
|
| 211 |
+
gap: 8px;
|
| 212 |
+
background-color: var(--primary-color);
|
| 213 |
+
color: white;
|
| 214 |
+
padding: 10px 20px;
|
| 215 |
+
text-decoration: none;
|
| 216 |
+
border-radius: 25px;
|
| 217 |
+
font-weight: 600;
|
| 218 |
+
margin-top: 12px;
|
| 219 |
+
transition: background-color 0.2s;
|
| 220 |
+
font-size: 0.9rem;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.download-btn:hover {
|
| 224 |
+
background-color: var(--primary-dark);
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
/* Modal Styling */
|
| 228 |
+
.modal-overlay {
|
| 229 |
+
position: fixed;
|
| 230 |
+
top: 0;
|
| 231 |
+
left: 0;
|
| 232 |
+
width: 100%;
|
| 233 |
+
height: 100%;
|
| 234 |
+
background: rgba(0, 0, 0, 0.5);
|
| 235 |
+
display: flex;
|
| 236 |
+
justify-content: center;
|
| 237 |
+
align-items: center;
|
| 238 |
+
z-index: 1000;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.modal-content {
|
| 242 |
+
background: white;
|
| 243 |
+
padding: 2rem;
|
| 244 |
+
border-radius: 8px;
|
| 245 |
+
max-width: 500px;
|
| 246 |
+
width: 90%;
|
| 247 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.modal-title {
|
| 251 |
+
font-size: 1.5rem;
|
| 252 |
+
font-weight: 600;
|
| 253 |
+
margin-bottom: 1rem;
|
| 254 |
+
color: var(--primary-color);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.modal-body {
|
| 258 |
+
margin-bottom: 1.5rem;
|
| 259 |
+
line-height: 1.6;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.modal-btn {
|
| 263 |
+
background: var(--primary-color);
|
| 264 |
+
color: white;
|
| 265 |
+
border: none;
|
| 266 |
+
padding: 0.75rem 1.5rem;
|
| 267 |
+
border-radius: 4px;
|
| 268 |
+
cursor: pointer;
|
| 269 |
+
font-weight: 600;
|
| 270 |
+
width: 100%;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.modal-btn:hover {
|
| 274 |
+
background-color: var(--primary-dark);
|
| 275 |
+
}
|
| 276 |
+
</style>
|
| 277 |
+
</head>
|
| 278 |
+
|
| 279 |
+
<body>
|
| 280 |
+
<div class="chat-container">
|
| 281 |
+
<div class="header">
|
| 282 |
+
<svg viewBox="0 0 24 24">
|
| 283 |
+
<path
|
| 284 |
+
d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7V5.73C9.4 5.39 9 4.74 9 4a2 2 0 0 1 2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18A2.5 2.5 0 0 0 10 15.5A2.5 2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M12 8a6 6 0 0 0-6 6h12a6 6 0 0 0-6-6z" />
|
| 285 |
+
</svg>
|
| 286 |
+
BMS AI Assistant
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<div class="chat-window" id="chat-window">
|
| 290 |
+
<div class="message bot">
|
| 291 |
+
<div class="icon-container">
|
| 292 |
+
<svg viewBox="0 0 24 24">
|
| 293 |
+
<path
|
| 294 |
+
d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7V5.73C9.4 5.39 9 4.74 9 4a2 2 0 0 1 2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18A2.5 2.5 0 0 0 10 15.5A2.5 2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M12 8a6 6 0 0 0-6 6h12a6 6 0 0 0-6-6z" />
|
| 295 |
+
</svg>
|
| 296 |
+
</div>
|
| 297 |
+
<div class="message-content">
|
| 298 |
+
Hello! I am the BMS AI Assistant. How can I help you with demand forecasting, inventory, or supplier
|
| 299 |
+
information today?
|
| 300 |
+
</div>
|
| 301 |
+
</div>
|
| 302 |
+
</div>
|
| 303 |
+
|
| 304 |
+
<div class="input-area">
|
| 305 |
+
<input type="text" id="user-input" placeholder="Type your query..." autocomplete="off">
|
| 306 |
+
<button onclick="sendMessage()">
|
| 307 |
+
Send
|
| 308 |
+
<svg viewBox="0 0 24 24" style="width: 16px; height: 16px;">
|
| 309 |
+
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
|
| 310 |
+
</svg>
|
| 311 |
+
</button>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
|
| 315 |
+
<!-- Disclaimer Modal -->
|
| 316 |
+
<div class="modal-overlay" id="disclaimer-modal">
|
| 317 |
+
<div class="modal-content">
|
| 318 |
+
<div class="modal-title">Welcome to BMS AI Assistant</div>
|
| 319 |
+
<div class="modal-body">
|
| 320 |
+
<p>I am an AI assistant designed to help you with: </p>
|
| 321 |
+
<ul>
|
| 322 |
+
<li>Demand Forecasting</li>
|
| 323 |
+
<li>Inventory Management</li>
|
| 324 |
+
<li>Supplier Information</li>
|
| 325 |
+
</ul>
|
| 326 |
+
<p><strong>Important Disclaimer:</strong></p>
|
| 327 |
+
<p>While I strive to provide accurate information, I am an AI and may occasionally make mistakes. Please
|
| 328 |
+
verify critical data with the respective BMS departments before making significant business
|
| 329 |
+
decisions.</p>
|
| 330 |
+
<p>By clicking "I Understand", you acknowledge these terms.</p>
|
| 331 |
+
</div>
|
| 332 |
+
<button class="modal-btn" onclick="closeModal()">I Understand</button>
|
| 333 |
+
</div>
|
| 334 |
+
</div>
|
| 335 |
+
|
| 336 |
+
<script>
|
| 337 |
+
const chatWindow = document.getElementById('chat-window');
|
| 338 |
+
const userInput = document.getElementById('user-input');
|
| 339 |
+
const modal = document.getElementById('disclaimer-modal');
|
| 340 |
+
|
| 341 |
+
function closeModal() {
|
| 342 |
+
modal.style.display = 'none';
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
userInput.addEventListener('keypress', function (e) {
|
| 346 |
+
if (e.key === 'Enter') {
|
| 347 |
+
sendMessage();
|
| 348 |
+
}
|
| 349 |
+
});
|
| 350 |
+
|
| 351 |
+
async function sendMessage() {
|
| 352 |
+
const text = userInput.value.trim();
|
| 353 |
+
if (!text) return;
|
| 354 |
+
|
| 355 |
+
addMessage(text, 'user');
|
| 356 |
+
userInput.value = '';
|
| 357 |
+
|
| 358 |
+
try {
|
| 359 |
+
const response = await fetch('/api/chat', {
|
| 360 |
+
method: 'POST',
|
| 361 |
+
headers: { 'Content-Type': 'application/json' },
|
| 362 |
+
body: JSON.stringify({ message: text })
|
| 363 |
+
});
|
| 364 |
+
|
| 365 |
+
const data = await response.json();
|
| 366 |
+
let botHtml = data.answer.replace(/\n/g, '<br>');
|
| 367 |
+
|
| 368 |
+
if (data.forecast && data.forecast.length > 0) {
|
| 369 |
+
botHtml += renderForecastTable(data.forecast);
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
addMessage(botHtml, 'bot', true);
|
| 373 |
+
} catch (error) {
|
| 374 |
+
console.error('Error:', error);
|
| 375 |
+
addMessage("Sorry, something went wrong connecting to the server.", 'bot');
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
function addMessage(text, sender, isHtml = false) {
|
| 380 |
+
const div = document.createElement('div');
|
| 381 |
+
div.className = `message ${sender}`;
|
| 382 |
+
|
| 383 |
+
const iconDiv = document.createElement('div');
|
| 384 |
+
iconDiv.className = 'icon-container';
|
| 385 |
+
|
| 386 |
+
if (sender === 'user') {
|
| 387 |
+
iconDiv.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" /></svg>`;
|
| 388 |
+
} else {
|
| 389 |
+
iconDiv.innerHTML = `<svg viewBox="0 0 24 24"><path d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1h-1v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1H2a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h1a7 7 0 0 1 7-7V5.73C9.4 5.39 9 4.74 9 4a2 2 0 0 1 2-2M7.5 13A2.5 2.5 0 0 0 5 15.5A2.5 2.5 0 0 0 7.5 18A2.5 2.5 0 0 0 10 15.5A2.5 2.5 0 0 0 7.5 13m9 0a2.5 2.5 0 0 0-2.5 2.5a2.5 2.5 0 0 0 2.5 2.5a2.5 2.5 0 0 0 2.5-2.5a2.5 2.5 0 0 0-2.5-2.5M12 8a6 6 0 0 0-6 6h12a6 6 0 0 0-6-6z" /></svg>`;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
const contentDiv = document.createElement('div');
|
| 393 |
+
contentDiv.className = 'message-content';
|
| 394 |
+
|
| 395 |
+
if (isHtml) {
|
| 396 |
+
contentDiv.innerHTML = text;
|
| 397 |
+
} else {
|
| 398 |
+
contentDiv.textContent = text;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
div.appendChild(iconDiv);
|
| 402 |
+
div.appendChild(contentDiv);
|
| 403 |
+
|
| 404 |
+
chatWindow.appendChild(div);
|
| 405 |
+
chatWindow.scrollTop = chatWindow.scrollHeight;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
function renderForecastTable(forecastData) {
|
| 409 |
+
const displayData = forecastData.slice(0, 7);
|
| 410 |
+
let html = `<div class="chart-container"><table><thead><tr><th>Date</th><th>Qty</th></tr></thead><tbody>`;
|
| 411 |
+
|
| 412 |
+
displayData.forEach(row => {
|
| 413 |
+
html += `<tr><td>${row.date.split('T')[0]}</td><td>${row.qty}</td></tr>`;
|
| 414 |
+
});
|
| 415 |
+
|
| 416 |
+
if (forecastData.length > 7) {
|
| 417 |
+
html += `<tr><td colspan="2">...and ${forecastData.length - 7} more days</td></tr>`;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
html += `</tbody></table></div>`;
|
| 421 |
+
return html;
|
| 422 |
+
}
|
| 423 |
+
</script>
|
| 424 |
+
</body>
|
| 425 |
+
|
| 426 |
+
</html>
|
static/reports/forecast_BMS0001_20251130230015.pdf
ADDED
|
Binary file (2.43 kB). View file
|
|
|
static/reports/forecast_BMS0015_20251129164645.pdf
ADDED
|
Binary file (2.43 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126172817.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126173353.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126175819.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126180611.pdf
ADDED
|
Binary file (2.45 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126180710.pdf
ADDED
|
Binary file (2.45 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126181554.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126182220.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126183143.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_FS1234_20251126184230.pdf
ADDED
|
Binary file (2.44 kB). View file
|
|
|
static/reports/forecast_TEST-ITEM_20251126172116.pdf
ADDED
|
Binary file (1.5 kB). View file
|
|
|
static/reports/report_20251126172116.pdf
ADDED
|
Binary file (1.34 kB). View file
|
|
|
static/reports/report_20251129165821.pdf
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 0 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%PDF-1.3
|
| 2 |
+
3 0 obj
|
| 3 |
+
<</Type /Page
|
| 4 |
+
/Parent 1 0 R
|
| 5 |
+
/Resources 2 0 R
|
| 6 |
+
/Contents 4 0 R>>
|
| 7 |
+
endobj
|
| 8 |
+
4 0 obj
|
| 9 |
+
<</Filter /FlateDecode /Length 549>>
|
| 10 |
+
stream
|
| 11 |
+
x�e��n�0E����k����m�)
|
| 12 |
+
�ht�
|
| 13 |
+
U��/n�4�J�
|
| 14 |
+
endstream
|
| 15 |
+
endobj
|
| 16 |
+
1 0 obj
|
| 17 |
+
<</Type /Pages
|
| 18 |
+
/Kids [3 0 R ]
|
| 19 |
+
/Count 1
|
| 20 |
+
/MediaBox [0 0 595.28 841.89]
|
| 21 |
+
>>
|
| 22 |
+
endobj
|
| 23 |
+
5 0 obj
|
| 24 |
+
<</Type /Font
|
| 25 |
+
/BaseFont /Helvetica-Bold
|
| 26 |
+
/Subtype /Type1
|
| 27 |
+
/Encoding /WinAnsiEncoding
|
| 28 |
+
>>
|
| 29 |
+
endobj
|
| 30 |
+
6 0 obj
|
| 31 |
+
<</Type /Font
|
| 32 |
+
/BaseFont /Helvetica
|
| 33 |
+
/Subtype /Type1
|
| 34 |
+
/Encoding /WinAnsiEncoding
|
| 35 |
+
>>
|
| 36 |
+
endobj
|
| 37 |
+
7 0 obj
|
| 38 |
+
<</Type /Font
|
| 39 |
+
/BaseFont /Helvetica-Oblique
|
| 40 |
+
/Subtype /Type1
|
| 41 |
+
/Encoding /WinAnsiEncoding
|
| 42 |
+
>>
|
| 43 |
+
endobj
|
| 44 |
+
2 0 obj
|
| 45 |
+
<<
|
| 46 |
+
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
|
| 47 |
+
/Font <<
|
| 48 |
+
/F1 5 0 R
|
| 49 |
+
/F2 6 0 R
|
| 50 |
+
/F3 7 0 R
|
| 51 |
+
>>
|
| 52 |
+
/XObject <<
|
| 53 |
+
>>
|
| 54 |
+
>>
|
| 55 |
+
endobj
|
| 56 |
+
8 0 obj
|
| 57 |
+
<<
|
| 58 |
+
/Producer (PyFPDF 1.7.2 http://pyfpdf.googlecode.com/)
|
| 59 |
+
/CreationDate (D:20251129165821)
|
| 60 |
+
>>
|
| 61 |
+
endobj
|
| 62 |
+
9 0 obj
|
| 63 |
+
<<
|
| 64 |
+
/Type /Catalog
|
| 65 |
+
/Pages 1 0 R
|
| 66 |
+
/OpenAction [3 0 R /FitH null]
|
| 67 |
+
/PageLayout /OneColumn
|
| 68 |
+
>>
|
| 69 |
+
endobj
|
| 70 |
+
xref
|
| 71 |
+
0 10
|
| 72 |
+
0000000000 65535 f
|
| 73 |
+
0000000706 00000 n
|
| 74 |
+
0000001094 00000 n
|
| 75 |
+
0000000009 00000 n
|
| 76 |
+
0000000087 00000 n
|
| 77 |
+
0000000793 00000 n
|
| 78 |
+
0000000894 00000 n
|
| 79 |
+
0000000990 00000 n
|
| 80 |
+
0000001218 00000 n
|
| 81 |
+
0000001327 00000 n
|
| 82 |
+
trailer
|
| 83 |
+
<<
|
| 84 |
+
/Size 10
|
| 85 |
+
/Root 9 0 R
|
| 86 |
+
/Info 8 0 R
|
| 87 |
+
>>
|
| 88 |
+
startxref
|
| 89 |
+
1430
|
| 90 |
+
%%EOF
|