DEV Community

Ekram Zafar
Ekram Zafar

Posted on

When My AI Stress Dashboard Started Making Up the Data

Summer Bug Smash: Clear the Lineup 🐛🛹

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

even if the AI had actually classified those sessions as:

Low
Low
High
Low
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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."
}
Enter fullscreen mode Exit fullscreen mode

The application validates the returned fields and only accepts these stress categories:

Low
Moderate
High
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Gemma returned:

Stress Level: Low
Emotion: Anxious
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
    )
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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."
    )
Enter fullscreen mode Exit fullscreen mode

This creates a much safer failure path.

Before

Gemma API failure
       ↓
Unhandled exception
       ↓
Application failure
Enter fullscreen mode Exit fullscreen mode

After

Gemma API failure
       ↓
Exception
   ┌───┴────┐
   ↓        ↓
Sentry    User
   ↓        ↓
Trace    Friendly
captured  message
Enter fullscreen mode Exit fullscreen mode

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"
)
Enter fullscreen mode Exit fullscreen mode

The application caught the exception and displayed:

AI analysis failed. Please try again.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

After

Gemma
  ↓
Structured result
  ↓
Stress Level + Emotion
  ↓
SQLite
  ↓
Dashboard
  ↓
Actual AI results
Enter fullscreen mode Exit fullscreen mode

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:

  1. Starting the Streamlit application.
  2. Sending natural-language input through the Chat page.
  3. Confirming that Gemma returned structured analysis.
  4. Confirming that the stress level and emotion were stored.
  5. Opening the Dashboard and verifying that the trend was based on stored AI classifications.
  6. Testing the Sentry exception path with a controlled error.
  7. Confirming that Sentry received the exception.
  8. Removing the temporary test exception.
  9. Running git diff --check.
  10. Confirming that the working tree was clean.
  11. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

NeuroSenseAI on GitHub

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)