# Project export: Postura

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Fix your posture with specialized hardware and AI agents!
- Devpost: https://devpost.com/software/postura-dvyoqf
- GitHub: https://github.com/kaitlynhuang26/cal_hacks
- Video: https://www.youtube.com/embed/gX9Y_jAXG7w?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Jerry (20 commits), kaitlynhuang26 (5 commits), miltoti (1 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

Nowadays, many teens and young adults overlook their health while constantly chasing academic or career success. We've seen it firsthand, countless friends dealing with neck and back pain from long study sessions or hours spent hunched over laptops in the library. We wanted to change that. After conducting market research, we found this issue is widespread among students and young professionals. When looking at competitors like Upright Go 2, we noticed a gap: their product works well, but it mainly targets older adults. The design isn't appealing to younger users, and the interface feels clunky and outdated. That's where our idea comes in, a portable, stylish posture tracking necklace designed specifically for teens and young adults. It's lightweight, easy to use, and connects to an app that gives users real-time posture feedback and monthly insights to help them track their progress. Our goal is to make good posture effortless and fashionable, turning health awareness into something people actually want to wear every day.


## Detected evidence (automated analysis)

Indexed codebase: 81 recognized source files, 312 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (86 of 86)

```
.gitignore
api_client.py
backend
ble_service.py
client
Design Postura Mobile Dashboard (1)/index.html
Design Postura Mobile Dashboard (1)/package.json
Design Postura Mobile Dashboard (1)/README.md
Design Postura Mobile Dashboard (1)/src/App.tsx
Design Postura Mobile Dashboard (1)/src/Attributions.md
Design Postura Mobile Dashboard (1)/src/components/AchievementsCard.tsx
Design Postura Mobile Dashboard (1)/src/components/BottomNav.tsx
Design Postura Mobile Dashboard (1)/src/components/chatbot-component.tsx
Design Postura Mobile Dashboard (1)/src/components/CircularProgress.tsx
Design Postura Mobile Dashboard (1)/src/components/DailyProgressCard.tsx
Design Postura Mobile Dashboard (1)/src/components/figma/ImageWithFallback.tsx
Design Postura Mobile Dashboard (1)/src/components/FriendsLeaderboard.tsx
Design Postura Mobile Dashboard (1)/src/components/MinimalistPostureSummary.tsx
Design Postura Mobile Dashboard (1)/src/components/PostureSummaryCard.tsx
Design Postura Mobile Dashboard (1)/src/components/ProgressScreen.tsx
Design Postura Mobile Dashboard (1)/src/components/QuickActionsCard.tsx
Design Postura Mobile Dashboard (1)/src/components/RealTimePostureTracking.tsx
Design Postura Mobile Dashboard (1)/src/components/SettingsScreen.tsx
Design Postura Mobile Dashboard (1)/src/components/SlouchAlertsCard.tsx
Design Postura Mobile Dashboard (1)/src/components/ThemeContext.tsx
Design Postura Mobile Dashboard (1)/src/components/TodayStatsCard.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/accordion.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/alert-dialog.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/alert.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/aspect-ratio.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/avatar.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/badge.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/breadcrumb.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/button.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/calendar.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/card.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/carousel.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/chart.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/checkbox.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/collapsible.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/command.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/context-menu.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/dialog.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/drawer.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/dropdown-menu.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/form.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/hover-card.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/input-otp.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/input.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/label.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/menubar.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/navigation-menu.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/pagination.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/popover.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/progress.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/radio-group.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/resizable.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/scroll-area.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/select.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/separator.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/sheet.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/sidebar.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/skeleton.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/slider.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/sonner.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/switch.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/table.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/tabs.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/textarea.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/toggle-group.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/toggle.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/tooltip.tsx
Design Postura Mobile Dashboard (1)/src/components/ui/use-mobile.ts
Design Postura Mobile Dashboard (1)/src/components/ui/utils.ts
Design Postura Mobile Dashboard (1)/src/components/VibrationSensitivityCard.tsx
Design Postura Mobile Dashboard (1)/src/guidelines/Guidelines.md
Design Postura Mobile Dashboard (1)/src/index.css
Design Postura Mobile Dashboard (1)/src/main.tsx
Design Postura Mobile Dashboard (1)/src/styles/globals.css
Design Postura Mobile Dashboard (1)/vite.config.ts
main.py
mcp_client.py
posture_mcp_server.py
README.md
requirements.txt
server.py
```

