This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.
When My AI Stress Dashboard Started Making Up the Data
I built NeuroSense AI as a human-centered stress insight assistant powered by Gemma.
The idea is simple: a user describes how they are feeling naturally, the AI analyzes the text, and the application presents stress and emotion insights through a dashboard.
Everything appeared to be working.
Gemma was returning meaningful results.
The database was storing sessions.
The dashboard was displaying a stress trend.
But when I looked more closely at the dashboard, I found a problem:
The trend wasn't actually coming from the AI results.
The dashboard was generating its own stress numbers.
That turned what looked like a working visualization into a data correctness bug.
Project Overview
NeuroSense AI is a Streamlit-based AI application that allows users to express their thoughts naturally and receive emotional insights.
The application uses:
- Python
- Streamlit
- OpenRouter
- Gemma 3
- SQLite
- Pandas
- Plotly
- Sentry
The original data flow looked roughly like this:
User
↓
Streamlit Chat
↓
Gemma AI
↓
Analysis
↓
SQLite
↓
Dashboard
The goal was for the dashboard to represent the actual AI analysis produced for each session.
The Bug
While inspecting the dashboard implementation, I found this code:
stress = []
score = 30
for i in range(count):
score = min(
score + 8,
100
)
stress.append(score)
The problem was that score had no relationship to the stress level returned by Gemma.
It only depended on the number of sessions.
For example, with four sessions, the dashboard could generate:
Session 1 → 38
Session 2 → 46
Session 3 → 54
Session 4 → 62
even if the AI had actually classified those sessions as:
Low
Low
High
Low
The visualization therefore suggested an increasing stress trend that the user had never actually produced.
This was a data correctness problem rather than just a UI issue.
Root Cause
The root cause was that the original database schema did not store structured stress information.
The original table contained:
history
├── id
├── message
└── response
The AI response was stored as a general response, while the dashboard only knew the total number of sessions.
Because the dashboard did not have access to an actual stress_level field, it generated a synthetic trend.
Fixing the AI Data Pipeline
I changed the Gemma response format so that the model returns structured JSON containing:
{
"stress_level": "Low",
"emotion": "Anxious",
"explanation": "The user expresses calmness but also acknowledges a slight worry regarding upcoming exams.",
"recommendations": "Practice relaxation techniques and focus on preparing for exams to reduce worry."
}
The application validates the returned fields and only accepts these stress categories:
Low
Moderate
High
This gives the rest of the application structured information instead of treating the complete AI response as one block of text.
Testing the Gemma Integration
I tested the updated AI pipeline using natural-language input.
For example:
I feel calm today but slightly worried about my exams.
Gemma returned:
Stress Level: Low
Emotion: Anxious
along with an explanation and recommendations.
The important part is that the result is now structured and can be persisted as individual fields.
Updating the Database
I extended the SQLite history table to store:
history
├── id
├── message
├── response
├── stress_level
└── emotion
I also added a safe migration for existing databases.
Instead of deleting the existing database, the application checks whether the new columns exist and adds them when necessary:
cursor.execute("PRAGMA table_info(history)")
columns = [column[1] for column in cursor.fetchall()]
if "stress_level" not in columns:
cursor.execute(
"ALTER TABLE history ADD COLUMN stress_level TEXT"
)
if "emotion" not in columns:
cursor.execute(
"ALTER TABLE history ADD COLUMN emotion TEXT"
)
This allowed me to introduce the fix without throwing away previously stored sessions.
Replacing the Synthetic Dashboard
The dashboard was then changed to read the actual stress_level stored for each session.
Instead of:
score = 30
for i in range(count):
score = min(score + 8, 100)
the dashboard now uses the stored AI classification.
Because Gemma returns categorical values rather than numerical stress scores, I use visualization positions:
stress_mapping = {
"Low": 33,
"Moderate": 66,
"High": 100
}
These values are only chart coordinates.
They are not claimed to be numerical stress scores generated by Gemma.
The chart labels remain:
Low
Moderate
High
This makes the visualization represent the actual AI classifications.
Now the data flow is:
User Input
↓
Gemma
↓
Structured AI Analysis
↓
Stress Level + Emotion
↓
SQLite
↓
Dashboard
↓
Actual Session Data
Adding Sentry for AI Failures
Fixing the dashboard solved the data correctness problem, but I also wanted the AI failure path to be observable.
Previously, an API exception could interrupt the Streamlit application.
I added Sentry monitoring around the AI analysis and persistence operation:
try:
result = analyze_with_gemma(text)
save_history(
text,
result["explanation"],
result["stress_level"],
result["emotion"]
)
except Exception as e:
sentry_sdk.capture_exception(e)
st.error(
"AI analysis failed. Please try again."
)
This creates a much safer failure path.
Before
Gemma API failure
↓
Unhandled exception
↓
Application failure
After
Gemma API failure
↓
Exception
┌───┴────┐
↓ ↓
Sentry User
↓ ↓
Trace Friendly
captured message
Most importantly, the database write occurs only after a successful AI response.
Therefore, a failed AI request is not recorded as a successful analysis.
Best Use of Sentry
To verify that the monitoring path actually worked, I temporarily introduced a controlled exception:
raise RuntimeError(
"NeuroSenseAI Sentry test: simulated AI failure"
)
The application caught the exception and displayed:
AI analysis failed. Please try again.
Sentry also received the exception and recorded the Python traceback.
This confirmed that the application can report failures from the AI analysis path to Sentry.
The simulated exception was used only for testing and was removed immediately afterward.
I did not use Sentry to claim that it discovered the original dashboard bug. The dashboard bug was identified by inspecting the application's data flow; Sentry was added to provide observability for runtime failures in the AI pipeline.
Before vs After
Before
Gemma
↓
AI response
↓
SQLite
↓
Dashboard
↓
Synthetic trend
38 → 46 → 54 → 62
After
Gemma
↓
Structured result
↓
Stress Level + Emotion
↓
SQLite
↓
Dashboard
↓
Actual AI results
The application now has a consistent path from AI analysis to stored data to visualization.
What Changed Technically
| Area | Before | After |
|---|---|---|
| AI output | Unstructured response | Structured JSON |
| Stress level storage | Not stored separately | Stored in SQLite |
| Emotion storage | Not stored separately | Stored in SQLite |
| Dashboard data | Synthetic | Actual AI classifications |
| Existing database | No migration | Safe column migration |
| AI failure handling | Could interrupt execution | Exception captured |
| Error monitoring | No Sentry monitoring | Sentry exception monitoring |
| Failed analysis | Could become problematic output | Not persisted as a successful session |
Testing
I tested the application locally after making the changes.
The testing process included:
- Starting the Streamlit application.
- Sending natural-language input through the Chat page.
- Confirming that Gemma returned structured analysis.
- Confirming that the stress level and emotion were stored.
- Opening the Dashboard and verifying that the trend was based on stored AI classifications.
- Testing the Sentry exception path with a controlled error.
- Confirming that Sentry received the exception.
- Removing the temporary test exception.
- Running
git diff --check. - Confirming that the working tree was clean.
- Pushing the final changes to GitHub.
The final Git state was:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
What I Learned
The biggest lesson from this bug was that a visualization can look completely reasonable while still representing incorrect data.
The original dashboard was technically capable of rendering a graph, but the graph wasn't connected to the actual source of truth.
The fix wasn't about making the chart prettier.
It was about making sure that:
AI result
=
Stored result
=
Displayed result
I also learned that AI applications need explicit failure handling. An AI API failure shouldn't silently become application data. It should be observable, recoverable, and excluded from successful-session history.
Source Code
The complete project is available here:
Conclusion
This bug started as a simple dashboard inspection and turned into a broader data-flow improvement.
NeuroSense AI now:
- Stores structured AI analysis results.
- Preserves stress and emotion information with each session.
- Displays actual AI classifications instead of a synthetic stress progression.
- Handles AI failures without storing them as successful sessions.
- Reports runtime exceptions to Sentry.
- Preserves existing database data through a safe schema migration.
For me, the most important part of the fix was making the dashboard tell the truth about the data behind it.
Thanks to the DEV and Sentry teams for organizing the Summer Bug Smash!



Top comments (0)