# Project export: Cielo

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: TreeHacks 2026
- Tagline: AI-Driven Multimodal Detection for Neonatal Asphyxia , Jaundice and Cyanosis and gives real-time clinical guidance
- Devpost: https://devpost.com/software/cielo
- GitHub: https://github.com/raynaborah/soesssssss
- Video: https://www.youtube.com/embed/ubU0Fi0-CFo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 0 GitHub contributor(s) — 

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Mobile App

A mobile application built with Expo and React Native.

## Getting Started

### Prerequisites

- Node.js (v18 or higher)
- Expo Go app installed on your mobile device
- npm or yarn

### Installation

1. Install dependencies:
```bash
npm install
```

2. Start the development server:
```bash
npm start
```

3. Scan the QR code with Expo Go app (iOS) or Camera app (Android)

### Available Scripts

- `npm start` - Start the Expo development server
- `npm run android` - Start on Android device/emulator
- `npm run ios` - Start on iOS device/simulator
- `npm run web` - Start in web browser

## Project Structure

```
mobile_app/
├── app/
│   ├── _layout.tsx    # Root layout with navigation
│   └── index.tsx      # Home screen (first page)
├── assets/            # Images, fonts, etc.
├── app.json           # Expo configuration
└── package.json       # Dependencies
```


## Detected evidence (automated analysis)

Indexed codebase: 52 recognized source files, 455 KB.
- Flask (technology) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — 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 (64 of 64)

```
.gitignore
.npmrc
app.json
app/_layout.tsx
app/analysis.tsx
app/audio-learn.tsx
app/carrier-status.tsx
app/crying-analysis.tsx
app/family-health-tree.tsx
app/find-pediatricians.tsx
app/genetic-screening.tsx
app/index.tsx
app/nutrition-plan.tsx
app/pharmacogenomics.tsx
app/rare-disease-matcher.tsx
app/settings.tsx
app/video-call.tsx
babel.config.js
components/BottomNavigation.tsx
components/Header.tsx
JAUNDICE_SETUP.md
jaundice/api_server.py
jaundice/app.py
jaundice/baby_detector.py
jaundice/face_landmarker.task
jaundice/README.md
jaundice/requirements.txt
jaundice/start_server.bat
jaundice/start_server.sh
lib/genomicsData.ts
lib/guidelines.json
metro.config.js
package.json
QUICK_FIX.md
README.md
RUN_THIS_PROJECT.md
seos-care-voice-agent/app/api/rag/route.ts
seos-care-voice-agent/app/api/realtime-token/route.ts
seos-care-voice-agent/app/api/validate/route.ts
seos-care-voice-agent/app/layout.tsx
seos-care-voice-agent/app/page.tsx
seos-care-voice-agent/app/session/page.tsx
seos-care-voice-agent/components/IntakeForm.tsx
seos-care-voice-agent/guidelines/crying-patterns.md
seos-care-voice-agent/guidelines/feeding-guidelines.md
seos-care-voice-agent/guidelines/general-newborn-care.md
seos-care-voice-agent/guidelines/jaundice.md
seos-care-voice-agent/guidelines/newborn-sleep.md
seos-care-voice-agent/lib/guidelines.json
seos-care-voice-agent/lib/rag.ts
seos-care-voice-agent/lib/triage.ts
seos-care-voice-agent/next-env.d.ts
seos-care-voice-agent/package.json
seos-care-voice-agent/scripts/ingest_guidelines.ts
seos-care-voice-agent/tsconfig.json
tsconfig.json
utils/audioAssets.ts
utils/audioLoader.ts
utils/audioProcessor.ts
utils/genomicsAI.ts
utils/genomicsEngine.ts
utils/pediatricianSearch.ts
utils/ragApi.ts
utils/realtimeVoice.ts
```

### Dependencies