### Dependencies

- Design Postura Mobile Dashboard (1)/package.json: @radix-ui/react-accordion@^1.2.3, @radix-ui/react-alert-dialog@^1.1.6, @radix-ui/react-aspect-ratio@^1.1.2, @radix-ui/react-avatar@^1.1.3, @radix-ui/react-checkbox@^1.1.4, @radix-ui/react-collapsible@^1.1.3, @radix-ui/react-context-menu@^2.2.6, @radix-ui/react-dialog@^1.1.6, @radix-ui/react-dropdown-menu@^2.1.6, @radix-ui/react-hover-card@^1.1.6, @radix-ui/react-label@^2.1.2, @radix-ui/react-menubar@^1.1.6, @radix-ui/react-navigation-menu@^1.2.5, @radix-ui/react-popover@^1.1.6, @radix-ui/react-progress@^1.1.2, @radix-ui/react-radio-group@^1.2.3, @radix-ui/react-scroll-area@^1.2.3, @radix-ui/react-select@^2.1.6, @radix-ui/react-separator@^1.1.2, @radix-ui/react-slider@^1.2.3, @radix-ui/react-slot@^1.1.2, @radix-ui/react-switch@^1.1.3, @radix-ui/react-tabs@^1.1.3, @radix-ui/react-toggle@^1.1.2, @radix-ui/react-toggle-group@^1.1.2, @radix-ui/react-tooltip@^1.1.8, @types/node@^20.10.0, @types/react@^19.2.2, @types/react-dom@^19.2.2, @vitejs/plugin-react-swc@^3.10.2, class-variance-authority@^0.7.1, clsx@*, cmdk@^1.1.1, date-fns@*, embla-carousel-react@^8.6.0, input-otp@^1.4.2, lucide-react@^0.487.0, next-themes@^0.4.6, react@^18.3.1, react-day-picker@^8.10.1, react-dom@^18.3.1, react-hook-form@^7.55.0, react-resizable-panels@^2.1.7, recharts@^2.15.2, sonner@^2.0.3, tailwind-merge@*, vaul@^1.1.2, vite@6.3.5
- requirements.txt: aiohappyeyeballs@==2.6.1, aiohttp@==3.13.1, aiosignal@==1.4.0, annotated-doc@==0.0.3, annotated-types@==0.7.0, anyio@==4.11.0, attrs@==25.4.0, bleak@==1.1.1, certifi@==2025.10.5, charset-normalizer@==3.4.4, click@==8.3.0, contourpy@==1.3.3, cycler@==0.12.1, distro@==1.9.0, dotenv@==0.9.9, fastapi@==0.120.0, fonttools@==4.60.1, frozenlist@==1.8.0, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, idna@==3.11, jiter@==0.11.1, kiwisolver@==1.4.9, matplotlib@==3.10.7, multidict@==6.7.0, numpy@==2.3.4, openai@==2.6.1, packaging@==25.0, pillow@==12.0.0, propcache@==0.4.1, pydantic@==2.12.3, pydantic_core@==2.41.4, pyobjc-core@==12.0, pyobjc-framework-Cocoa@==12.0, pyobjc-framework-CoreBluetooth@==12.0, pyobjc-framework-libdispatch@==12.0, pyparsing@==3.2.5, python-dateutil@==2.9.0.post0, python-dotenv@==1.1.1, requests@==2.32.5, six@==1.17.0, sniffio@==1.3.1, starlette@==0.48.0, tqdm@==4.67.1, typing_extensions@==4.15.0, typing-inspection@==0.4.2, urllib3@==2.5.0, uvicorn@==0.38.0, websockets@==15.0.1, yarl@==1.22.0

### Recent commits (newest first)

