Industrial AI assistant for a resin-coated paper manufacturing plant.
APEX connects directly to the factory's live database and lets engineers and plant managers ask plain-English questions about production — reel output, work orders, machine performance, gas consumption, dryer temperatures, and MES status — and get back data-grounded, context-aware answers in real time.
Instead of navigating dashboards and running SQL queries, operators ask APEX:
"Which machine had the most failed MES entries this week?" "Compare gas consumption between IM6 and IM7 for work order WO-TEST-042." "Show me all reels where zone 8 dropped below 185°C."
APEX pulls the relevant data from the database, injects it as context into a local LLM, and returns an answer that interprets the numbers in production context — not just repeating them.
IM6 / IM7 machines
│ (MQTT)
Node-RED broker
│
PostgreSQL (reel_data table)
│
APEX Backend (FastAPI)
├── Query router — detects intent from user message, fetches relevant DB data
├── System prompt — gives the LLM full plant context (machines, reel ID format, KPIs)
└── Ollama (local LLM, e.g. gemma3) — generates the final response
│
React Frontend — dark industrial chat UI
No data leaves the plant. The LLM runs locally via Ollama — no cloud API calls, no external data transmission.
Every reel produced on IM6 or IM7 generates a record in the reel_data table:
| Field | Description |
|---|---|
reel_number |
Unique ID — format YY<M>XXXXXXX (e.g. 256000001 = 2025, IM6, seq 1) |
work_order_no |
Links the reel to a production order |
machine |
IM6 or IM7 |
roller |
A or B (flying splice unwind position) |
reel_length |
Finished reel length (m) |
web_speed_average/max/min |
Web speed across the reel run (m/min) |
web_tension_average |
Web tension (N) |
web_temp_avg |
Average web surface temperature (°C) |
gas_consumed |
Gas used per reel (m³) — efficiency indicator |
zone1_average … zone8_average |
Dryer oven zone temps (°C), typical 180–200°C |
mes_status |
SUCCESS or failure code — MES integration result |
The backend detects intent from the user's message and fetches only the relevant data — avoiding unnecessary DB load and keeping the LLM context focused:
| Trigger keywords | Data fetched |
|---|---|
reel number (e.g. 256000001) |
Single reel detail |
WO-xxx / work order number |
All reels for that work order |
production, output, reel |
Recent reels + production summary |
speed, tension |
Speed/tension analysis |
gas, consumption, energy |
Gas consumption report |
fail, error, MES |
Failed/errored reel records |
Time words (today, week, month) |
Adjusts lookback window automatically |
| Layer | Technology |
|---|---|
| Backend | FastAPI (Python) |
| LLM | Ollama — runs locally (tested with gemma3) |
| Database | PostgreSQL + SQLAlchemy |
| Data pipeline | MQTT → Node-RED → PostgreSQL |
| Frontend | React |
| Data processing | pandas |
- Python 3.10+
- Node.js 18+
- PostgreSQL with the
reel_datatable populated - Ollama installed and running locally
git clone https://github.com/your-username/apex.git
cd apex
cp .env.example .env
# Edit .env with your PostgreSQL credentials and Ollama model namecd backend
pip install fastapi uvicorn sqlalchemy psycopg2-binary pandas python-dotenv ollama
uvicorn main:app --reload --port 8000ollama pull gemma3cd frontend/apex-ui
npm install
npm startOpen http://localhost:3000 — the chat UI connects to the backend at http://localhost:8000.
What was total production output this week?
Compare IM6 and IM7 reel lengths for the last 30 days.
Show me work order WO-TEST-001.
Which reels had MES failures yesterday?
What's the gas consumption trend over the last month?
Give me the detail for reel 256000042.