- jaundice/requirements.txt: flask@>=2.3.0, flask-cors@>=4.0.0, mediapipe@>=0.10.0, numpy@>=1.24.0, opencv-python@>=4.8.0, Pillow@>=10.0.0, ultralytics@>=8.0.0
- package.json: @babel/core@^7.24.0, @types/react@~19.1.10, expo@~54.0.0, expo-asset@~12.0.12, expo-av@~16.0.8, expo-camera@~17.0.10, expo-constants@~18.0.13, expo-file-system@~19.0.21, expo-linking@~8.0.11, expo-location@~19.0.8, expo-router@~6.0.21, expo-status-bar@~3.0.9, openai@^6.16.0, react@19.1.0, react-native@0.81.5, react-native-maps@1.20.1, react-native-safe-area-context@~5.6.0, react-native-screens@~4.16.0, typescript@^5.1.3
- seos-care-voice-agent/package.json: @openai/agents@^0.2.0, @openai/agents-realtime@^0.2.0, @types/node@^20.11.30, @types/react@^18.2.64, @types/react-dom@^18.2.21, next@14.2.0, openai@^4.0.0, react@^18.2.0, react-dom@^18.2.0, tsx@^4.7.2, typescript@^5.4.5, zod@^3.23.8

### Recent commits (newest first)

- SEOS - Newborn Care App with Genomics

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

### QUICK_FIX.md

```markdown
# Quick Fix: API Connection Issue

## Problem
The app shows "Network request failed" because it's trying to connect to `localhost:5000`, which doesn't work on physical devices or iOS simulators.

## Solution

### Step 1: Find Your Computer's IP Address

**Windows:**
```bash
ipconfig
```
Look for "IPv4 Address" (usually something like `192.168.1.100`)

**Mac/Linux:**
```bash
ifconfig
# or
ip addr
```
Look for your network interface's IP address (usually `192.168.x.x` or `10.0.x.x`)

### Step 2: Update .env File

Open `.env` and update the API URL:

```env
EXPO_PUBLIC_JAUNDICE_API_URL=http://YOUR_IP_ADDRESS:5000
```

**Example:**
```env
EXPO_PUBLIC_JAUNDICE_API_URL=http://192.168.1.100:5000
```

### Step 3: Make Sure API Server is Running

```bash
cd jaundice
python api_server.py
```

You should see:
```
Starting Jaundice Detection API server on 0.0.0.0:5000
```

### Step 4: Restart Expo App

1. Stop the Expo app (Ctrl+C)
2. Restart: `npm start`
3. Reload the app on your device

### Step 5: Test Connection

1. Open Video Consultation screen
2. Tap "Show Debug" button
3. Tap "Test Connection" button
4. You should see "✅ API test successful"

## For iOS Simulator

If you're using iOS Simulator, `localhost` should work, but make sure:
1. API server is running
2. Firewall allows connections on port 5000

## Troubleshooting

**Still not working?**

1. Check if API server is accessible from your device:
   - Open browser on your device
   - Go to: `http://YOUR_IP:5000/health`
   - Should show: `{"status":"healthy","service":"jaundice-detection-api"}`

2. Check firewall:
   - Windows: Allow Python through firewall
   - Mac: System Preferences > Security > Firewall

3. Check both devices are on same network:
   - Computer and phone must be on same WiFi network

4. Try the debug panel:
   - Tap "Show Debug" to see detailed logs
   - Check what URL is being used
   - Look for error messages

```

### JAUNDICE_SETUP.md

```markdown
# Jaundice Detection Integration Setup Guide

This guide explains how to set up and use the jaundice detection feature in the video consultation screen.

## Overview

The jaundice detection system consists of:
1. **Python Flask API Server** (`jaundice/api_server.py`) - Processes images and detects jaundice risk
2. **React Native Mobile App** (`app/video-call.tsx`) - Captures camera frames and displays results

## Prerequisites

1. Python 3.8 or higher
2. Node.js and Expo CLI
3. `yolo11n.pt` model file (already in the main folder)

## Setup Steps

