
From Raw Data to AI Insights: Building a Serverless Analytics + GenAI Dashboard with Athena & Bedrock
How I built a fully serverless analytics system that combines Amazon QuickSight dashboards with a two-step Amazon Bedrock AI pipeline — and presented it at AWS Summit Singapore 2026.
The Problem That Started It All
I'm a data engineer working in manufacturing. We have dashboards everywhere — production lines, shifts, sites, products, KPIs. The data is there. The charts are there.
But here's what I kept hearing from our engineering heads:
"Why are we low this week? What changed? What should I look at first?"
Dashboards answer what happened. They show you OEE (Overall Equipment Effectiveness) dropped to 58%. Great. But they don't tell you why it dropped, or what to do about it.
That gap — between seeing data and understanding it — is what this project fills.
I presented this system at AWS Summit Singapore 2026 (session DEV202) as a reusable architecture pattern for combining serverless analytics with generative AI. This article is the deep dive into how it works.

What I Built
A fully serverless system that takes a natural language question like:
"Why is Line 1 performing poorly?"
...and returns a data-grounded explanation:
"Line 1's OEE averaged 63% over the last 7 days, primarily dragged down by low availability (68%). Machine breakdowns totaled 142 minutes — the single largest loss contributor. Recommended action: investigate recurring equipment failures on Line 1 and review preventive maintenance schedules."
No hardcoded logic. No regex NLP. The AI genuinely understands the question, queries real data, and reasons about it.

The Architecture
1
2
3
4
5
User question
→ Lambda (Step 1: Bedrock intent extraction)
→ Athena (dynamic SQL query)
→ Lambda (Step 2: Bedrock data-grounded explanation)
→ Structured JSON responseThe full stack:
| Layer | Service | Purpose |
|---|---|---|
| Storage | Amazon S3 | Raw OEE data (CSV) |
| Catalog | AWS Glue | Schema discovery, Data Catalog |
| Query | Amazon Athena | Serverless SQL on S3 |
| Visualization | Amazon QuickSight | Embedded BI dashboards |
| NL Queries | QuickSight Q | Natural language data exploration |
| AI Orchestration | AWS Lambda | Two-step Bedrock pipeline |
| AI Reasoning | Amazon Bedrock (Nova Lite) | Intent extraction + explanation generation |