- description
- Added date data
- Made the MCP server actually work
- thank you richard
- added reqs
- MOVED ISAAC
- add
- Merge branch 'main' of https://github.com/kaitlynhuang26/cal_hacks
- Implemented MCP function
- Add files via upload
- Changed thresholds for slouch
- Isack
- Merge branch 'main' of https://github.com/kaitlynhuang26/cal_hacks
- FRONTENDgit add Design\ Postura\ Mobile\ Dashboard\ \(1\)/*git add Design\ Postura\ Mobile\ Dashboard\ \(1\)/*git add Design\ Postura\ Mobile\ Dashboard\ \(1\)/*
- Add files via upload
- Add files via upload
- Added slouch time metric
- Added api endpoint for slouch frequency
- Fixed sensor code, implemented db
- Merge branch 'main' of https://github.com/kaitlynhuang26/cal_hacks

## Key source files (fetched from GitHub, selected and truncated for size)

### Design Postura Mobile Dashboard (1)/src/Attributions.md

```markdown
This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).

This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).
```

### Design Postura Mobile Dashboard (1)/src/guidelines/Guidelines.md

```markdown
**Add your own guidelines here**
<!--

System Guidelines

Use this file to provide the AI with rules and guidelines you want it to follow.
This template outlines a few examples of things you can add. You can add your own sections and format it to suit your needs

TIP: More context isn't always better. It can confuse the LLM. Try and add the most important rules you need

# General guidelines

Any general rules you want the AI to follow.
For example:

* Only use absolute positioning when necessary. Opt for responsive and well structured layouts that use flexbox and grid by default
* Refactor code as you go to keep code clean
* Keep file sizes small and put helper functions and components in their own files.

--------------

# Design system guidelines
Rules for how the AI should make generations look like your company's design system

Additionally, if you select a design system to use in the prompt box, you can reference
your design system's components, tokens, variables and components.
For example:

* Use a base font-size of 14px
* Date formats should always be in the format “Jun 10”
* The bottom toolbar should only ever have a maximum of 4 items
* Never use the floating action button with the bottom toolbar
* Chips should always come in sets of 3 or more
* Don't use a dropdown if there are 2 or fewer options

You can also create sub sections and add more specific details
For example:


## Button
The Button component is a fundamental interactive element in our design system, designed to trigger actions or navigate
users through the application. It provides visual feedback and clear affordances to enhance user experience.

### Usage
Buttons should be used for important actions that users need to take, such as form submissions, confirming choices,
or initiating processes. They communicate interactivity and should have clear, action-oriented labels.

### Variants
* Primary Button
  * Purpose : Used for the main action in a section or page
  * Visual Style : Bold, filled with the primary brand color
  * Usage : One primary button per section to guide users toward the most important action
* Secondary Button
  * Purpose : Used for alternative or supporting actions
  * Visual Style : Outlined with the primary color, transparent background
  * Usage : Can appear alongside a primary button for less important actions
* Tertiary Button
  * Purpose : Used for the least important actions
  * Visual Style : Text-only with no border, using primary color
  * Usage : For actions that should be available but not emphasized
-->

```

### requirements.txt

```
aiohappyeyeballs==2.6.1
aiohttp==3.13.1
aiosignal==1.4.0
annotated-doc==0.0.3
annotated-types==0.7.0
anyio==4.11.0
attrs==25.4.0
bleak==1.1.1
certifi==2025.10.5
charset-normalizer==3.4.4
click==8.3.0
contourpy==1.3.3
cycler==0.12.1
distro==1.9.0
dotenv==0.9.9
fastapi==0.120.0
fonttools==4.60.1
frozenlist==1.8.0
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.11
jiter==0.11.1
kiwisolver==1.4.9
matplotlib==3.10.7
multidict==6.7.0
numpy==2.3.4
openai==2.6.1
packaging==25.0
pillow==12.0.0
propcache==0.4.1
pydantic==2.12.3
pydantic_core==2.41.4
pyobjc-core==12.0
pyobjc-framework-Cocoa==12.0
pyobjc-framework-CoreBluetooth==12.0
pyobjc-framework-libdispatch==12.0
pyparsing==3.2.5
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
requests==2.32.5
six==1.17.0
sniffio==1.3.1
starlette==0.48.0
tqdm==4.67.1
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.5.0
uvicorn==0.38.0
websockets==15.0.1
yarl==1.22.0

```

### Design Postura Mobile Dashboard (1)/package.json

```
{
      "name": "Design Postura Mobile Dashboard",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
            "@radix-ui/react-accordion": "^1.2.3",
            "@radix-ui/react-alert-dialog": "^1.1.6",
            "@radix-ui/react-aspect-ratio": "^1.1.2",
            "@radix-ui/react-avatar": "^1.1.3",
            "@radix-ui/react-checkbox": "^1.1.4",
            "@radix-ui/react-collapsible": "^1.1.3",
            "@radix-ui/react-context-menu": "^2.2.6",
            "@radix-ui/react-dialog": "^1.1.6",
            "@radix-ui/react-dropdown-menu": "^2.1.6",
            "@radix-ui/react-hover-card": "^1.1.6",
            "@radix-ui/react-label": "^2.1.2",
            "@radix-ui/react-menubar": "^1.1.6",
            "@radix-ui/react-navigation-menu": "^1.2.5",
            "@radix-ui/react-popover": "^1.1.6",
            "@radix-ui/react-progress": "^1.1.2",
            "@radix-ui/react-radio-group": "^1.2.3",
            "@radix-ui/react-scroll-area": "^1.2.3",
            "@radix-ui/react-select": "^2.1.6",
            "@radix-ui/react-separator": "^1.1.2",
            "@radix-ui/react-slider": "^1.2.3",
            "@radix-ui/react-slot": "^1.1.2",
            "@radix-ui/react-switch": "^1.1.3",
            "@radix-ui/react-tabs": "^1.1.3",
            "@radix-ui/react-toggle": "^1.1.2",
            "@radix-ui/react-toggle-group": "^1.1.2",
            "@radix-ui/react-tooltip": "^1.1.8",
            "class-variance-authority": "^0.7.1",
            "clsx": "*",
            "cmdk": "^1.1.1",
            "date-fns": "*",
            "embla-carousel-react": "^8.6.0",
            "input-otp": "^1.4.2",
            "lucide-react": "^0.487.0",
            "next-themes": "^0.4.6",
            "react": "^18.3.1",
            "react-day-picker": "^8.10.1",
            "react-dom": "^18.3.1",
            "react-hook-form": "^7.55.0",
            "react-resizable-panels": "^2.1.7",
            "recharts": "^2.15.2",
            "sonner": "^2.0.3",
            "tailwind-merge": "*",
            "vaul": "^1.1.2"
      },
      "devDependencies": {
            "@types/node": "^20.10.0",
            "@types/react": "^19.2.2",
            "@types/react-dom": "^19.2.2",
            "@vitejs/plugin-react-swc": "^3.10.2",
            "vite": "6.3.5"
      },
      "scripts": {
            "dev": "vite",
            "build": "vite build"
      }
}

```

### main.py

```python
print('ISAAC HURRY UP')
print('ISAAC SHUT THE FUCK UP AND GET THINGS DONE')
```

### server.py

```python
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from starlette.responses import PlainTextResponse
from pydantic import BaseModel
from typing import Optional
import ble_service
import mcp_client
import uvicorn
import os
import time
from dotenv import load_dotenv
load_dotenv()

app = FastAPI(title="BLE data service")
# Allow CORS for local frontend development so browser preflight (OPTIONS)
# requests succeed. In production, narrow allow_origins appropriately.
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:8000",
        "http://127.0.0.1:8000",
        "http://localhost:3000",
        "http://127.0.0.1:3000",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
        # Allow common local dev origins (e.g. Vite default 5173) and any
        # localhost/127.0.0.1 port via regex. This is permissive for local
        # development; narrow or remove for production.
        allow_origin_regex=r"http://(localhost|127\.0\.0\.1):\d+",
)


class ControlRequest(BaseModel):
    device_name: str = "XIAOMG24_BLE"


@app.on_event("startup")
async def startup_event():
    # Start BLE data collection automatically on server startup.
    # If you prefer manual control, remove/modify this.
    ble_service.start()


@app.get("/status")
def status():
    # very small status endpoint
    count = len(ble_service.get_data().get("t", []))
    return {"running": True, "samples": count}


@app.get("/data")
def read_all():
    return ble_service.get_data()


@app.get("/data/latest")
def read_latest():
    latest = ble_service.get_latest()
    if not latest:
        raise HTTPException(status_code=404, detail="no data yet")
    return latest


@app.get("/db/samples")
def db_samples(limit: int = 100, offset: int = 0, start_t: Optional[float] = None, end_t: Optional[float] = None):
    """Return samples from the underlying SQLite DB.

    Query params:
    - limit: max rows to return (default 100)
    - offset: rows to skip (paging)
    - start_t, end_t: optional numeric t-range filters
    """
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    rows = ble_service.query_samples(limit=limit, offset=offset, start_t=start_t, end_t=end_t)
    return {"count": len(rows), "samples": rows}


@app.get("/db/counters/slouch_frequency")
def db_get_slouch_counter():
    """Return the 'slouch_frequency' counter value."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "slouch_frequency"
    return {"name": name, "value": ble_service.get_counter(name)}


@app.post("/db/counters/slouch_frequency/reset")
def db_reset_slouch_counter():
    """Reset the 'slouch_frequency' counter to zero."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "slouch_frequency"
    ble_service.reset_counter(name)
    return {"status": "ok", "name": name}


@app.get("/db/counters/slouch_time")
def db_get_slouch_time():
    """Return the 'slouch_time' counter value."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "slouch_time"
    return {"name": name, "value": ble_service.get_counter(name)}


@app.post("/db/counters/slouch_time/reset")
def db_reset_slouch_time():
    """Reset the 'slouch_time' counter to zero."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "slouch_time"
    ble_service.reset_counter(name)
    return {"status": "ok", "name": name}


@app.get("/db/counters/straight_time")
def db_get_straight_time():
    """Return the 'straight_time' counter value."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "straight_time"
    return {"name": name, "value": ble_service.get_counter(name)}


@app.post("/db/counters/straight_time/reset")
def db_reset_straight_time():
    """Reset the 'straight_time' counter to zero."""
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")
    name = "straight_time"
    ble_service.reset_counter(name)
    return {"status": "ok", "name": name}


@app.get("/db/description", response_class=PlainTextResponse)
def db_description():
    """
    Return a human-readable description of slouch activity.

    Uses the new date-based counters table.
    Summarizes today's posture data and also provides
    a total across the stored date range (up to 30 days). The biggest
    """
    if not ble_service.persistence_enabled():
        raise HTTPException(status_code=400, detail="persistence disabled")

    try:
        conn = ble_service._open_db()
        cur = conn.execute("SELECT MIN(date), MAX(date) FROM counters")
        row = cur.fetchone()
        if not row or not row[0]:
            return "No posture data available yet."

        start_date, end_date = row
        today = time.strftime("%Y-%m-%d")

        # --- Retrieve today's counters ---
        freq_today = ble_service.get_counter("slouch_frequency")
        slouch_time_today = ble_service.get_counter("slouch_time")
        straight_time_today = ble_service.get_counter("straight_time")
        total_today = slouch_time_today + straight_time_today

        # --- Retrieve overall sums (for range summary) ---
        cur = conn.execute(
            """
            SELECT name, SUM(value) AS total
            FROM counters
            WHERE date BETWEEN ? AND ?
            GROUP BY name
            """,
            (start_date, end_date),
        )
        totals = {row["name"]: row["total"] for row in cur.fetchall()}

        freq_total = totals.get("slouch_freque
[truncated — 5120 more characters]
```

### Design Postura Mobile Dashboard (1)/src/main.tsx

```typescript

  import { createRoot } from "react-dom/client";
  import App from "./App.tsx";
  import "./index.css";

  createRoot(document.getElementById("root")!).render(<App />);
  
```

### Design Postura Mobile Dashboard (1)/src/App.tsx

```typescript
import { useState } from "react";
import { MinimalistPostureSummary } from "./components/MinimalistPostureSummary";
import { ChatBot } from "./components/chatbot-component";
import { TodayStatsCard } from "./components/TodayStatsCard";
import { QuickActionsCard } from "./components/QuickActionsCard";
import { FriendsLeaderboard } from "./components/FriendsLeaderboard";
import { SettingsScreen } from "./components/SettingsScreen";
import { ProgressScreen } from "./components/ProgressScreen";
import { BottomNav } from "./components/BottomNav";
import { ThemeProvider, useTheme } from "./components/ThemeContext";
import { Moon, Sun } from "lucide-react";

function AppContent() {
  const [activeTab, setActiveTab] = useState("dashboard");
  const { theme, toggleTheme } = useTheme();
  const [aiOpen, setAiOpen] = useState(false);

  const isDark = theme === "dark";

  const handleAIAgentClick = () => {
    setAiOpen(true);
  };

  const handleCloseChatBot = () => {
    setAiOpen(false);
  };

  return (
    <div
      className={`min-h-screen pb-24 ${
        isDark ? "bg-[#0A0E27]" : "bg-gray-50"
      }`}
    >
      {/* Header - hide for friends tab */}
      {activeTab !== "friends" && (
        <header
          className={`shadow-sm ${
            isDark
              ? "bg-[#0F1535] border-b border-[#1a2040]"
              : "bg-[#1E293B]"
          }`}
        >
          <div className="max-w-md mx-auto px-6 py-6 flex items-center justify-between">
            <div>
              <h1 className="text-2xl text-white">Postura</h1>
              <p
                className={`text-sm mt-1 ${
                  isDark ? "text-gray-400" : "text-blue-200"
                }`}
              >
                Sunday, October 26
              </p>
            </div>
            <button
              onClick={toggleTheme}
              className={`p-2 rounded-full transition-colors ${
                isDark
                  ? "bg-[#1a2040] hover:bg-[#222b50] text-blue-400"
                  : "bg-slate-700 hover:bg-slate-600 text-yellow-300"
              }`}
            >
              {isDark ? (
                <Sun className="w-5 h-5" />
              ) : (
                <Moon className="w-5 h-5" />
              )}
            </button>
          </div>
        </header>
      )}

      {/* Main Content */}
      <main
        className={`max-w-md mx-auto ${
          activeTab === "friends" ? "" : "px-6 py-8"
        } space-y-6`}
      >
        {activeTab === "dashboard" && (
          <>
            <MinimalistPostureSummary percentage={78} />
            <TodayStatsCard />
            <QuickActionsCard onSettingsClick={() => setActiveTab("settings")} />
          </>
        )}

        {activeTab === "progress" && <ProgressScreen />}

        {activeTab === "friends" && <FriendsLeaderboard />}

        {activeTab === "settings" && <SettingsScreen />}
      </main>

      {/* Bottom Navigation */}
      <BottomNav
        activeTab={activeTab}
        onTabChange={setActiveTab}
        onAIAgentClick={handleAIAgentClick}
      />

      {/* ChatBot popup (appears when AI Agent is clicked) */}
      {aiOpen && <ChatBot onClose={handleCloseChatBot} />}
    </div>
  );
}