### 1. Install Python Dependencies

```bash
cd jaundice
pip install -r requirements.txt
```

Or use the startup scripts:
- **Windows**: `start_server.bat`
- **Linux/Mac**: `chmod +x start_server.sh && ./start_server.sh`

### 2. Start the API Server

```bash
cd jaundice
python api_server.py
```

The server will start on `http://localhost:5000` by default.

**Verify it's running:**
```bash
curl http://localhost:5000/health
```

You should see:
```json
{"status": "healthy", "service": "jaundice-detection-api"}
```

### 3. Configure Mobile App

The API URL is configured in `.env`:
```
EXPO_PUBLIC_JAUNDICE_API_URL=http://localhost:5000
```

For production, update this to your production API URL.

### 4. Run the Mobile App

```bash
npm start
# or
expo start
```

## How It Works

1. **Video Consultation Screen** (`app/video-call.tsx`):
   - Opens camera view
   - Captures frames every 2 seconds
   - Converts frames to base64
   - Sends to API server

2. **API Server** (`jaundice/api_server.py`):
   - Receives base64 image
   - Uses YOLO11 to detect baby/person
   - Uses MediaPipe to detect face landmarks
   - Analyzes sclera (eye whites) for yellowing
   - Returns risk assessment (Low/Moderate/High)

3. **Results Display**:
   - Shows baby detection confidence
   - Displays risk band with color coding:
     - 🟢 Low (green)
     - 🟡 Moderate (orange)
     - 🔴 High (red)
   - Shows b* value (yellowing measurement)
   - Includes medical disclaimer

## API Endpoints

### POST /detect
Analyze an image for jaundice risk.

**Request:**
```json
{
  "image": "base64_encoded_image_string"
}
```

**Response:**
```json
{
  "success": true,
  "has_baby": true,
  "baby_confidence": 0.95,
  "sclera_analysis": {
    "valid": true,
    "risk_band": "Low",
    "b_med": 8.5,
    "message": "Sclera yellowing estimate: Low"
  }
}
```

## Troubleshooting

### API Server Issues

1. **Model not found:**
   - Ensure `yolo11n.pt` is in the main folder (parent of `jaundice/`)
   - Or in the `jaundice/` folder

2. **Port already in use:**
   - Change port: `PORT=5001 python api_server.py`
   - Update `.env` with new port

3. **MediaPipe model download:**
   - First run will download `face_landmarker.task` automatically
   - Ensure internet connection

### Mobile App Issues

1. **Cannot connect to API:**
   - Check API server is running
   - For physical device, use your computer's IP: `http://192.168.x.x:5000`
   - Update `.env` with c
[truncated — 1045 more characters]
```

### package.json

```
{
  "name": "mobile-app",
  "version": "1.0.0",
  "main": "expo-router/entry",
  "scripts": {
    "start": "expo start",
    "start:tunnel": "expo start --tunnel",
    "start:lan": "expo start --lan",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "~54.0.0",
    "expo-asset": "~12.0.12",
    "expo-av": "~16.0.8",
    "expo-camera": "~17.0.10",
    "expo-constants": "~18.0.13",
    "expo-file-system": "~19.0.21",
    "expo-linking": "~8.0.11",
    "expo-location": "~19.0.8",
    "expo-router": "~6.0.21",
    "expo-status-bar": "~3.0.9",
    "openai": "^6.16.0",
    "react": "19.1.0",
    "react-native": "0.81.5",
    "react-native-maps": "1.20.1",
    "react-native-safe-area-context": "~5.6.0",
    "react-native-screens": "~4.16.0"
  },
  "devDependencies": {
    "@babel/core": "^7.24.0",
    "@types/react": "~19.1.10",
    "typescript": "^5.1.3"
  },
  "private": true
}

```

### jaundice/requirements.txt

```
flask>=2.3.0
flask-cors>=4.0.0
opencv-python>=4.8.0
numpy>=1.24.0
Pillow>=10.0.0
ultralytics>=8.0.0
mediapipe>=0.10.0