Everything is serverless. No servers to manage. Pay for what you use. Costs near-zero when idle.
The Dataset
A manufacturing OEE (Overall Equipment Effectiveness) dataset:
- 600 rows spanning January–March 2026
- Dimensions: 3 production lines, 3 sites, 2 shifts, 2 products
- KPIs: OEE, Availability, Efficiency, Quality
- Loss drivers: break_time, maintenance, shift_meeting, machine_breakdown, material_shortage, changeover_time, defects
OEE is the gold standard for manufacturing productivity:
1
OEE = Availability × Performance × QualityA drop in any component immediately lowers the overall score.
Three Layers of Intelligence
The system provides three ways to interact with the data:
1. Dashboard (Amazon QuickSight)
KPI monitoring and visual analytics. Embedded directly into the React frontend. This is the "at-a-glance" layer — trends, comparisons, breakdowns by dimension.
2. Natural Language Q&A (QuickSight Q)
Ask the dataset questions in plain English: "What is the average OEE?", "Total output by production line." Q interprets the question, queries the dataset, and returns charts and numbers. Great for data exploration — but limited to "what happened" questions.
3. AI Insights (Lambda + Bedrock)
The reasoning layer. Explains why something is happening, identifies root causes, detects trends, and recommends actions. This is where generative AI adds value beyond traditional BI.
Why are they separate? QuickSight Q is a managed feature — you embed it as-is, no customization of responses. The AI layer is your own Lambda — you control the prompts, the reasoning logic, and the output format. Different tools, different purposes.
How the AI Works: Two-Step Bedrock Pipeline
Every question goes through two Bedrock calls. Here's why you can't do it in one: you need Athena data to give a good answer, but you can't query Athena with a messy human question. You need structured filters first.
Step 1: Intent Extraction
The user's raw question is sent to Bedrock with a structured prompt:
1
2
3
4
5
6
7
8
9
INTENT_EXTRACTION_PROMPT = """
Extract structured intent from this user question about a manufacturing OEE dashboard.
Question: "{question}"
Available KPIs: OEE, Availability, Efficiency, Quality
Entity types: production_line, site, shift, product
...
"""Bedrock returns structured JSON:
1
2
3
4
5
6
{
"kpi": "OEE",
"entities": [{"type": "production_line", "name": "Line 1"}],
"time_range": "last 7 days",
"mode": "explain_context"
}This handles synonyms ("rejects" → Quality, "uptime" → Availability), typos, messy phrasing — the LLM does all the understanding. No regex, no hardcoded keyword lists.
It also decides the mode:
- Knowledge mode — concept questions ("What is OEE?"). No data needed.
- Explain context mode — analytical questions ("Why is Line 1 low?"). Queries live data.
Step 2: Athena Query + Explanation
For explain context mode, the Lambda builds a dynamic SQL query from the extracted intent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
"site", "production_line", "shift", "product",
AVG("oee") AS kpi_value,
AVG("availability") AS "availability",
AVG("efficiency") AS "efficiency",
AVG("quality") AS "quality",
COUNT(*) AS record_count,
SUM("machine_breakdown_minutes") AS "machine_breakdown_minutes",
...
FROM "oee_db"."oeeraw"
WHERE lower("production_line") = lower('Line 1')
AND TRY_CAST("date" AS DATE) BETWEEN date_add('day', -6, max_date) AND max_date
GROUP BY "site", "production_line", "shift", "product"
UNION ALL
SELECT 'ALL', 'ALL', 'ALL', 'ALL', AVG("oee"), ... -- Summary row
ORDER BY kpi_value ASCKey design decisions:
- GROUP BY all dimensions — so Bedrock sees the full breakdown (which site/shift/product is dragging performance down)
- UNION ALL summary row — pre-computed averages so Bedrock doesn't do mental math wrong
- Parallel trend query — daily KPIs for trend detection ("OEE has declined from 72% to 58% over the past two weeks")
The Athena results are then passed to Bedrock with a detailed explanation prompt including a column guide and analysis instructions. Bedrock reads the numbers and generates a structured response:
1
2
3
4
5
6
{
"summary": "Line 1 averaged 63% OEE over the last 7 days...",
"why": ["Availability is the weakest component at 68%...", ...],
"actions": ["Investigate machine breakdowns on Line 1...", ...],
"grounding_note": "Based on Athena query of Line 1 data (12 records)"
}Multi-Entity Comparisons
The system supports comparing multiple entities in one question:
"Line 1 vs Line 3 in February in terms of quality"
Intent extraction returns multiple entities:
1
2
3
4
5
6
7
8
{
"kpi": "Quality",
"entities": [
{"type": "production_line", "name": "Line 1"},
{"type": "production_line", "name": "Line 3"}
],
"time_range": "February"
}The query uses an
IN clause:1
2
WHERE lower("production_line") IN (lower('Line 1'), lower('Line 3'))
AND date_trunc('month', ...) = DATE '2026-02-01'Bedrock gets rows for both lines and can compare them directly.
Trend Detection
A second lightweight Athena query runs in parallel — daily KPI averages for the same filters:
1
2
3
4
5
6
7
8
SELECT
TRY_CAST("date" AS DATE) AS trend_date,
AVG("oee"), AVG("availability"), AVG("efficiency"), AVG("quality"),
COUNT(*) AS record_count
FROM "oee_db"."oeeraw"
WHERE [same filters as main query]
GROUP BY TRY_CAST("date" AS DATE)
ORDER BY trend_date ASCThis gives Bedrock a time series. Instead of just saying "OEE is 66%", it can now say "OEE has been declining — it dropped from 74% on March 18 to 58% on March 28."
Both queries run in parallel using
ThreadPoolExecutor to minimize latency.Preventing Hallucination
The system uses Athena grounding — every analytical answer is based on real query results, not imagination:
- The explanation prompt explicitly says: "Do NOT invent numbers — only reference values present in the data"
- Athena results are pasted directly into the prompt as JSON
- If Athena returns no data, the AI acknowledges it honestly instead of guessing
- Empty rows (record_count = 0) are filtered out before reaching Bedrock
The Frontend
The frontend is intentionally minimal — a React app with:
- Embedded QuickSight dashboard
- An assistant panel with two tabs: Data Q&A (QuickSight Q) and AI Insights (Bedrock)
- An "I understood:" intent line showing what the AI extracted from the question
1
2
3
4
5
6
7
8
9
10
11
{message.content.intent && (
<p className="intent-line">
I understood: {[
message.content.intent.entities?.length > 0
? message.content.intent.entities.map((e) => e.name).join(' vs ')
: null,
message.content.intent.kpi,
message.content.intent.time_range,
].filter(Boolean).join(' · ') || 'general question'}
</p>
)}The frontend sends just
{"user_question": "..."} to the Lambda. All intelligence lives server-side.What's Intentionally Out of Scope
- QuickSight visual callbacks — clicking a chart doesn't pass context to the AI (yet)
- Real-time streaming — batch data in S3, not live sensor feeds
- ML forecasting — the AI explains what happened, doesn't predict the future
- Conversation memory — each question is independent
These are natural extensions, not limitations of the architecture.
Cost
Per question: ~$0.003 USD (two Bedrock calls + two Athena queries + Lambda execution).
For a small team asking ~100 questions/month: ~$25/month total (QuickSight license is the biggest cost).
For enterprise at ~10,000 questions/month: ~$350–850/month. Still cheaper than managing your own BI servers and ML infrastructure.
Beyond Manufacturing
The architecture is domain-agnostic. The manufacturing OEE dataset is just the example. Swap the dataset and the prompts:
- Fintech — fraud detection, transaction anomaly explanation
- IoT — device health monitoring, sensor anomaly reasoning
- SaaS — usage analytics, churn prediction, engagement insights
Same pattern: Data → Analytics → AI-driven decisions.
All configuration lives in Lambda environment variables — database name, table name, column mappings, driver columns. No code change needed to point at a different domain.
Key Takeaways
- Dashboards are the baseline, not the goal. The real value is explaining why and recommending what next.
- Two Bedrock calls > one. The first understands the question. The second explains the data. You can't skip the first.
- Athena grounding prevents hallucination. Give the AI real numbers and it stays honest.
- No hardcoded NLP. Let the LLM do the understanding — it handles synonyms, typos, and messy questions that regex never could.
- Serverless means zero idle cost. The whole system costs fractions of a cent per question and scales automatically.
Links
- 🔗 AWS Summit Vlog: Watch Here
- 📊 Demonstration: Watch Here
- 🌐 Website: Know More
The goal isn't better reporting. It's better decisions.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article