export default function App() {
  return (
    <ThemeProvider>
      <AppContent />
    </ThemeProvider>
  );
}

function TrendingUpIcon({ className }: { className?: string }) {
  return (
    <svg
      className={className}
      fill="none"
      viewBox="0 0 24 24"
      stroke="currentColor"
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
      />
    </svg>
  );
}

```

### posture_mcp_server.py

```python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import uvicorn
from typing import Any, Dict, Optional
import httpx

app = FastAPI()

async def _send_mcp_request(_, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    # Mock data mode
    if method == "tools/list":
        return {
            "result": {
                "tools": [
                    {
                        "name": "get_posture_data",
                        "description": "This function gets a descriptive string for posture frequency and slouch percentage by date.",
                        "inputSchema": {"type": "object", "properties": {}}
                    }
                ]
            }
        }

    elif method == "tools/call":
        # Call the local FastAPI endpoint that returns the human-readable
        # posture description and return it as the MCP call result. This
        # allows the model (Groq Llama) to receive the summary text as a
        # tool response.
        try:
            print('YAS')
            async with httpx.AsyncClient(timeout=5.0) as client:
                resp = await client.get("http://127.0.0.1:8000/db/description")
            if resp.status_code == 200:
                text = resp.text
                return {"result": {"description": text}}
            else:
                return {"error": {"message": f"Upstream returned {resp.status_code}: {resp.text}"}}
        except Exception as exc:
            return {"error": {"message": f"Failed to call upstream description API: {exc}"}}

    elif method == "initialize":
        return {"result": {"status": "ok"}}

    return {"error": {"message": "Mock: Unknown method"}}


@app.post("/mcp")
async def handle_mcp(request: Request):
    print('watthefuck')
    body = await request.json()
    method = body.get("method")
    params = body.get("params")
    result = await _send_mcp_request(None, method, params)
    print('server side', result)
    return JSONResponse(result)


if __name__ == "__main__":
    print(" Starting mock MCP posture server on http://localhost:5000/mcp ...")
    uvicorn.run(app, host="localhost", port=5000, log_level='debug')

```

### api_client.py

```python
#!/usr/bin/env python3
"""Lightweight API client for the BLE FastAPI service.

Behavior:
- Tries to use the WebSocket `/ws` endpoint for live updates if the `websockets`
  package is installed.
- If `websockets` isn't available, falls back to HTTP polling `/data/latest`
  using `requests` (if installed); otherwise it will print an instruction.

Usage:
  python api_client.py            # try ws, fallback to polling
  python api_client.py --poll     # force HTTP polling
  python api_client.py --ws      # force WebSocket

This script is intentionally small and dependency-light. It attempts to do
the right thing with minimal setup.
"""

from __future__ import annotations

import argparse
import asyncio
import json
import time
import sys


DEFAULT_HTTP = "http://localhost:8000/data/latest"
DEFAULT_WS = "ws://localhost:8000/ws"


def print_sample(sample: dict) -> None:
    # compact one-line output with timestamp
    t = sample.get("t")
    ax = sample.get("ax")
    ay = sample.get("ay")
    az = sample.get("az")
    gx = sample.get("gx")
    gy = sample.get("gy")
    gz = sample.get("gz")
    ts = f"{t:6.2f}s" if isinstance(t, (int, float)) else str(t)
    print(f"{ts}  ax={ax:3} ay={ay:3} az={az:3} | gx={gx:3} gy={gy:3} gz={gz:3}")


async def ws_client(uri: str) -> None:
    try:
        import websockets
    except Exception:
        print("'websockets' package not installed; cannot use WebSocket mode.")
        return

    print(f"Connecting WebSocket {uri}...")
    try:
        async with websockets.connect(uri) as ws:
            async for msg in ws:
                try:
                    sample = json.loads(msg)
                except Exception:
                    print("received non-json message:", msg)
                    continue
                print_sample(sample)
    except Exception as exc:
        print("WebSocket error:", exc)


def poll_http(url: str, interval: float = 1.0) -> None:
    try:
        import requests
    except Exception:
        print("'requests' not installed; install it or run with websockets available.")
        print("pip install requests")
        return

    print(f"Polling {url} every {interval}s...")
    while True:
        try:
            r = requests.get(url, timeout=2.0)
            if r.status_code == 200:
                try:
                    sample = r.json()
                    print_sample(sample)
                except Exception:
                    print("failed to decode JSON from response")
            else:
                print(f"HTTP {r.status_code}")
        except Exception as exc:
            print("request error:", exc)
        time.sleep(interval)


def main() -> None:
    p = argparse.ArgumentParser()
    p.add_argument("--ws", action="store_true", help="force WebSocket mode")
    p.add_argument("--poll", action="store_true", help="force HTTP polling mode")
    p.add_argument("--url", default=None, help="custom URL for HTTP polling or WS (prefix ws:// for WS)")
    args = p.parse_args()

    if args.url:
        if args.url.startswith("ws://") or args.url.startswith("wss://"):
            uri = args.url
            mode = "ws"
        else:
            uri = args.url
            mode = "poll"
    else:
        uri = None
        mode = None

    # Determine mode
    if args.ws:
        mode = "ws"
    if args.poll:
        mode = "poll"

    if mode == "ws":
        ws_uri = uri or DEFAULT_WS
        asyncio.run(ws_client(ws_uri))
        return

    if mode == "poll":
        http_url = uri or DEFAULT_HTTP
        poll_http(http_url)
        return

    # Auto mode: prefer WebSocket if websockets installed
    try:
        import websockets  # type: ignore
        asyncio.run(ws_client(DEFAULT_WS))
        return
    except Exception:
        # fall back to requests polling
        poll_http(DEFAULT_HTTP)


if __name__ == "__main__":
    main()

```

[70 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]