```

### seos-care-voice-agent/package.json

```
{
  "name": "seos-care-voice-agent",
  "private": true,
  "version": "0.1.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "ingest": "tsx scripts/ingest_guidelines.ts"
  },
  "dependencies": {
    	"@openai/agents": "^0.2.0",
	"@openai/agents-realtime": "^0.2.0",
    "next": "14.2.0",
    "openai": "^4.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20.11.30",
    "@types/react": "^18.2.64",
    "@types/react-dom": "^18.2.21",
    "tsx": "^4.7.2",
    "typescript": "^5.4.5"
  }
}

```

### app/_layout.tsx

```typescript
import { Stack } from 'expo-router';
import { View, StyleSheet } from 'react-native';
import { usePathname, useSegments } from 'expo-router';
import BottomNavigation from '../components/BottomNavigation';
import Header from '../components/Header';

function LayoutContent() {
  const pathname = usePathname();
  const segments = useSegments();
  
  // Hide header and bottom navigation on modal/fullscreen screens
  const hideNavigation = pathname === '/video-call' || pathname === '/audio-learn' || 
                        segments.includes('video-call') || segments.includes('audio-learn');
  
  return (
    <View style={styles.container}>
      {!hideNavigation && <Header />}
      <View style={styles.stackContainer}>
        <Stack>
          <Stack.Screen name="index" options={{ title: 'Home', headerShown: false }} />
          <Stack.Screen 
            name="video-call" 
            options={{ 
              title: 'AI Consultation',
              headerShown: false,
              presentation: 'fullScreenModal'
            }} 
          />
          <Stack.Screen 
            name="audio-learn" 
            options={{ 
              title: 'Audio Learning',
              headerShown: false
            }} 
          />
          <Stack.Screen 
            name="analysis" 
            options={{ 
              title: 'Analysis',
              headerShown: false
            }} 
          />
          <Stack.Screen 
            name="crying-analysis" 
            options={{ 
              title: 'Crying Analysis',
              headerShown: false
            }} 
          />
          <Stack.Screen 
            name="find-pediatricians" 
            options={{ 
              title: 'Find Pediatricians',
              headerShown: false
            }} 
          />
          <Stack.Screen
            name="settings"
            options={{
              title: 'Settings',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="genetic-screening"
            options={{
              title: 'Genetic Screening',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="pharmacogenomics"
            options={{
              title: 'Drug Response',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="carrier-status"
            options={{
              title: 'Carrier Status',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="nutrition-plan"
            options={{
              title: 'Nutrition Plan',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="rare-disease-matcher"
            options={{
              title: 'Rare Disease Matcher',
              headerShown: false
            }}
          />
          <Stack.Screen
            name="family-health-tree"
            options={{
              title: 'Family Health Tree',
              headerShown: false
            }}
          />
        </Stack>
      </View>
      {!hideNavigation && <BottomNavigation />}
    </View>
  );
}

export default function RootLayout() {
  return <LayoutContent />;
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  stackContainer: {
    flex: 1,
  },
});

```

### app/index.tsx

```typescript
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useRouter } from 'expo-router';

export default function HomeScreen() {
  const router = useRouter();

  const handleVideoCall = () => {
    router.push('/video-call');
  };

  const handleCryAnalysis = () => {
    router.push('/crying-analysis');
  };

  const handleFindPediatricians = () => {
    router.push('/find-pediatricians');
  };

  const features = [
    { 
      title: 'Common Problems', 
      description: 'Learn about typical newborn issues and solutions',
      route: null
    },
    { 
      title: 'Feeding Guide', 
      description: 'Understand feeding schedules and signs',
      route: null
    },
    { 
      title: 'Sleep Patterns', 
      description: 'Track and understand baby sleep',
      route: null
    },
    { 
      title: 'Health Symptoms', 
      description: 'Identify when to seek medical help',
      route: '/audio-learn'
    },
  ];

  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <ScrollView 
        style={styles.scrollView}
        contentContainerStyle={styles.scrollContent}
        showsVerticalScrollIndicator={false}
      >
        <View style={styles.welcomeSection}>
          <Text style={styles.welcomeTitle}>Welcome</Text>
          <Text style={styles.welcomeSubtitle}>Your guide to understanding your baby</Text>
        </View>

        <View style={styles.doctorAISection}>
          <Text style={styles.sectionTitle}>Doctor AI</Text>
          <View style={styles.twoColumnLayout}>
            <TouchableOpacity 
              style={styles.doctorAIButton}
              onPress={handleVideoCall}
            >
              <View style={styles.doctorAIIcon}>
                <Image 
                  source={require('../assets/images/doctor.png')} 
                  style={styles.doctorImage}
                  resizeMode="contain"
                />
              </View>
              <Text style={styles.doctorAITitle}>Video Consultation</Text>
              <Text style={styles.doctorAIDescription}>
                Get instant help from AI specialist
              </Text>
            </TouchableOpacity>

            <TouchableOpacity 
              style={styles.doctorAIButton}
              onPress={handleCryAnalysis}
            >
              <View style={styles.doctorAIIcon}>
                <Image 
                  source={require('../assets/images/doctor.png')} 
                  style={styles.doctorImage}
                  resizeMode="contain"
                />
              </View>
              <Text style={styles.doctorAITitle}>Cry Analysis</Text>
              <Text style={styles.doctorAIDescription}>
                Analyze baby's crying patterns
              </Text>
            </TouchableOpacity>
          </View>
        </View>

        <View style={styles.doctorAISection}>
          <Text style={styles.sectionTitle}>Find Care</Text>
          <TouchableOpacity 
            style={styles.findCareButton}
            onPress={handleFindPediatricians}
          >
            <View style={styles.findCareIcon}>
              <Text style={styles.findCareIconText}>📍</Text>
            </View>
            <Text style={styles.findCareTitle}>Find Pediatricians</Text>
            <Text style={styles.findCareDescription}>
              Locate nearby pediatricians and medical facilities
            </Text>
          </TouchableOpacity>
        </View>

        {/* Genomics & Precision Medicine */}
        <View style={styles.doctorAISection}>
          <Text style={styles.sectionTitle}>Genomics & Precision Medicine</Text>
          <Text style={[styles.welcomeSubtitle, { marginBottom: 16 }]}>
            Personalized genetic insights for your baby
          </Text>

          <View style={styles.twoColumnLayout}>
            <TouchableOpacity
              style={[styles.doctorAIButton, { backgroundColor: '#7B1FA2' }]}
              onPress={() => router.push('/genetic-screening')}
            >
              <View style={styles.doctorAIIcon}>
                <Text style={{ fontSize: 28 }}>🧬</Text>
              </View>
              <Text style={styles.doctorAITitle}>Genetic Screening</Text>
              <Text style={styles.doctorAIDescription}>Newborn risk assessment</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.doctorAIButton, { backgroundColor: '#00838F' }]}
              onPress={() => router.push('/pharmacogenomics')}
            >
              <View style={styles.doctorAIIcon}>
                <Text style={{ fontSize: 28 }}>💊</Text>
              </View>
              <Text style={styles.doctorAITitle}>Drug Response</Text>
              <Text style={styles.doctorAIDescription}>Pharmacogenomics insights</Text>
            </TouchableOpacity>
          </View>

          <View style={[styles.twoColumnLayout, { marginTop: 12 }]}>
            <TouchableOpacity
              style={[styles.doctorAIButton, { backgroundColor: '#E65100' }]}
              onPress={() => router.push('/carrier-status')}
            >
              <View style={styles.doctorAIIcon}>
                <Text style={{ fontSize: 28 }}>👨‍👩‍👧</Text>
              </View>
              <Text style={styles.doctorAITitle}>Carrier Status</Text>
              <Text style={styles.doctorAIDescription}>Inheritance calculator</Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.doctorAIButton, { backgroundColor: '#2E7D32' }]}
              onPress={() => router.push('/nutrition-plan')}
            >
              <View style={styles.doctorAIIcon}>
                <Text style={{ fontSize: 28 }}>🥗</Text>
              </View>
              <Text style={styles.doctorAITitle}>Nutrition Plan</Text>
              <Text style={styles.doctorAIDescription}>Genetic-based feeding</Text>
            </Touch
[truncated — 5010 more characters]
```

### jaundice/app.py

```python
"""
Streamlit Web UI for Baby Detection with Jaundice Risk Assessment
"""

import streamlit as st
import cv2  # type: ignore
import numpy as np
from PIL import Image, ImageOps
from baby_detector import BabyDetector
import io
import time

# Page config
st.set_page_config(
    page_title="Baby Jaundice Risk Assessment",
    page_icon="👶",
    layout="wide"
)

# Initialize detector (cached to avoid reloading)
@st.cache_resource
def load_detector():
    """Load the BabyDetector model (cached for performance)"""
    detector = BabyDetector()
    # Disable freeze for single image processing
    detector.freeze_enabled = False
    # Reset all freeze-related state
    detector.frozen = False
    detector.frozen_result = None
    detector.measurements.clear()
    detector.collect_start_time = 0.0
    return detector

# Title and description
st.title("👶 Baby Jaundice Risk Assessment")
st.markdown("""
This tool uses AI to detect babies in images and assess jaundice risk by analyzing sclera (eye whites) color.
**⚠️ Important:** This is not a medical diagnosis. Always confirm with professional medical testing (TcB/TSB).
""")

# Sidebar
st.sidebar.header("Settings")
st.sidebar.markdown("---")

# Mode selection
mode = st.sidebar.radio(
    "Select Input Mode",
    ["📷 Upload Image", "📹 Webcam"],
    index=0
)

# Initialize detector
with st.spinner("Loading AI models... This may take a moment on first run."):
    detector = load_detector()

st.sidebar.markdown("---")
st.sidebar.markdown("### ℹ️ How it works")
st.sidebar.markdown("""
1. **Baby Detection**: Uses YOLO11 to detect babies in the image
2. **Face Analysis**: Uses MediaPipe to detect facial landmarks
3. **Sclera Analysis**: Analyzes eye whites for yellowing (jaundice indicator)
4. **Risk Assessment**: Classifies risk as Low, Moderate, or High
""")

st.sidebar.markdown("### 📋 Tips for best results")
st.sidebar.markdown("""
- Use clear, well-lit images
- Baby's face should be clearly visible
- Eyes should be open and visible
- Neutral lighting works best
- Close-up face shots give better results
""")

# Main content area
if mode == "📷 Upload Image":
    st.header("Upload an Image")
    
    # Add a button to clear cache if needed
    if st.button("🔄 Clear Cache & Reset", help="Click if you see stuck messages"):
        st.cache_resource.clear()
        st.rerun()
    
    uploaded_file = st.file_uploader(
        "Choose an image file",
        type=['jpg', 'jpeg', 'png', 'bmp'],
        help="Upload a photo of a baby to analyze"
    )
    
    if uploaded_file is not None:
        # Read image
        image = Image.open(uploaded_file)
        
        # Fix image orientation based on EXIF data (handles rotated phone photos)
        try:
            # ImageOps.exif_transpose automatically rotates based on EXIF orientation
            image = ImageOps.exif_transpose(image)
        except (AttributeError, KeyError, TypeError, Exception):
            # No EXIF data or can't process it, continue with original
            pass
        
        # Convert to RGB if needed (handles RGBA, P, etc.)
        if image.mode != 'RGB':
            image = image.convert('RGB')
        image_np = np.array(image)
        
        # Convert RGB to BGR for OpenCV
        if len(image_np.shape) == 3 and image_np.shape[2] == 3:
            image_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
        elif len(image_np.shape) == 2:
            # Grayscale image - convert to BGR
            image_bgr = cv2.cvtColor(image_np, cv2.COLOR_GRAY2BGR)
        else:
            image_bgr = image_np
        
        # Display original image
        col1, col2 = st.columns(2)
        
        with col1:
            st.subheader("Original Image")
            st.image(image, width='stretch')
        
        # Process image - reset detector state first to avoid freeze logic
        # Force reset all state to ensure clean processing
        detector.frozen = False
        detector.frozen_result = None
        detector.measurements.clear()
        detector.collect_start_time = 0.0
        detector.freeze_enabled = False  # Ensure freeze is disabled
        
        with st.spinner("Analyzing image..."):
            processed_image, result = detector.process_image(image_bgr)
            
            # Ensure result message doesn't contain "Collecting" text
            if result.get('message', '').startswith('Collecting'):
                result['message'] = 'Analysis complete'
        
        # Convert BGR back to RGB for display
        if processed_image is not None:
            processed_image_rgb = cv2.cvtColor(processed_image, cv2.COLOR_BGR2RGB)
            
            with col2:
                st.subheader("Analysis Result")
                st.image(processed_image_rgb, width='stretch')
        
        # Display results
        st.markdown("---")
        st.header("📊 Detection Results")
        
        if result['has_baby']:
            st.success(f"✅ Baby detected! (Confidence: {result['baby_confidence']:.2%})")
            
            # Sclera analysis results
            if result.get('sclera_analysis'):
                sclera = result['sclera_analysis']
                
                if sclera.get('valid'):
                    risk_band = sclera['risk_band']
                    b_med = sclera.get('b_med', 0)
                    
                    # Color-coded risk display
                    if risk_band == 'Low':
                        st.success(f"🟢 **Risk Level: {risk_band}**")
                        st.info(f"**b* value:** {b_med:.2f} (Low yellowing detected)")
                    elif risk_band == 'Moderate':
                        st.warning(f"🟡 **Risk Level: {risk_band}**")
                        st.info(f"**b* value:** {b_med:.2f} (Moderate yellowing detected)")
                    else:
                        st.error(f"🔴 **Risk Level: {risk_band}**")
                        st.info(f"**b* value:** {b_med:.2f} (High yellowing detected)")
  
[truncated — 5494 more characters]
```

### seos-care-voice-agent/app/layout.tsx

```typescript
export const metadata = {
  title: 'Next.js',
  description: 'Generated by Next.js',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

```

### seos-care-voice-agent/app/page.tsx

```typescript
"use client";

import React, { useMemo, useRef, useState } from "react";
import { z } from "zod";
import { RealtimeAgent, RealtimeSession } from "@openai/agents/realtime";

type RagContext = { source: string; excerpt: string };

export default function Home() {
  const [status, setStatus] = useState<"idle" | "connecting" | "connected" | "stopped">("idle");
  const [lastCitations, setLastCitations] = useState<RagContext[]>([]);
  const [error, setError] = useState<string | null>(null);

  const sessionRef = useRef<RealtimeSession | null>(null);

  const agent = useMemo(() => {
    return new RealtimeAgent({
      name: "SEOS Care Agent",
      instructions: `
You are SEOS Care Voice Agent for parents after a newborn jaundice (or similar risk) detection.

CRITICAL SAFETY RULES:
- You must be conservative and safe.
- If ANY red flags: difficulty waking, poor feeding, fever, blue lips, breathing trouble, seizures, dehydration, baby <24h old with jaundice, rapidly worsening symptoms -> advise urgent ER / emergency services immediately.
- Do not diagnose. Give guidance and encourage a licensed clinician evaluation when appropriate.
- Always cite the guideline excerpts returned by the "retrieve_guidelines" tool when you use them.
- Keep answers short and actionable (voice friendly).
      `.trim(),
      // Tool: RAG retrieval
      tools: [
        {
          name: "retrieve_guidelines",
          description: "Search local medical guideline excerpts and return top matches with citations.",
          parameters: z.object({
            question: z.string().min(3),
          }),
          execute: async ({ question }: { question: string }) => {
            const r = await fetch("/api/rag", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ question }),
            });

            if (!r.ok) {
              const t = await r.text();
              throw new Error(`RAG failed: ${t}`);
            }

            const data = (await r.json()) as { context: RagContext[] };
            setLastCitations(data.context ?? []);
            return data;
          },
        },
      ],
    });
  }, []);

  async function start() {
    setError(null);
    setStatus("connecting");

    try {
      // 1) Mint ephemeral client secret on backend
      const tokenRes = await fetch("/api/realtime-token", { method: "POST" });
      if (!tokenRes.ok) {
        const t = await tokenRes.text();
        throw new Error(`Token endpoint failed: ${t}`);
      }
      const { value } = (await tokenRes.json()) as { value: string };
      if (!value || !value.startsWith("ek_")) {
        throw new Error(`Bad token returned (expected ek_...): ${value}`);
      }

      // 2) Create session and connect (WebRTC in browser by default) :contentReference[oaicite:3]{index=3}
      const session = new RealtimeSession(agent);
      sessionRef.current = session;

      await session.connect({ apiKey: value });

      setStatus("connected");
    } catch (e: any) {
      setError(e?.message ?? String(e));
      setStatus("idle");
    }
  }

  async function stop() {
    try {
      await sessionRef.current?.disconnect?.();
    } catch {}
    sessionRef.current = null;
    setStatus("stopped");
  }

  return (
    <main style={{ fontFamily: "system-ui", padding: 24, maxWidth: 900 }}>
      <h1 style={{ marginBottom: 8 }}>SEOS Care Voice Agent</h1>
      <p style={{ marginTop: 0, color: "#555" }}>
        Click Start, allow microphone, then speak. The agent will use RAG tool calls for guideline-grounded guidance.
      </p>

      <div style={{ display: "flex", gap: 12, alignItems: "center", marginTop: 16 }}>
        <button
          onClick={start}
          disabled={status === "connecting" || status === "connected"}
          style={{ padding: "10px 14px" }}
        >
          {status === "connecting" ? "Connecting..." : status === "connected" ? "Connected" : "Start Voice"}
        </button>

        <button onClick={stop} disabled={status !== "connected"} style={{ padding: "10px 14px" }}>
          Stop
        </button>

        <span style={{ color: "#333" }}>
          Status: <b>{status}</b>
        </span>
      </div>

      {error && (
        <pre style={{ marginTop: 16, padding: 12, background: "#fff3f3", border: "1px solid #ffd0d0" }}>
          {error}
        </pre>
      )}

      <section style={{ marginTop: 24 }}>
        <h2 style={{ marginBottom: 8 }}>Last citations (from RAG tool)</h2>
        {lastCitations.length === 0 ? (
          <p style={{ color: "#666" }}>No citations yet. Ask something so the agent calls retrieve_guidelines.</p>
        ) : (
          <ul>
            {lastCitations.map((c, i) => (
              <li key={i} style={{ marginBottom: 10 }}>
                <div>
                  <b>{c.source}</b>
                </div>
                <div style={{ color: "#444" }}>{c.excerpt}</div>
              </li>
            ))}
          </ul>
        )}
      </section>

      <section style={{ marginTop: 24 }}>
        <h2 style={{ marginBottom: 8 }}>Try saying</h2>
        <ul>
          <li>“SEOS detected jaundice. What should I do next?”</li>
          <li>“Baby looks more yellow and is sleepy — is that urgent?”</li>
          <li>“When should we go to the hospital for jaundice?”</li>
        </ul>
      </section>
    </main>
  );
}

```

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