#!/usr/bin/env python3
"""
Dashboard Server - Serves dashboard.html and handles various APIs.

Now uses Supabase for data storage instead of JSON files.

APIs:
- GET /api/captures - Active captures
- GET /api/captures/archived - Archived captures
- POST /api/capture - Create capture
- POST /api/capture/{id}/archive - Archive capture
- POST /api/capture/{id}/unarchive - Unarchive capture
- DELETE /api/capture/{id} - Delete capture
- GET /api/research/latest - Latest research digest
- GET /api/research/history - Research history
- GET /api/research/{id} - Specific research digest
- PATCH /api/research/{id} - Update research topic
- DELETE /api/research/{id} - Delete research
- GET /api/bioage/latest - Latest bioage entry
- GET /api/bioage/trend - Bioage trend data (12 months)
- POST /api/bioage - Save bioage entry
- GET /api/longevity/dashboard - Full longevity dashboard (domains, scores, WHOOP avgs)
- GET /api/activities/config - Activity tracking configuration
- GET /api/activities/logs - Activity logs (last 90 days)
- GET /api/activities/stats - Activity streaks and weekly summary
- POST /api/activity - Log an activity (from Tasker or manual)
- GET /api/diet - All active diet items
- POST /api/diet - Create diet item
- PATCH /api/diet/{id} - Update diet item
- DELETE /api/diet/{id} - Delete diet item
- GET /api/supplements - All supplements
- POST /api/supplements - Create supplement
- PATCH /api/supplements/{id} - Update supplement
- DELETE /api/supplements/{id} - Delete supplement
- GET /api/body-focus - All active/monitoring body focus areas
- GET /api/body-focus/resolved - Resolved body focus areas
- POST /api/body-focus - Create body focus area
- PATCH /api/body-focus/{id} - Update body focus area
- DELETE /api/body-focus/{id} - Delete body focus area
- GET /api/body-checkins?focus_id=X - Check-ins for a focus area
- GET /api/body-checkins/latest - Latest check-in date + all ratings
- POST /api/body-checkins - Save weekly check-in (batch)
- GET /api/budget - All active budget items
- POST /api/budget - Create budget item
- PATCH /api/budget/{id} - Update budget item
- DELETE /api/budget/{id} - Delete budget item
- GET /api/transactions - Transactions with filters (start_date, end_date, category, limit)
- POST /api/transactions/upload - Upload and parse CommBank CSV
- POST /api/transactions/confirm - Confirm and save parsed transactions
- GET /api/spending-summary - Spending summary with pro-rated budget (period: week/month/quarter)
- GET /api/investments - All active investment assets with latest values
- POST /api/investments - Create investment asset
- PATCH /api/investments/{id} - Update investment asset
- DELETE /api/investments/{id} - Delete investment asset
- GET /api/investment-values?asset_id=X - Value history for an asset
- POST /api/investment-values - Log an investment value
- GET /api/community/overview - All communities with member counts, health, next event
- GET /api/community/here-summary - Here project summary from JSON files
- GET /api/community-members?community_id=X - Members for a community
- GET /api/community-interactions?community_id=X&limit=N - Interaction timeline
- GET /api/community-interactions/stale?community_id=X&days=N - Nudge list
- GET /api/community-events?community_id=X - Events by community
- POST /api/community-members - Create member
- POST /api/community-interactions - Log interaction + update last_interaction_date
- POST /api/community-events - Create event
- PATCH /api/community-members/{id} - Edit member
- PATCH /api/community-events/{id} - Edit event
- DELETE /api/community-members/{id} - Delete member
- DELETE /api/community-interactions/{id} - Delete interaction
- DELETE /api/community-events/{id} - Delete event
- GET /api/community-contacts?community_id=X - Shared contacts for a community
- POST /api/community-contacts - Create shared contact
- PATCH /api/community-contacts/{id} - Update shared contact
- DELETE /api/community-contacts/{id} - Delete shared contact
- GET /api/enrichment/{activity_log_id} - Get enrichment for an activity log
- POST /api/enrichment - Create enrichment for an activity log
- PATCH /api/enrichment/{id} - Update enrichment
- DELETE /api/enrichment/{id} - Delete enrichment
- GET /api/play/spots?sport=X - Active spots
- GET /api/play/goals?activity=X - Goals/focus areas
- POST /api/play/spots - Add a spot
- POST /api/play/goals - Add a goal
- PATCH /api/play/goals/{id} - Update goal
- DELETE /api/play/spots/{id} - Delete spot
- DELETE /api/play/goals/{id} - Delete goal
- GET /api/recovery/protocols - Recovery protocols
- POST /api/recovery/protocols - Upsert recovery protocol
- GET /api/training/programs - Training programs
- POST /api/training/programs - Upsert training program
- GET /api/journal-entries - Journal entries (with ?limit=N&since=YYYY-MM-DD)
- POST /api/journal-entry - Create typed journal entry (AI metadata extraction)
- GET /api/awareness/stats - Awareness stats (presence trend, schema/theme counts)
- POST /api/photos/upload?type=journal - Upload photo (auto-transcription for journal)
- GET /api/photos/pending - Unprocessed journal photos

Usage:
    python dashboard-server.py

Then open: http://localhost:8765/dashboard.html
"""

import json
import http.server
import socketserver
from datetime import datetime
from pathlib import Path
import hashlib
import os
import re
import sys

# Brisbane timezone handling
try:
    from zoneinfo import ZoneInfo
    BRISBANE_TZ = ZoneInfo("Australia/Brisbane")
except ImportError:
    # Fallback for older Python
    import pytz
    BRISBANE_TZ = pytz.timezone("Australia/Brisbane")


def get_brisbane_now():
    """Get current datetime in Brisbane timezone"""
    return datetime.now(BRISBANE_TZ)


_cached_user_dob = None

def get_user_dob():
    """Read DOB from personal-profile.md context file. Caches after first read."""
    global _cached_user_dob
    if _cached_user_dob is not None:
        return _cached_user_dob
    try:
        profile_path = Path(__file__).parent / '.claude' / 'context' / 'personal-profile.md'
        with open(profile_path, 'r') as f:
            for line in f:
                if '| Date of Birth |' in line:
                    # Parse "| Date of Birth | 20 July 1974 |"
                    parts = [p.strip() for p in line.split('|')]
                    from dateutil.parser import parse as dateparse
                    _cached_user_dob = dateparse(parts[2]).replace(tzinfo=BRISBANE_TZ)
                    return _cached_user_dob
    except Exception:
        pass
    # Fallback: 20 July 1974
    _cached_user_dob = datetime(1974, 7, 20, tzinfo=BRISBANE_TZ)
    return _cached_user_dob


# Add skills directory to path for imports
sys.path.insert(0, str(Path(__file__).parent / '.claude' / 'skills' / 'supabase-connector'))

PORT = 8765

# Initialize Supabase client (lazy loading)
_supabase = None


def get_supabase():
    """Get Supabase client (lazy initialization)"""
    global _supabase
    if _supabase is None:
        try:
            from client import get_supabase_client
            _supabase = get_supabase_client()
            print("  Connected to Supabase")
        except Exception as e:
            print(f"  Warning: Supabase unavailable ({e})")
            print("  Captures will not be saved!")
    return _supabase


# Activity config (lazy loading)
_activity_config = None


def get_activity_config():
    """Load activity config from JSON file"""
    global _activity_config
    if _activity_config is None:
        config_path = Path(__file__).parent / '.claude' / 'context' / 'activity-config.json'
        try:
            with open(config_path) as f:
                _activity_config = json.load(f)
        except Exception as e:
            print(f"  Warning: Could not load activity config ({e})")
            _activity_config = {"activities": {}, "tasker_mappings": {}, "api_sources": {}}
    return _activity_config


def reload_activity_config():
    """Force reload of activity config"""
    global _activity_config
    _activity_config = None
    return get_activity_config()


# House admin data (lazy loading)
_house_admin = None
HOUSE_ADMIN_PATH = Path(__file__).parent / '.claude' / 'context' / 'house-admin.json'


def get_house_admin():
    """Load house admin data from JSON file"""
    global _house_admin
    if _house_admin is None:
        try:
            with open(HOUSE_ADMIN_PATH) as f:
                _house_admin = json.load(f)
        except Exception as e:
            print(f"  Warning: Could not load house admin ({e})")
            _house_admin = {"contacts": []}
    return _house_admin


def save_house_admin(data):
    """Save house admin data to JSON file"""
    global _house_admin
    with open(HOUSE_ADMIN_PATH, 'w') as f:
        json.dump(data, f, indent=2)
    _house_admin = data
    return data


# Merchant categories config (lazy loading)
_merchant_categories = None


def get_merchant_categories():
    """Load merchant categories from JSON file"""
    global _merchant_categories
    if _merchant_categories is None:
        config_path = Path(__file__).parent / '.claude' / 'context' / 'merchant-categories.json'
        try:
            with open(config_path) as f:
                _merchant_categories = json.load(f)
        except Exception as e:
            print(f"  Warning: Could not load merchant categories ({e})")
            _merchant_categories = {"categories": {}, "mappings": [], "unknown_default": "other"}
    return _merchant_categories


# =========================================================================
# JOURNAL AI PROCESSING (Anthropic SDK)
# =========================================================================

_anthropic_client = None
_schema_context_cache = None


def _load_schema_context() -> str:
    """
    Load and format Kim's schema definitions from psychological-patterns.md.
    Returns a compact formatted string for injection into prompts.
    Caches result so it's only read from disk once per process.
    Falls back to bare names if file can't be read.
    """
    global _schema_context_cache
    if _schema_context_cache is not None:
        return _schema_context_cache

    try:
        patterns_path = Path(__file__).parent / '.claude' / 'context' / 'psychological-patterns.md'
        if not patterns_path.exists():
            return "Known schemas: subjugation, unrelenting_standards, self_sacrifice."

        with open(patterns_path, 'r') as f:
            content = f.read()

        # Extract schema sections from the markdown
        schemas = {}

        # Parse SUBJUGATION section
        if "### Subjugation" in content:
            start = content.find("### Subjugation")
            end = content.find("---", start + 1)
            if end == -1:
                end = content.find("### ", start + 20)
            section = content[start:end].strip()
            # Extract key behavioral signals
            watch_for = section.find("**Watch for**:")
            growth = section.find("**Growth work**:")
            if watch_for > 0:
                watch_text = section[watch_for+14:growth if growth > 0 else len(section)]
                schemas['subjugation'] = (
                    "SUBJUGATION: Suppressing own needs/feelings to avoid conflict. "
                    "Watch for: staying silent, not expressing preferences, feeling controlled, "
                    "passivity. Growth: recognize your feelings, express yourself, ask 'What do I need?'"
                )

        # Parse UNRELENTING_STANDARDS section
        if "### Unrelenting Standards" in content:
            start = content.find("### Unrelenting Standards")
            end = content.find("---", start + 1)
            if end == -1:
                end = content.find("### ", start + 30)
            section = content[start:end].strip()
            schemas['unrelenting_standards'] = (
                "UNRELENTING_STANDARDS: The 'not good enough' story. Never feeling satisfied. "
                "Watch for: rigid 'should' thinking, irritation, impatience, pushing too hard, "
                "comparing yourself to others, guilt when imperfect. Growth: slow down, self-compassion, "
                "notice the story: 'I'm noticing the not-good-enough story is here again.'"
            )

        # Parse SELF_SACRIFICE section
        if "### Self-Sacrifice" in content:
            start = content.find("### Self-Sacrifice")
            end = content.find("---", start + 1)
            if end == -1:
                end = content.find("### ", start + 25)
            section = content[start:end].strip()
            schemas['self_sacrifice'] = (
                "SELF_SACRIFICE: Feeling responsible for fixing others' emotional distress. "
                "Watch for: 'I need to make them feel better', guilt when not helping, "
                "throwing money/time/energy at problems. Key truth: 'Fixing emotional distress is not my job.'"
            )

        # Build the formatted context string
        if schemas:
            context = "Kim's known psychological schemas — only tag what's clearly present:\n\n"
            context += "\n".join(schemas.values())
            _schema_context_cache = context
            return context
        else:
            # Fallback if parsing fails
            _schema_context_cache = "Known schemas: subjugation, unrelenting_standards, self_sacrifice."
            return _schema_context_cache

    except Exception as e:
        print(f"[WARN] Could not load schema context: {e}")
        _schema_context_cache = "Known schemas: subjugation, unrelenting_standards, self_sacrifice."
        return _schema_context_cache


def _get_anthropic_client():
    """Get or create Anthropic client singleton. Returns None if unavailable."""
    global _anthropic_client
    if _anthropic_client is None:
        # Try environment variable first, then config file
        api_key = os.environ.get("ANTHROPIC_API_KEY")
        if not api_key:
            try:
                config_path = Path(__file__).parent / ".claude" / "context" / "anthropic-config.json"
                if config_path.exists():
                    with open(config_path) as f:
                        config = json.load(f)
                        api_key = config.get("api_key")
            except Exception:
                pass

        if not api_key:
            return None
        try:
            import anthropic
            _anthropic_client = anthropic.Anthropic(api_key=api_key)
        except ImportError:
            print("[WARN] anthropic package not installed — pip install anthropic")
            return None
    return _anthropic_client


def _extract_journal_metadata(text: str) -> dict:
    """Extract mood, schemas, themes from journal text using Claude API."""
    client = _get_anthropic_client()
    if not client:
        return {}

    try:
        schema_context = _load_schema_context()
        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=512,
            system="You analyze journal entries for a 51-year-old man doing ACT/schema therapy work. "
                   "Extract psychological metadata. Be conservative — only tag what's clearly present. "
                   f"{schema_context} "
                   "Return ONLY valid JSON, no markdown.",
            messages=[{
                "role": "user",
                "content": f"Analyze this journal entry and return JSON with these fields:\n"
                           f"- mood_indicators: array of 1-3 mood words (e.g. calm, frustrated, reflective, anxious, grateful)\n"
                           f"- schemas_detected: array of activated schemas (subjugation, unrelenting_standards, self_sacrifice) — empty if none\n"
                           f"- themes: array of 1-4 life themes (e.g. work, surfing, smilla, relationships, sleep, health)\n"
                           f"- presence_note: one sentence about presence/calmness level, or null\n\n"
                           f"Journal entry:\n{text}"
            }]
        )
        result_text = response.content[0].text.strip()
        # Handle potential markdown wrapping
        if result_text.startswith("```"):
            result_text = result_text.split("\n", 1)[1].rsplit("```", 1)[0].strip()
        return json.loads(result_text)
    except Exception as e:
        print(f"[WARN] Metadata extraction failed: {e}")
        return {}


def _transcribe_journal_photo(photo_path: str) -> dict:
    """Transcribe handwritten journal photo and extract metadata using Claude vision."""
    client = _get_anthropic_client()
    if not client:
        raise RuntimeError("Anthropic API key not configured. Set ANTHROPIC_API_KEY env var or add api_key to .claude/context/anthropic-config.json")

    import base64

    with open(photo_path, "rb") as f:
        image_data = base64.standard_b64encode(f.read()).decode("utf-8")

    # Determine media type
    ext = Path(photo_path).suffix.lower()
    media_types = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
                   ".webp": "image/webp", ".heic": "image/heic"}
    media_type = media_types.get(ext, "image/jpeg")

    try:
        schema_context = _load_schema_context()
        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=2048,
            system="You transcribe handwritten journal entries faithfully. "
                   "Preserve the writer's exact words, spelling, and structure. "
                   "Do NOT interpret, clean up, or add commentary. "
                   "After transcription, extract psychological metadata. "
                   f"{schema_context} "
                   "Return ONLY valid JSON, no markdown.",
            messages=[{
                "role": "user",
                "content": [
                    {
                        "type": "image",
                        "source": {
                            "type": "base64",
                            "media_type": media_type,
                            "data": image_data
                        }
                    },
                    {
                        "type": "text",
                        "text": "Transcribe this handwritten journal entry faithfully, then return JSON with:\n"
                                "- raw_text: the full transcription (preserve exact words)\n"
                                "- mood_indicators: array of 1-3 mood words\n"
                                "- schemas_detected: array of activated schemas (empty if none)\n"
                                "- themes: array of 1-4 life themes\n"
                                "- presence_note: one sentence about presence/calmness, or null"
                    }
                ]
            }]
        )
        result_text = response.content[0].text.strip()
        if result_text.startswith("```"):
            result_text = result_text.split("\n", 1)[1].rsplit("```", 1)[0].strip()
        return json.loads(result_text)
    except Exception as e:
        print(f"[ERROR] Photo transcription failed: {e}")
        return None


class DashboardHandler(http.server.SimpleHTTPRequestHandler):

    def do_GET(self):
        """Handle GET requests - serve files and API endpoints"""
        if self.path == "/api/captures":
            # GET /api/captures - return all active captures
            self._get_captures()
        elif self.path == "/api/captures/archived":
            # GET /api/captures/archived - return archived captures
            self._get_captures(status='archived')
        elif self.path == "/api/research/latest":
            # GET /api/research/latest - return most recent research digest
            self._get_latest_research()
        elif self.path == "/api/research/history":
            # GET /api/research/history - return research history
            self._get_research_history()
        elif self.path.startswith("/api/research/") and len(self.path.split("/")) == 4:
            # GET /api/research/{id} - return specific research digest
            research_id = self.path.split("/")[3]
            self._get_research_by_id(research_id)
        elif self.path == "/api/longevity/dashboard":
            # GET /api/longevity/dashboard - full longevity dashboard data
            self._get_longevity_dashboard()
        elif self.path == "/api/bioage/latest":
            # GET /api/bioage/latest - return most recent bioage entry
            self._get_latest_bioage()
        elif self.path == "/api/bioage/trend":
            # GET /api/bioage/trend - return bioage trend data
            self._get_bioage_trend()
        elif self.path == "/api/activities/config":
            # GET /api/activities/config - return activity configuration
            self._get_activities_config()
        elif self.path == "/api/activities/logs" or self.path.startswith("/api/activities/logs?"):
            # GET /api/activities/logs - return activity logs
            self._get_activities_logs()
        elif self.path == "/api/activities/stats":
            # GET /api/activities/stats - return activity streaks and summary
            self._get_activities_stats()
        elif self.path == "/api/admin":
            # GET /api/admin - return house admin contacts
            self._get_admin_contacts()
        elif self.path == "/api/sync-status":
            # GET /api/sync-status - return sync health status
            self._get_sync_status()
        elif self.path == "/api/diet":
            # GET /api/diet - return all active diet items
            self._get_diet_items()
        elif self.path == "/api/supplements":
            # GET /api/supplements - return all supplements
            self._get_supplements()
        elif self.path == "/api/body-focus":
            # GET /api/body-focus - return active/monitoring body focus areas
            self._get_body_focus()
        elif self.path == "/api/body-focus/resolved":
            # GET /api/body-focus/resolved - return resolved body focus areas
            self._get_body_focus(resolved=True)
        elif self.path.startswith("/api/body-checkins/latest"):
            # GET /api/body-checkins/latest - return latest check-in date + ratings
            self._get_body_checkins_latest()
        elif self.path.startswith("/api/body-checkins"):
            # GET /api/body-checkins?focus_id=X - return check-ins for a focus area
            self._get_body_checkins()
        elif self.path == "/api/budget":
            self._get_budget()
        elif self.path.startswith("/api/transactions"):
            self._get_transactions()
        elif self.path.startswith("/api/spending-summary"):
            self._get_spending_summary()
        elif self.path == "/api/investments":
            self._get_investments()
        elif self.path.startswith("/api/investment-values"):
            self._get_investment_values()
        # Blood Test endpoints
        elif self.path == "/api/bloodtests":
            self._get_bloodtests()
        elif self.path == "/api/bloodtests/latest":
            self._get_bloodtest_latest()
        elif self.path == "/api/bloodtests/trends":
            self._get_bloodtest_trends()
        elif self.path == "/api/bloodtests/actions":
            self._get_bloodtest_actions_all()
        elif self.path == "/api/bloodtests/health-context":
            self._get_bloodtest_health_context()
        elif self.path.startswith("/api/bloodtests/") and len(self.path.split("/")) == 4:
            panel_id = self.path.split("/")[3]
            self._get_bloodtest_by_id(panel_id)
        # Community endpoints
        elif self.path == "/api/community/overview":
            self._get_community_overview()
        elif self.path == "/api/community/here-summary":
            self._get_here_summary()
        elif self.path.startswith("/api/community-members"):
            self._get_community_members()
        elif self.path.startswith("/api/community-interactions/stale"):
            self._get_community_interactions_stale()
        elif self.path.startswith("/api/community-interactions"):
            self._get_community_interactions()
        elif self.path.startswith("/api/community-events"):
            self._get_community_events()
        elif self.path.startswith("/api/community-contacts"):
            self._get_community_contacts()
        # Enrichment + spots/goals endpoints
        elif self.path.startswith("/api/enrichment/"):
            activity_log_id = self.path.split("/")[3]
            self._get_enrichment(activity_log_id)
        elif self.path.startswith("/api/play/spots"):
            self._get_play_spots()
        elif self.path.startswith("/api/play/goals"):
            self._get_play_goals()
        elif self.path == "/api/recovery/protocols":
            self._get_recovery_protocols()
        elif self.path == "/api/training/programs":
            self._get_training_programs()
        # Journal & Awareness endpoints
        elif self.path == "/api/journal-entries" or self.path.startswith("/api/journal-entries?"):
            self._get_journal_entries()
        elif self.path == "/api/awareness/stats" or self.path.startswith("/api/awareness/stats?"):
            self._get_awareness_stats()
        elif self.path == "/api/photos/pending":
            self._get_pending_photos()
        else:
            # Serve static files
            super().do_GET()

    def _get_captures(self, status='active'):
        """Get captures from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_captures')\
                    .select('*')\
                    .eq('status', status)\
                    .order('date', desc=True)\
                    .order('time', desc=True)\
                    .limit(100)\
                    .execute()
                captures = result.data or []
            else:
                captures = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"logs": captures}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_latest_research(self):
        """Get most recent health research digest from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_health_research')\
                    .select('*')\
                    .order('date', desc=True)\
                    .limit(1)\
                    .execute()
                research = result.data[0] if result.data else {}
            else:
                research = {}

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(research).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_research_history(self):
        """Get health research history from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_health_research')\
                    .select('id, date, topic, executive_summary, quality_score, created_at')\
                    .order('date', desc=True)\
                    .limit(12)\
                    .execute()
                history = result.data or []
            else:
                history = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"history": history}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_research_by_id(self, research_id):
        """Get specific health research digest from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_health_research')\
                    .select('*')\
                    .eq('id', research_id)\
                    .execute()
                research = result.data[0] if result.data else {}
            else:
                research = {}

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(research).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_longevity_dashboard(self):
        """Assemble full longevity dashboard: domain config, scores, WHOOP averages, blood tests"""
        try:
            from datetime import timedelta
            supabase = get_supabase()
            if not supabase:
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "No database connection"}).encode())
                return

            # 1. Fetch domain config
            domains_result = supabase.table('chl_longevity_domains')\
                .select('*')\
                .order('sort_order')\
                .execute()
            domains = domains_result.data or []

            # 2. Fetch latest bioage log (manual data)
            bioage_result = supabase.table('chl_bioage_logs')\
                .select('*')\
                .order('date', desc=True)\
                .limit(1)\
                .execute()
            latest_bioage = bioage_result.data[0] if bioage_result.data else {}

            # 3. Fetch latest blood test markers (join latest panel)
            blood_markers = {}
            blood_panel_date = None
            try:
                panel_result = supabase.table('chl_bloodtest_panels')\
                    .select('id, date')\
                    .order('date', desc=True)\
                    .limit(1)\
                    .execute()
                if panel_result.data:
                    panel_id = panel_result.data[0]['id']
                    blood_panel_date = panel_result.data[0]['date']
                    markers_result = supabase.table('chl_bloodtest_markers')\
                        .select('name, value, unit')\
                        .eq('panel_id', panel_id)\
                        .execute()
                    for m in (markers_result.data or []):
                        blood_markers[m['name']] = {'value': float(m['value']), 'unit': m['unit']}
            except Exception:
                pass

            # 4. Compute 14-day WHOOP averages
            whoop_start = (get_brisbane_now() - timedelta(days=14)).strftime('%Y-%m-%d')
            whoop_avgs = {}
            try:
                whoop_result = supabase.table('chl_health_logs')\
                    .select('recovery_hrv, recovery_rhr, recovery_score, sleep_efficiency, sleep_deep_hours, sleep_total_hours')\
                    .gte('date', whoop_start)\
                    .execute()
                whoop_rows = whoop_result.data or []
                if whoop_rows:
                    fields = ['recovery_hrv', 'recovery_rhr', 'recovery_score', 'sleep_efficiency', 'sleep_deep_hours', 'sleep_total_hours']
                    for f in fields:
                        vals = [float(r[f]) for r in whoop_rows if r.get(f) is not None]
                        if vals:
                            whoop_avgs[f] = round(sum(vals) / len(vals), 1)
                    # Compute deep sleep % if we have both
                    if whoop_avgs.get('sleep_deep_hours') and whoop_avgs.get('sleep_total_hours') and whoop_avgs['sleep_total_hours'] > 0:
                        whoop_avgs['deep_sleep_pct'] = round((whoop_avgs['sleep_deep_hours'] / whoop_avgs['sleep_total_hours']) * 100, 1)
            except Exception:
                pass

            # 5. Score each biomarker per domain
            def score_biomarker(value, bio):
                """Score a biomarker 0-100 against longevity optimal ranges"""
                if value is None:
                    return None
                opt_low = bio.get('optimal_low')
                opt_high = bio.get('optimal_high')
                ref_low = bio.get('ref_low', opt_low)
                ref_high = bio.get('ref_high', opt_high)
                direction = bio.get('direction', 'range')

                if opt_low is None or opt_high is None:
                    return None

                # Within optimal range → 100
                if opt_low <= value <= opt_high:
                    return 100

                if direction == 'lower_better':
                    if value < opt_low:
                        return 100  # Even better than optimal
                    # Between opt_high and ref_high → linear 100→0
                    if ref_high > opt_high:
                        return max(0, round(100 * (1 - (value - opt_high) / (ref_high - opt_high))))
                    return 0

                if direction == 'higher_better':
                    if value > opt_high:
                        return 100  # Even better than optimal
                    # Between ref_low and opt_low → linear 0→100
                    if opt_low > ref_low:
                        return max(0, round(100 * (value - ref_low) / (opt_low - ref_low)))
                    return 0

                # direction == 'range'
                if value < opt_low:
                    if opt_low > ref_low:
                        return max(0, round(100 * (value - ref_low) / (opt_low - ref_low)))
                    return 0
                if value > opt_high:
                    if ref_high > opt_high:
                        return max(0, round(100 * (1 - (value - opt_high) / (ref_high - opt_high))))
                    return 0

                return 100

            def get_biomarker_value(bio, blood_markers, whoop_avgs, latest_bioage):
                """Resolve a biomarker's current value from its source"""
                source = bio.get('source', '')
                if source == 'bloodtest':
                    bt_name = bio.get('bloodtest_name', '')
                    if bt_name in blood_markers:
                        return blood_markers[bt_name]['value']
                elif source == 'whoop':
                    field = bio.get('whoop_field', '')
                    # Special: deep sleep % is calculated
                    if bio.get('key') == 'deep_sleep_pct':
                        return whoop_avgs.get('deep_sleep_pct')
                    return whoop_avgs.get(field)
                elif source == 'manual':
                    key = bio.get('key', '')
                    field_map = {
                        'vascular_age': 'vascular_age',
                        'vo2max': None,  # from functional_fitness
                        'whoop_age': 'whoop_age',
                        'body_fat': 'body_fat_pct',
                        'visceral_fat': 'visceral_fat_level',
                        'muscle_mass': 'muscle_mass_kg',
                    }
                    if key == 'vo2max':
                        ff = latest_bioage.get('functional_fitness') or {}
                        if isinstance(ff, str):
                            ff = json.loads(ff)
                        return ff.get('vo2max')
                    mapped = field_map.get(key)
                    if mapped and latest_bioage.get(mapped) is not None:
                        return float(latest_bioage[mapped])
                elif source == 'calculated':
                    if bio.get('key') == 'trig_hdl_ratio':
                        trig = blood_markers.get('Triglycerides', {}).get('value')
                        hdl = blood_markers.get('HDL Cholesterol', {}).get('value')
                        if trig is not None and hdl is not None and hdl > 0:
                            return round(trig / hdl, 2)
                return None

            now = get_brisbane_now()
            birth = get_user_dob()
            chrono_age = round((now - birth).days / 365.25, 1)

            domain_results = []
            total_weighted_score = 0
            total_weight = 0
            total_biomarkers = 0
            scored_biomarkers = 0

            for domain in domains:
                biomarkers_list = domain.get('biomarkers', [])
                if isinstance(biomarkers_list, str):
                    biomarkers_list = json.loads(biomarkers_list)

                scored = []
                domain_weighted_sum = 0
                domain_weight_sum = 0

                for bio in biomarkers_list:
                    total_biomarkers += 1
                    value = get_biomarker_value(bio, blood_markers, whoop_avgs, latest_bioage)
                    score = score_biomarker(value, bio) if value is not None else None

                    bio_result = {
                        'key': bio.get('key'),
                        'label': bio.get('label'),
                        'source': bio.get('source'),
                        'unit': bio.get('unit'),
                        'value': value,
                        'score': score,
                        'optimal_low': bio.get('optimal_low'),
                        'optimal_high': bio.get('optimal_high'),
                        'ref_low': bio.get('ref_low'),
                        'ref_high': bio.get('ref_high'),
                        'direction': bio.get('direction'),
                        'weight': bio.get('weight', 1),
                    }
                    scored.append(bio_result)

                    if score is not None:
                        scored_biomarkers += 1
                        w = bio.get('weight', 1)
                        domain_weighted_sum += score * w
                        domain_weight_sum += w

                domain_score = round(domain_weighted_sum / domain_weight_sum) if domain_weight_sum > 0 else None

                # Data freshness
                freshness = None
                sources = set(b.get('source') for b in biomarkers_list)
                if 'bloodtest' in sources and blood_panel_date:
                    freshness = blood_panel_date
                elif 'whoop' in sources:
                    freshness = now.strftime('%Y-%m-%d')
                elif 'manual' in sources and latest_bioage.get('date'):
                    freshness = latest_bioage['date']

                # Per-domain bio age: same formula as composite
                domain_bio_age = None
                domain_age_delta = None
                if domain_score is not None:
                    domain_bio_age = round(chrono_age - 10 + (100 - domain_score) * 25 / 100, 1)
                    domain_age_delta = round(domain_bio_age - chrono_age, 1)

                domain_results.append({
                    'id': domain['id'],
                    'domain': domain['domain'],
                    'display_name': domain['display_name'],
                    'icon': domain.get('icon', ''),
                    'weight': float(domain['weight']),
                    'score': domain_score,
                    'bio_age': domain_bio_age,
                    'age_delta': domain_age_delta,
                    'freshness': freshness,
                    'biomarkers': scored,
                })

                if domain_score is not None:
                    dw = float(domain['weight'])
                    total_weighted_score += domain_score * dw
                    total_weight += dw

            # 6. Composite score and bio age mapping
            composite_score = round(total_weighted_score / total_weight) if total_weight > 0 else None

            # Map score to bio age: score 100 → chrono_age - 10, score 0 → chrono_age + 15
            composite_bio_age = None
            age_delta = None
            if composite_score is not None:
                composite_bio_age = round(chrono_age - 10 + (100 - composite_score) * 25 / 100, 1)
                age_delta = round(composite_bio_age - chrono_age, 1)

            # 7. Confidence = ratio of biomarkers with values, blood >6mo gets 50% weight
            confidence = round(scored_biomarkers / total_biomarkers * 100) if total_biomarkers > 0 else 0

            # Pace of aging from latest bioage entry
            pace = latest_bioage.get('pace_of_aging')

            response = {
                'chronological_age': chrono_age,
                'composite_score': composite_score,
                'composite_bio_age': composite_bio_age,
                'age_delta': age_delta,
                'pace_of_aging': pace,
                'confidence': confidence,
                'domains': domain_results,
                'whoop_averages': whoop_avgs,
                'latest_bioage': latest_bioage,
                'blood_panel_date': blood_panel_date,
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        except Exception as e:
            import traceback
            traceback.print_exc()
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_latest_bioage(self):
        """Get most recent bioage entry from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_bioage_logs')\
                    .select('*')\
                    .order('date', desc=True)\
                    .limit(1)\
                    .execute()
                bioage = result.data[0] if result.data else {}
            else:
                bioage = {}

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(bioage).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bioage_trend(self):
        """Get bioage trend data for the last 12 months from Supabase"""
        try:
            from datetime import timedelta
            start_date = (get_brisbane_now() - timedelta(days=365)).strftime('%Y-%m-%d')

            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_bioage_logs')\
                    .select('date, whoop_age, vascular_age, composite_bio_age, pace_of_aging, age_delta, chronological_age, domain_scores')\
                    .gte('date', start_date)\
                    .order('date', desc=False)\
                    .execute()
                trend = result.data or []
            else:
                trend = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"trend": trend}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_activities_config(self):
        """Get activity tracking configuration"""
        try:
            config = get_activity_config()
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(config).encode())
        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_activities_logs(self):
        """Get activity logs for the last 90 days, LEFT JOIN enrichment from chl_sport_sessions"""
        try:
            from datetime import timedelta
            start_date = (get_brisbane_now() - timedelta(days=90)).strftime('%Y-%m-%d')

            supabase = get_supabase()
            logs = []
            if supabase:
                result = supabase.table('chl_activity_logs')\
                    .select('*')\
                    .gte('date', start_date)\
                    .order('date', desc=True)\
                    .order('time', desc=True)\
                    .execute()
                logs = result.data or []

                # LEFT JOIN enrichment from chl_sport_sessions
                enrichment_result = supabase.table('chl_sport_sessions')\
                    .select('*')\
                    .gte('date', start_date)\
                    .execute()
                enrichment_map = {}
                for e in (enrichment_result.data or []):
                    if e.get('activity_log_id'):
                        enrichment_map[e['activity_log_id']] = e

                for log in logs:
                    enrichment = enrichment_map.get(log['id'])
                    if enrichment:
                        log['enrichment'] = {
                            'id': enrichment['id'],
                            'enjoyment': enrichment.get('enjoyment'),
                            'spot': enrichment.get('spot'),
                            'notes': enrichment.get('notes'),
                            'focus_area': enrichment.get('focus_area'),
                            'conditions_summary': enrichment.get('conditions_summary'),
                            'highlights': enrichment.get('highlights'),
                        }
                        log['is_enriched'] = True
                    else:
                        log['enrichment'] = None
                        log['is_enriched'] = False

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"logs": logs}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_activities_stats(self):
        """Get activity streaks and weekly summary"""
        try:
            from datetime import timedelta
            from collections import defaultdict

            config = get_activity_config()
            activities = config.get('activities', {})
            today = get_brisbane_now().date()
            start_date = (today - timedelta(days=90)).strftime('%Y-%m-%d')

            supabase = get_supabase()
            if not supabase:
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"streaks": {}, "weekly": {}, "monthly": {}}).encode())
                return

            result = supabase.table('chl_activity_logs')\
                .select('date, activity, duration_minutes')\
                .gte('date', start_date)\
                .order('date', desc=True)\
                .execute()
            logs = result.data or []

            # Group logs by activity (filter out activities under 2 minutes)
            activity_dates = defaultdict(set)
            for log in logs:
                # Skip activities under 2 minutes
                duration = log.get('duration_minutes')
                if duration is not None and duration < 2:
                    continue
                activity_dates[log['activity']].add(log['date'])

            # Calculate streaks for each activity
            streaks = {}
            for activity_key in activities.keys():
                dates = sorted(activity_dates.get(activity_key, []), reverse=True)
                if not dates:
                    streaks[activity_key] = {"current": 0, "longest": 0}
                    continue

                # Current streak (consecutive days from today)
                current_streak = 0
                check_date = today
                for i in range(len(dates)):
                    date_str = check_date.strftime('%Y-%m-%d')
                    if date_str in dates:
                        current_streak += 1
                        check_date -= timedelta(days=1)
                    elif i == 0:
                        # Check if yesterday counts (allow 1 day gap)
                        check_date -= timedelta(days=1)
                        date_str = check_date.strftime('%Y-%m-%d')
                        if date_str in dates:
                            current_streak += 1
                            check_date -= timedelta(days=1)
                        else:
                            break
                    else:
                        break

                # Longest streak
                dates_sorted = sorted(dates)
                longest = 0
                current = 1
                for i in range(1, len(dates_sorted)):
                    prev = datetime.strptime(dates_sorted[i-1], '%Y-%m-%d').date()
                    curr = datetime.strptime(dates_sorted[i], '%Y-%m-%d').date()
                    if (curr - prev).days == 1:
                        current += 1
                    else:
                        longest = max(longest, current)
                        current = 1
                longest = max(longest, current) if dates_sorted else 0

                streaks[activity_key] = {
                    "current": current_streak,
                    "longest": longest
                }

            # Weekly summary (this week)
            week_start = today - timedelta(days=today.weekday())
            week_start_str = week_start.strftime('%Y-%m-%d')
            weekly = {}
            for activity_key in activities.keys():
                week_dates = [d for d in activity_dates.get(activity_key, []) if d >= week_start_str]
                weekly[activity_key] = len(set(week_dates))

            # Monthly summary (this month)
            month_start = today.replace(day=1).strftime('%Y-%m-%d')
            monthly = {}
            for activity_key in activities.keys():
                month_dates = [d for d in activity_dates.get(activity_key, []) if d >= month_start]
                monthly[activity_key] = len(set(month_dates))

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({
                "streaks": streaks,
                "weekly": weekly,
                "monthly": monthly
            }).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_admin_contacts(self):
        """Get house admin contacts"""
        try:
            data = get_house_admin()
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(data).encode())
        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_sync_status(self):
        """Get sync status for WHOOP and Chess"""
        try:
            from datetime import timedelta

            supabase = get_supabase()
            now = get_brisbane_now()
            today = now.date()

            # Check WHOOP sync - get most recent whoop activity
            whoop_last = None
            whoop_status = "unknown"
            if supabase:
                result = supabase.table('chl_activity_logs')\
                    .select('date, time')\
                    .eq('source', 'whoop')\
                    .order('date', desc=True)\
                    .order('time', desc=True)\
                    .limit(1)\
                    .execute()
                if result.data:
                    whoop_last = f"{result.data[0]['date']} {result.data[0]['time']}"
                    last_date = datetime.strptime(result.data[0]['date'], '%Y-%m-%d').date()
                    days_ago = (today - last_date).days
                    if days_ago <= 1:
                        whoop_status = "healthy"
                    elif days_ago <= 3:
                        whoop_status = "stale"
                    else:
                        whoop_status = "error"

            # Check Chess sync - get most recent chess activity
            chess_last = None
            chess_status = "unknown"
            if supabase:
                result = supabase.table('chl_activity_logs')\
                    .select('date, time')\
                    .in_('source', ['chess.com', 'lichess'])\
                    .order('date', desc=True)\
                    .order('time', desc=True)\
                    .limit(1)\
                    .execute()
                if result.data:
                    chess_last = f"{result.data[0]['date']} {result.data[0]['time']}"
                    last_date = datetime.strptime(result.data[0]['date'], '%Y-%m-%d').date()
                    days_ago = (today - last_date).days
                    # Chess is less frequent, so give more leeway
                    if days_ago <= 7:
                        chess_status = "healthy"
                    elif days_ago <= 14:
                        chess_status = "stale"
                    else:
                        chess_status = "error"
                else:
                    # No chess games is fine if user doesn't play
                    chess_status = "healthy"

            # Overall status
            if whoop_status == "error":
                overall = "error"
            elif whoop_status == "stale":
                overall = "warning"
            else:
                overall = "healthy"

            status = {
                "overall": overall,
                "whoop": {
                    "status": whoop_status,
                    "last_sync": whoop_last
                },
                "chess": {
                    "status": chess_status,
                    "last_sync": chess_last
                },
                "checked_at": now.strftime('%Y-%m-%d %H:%M:%S')
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(status).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_diet_items(self):
        """Get all active diet items ordered by meal_slot and sort_order"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_diet_items')\
                    .select('*')\
                    .eq('is_active', True)\
                    .order('sort_order', desc=False)\
                    .execute()
                items = result.data or []
            else:
                items = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"items": items}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_supplements(self):
        """Get all supplements (active first, then by sort_order)"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_supplements')\
                    .select('*')\
                    .order('sort_order', desc=False)\
                    .execute()
                supplements = result.data or []
            else:
                supplements = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"supplements": supplements}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_body_focus(self, resolved=False):
        """Get body focus areas from Supabase"""
        try:
            supabase = get_supabase()
            if supabase:
                query = supabase.table('chl_body_focus')\
                    .select('*')\
                    .order('sort_order', desc=False)
                if resolved:
                    query = query.eq('status', 'resolved')
                else:
                    query = query.neq('status', 'resolved')
                result = query.execute()
                items = result.data or []
            else:
                items = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"items": items}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_body_checkins(self):
        """Get body check-ins, optionally filtered by focus_id"""
        try:
            from urllib.parse import urlparse, parse_qs
            parsed = urlparse(self.path)
            params = parse_qs(parsed.query)
            focus_id = params.get('focus_id', [None])[0]

            supabase = get_supabase()
            if supabase:
                query = supabase.table('chl_body_checkins')\
                    .select('*')\
                    .order('date', desc=True)\
                    .limit(12)
                if focus_id:
                    query = query.eq('focus_id', focus_id)
                result = query.execute()
                checkins = result.data or []
            else:
                checkins = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"checkins": checkins}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_body_checkins_latest(self):
        """Get the latest check-in date and all ratings for that date"""
        try:
            supabase = get_supabase()
            if supabase:
                # Get most recent check-in date
                result = supabase.table('chl_body_checkins')\
                    .select('date')\
                    .order('date', desc=True)\
                    .limit(1)\
                    .execute()
                if result.data:
                    latest_date = result.data[0]['date']
                    # Get all check-ins for that date
                    result2 = supabase.table('chl_body_checkins')\
                        .select('*')\
                        .eq('date', latest_date)\
                        .execute()
                    checkins = result2.data or []
                else:
                    latest_date = None
                    checkins = []
            else:
                latest_date = None
                checkins = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({
                "latest_date": latest_date,
                "checkins": checkins
            }).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_budget(self):
        """Get all active budget items ordered by sort_order"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_budget')\
                    .select('*')\
                    .eq('is_active', True)\
                    .order('sort_order', desc=False)\
                    .execute()
                items = result.data or []
            else:
                items = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"items": items}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_transactions(self):
        """Get transactions with optional filters"""
        try:
            from urllib.parse import urlparse, parse_qs
            parsed = urlparse(self.path)
            params = parse_qs(parsed.query)
            start_date = params.get('start_date', [None])[0]
            end_date = params.get('end_date', [None])[0]
            category = params.get('category', [None])[0]
            limit = int(params.get('limit', [200])[0])

            supabase = get_supabase()
            if supabase:
                query = supabase.table('chl_transactions')\
                    .select('*')\
                    .lt('amount', 0)\
                    .order('date', desc=True)\
                    .limit(limit)
                if start_date:
                    query = query.gte('date', start_date)
                if end_date:
                    query = query.lte('date', end_date)
                if category:
                    query = query.eq('category', category)
                result = query.execute()
                transactions = result.data or []
            else:
                transactions = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"transactions": transactions}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_spending_summary(self):
        """Get spending summary with pro-rated budget for a period"""
        try:
            from urllib.parse import urlparse, parse_qs
            from datetime import timedelta
            import calendar

            parsed = urlparse(self.path)
            params = parse_qs(parsed.query)
            period = params.get('period', ['month'])[0]

            today = get_brisbane_now().date()

            # Calculate date range based on period
            if period == 'week':
                start_date = today - timedelta(days=today.weekday())  # Monday
                end_date = today
                days_elapsed = (end_date - start_date).days + 1
                days_in_period = 7
            elif period == 'quarter':
                quarter_month = ((today.month - 1) // 3) * 3 + 1
                start_date = today.replace(month=quarter_month, day=1)
                end_date = today
                days_elapsed = (end_date - start_date).days + 1
                # Days in full quarter
                if quarter_month + 2 <= 12:
                    last_month = quarter_month + 2
                    days_in_period = sum(
                        calendar.monthrange(today.year, m)[1]
                        for m in range(quarter_month, quarter_month + 3)
                    )
                else:
                    days_in_period = 90  # fallback
            else:  # month (default)
                start_date = today.replace(day=1)
                end_date = today
                days_elapsed = (end_date - start_date).days + 1
                days_in_period = calendar.monthrange(today.year, today.month)[1]

            start_str = start_date.strftime('%Y-%m-%d')
            end_str = end_date.strftime('%Y-%m-%d')

            supabase = get_supabase()
            if not supabase:
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"summary": {}, "categories": []}).encode())
                return

            # Get all active budget items
            budget_result = supabase.table('chl_budget')\
                .select('*')\
                .eq('is_active', True)\
                .execute()
            budget_items = budget_result.data or []

            # Get all transactions in the date range (expenses only)
            txn_result = supabase.table('chl_transactions')\
                .select('*')\
                .gte('date', start_str)\
                .lte('date', end_str)\
                .lt('amount', 0)\
                .execute()
            transactions = txn_result.data or []

            # Aggregate transactions by category
            spending_by_category = {}
            for txn in transactions:
                cat = txn.get('category', 'other')
                spending_by_category[cat] = spending_by_category.get(cat, 0) + abs(txn['amount'])

            # Calculate pro-rated budget for each category
            categories = []
            total_budgeted = 0
            total_spent = 0

            for item in budget_items:
                monthly_amount = item.get('monthly_amount', 0) or 0
                cat = item.get('category', '')

                # Pro-rate based on period
                if period == 'week':
                    budgeted = monthly_amount / 4.33
                elif period == 'quarter':
                    budgeted = monthly_amount * 3 * days_elapsed / days_in_period
                else:  # month
                    budgeted = monthly_amount * days_elapsed / days_in_period

                spent = round(spending_by_category.get(cat, 0), 2)
                budgeted = round(budgeted, 2)
                remaining = round(budgeted - spent, 2)
                pct_used = round((spent / budgeted * 100), 1) if budgeted > 0 else 0

                categories.append({
                    "id": item.get('id'),
                    "category": cat,
                    "display_name": item.get('display_name', cat),
                    "budget_type": item.get('budget_type', ''),
                    "monthly_amount": monthly_amount,
                    "budgeted": budgeted,
                    "spent": spent,
                    "remaining": remaining,
                    "pct_used": pct_used
                })

                total_budgeted += budgeted
                total_spent += spent

            total_remaining = round(total_budgeted - total_spent, 2)

            summary = {
                "total_budgeted": round(total_budgeted, 2),
                "total_spent": round(total_spent, 2),
                "total_remaining": total_remaining,
                "period": period,
                "start_date": start_str,
                "end_date": end_str,
                "days_elapsed": days_elapsed,
                "days_in_period": days_in_period,
                "monthly_income": 11180
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"summary": summary, "categories": categories}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_investments(self):
        """Get all active investment assets with latest values"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_investment_assets')\
                    .select('*')\
                    .eq('is_active', True)\
                    .order('sort_order', desc=False)\
                    .execute()
                assets = result.data or []

                # For each asset, get the latest value
                for asset in assets:
                    val_result = supabase.table('chl_investment_values')\
                        .select('value, date')\
                        .eq('asset_id', asset['id'])\
                        .order('date', desc=True)\
                        .limit(1)\
                        .execute()
                    if val_result.data:
                        asset['latest_value'] = val_result.data[0]['value']
                        asset['latest_date'] = val_result.data[0]['date']
                    else:
                        asset['latest_value'] = None
                        asset['latest_date'] = None
            else:
                assets = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"assets": assets}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_investment_values(self):
        """Get value history for an investment asset"""
        try:
            from urllib.parse import urlparse, parse_qs
            parsed = urlparse(self.path)
            params = parse_qs(parsed.query)
            asset_id = params.get('asset_id', [None])[0]

            if not asset_id:
                self.send_response(400)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "asset_id is required"}).encode())
                return

            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_investment_values')\
                    .select('*')\
                    .eq('asset_id', asset_id)\
                    .order('date', desc=True)\
                    .limit(50)\
                    .execute()
                values = result.data or []
            else:
                values = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"values": values}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    # ================================================================
    # BLOOD TEST API - GET handlers
    # ================================================================

    def _get_bloodtests(self):
        """Get all blood test panels (summary)"""
        try:
            supabase = get_supabase()
            if supabase:
                result = supabase.table('chl_bloodtest_panels')\
                    .select('id, date, lab_name, ordered_by, panel_name, analysis_summary, health_score, markers_optimal, markers_good, markers_warning, markers_high, markers_low, created_at')\
                    .order('date', desc=True)\
                    .execute()
                panels = result.data or []
            else:
                panels = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"panels": panels}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bloodtest_latest(self):
        """Get latest panel + markers + active actions"""
        try:
            supabase = get_supabase()
            panel = {}
            markers = []
            actions = []

            if supabase:
                # Get latest panel
                p_result = supabase.table('chl_bloodtest_panels')\
                    .select('*')\
                    .order('date', desc=True)\
                    .limit(1)\
                    .execute()
                if p_result.data:
                    panel = p_result.data[0]
                    # Get markers for this panel
                    m_result = supabase.table('chl_bloodtest_markers')\
                        .select('*')\
                        .eq('panel_id', panel['id'])\
                        .order('category')\
                        .order('sort_order')\
                        .execute()
                    markers = m_result.data or []
                    # Get active actions for this panel
                    a_result = supabase.table('chl_bloodtest_actions')\
                        .select('*')\
                        .eq('panel_id', panel['id'])\
                        .in_('status', ['pending', 'in_progress'])\
                        .order('priority')\
                        .execute()
                    actions = a_result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({
                "panel": panel,
                "markers": markers,
                "actions": actions
            }).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bloodtest_by_id(self, panel_id):
        """Get specific panel + markers + actions"""
        try:
            supabase = get_supabase()
            panel = {}
            markers = []
            actions = []

            if supabase:
                p_result = supabase.table('chl_bloodtest_panels')\
                    .select('*')\
                    .eq('id', panel_id)\
                    .execute()
                if p_result.data:
                    panel = p_result.data[0]
                    m_result = supabase.table('chl_bloodtest_markers')\
                        .select('*')\
                        .eq('panel_id', panel_id)\
                        .order('category')\
                        .order('sort_order')\
                        .execute()
                    markers = m_result.data or []
                    a_result = supabase.table('chl_bloodtest_actions')\
                        .select('*')\
                        .eq('panel_id', panel_id)\
                        .order('priority')\
                        .execute()
                    actions = a_result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({
                "panel": panel,
                "markers": markers,
                "actions": actions
            }).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bloodtest_trends(self):
        """Get marker trends across all panels"""
        try:
            supabase = get_supabase()
            trends = []

            if supabase:
                # Get all panels ordered by date
                p_result = supabase.table('chl_bloodtest_panels')\
                    .select('id, date, lab_name, health_score')\
                    .order('date', desc=False)\
                    .execute()
                panels = p_result.data or []

                # Get all markers
                m_result = supabase.table('chl_bloodtest_markers')\
                    .select('panel_id, name, short_name, category, value, unit, ref_low, ref_high, optimal_low, optimal_high, status')\
                    .execute()
                all_markers = m_result.data or []

                # Group markers by name
                marker_map = {}
                for m in all_markers:
                    key = m['name']
                    if key not in marker_map:
                        marker_map[key] = {
                            'name': m['name'],
                            'short_name': m['short_name'],
                            'category': m['category'],
                            'unit': m['unit'],
                            'ref_low': m['ref_low'],
                            'ref_high': m['ref_high'],
                            'optimal_low': m['optimal_low'],
                            'optimal_high': m['optimal_high'],
                            'values': []
                        }
                    # Find panel date for this marker
                    panel = next((p for p in panels if p['id'] == m['panel_id']), None)
                    if panel:
                        marker_map[key]['values'].append({
                            'date': panel['date'],
                            'value': m['value'],
                            'status': m['status']
                        })

                # Sort values by date within each marker
                for key in marker_map:
                    marker_map[key]['values'].sort(key=lambda v: v['date'])

                trends = list(marker_map.values())

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"trends": trends, "panels": panels}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bloodtest_actions_all(self):
        """Get all active actions across all panels"""
        try:
            supabase = get_supabase()
            actions = []

            if supabase:
                result = supabase.table('chl_bloodtest_actions')\
                    .select('*')\
                    .in_('status', ['pending', 'in_progress'])\
                    .order('priority')\
                    .execute()
                actions = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"actions": actions}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_bloodtest_health_context(self):
        """Aggregate snapshot of all health data for holistic evaluation"""
        try:
            from datetime import timedelta
            supabase = get_supabase()
            context = {}

            if supabase:
                today = get_brisbane_now().date()

                # Each data source wrapped individually so missing tables don't kill everything

                # WHOOP: Last 14 days from chl_health_logs
                try:
                    start_14d = (today - timedelta(days=14)).strftime('%Y-%m-%d')
                    h_result = supabase.table('chl_health_logs')\
                        .select('recovery_score, recovery_hrv, recovery_rhr, sleep_total_hours, strain_day_strain')\
                        .gte('date', start_14d)\
                        .execute()
                    health_logs = h_result.data or []
                    if health_logs:
                        def avg(key):
                            vals = [h[key] for h in health_logs if h.get(key) is not None]
                            return round(sum(vals) / len(vals), 1) if vals else None
                        context['whoop'] = {
                            'days': len(health_logs),
                            'avg_recovery': avg('recovery_score'),
                            'avg_hrv': avg('recovery_hrv'),
                            'avg_rhr': avg('recovery_rhr'),
                            'avg_sleep': avg('sleep_total_hours'),
                            'avg_strain': avg('strain_day_strain')
                        }
                except Exception:
                    pass

                # Activities: Last 30 days
                try:
                    start_30d = (today - timedelta(days=30)).strftime('%Y-%m-%d')
                    a_result = supabase.table('chl_activity_logs')\
                        .select('activity, date')\
                        .gte('date', start_30d)\
                        .execute()
                    act_logs = a_result.data or []
                    if act_logs:
                        from collections import Counter
                        act_counts = Counter(a['activity'] for a in act_logs)
                        context['activities'] = {
                            'total_sessions': len(act_logs),
                            'breakdown': dict(act_counts)
                        }
                except Exception:
                    pass

                # Diet: Active items
                try:
                    d_result = supabase.table('chl_diet_items')\
                        .select('meal_slot, title')\
                        .eq('is_active', True)\
                        .execute()
                    diet = d_result.data or []
                    if diet:
                        diet_by_slot = {}
                        for d in diet:
                            slot = d['meal_slot']
                            if slot not in diet_by_slot:
                                diet_by_slot[slot] = []
                            diet_by_slot[slot].append(d['title'])
                        context['diet'] = diet_by_slot
                except Exception:
                    pass

                # Supplements: Active
                try:
                    s_result = supabase.table('chl_supplements')\
                        .select('name, dose, timing')\
                        .eq('status', 'active')\
                        .execute()
                    supps = s_result.data or []
                    if supps:
                        context['supplements'] = supps
                except Exception:
                    pass

                # Body Focus: Active
                try:
                    bf_result = supabase.table('chl_body_focus')\
                        .select('title, body_area, severity, category, status')\
                        .neq('status', 'resolved')\
                        .execute()
                    body = bf_result.data or []
                    if body:
                        context['body_focus'] = body
                except Exception:
                    pass

                # Bio Age: Latest
                try:
                    ba_result = supabase.table('chl_bioage_logs')\
                        .select('date, composite_bio_age, chronological_age, age_delta, whoop_age, vascular_age')\
                        .order('date', desc=True)\
                        .limit(1)\
                        .execute()
                    if ba_result.data:
                        context['bio_age'] = ba_result.data[0]
                except Exception:
                    pass

                # Blood Tests: Latest panel summary + warning markers
                try:
                    bt_result = supabase.table('chl_bloodtest_panels')\
                        .select('id, date, health_score, markers_warning, markers_high, markers_low')\
                        .order('date', desc=True)\
                        .limit(1)\
                        .execute()
                    if bt_result.data:
                        panel = bt_result.data[0]
                        context['blood_tests'] = panel
                        warn_result = supabase.table('chl_bloodtest_markers')\
                            .select('name, value, unit, status, notes')\
                            .eq('panel_id', panel['id'])\
                            .in_('status', ['warning', 'high', 'low', 'critical', 'borderline'])\
                            .execute()
                        context['blood_tests']['flagged_markers'] = warn_result.data or []
                except Exception:
                    pass

                # Experiments: Active
                try:
                    exp_result = supabase.table('chl_experiments')\
                        .select('title, category, protocol, status')\
                        .eq('status', 'active')\
                        .execute()
                    exps = exp_result.data or []
                    if exps:
                        context['experiments'] = exps
                except Exception:
                    pass

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(context).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    # ========================================================================
    # Community API handlers
    # ========================================================================

    def _get_community_overview(self):
        """All communities with member counts, recent interactions, next event"""
        try:
            supabase = get_supabase()
            communities = []
            if supabase:
                result = supabase.table('chl_communities')\
                    .select('*')\
                    .eq('status', 'active')\
                    .order('sort_order')\
                    .execute()
                communities = result.data or []

                for c in communities:
                    cid = c['id']
                    # Member count
                    m_result = supabase.table('chl_community_members')\
                        .select('id', count='exact')\
                        .eq('community_id', cid)\
                        .eq('status', 'active')\
                        .execute()
                    c['member_count'] = m_result.count or 0

                    # Recent interactions (last 30 days)
                    from datetime import timedelta
                    thirty_ago = (get_brisbane_now().date() - timedelta(days=30)).strftime('%Y-%m-%d')
                    i_result = supabase.table('chl_community_interactions')\
                        .select('id', count='exact')\
                        .eq('community_id', cid)\
                        .gte('date', thirty_ago)\
                        .execute()
                    c['recent_interactions'] = i_result.count or 0

                    # Next event
                    today = get_brisbane_now().date().strftime('%Y-%m-%d')
                    e_result = supabase.table('chl_community_events')\
                        .select('name, date, status')\
                        .eq('community_id', cid)\
                        .in_('status', ['planned', 'confirmed'])\
                        .gte('date', today)\
                        .order('date')\
                        .limit(1)\
                        .execute()
                    c['next_event'] = e_result.data[0] if e_result.data else None

                    # Health: green if interaction in last 7 days, yellow 7-14, red 14+
                    seven_ago = (get_brisbane_now().date() - timedelta(days=7)).strftime('%Y-%m-%d')
                    fourteen_ago = (get_brisbane_now().date() - timedelta(days=14)).strftime('%Y-%m-%d')
                    recent_7 = supabase.table('chl_community_interactions')\
                        .select('id', count='exact')\
                        .eq('community_id', cid)\
                        .gte('date', seven_ago)\
                        .execute()
                    if (recent_7.count or 0) > 0:
                        c['health'] = 'green'
                    else:
                        recent_14 = supabase.table('chl_community_interactions')\
                            .select('id', count='exact')\
                            .eq('community_id', cid)\
                            .gte('date', fourteen_ago)\
                            .execute()
                        c['health'] = 'yellow' if (recent_14.count or 0) > 0 else 'red'

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"communities": communities}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_here_summary(self):
        """Read Here project's JSON files from disk"""
        try:
            here_data_path = Path('/Users/kimhansen/Desktop/03 Workspace/ceo-agents/here-community/data/')
            summary = {}
            for fname in ['members.json', 'events.json', 'metrics.json']:
                fpath = here_data_path / fname
                if fpath.exists():
                    with open(fpath) as f:
                        summary[fname.replace('.json', '')] = json.load(f)

            # Quick stats
            members = summary.get('members', [])
            events = summary.get('events', [])
            summary['stats'] = {
                'total_members': len(members) if isinstance(members, list) else 0,
                'total_events': len(events) if isinstance(events, list) else 0
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(summary).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_community_members(self):
        """Get members for a community"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            community_id = params.get('community_id', [None])[0]

            supabase = get_supabase()
            members = []
            if supabase and community_id:
                result = supabase.table('chl_community_members')\
                    .select('*')\
                    .eq('community_id', community_id)\
                    .neq('status', 'moved')\
                    .order('name')\
                    .execute()
                members = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"members": members}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_community_interactions(self):
        """Get interactions for a community"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            community_id = params.get('community_id', [None])[0]
            limit = int(params.get('limit', ['50'])[0])

            supabase = get_supabase()
            interactions = []
            if supabase and community_id:
                result = supabase.table('chl_community_interactions')\
                    .select('*')\
                    .eq('community_id', community_id)\
                    .order('date', desc=True)\
                    .order('created_at', desc=True)\
                    .limit(limit)\
                    .execute()
                interactions = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"interactions": interactions}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_community_interactions_stale(self):
        """Members not contacted in N days"""
        try:
            from urllib.parse import urlparse, parse_qs
            from datetime import timedelta
            params = parse_qs(urlparse(self.path).query)
            community_id = params.get('community_id', [None])[0]
            days = int(params.get('days', ['14'])[0])

            supabase = get_supabase()
            stale = []
            if supabase and community_id:
                cutoff = (get_brisbane_now().date() - timedelta(days=days)).strftime('%Y-%m-%d')
                # Members with no interaction or interaction before cutoff
                result = supabase.table('chl_community_members')\
                    .select('*')\
                    .eq('community_id', community_id)\
                    .eq('status', 'active')\
                    .execute()
                all_members = result.data or []
                for m in all_members:
                    if not m.get('last_interaction_date') or m['last_interaction_date'] < cutoff:
                        stale.append(m)

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"stale_members": stale}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_community_events(self):
        """Get events for a community"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            community_id = params.get('community_id', [None])[0]

            supabase = get_supabase()
            events = []
            if supabase and community_id:
                result = supabase.table('chl_community_events')\
                    .select('*')\
                    .eq('community_id', community_id)\
                    .order('date', desc=True)\
                    .execute()
                events = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"events": events}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_community_contacts(self):
        """Get shared contacts for a community"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            community_id = params.get('community_id', [None])[0]

            supabase = get_supabase()
            contacts = []
            if supabase and community_id:
                result = supabase.table('chl_community_contacts')\
                    .select('*')\
                    .eq('community_id', community_id)\
                    .order('category')\
                    .execute()
                contacts = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"contacts": contacts}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    # ================================================================
    # ENRICHMENT + SPOTS/GOALS HANDLERS
    # ================================================================

    def _get_enrichment(self, activity_log_id):
        """Get full enrichment for a specific activity log"""
        try:
            supabase = get_supabase()
            enrichment = None
            if supabase:
                result = supabase.table('chl_sport_sessions')\
                    .select('*')\
                    .eq('activity_log_id', activity_log_id)\
                    .limit(1)\
                    .execute()
                if result.data:
                    enrichment = result.data[0]

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"enrichment": enrichment}).encode())

        except Exception as e:
            print(f"[ERROR] GET /api/enrichment: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_play_spots(self):
        """Get active spots, optional sport filter"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            sport_filter = params.get('sport', [None])[0]

            supabase = get_supabase()
            spots = []
            if supabase:
                query = supabase.table('chl_sport_spots')\
                    .select('*')\
                    .eq('is_active', True)\
                    .order('sport')\
                    .order('drive_time_minutes')

                if sport_filter:
                    query = query.eq('sport', sport_filter)

                result = query.execute()
                spots = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"spots": spots}).encode())

        except Exception as e:
            print(f"[ERROR] GET /api/play/spots: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_play_goals(self):
        """Get goals/focus areas, optional activity filter"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            activity_filter = params.get('activity', [None])[0] or params.get('sport', [None])[0]

            supabase = get_supabase()
            goals = []
            if supabase:
                query = supabase.table('chl_sport_goals')\
                    .select('*')\
                    .order('activity')\
                    .order('created_at', desc=True)

                if activity_filter:
                    query = query.eq('activity', activity_filter)

                result = query.execute()
                goals = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"goals": goals}).encode())

        except Exception as e:
            print(f"[ERROR] GET /api/play/goals: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_recovery_protocols(self):
        """Get recovery protocols from Supabase"""
        try:
            supabase = get_supabase()
            protocols = []
            if supabase:
                result = supabase.table('chl_recovery_protocols')\
                    .select('*')\
                    .order('sequence_order')\
                    .execute()
                protocols = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"protocols": protocols}).encode())

        except Exception as e:
            print(f"[ERROR] GET /api/recovery/protocols: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_training_programs(self):
        """Get training programs from Supabase"""
        try:
            supabase = get_supabase()
            programs = []
            if supabase:
                result = supabase.table('chl_training_programs')\
                    .select('*')\
                    .eq('active', True)\
                    .order('day_of_week')\
                    .execute()
                programs = result.data or []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"programs": programs}).encode())

        except Exception as e:
            print(f"[ERROR] GET /api/training/programs: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _post_recovery_protocol(self):
        """Upsert a recovery protocol"""
        try:
            content_length = int(self.headers["Content-Length"])
            body = self.rfile.read(content_length)
            data = json.loads(body.decode("utf-8"))

            supabase = get_supabase()
            if supabase:
                protocol_id = data.get("id")
                data["updated_at"] = get_brisbane_now().isoformat()

                if protocol_id:
                    # Update existing
                    update_data = {k: v for k, v in data.items() if k != "id" and k != "created_at"}
                    result = supabase.table('chl_recovery_protocols')\
                        .update(update_data)\
                        .eq('id', protocol_id)\
                        .execute()
                else:
                    # Insert new
                    data.pop("id", None)
                    result = supabase.table('chl_recovery_protocols')\
                        .insert(data)\
                        .execute()

                saved = result.data[0] if result.data else data

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(saved).encode())
            else:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "Supabase not available"}).encode())

        except Exception as e:
            print(f"[ERROR] POST /api/recovery/protocols: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _post_training_program(self):
        """Upsert a training program"""
        try:
            content_length = int(self.headers["Content-Length"])
            body = self.rfile.read(content_length)
            data = json.loads(body.decode("utf-8"))

            supabase = get_supabase()
            if supabase:
                program_id = data.get("id")
                data["updated_at"] = get_brisbane_now().isoformat()

                if program_id:
                    update_data = {k: v for k, v in data.items() if k != "id" and k != "created_at"}
                    result = supabase.table('chl_training_programs')\
                        .update(update_data)\
                        .eq('id', program_id)\
                        .execute()
                else:
                    data.pop("id", None)
                    result = supabase.table('chl_training_programs')\
                        .insert(data)\
                        .execute()

                saved = result.data[0] if result.data else data

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(saved).encode())
            else:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "Supabase not available"}).encode())

        except Exception as e:
            print(f"[ERROR] POST /api/training/programs: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def do_POST(self):
        if self.path == "/api/capture":
            try:
                # Read POST body
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                # Create capture entry
                now = get_brisbane_now()
                capture_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('title', '')}".encode()
                ).hexdigest()[:6]

                capture = {
                    "id": capture_id,
                    "date": now.strftime("%Y-%m-%d"),
                    "time": now.strftime("%H:%M:%S"),
                    "title": data.get("title", "Untitled"),
                    "description": data.get("description", ""),
                    "source": "web",
                    "status": "active"
                }

                # Save to Supabase
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_captures').insert(capture).execute()

                # Respond
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(capture).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/capture/") and self.path.endswith("/archive"):
            # Archive a capture
            capture_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_captures')\
                        .update({'status': 'archived'})\
                        .eq('id', capture_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/capture/") and self.path.endswith("/unarchive"):
            # Unarchive a capture
            capture_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_captures')\
                        .update({'status': 'active'})\
                        .eq('id', capture_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/bioage":
            # POST /api/bioage - Save bioage entry
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                # Clean up null values (but keep JSONB fields even if empty dicts)
                bioage_entry = {}
                for k, v in data.items():
                    if v is not None:
                        bioage_entry[k] = v

                # Ensure date is set
                if 'date' not in bioage_entry:
                    bioage_entry['date'] = get_brisbane_now().strftime('%Y-%m-%d')

                # Handle functional_fitness as JSONB
                if 'functional_fitness' in bioage_entry and isinstance(bioage_entry['functional_fitness'], dict):
                    # Remove empty values
                    ff = {k: v for k, v in bioage_entry['functional_fitness'].items() if v is not None}
                    bioage_entry['functional_fitness'] = json.dumps(ff) if ff else None
                    if bioage_entry['functional_fitness'] is None:
                        del bioage_entry['functional_fitness']

                # Handle domain_scores as JSONB
                if 'domain_scores' in bioage_entry and isinstance(bioage_entry['domain_scores'], dict):
                    bioage_entry['domain_scores'] = json.dumps(bioage_entry['domain_scores'])

                # Handle data_freshness as JSONB
                if 'data_freshness' in bioage_entry and isinstance(bioage_entry['data_freshness'], dict):
                    bioage_entry['data_freshness'] = json.dumps(bioage_entry['data_freshness'])

                # Save to Supabase
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_bioage_logs').insert(bioage_entry).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "entry": bioage_entry}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/activity":
            # POST /api/activity - Log an activity (from Tasker or manual)
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                config = get_activity_config()
                now = get_brisbane_now()

                # Handle Tasker request (package name) or direct activity
                activity = data.get('activity')
                if not activity and 'package' in data:
                    # Look up activity from package name
                    package = data['package']
                    activity = config.get('tasker_mappings', {}).get(package)
                    if not activity:
                        self.send_response(400)
                        self.send_header("Content-Type", "application/json")
                        self.send_header("Access-Control-Allow-Origin", "*")
                        self.end_headers()
                        self.wfile.write(json.dumps({
                            "error": f"Unknown package: {package}",
                            "hint": "Add mapping to activity-config.json"
                        }).encode())
                        return

                if not activity:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": "Missing 'activity' or 'package'"}).encode())
                    return

                # Validate activity exists in config
                if activity not in config.get('activities', {}):
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({
                        "error": f"Unknown activity: {activity}",
                        "hint": "Add to activities in activity-config.json"
                    }).encode())
                    return

                # Parse timestamp or use now
                if 'timestamp' in data:
                    try:
                        ts = datetime.fromisoformat(data['timestamp'].replace('Z', '+00:00'))
                        date_str = ts.strftime('%Y-%m-%d')
                        time_str = ts.strftime('%H:%M:%S')
                    except:
                        date_str = now.strftime('%Y-%m-%d')
                        time_str = now.strftime('%H:%M:%S')
                else:
                    date_str = data.get('date', now.strftime('%Y-%m-%d'))
                    time_str = data.get('time', now.strftime('%H:%M:%S'))

                # Create activity log entry
                activity_id = hashlib.md5(
                    f"{date_str}{time_str}{activity}{data.get('source', 'tasker')}".encode()
                ).hexdigest()[:8]

                entry = {
                    "id": activity_id,
                    "date": date_str,
                    "time": time_str,
                    "activity": activity,
                    "source": data.get('source', 'tasker'),
                    "duration_minutes": data.get('duration_minutes') or data.get('duration_seconds', 0) // 60 or None,
                    "metadata": data.get('metadata')
                }

                # Clean up None values
                entry = {k: v for k, v in entry.items() if v is not None}

                # Save to Supabase
                supabase = get_supabase()
                if supabase:
                    # Use upsert to handle duplicates gracefully
                    supabase.table('chl_activity_logs').upsert(entry).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "entry": entry}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/diet":
            # POST /api/diet - Create diet item
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                item_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('title', '')}".encode()
                ).hexdigest()[:8]

                item = {
                    "id": item_id,
                    "meal_slot": data.get("meal_slot", ""),
                    "sort_order": data.get("sort_order", 0),
                    "title": data.get("title", ""),
                    "description": data.get("description", ""),
                    "frequency": data.get("frequency", ""),
                    "is_active": data.get("is_active", True),
                    "notes": data.get("notes", "")
                }

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_diet_items').insert(item).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "item": item}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/supplements":
            # POST /api/supplements - Create supplement
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                supp_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                supplement = {
                    "id": supp_id,
                    "name": data.get("name", ""),
                    "dose": data.get("dose", ""),
                    "timing": data.get("timing", ""),
                    "purpose": data.get("purpose", ""),
                    "form": data.get("form", ""),
                    "brand": data.get("brand", ""),
                    "status": data.get("status", "active"),
                    "sort_order": data.get("sort_order", 0),
                    "experiment_id": data.get("experiment_id"),
                    "start_date": data.get("start_date"),
                    "expected_duration": data.get("expected_duration"),
                    "notes": data.get("notes", "")
                }
                # Remove None values
                supplement = {k: v for k, v in supplement.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_supplements').insert(supplement).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "supplement": supplement}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/body-focus":
            # POST /api/body-focus - Create body focus area
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                item_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('body_area', '')}".encode()
                ).hexdigest()[:8]

                item = {
                    "id": item_id,
                    "body_area": data.get("body_area", ""),
                    "title": data.get("title", ""),
                    "category": data.get("category", "injury"),
                    "severity": data.get("severity", 3),
                    "description": data.get("description", ""),
                    "protocol": data.get("protocol", ""),
                    "aggravated_by": data.get("aggravated_by", []),
                    "helped_by": data.get("helped_by", []),
                    "status": data.get("status", "active"),
                    "started_date": data.get("started_date", now.strftime("%Y-%m-%d")),
                    "sort_order": data.get("sort_order", 0),
                    "notes": data.get("notes", "")
                }
                item = {k: v for k, v in item.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_body_focus').insert(item).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "item": item}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/body-checkins":
            # POST /api/body-checkins - Save weekly check-in (batch)
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                checkin_date = data.get("date", now.strftime("%Y-%m-%d"))
                entries = data.get("entries", [])

                supabase = get_supabase()
                saved = []
                if supabase and entries:
                    for entry in entries:
                        checkin_id = hashlib.md5(
                            f"{checkin_date}{entry.get('focus_id', '')}".encode()
                        ).hexdigest()[:8]

                        checkin = {
                            "id": checkin_id,
                            "date": checkin_date,
                            "focus_id": entry.get("focus_id", ""),
                            "severity": entry.get("severity", 3),
                            "notes": entry.get("notes", "")
                        }
                        supabase.table('chl_body_checkins').upsert(
                            checkin, on_conflict='date,focus_id'
                        ).execute()

                        # Also update the focus area's current severity
                        supabase.table('chl_body_focus')\
                            .update({"severity": entry.get("severity", 3)})\
                            .eq('id', entry.get('focus_id', ''))\
                            .execute()

                        saved.append(checkin)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "saved": saved}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/admin":
            # POST /api/admin - Create new contact
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                # Generate ID if not provided
                contact_id = data.get('id') or hashlib.md5(
                    f"{get_brisbane_now().isoformat()}{data.get('category', '')}".encode()
                ).hexdigest()[:8]

                contact = {
                    "id": contact_id,
                    "category": data.get("category", ""),
                    "person": data.get("person", ""),
                    "phone": data.get("phone", ""),
                    "place": data.get("place", ""),
                    "website": data.get("website", ""),
                    "notes": data.get("notes", "")
                }

                admin_data = get_house_admin()
                admin_data["contacts"].append(contact)
                save_house_admin(admin_data)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "contact": contact}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/budget":
            # POST /api/budget - Create budget item
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                item_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('category', '')}".encode()
                ).hexdigest()[:8]

                item = {
                    "id": item_id,
                    "category": data.get("category", ""),
                    "display_name": data.get("display_name", ""),
                    "monthly_amount": data.get("monthly_amount", 0),
                    "budget_type": data.get("budget_type", "variable"),
                    "sort_order": data.get("sort_order", 0),
                    "is_active": data.get("is_active", True),
                    "notes": data.get("notes", "")
                }

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_budget').insert(item).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "item": item}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/transactions/upload":
            # POST /api/transactions/upload - Upload and parse CommBank CSV
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                csv_content = data.get('csv_content', '')
                source_file = data.get('source_file', 'web-upload')

                # Parse CSV lines
                def parse_csv_line(line):
                    parts = []
                    current = ''
                    in_quotes = False
                    for char in line:
                        if char == '"':
                            in_quotes = not in_quotes
                        elif char == ',' and not in_quotes:
                            parts.append(current.strip().strip('"'))
                            current = ''
                        else:
                            current += char
                    parts.append(current.strip().strip('"'))
                    if len(parts) >= 3:
                        date_str = parts[0]
                        amount_str = parts[1].replace('+', '').replace(',', '')
                        description = parts[2]
                        try:
                            amount = float(amount_str)
                            from datetime import datetime as dt
                            date = dt.strptime(date_str, '%d/%m/%Y').strftime('%Y-%m-%d')
                            balance = parts[3].replace('+', '').replace(',', '').strip() if len(parts) >= 4 else ''
                            return {'date': date, 'amount': amount, 'description': description, 'balance': balance}
                        except:
                            return None
                    return None

                # Categorize using merchant categories
                mc = get_merchant_categories()
                mappings = mc.get('mappings', [])

                def categorize(description):
                    for mapping in mappings:
                        pattern = mapping.get('pattern', '')
                        if re.search(pattern, description, re.IGNORECASE):
                            return mapping.get('category', 'other'), mapping.get('notes', '')
                    return 'other', ''

                # Parse all lines
                parsed = []
                for line in csv_content.strip().split('\n'):
                    line = line.strip()
                    if not line:
                        continue
                    txn = parse_csv_line(line)
                    if txn:
                        cat, notes = categorize(txn['description'])
                        txn_id = hashlib.md5(
                            f"{txn['date']}|{txn['description']}|{txn['amount']}|{txn.get('balance', '')}".encode()
                        ).hexdigest()[:12]
                        txn['id'] = txn_id
                        txn['category'] = cat
                        txn['category_notes'] = notes
                        txn['source_file'] = source_file
                        parsed.append(txn)

                # Check for duplicates
                if parsed:
                    supabase = get_supabase()
                    existing_ids = set()
                    existing_keys = set()
                    if supabase:
                        ids_to_check = [t['id'] for t in parsed]
                        # Check in batches (Supabase has query limits)
                        for i in range(0, len(ids_to_check), 50):
                            batch = ids_to_check[i:i+50]
                            result = supabase.table('chl_transactions')\
                                .select('id')\
                                .in_('id', batch)\
                                .execute()
                            existing_ids.update(r['id'] for r in (result.data or []))

                        # Also check by (date, description, amount) for old-format IDs
                        dates_in_batch = list(set(t['date'] for t in parsed))
                        for i in range(0, len(dates_in_batch), 10):
                            batch_dates = dates_in_batch[i:i+10]
                            result = supabase.table('chl_transactions')\
                                .select('date,description,amount')\
                                .in_('date', batch_dates)\
                                .execute()
                            for existing in (result.data or []):
                                key = (existing['date'], existing['description'], float(existing['amount']))
                                existing_keys.add(key)

                    for txn in parsed:
                        txn['is_duplicate'] = txn['id'] in existing_ids
                        if not txn['is_duplicate']:
                            key = (txn['date'], txn['description'], float(txn['amount']))
                            if key in existing_keys:
                                txn['is_duplicate'] = True

                new_count = sum(1 for t in parsed if not t.get('is_duplicate'))
                dup_count = sum(1 for t in parsed if t.get('is_duplicate'))

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({
                    "transactions": parsed,
                    "new_count": new_count,
                    "duplicate_count": dup_count
                }).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/transactions/confirm":
            # POST /api/transactions/confirm - Save parsed transactions
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                transactions = data.get('transactions', [])

                supabase = get_supabase()
                saved = 0
                if supabase and transactions:
                    rows = []
                    for txn in transactions:
                        row = {
                            "id": txn['id'],
                            "date": txn['date'],
                            "description": txn['description'],
                            "amount": txn['amount'],
                            "category": txn.get('category', 'other'),
                            "category_notes": txn.get('category_notes', ''),
                            "source_file": txn.get('source_file', '')
                        }
                        rows.append(row)

                    # Deduplicate within batch (safety net)
                    seen_ids = set()
                    unique_rows = []
                    for row in rows:
                        if row['id'] not in seen_ids:
                            seen_ids.add(row['id'])
                            unique_rows.append(row)
                    rows = unique_rows

                    result = supabase.table('chl_transactions')\
                        .upsert(rows, on_conflict='id')\
                        .execute()
                    saved = len(rows)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "saved": saved}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/investments":
            # POST /api/investments - Create investment asset
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                asset_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                asset = {
                    "id": asset_id,
                    "asset_class": data.get("asset_class", ""),
                    "name": data.get("name", ""),
                    "description": data.get("description", ""),
                    "currency": data.get("currency", "AUD"),
                    "metadata": data.get("metadata"),
                    "sort_order": data.get("sort_order", 0),
                    "is_active": data.get("is_active", True)
                }
                asset = {k: v for k, v in asset.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_investment_assets').insert(asset).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "asset": asset}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/investment-values":
            # POST /api/investment-values - Log an investment value
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                value_id = hashlib.md5(
                    f"{data.get('asset_id', '')}|{data.get('date', '')}|{data.get('value', '')}".encode()
                ).hexdigest()[:8]

                entry = {
                    "id": value_id,
                    "asset_id": data.get("asset_id", ""),
                    "date": data.get("date", get_brisbane_now().strftime('%Y-%m-%d')),
                    "value": data.get("value", 0),
                    "notes": data.get("notes", "")
                }

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_investment_values').insert(entry).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "entry": entry}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Blood Test POST endpoints
        elif self.path == "/api/bloodtests":
            # POST /api/bloodtests - Create panel
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                panel_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('date', '')}".encode()
                ).hexdigest()[:8]

                panel = {
                    "id": panel_id,
                    "date": data.get("date", now.strftime('%Y-%m-%d')),
                    "lab_name": data.get("lab_name", ""),
                    "ordered_by": data.get("ordered_by", ""),
                    "panel_name": data.get("panel_name", ""),
                    "analysis_report": data.get("analysis_report", ""),
                    "analysis_summary": data.get("analysis_summary", ""),
                    "health_score": data.get("health_score"),
                    "markers_optimal": data.get("markers_optimal", 0),
                    "markers_good": data.get("markers_good", 0),
                    "markers_warning": data.get("markers_warning", 0),
                    "markers_high": data.get("markers_high", 0),
                    "markers_low": data.get("markers_low", 0),
                    "context_snapshot": data.get("context_snapshot"),
                    "source": data.get("source", "manual"),
                    "notes": data.get("notes", "")
                }
                panel = {k: v for k, v in panel.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_bloodtest_panels').upsert(panel).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "panel": panel}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/") and self.path.endswith("/markers"):
            # POST /api/bloodtests/{id}/markers - Batch add markers
            panel_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                markers_list = data.get("markers", [])
                saved = []
                supabase = get_supabase()

                if supabase and markers_list:
                    rows = []
                    for m in markers_list:
                        marker_id = m.get('id') or hashlib.md5(
                            f"{panel_id}|{m.get('name', '')}".encode()
                        ).hexdigest()[:8]
                        row = {
                            "id": marker_id,
                            "panel_id": panel_id,
                            "name": m.get("name", ""),
                            "short_name": m.get("short_name", ""),
                            "category": m.get("category", "other"),
                            "sort_order": m.get("sort_order", 0),
                            "value": m.get("value"),
                            "unit": m.get("unit", ""),
                            "ref_low": m.get("ref_low"),
                            "ref_high": m.get("ref_high"),
                            "optimal_low": m.get("optimal_low"),
                            "optimal_high": m.get("optimal_high"),
                            "status": m.get("status", "good"),
                            "notes": m.get("notes", "")
                        }
                        row = {k: v for k, v in row.items() if v is not None}
                        rows.append(row)

                    supabase.table('chl_bloodtest_markers')\
                        .upsert(rows, on_conflict='panel_id,name')\
                        .execute()
                    saved = rows

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "saved": len(saved)}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/") and self.path.endswith("/actions"):
            # POST /api/bloodtests/{id}/actions - Add action items
            panel_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                # Support single action or batch
                actions_list = data.get("actions", [data] if "title" in data else [])
                saved = []
                supabase = get_supabase()

                if supabase and actions_list:
                    for a in actions_list:
                        action_id = a.get('id') or hashlib.md5(
                            f"{panel_id}|{now.isoformat()}|{a.get('title', '')}".encode()
                        ).hexdigest()[:8]
                        row = {
                            "id": action_id,
                            "panel_id": panel_id,
                            "title": a.get("title", ""),
                            "description": a.get("description", ""),
                            "category": a.get("category", "lifestyle"),
                            "priority": a.get("priority", "medium"),
                            "related_markers": a.get("related_markers", []),
                            "status": a.get("status", "pending"),
                            "due_date": a.get("due_date"),
                            "outcome_notes": a.get("outcome_notes", "")
                        }
                        row = {k: v for k, v in row.items() if v is not None}
                        supabase.table('chl_bloodtest_actions').upsert(row).execute()
                        saved.append(row)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True, "saved": len(saved)}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Community POST endpoints
        elif self.path == "/api/community-members":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                member_id = data.get('id') or hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                member = {
                    "id": member_id,
                    "community_id": data.get("community_id"),
                    "name": data.get("name", ""),
                    "unit_or_house": data.get("unit_or_house", ""),
                    "phone": data.get("phone", ""),
                    "email": data.get("email", ""),
                    "partner_name": data.get("partner_name", ""),
                    "kids": data.get("kids", ""),
                    "interests": data.get("interests", []),
                    "skills": data.get("skills", []),
                    "offers": data.get("offers", ""),
                    "needs": data.get("needs", ""),
                    "birthday": data.get("birthday"),
                    "photo_url": data.get("photo_url", ""),
                    "status": data.get("status", "active"),
                    "notes": data.get("notes", "")
                }
                member = {k: v for k, v in member.items() if v is not None and v != ""}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_members').insert(member).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(member).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/community-members: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/community-interactions":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                interaction_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('description', '')}".encode()
                ).hexdigest()[:8]

                interaction = {
                    "id": interaction_id,
                    "community_id": data.get("community_id"),
                    "member_ids": data.get("member_ids", []),
                    "date": data.get("date", now.strftime("%Y-%m-%d")),
                    "type": data.get("type", "chat"),
                    "description": data.get("description", ""),
                    "quality": data.get("quality"),
                    "notes": data.get("notes", "")
                }
                interaction = {k: v for k, v in interaction.items() if v is not None and v != ""}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_interactions').insert(interaction).execute()

                    # Update last_interaction_date on involved members
                    member_ids = data.get("member_ids", [])
                    interaction_date = interaction.get("date", now.strftime("%Y-%m-%d"))
                    for mid in member_ids:
                        try:
                            supabase.table('chl_community_members')\
                                .update({'last_interaction_date': interaction_date})\
                                .eq('id', mid)\
                                .execute()
                        except Exception:
                            pass

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(interaction).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/community-interactions: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/community-events":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                event_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                event = {
                    "id": event_id,
                    "community_id": data.get("community_id"),
                    "name": data.get("name", ""),
                    "date": data.get("date"),
                    "time": data.get("time", ""),
                    "description": data.get("description", ""),
                    "attendee_ids": data.get("attendee_ids", []),
                    "status": data.get("status", "idea"),
                    "category": data.get("category", "gathering"),
                    "outcome_notes": data.get("outcome_notes", "")
                }
                event = {k: v for k, v in event.items() if v is not None and v != ""}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_events').insert(event).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(event).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/community-events: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/community-contacts":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                contact_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                contact = {
                    "id": contact_id,
                    "community_id": data.get("community_id"),
                    "category": data.get("category", ""),
                    "name": data.get("name", ""),
                    "phone": data.get("phone", ""),
                    "website": data.get("website", ""),
                    "description": data.get("description", ""),
                    "recommended_by": data.get("recommended_by", "")
                }
                contact = {k: v for k, v in contact.items() if v is not None and v != ""}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_contacts').insert(contact).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(contact).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/community-contacts: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Enrichment POST endpoint
        elif self.path == "/api/enrichment":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                session_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('activity', '')}{data.get('date', '')}".encode()
                ).hexdigest()[:8]

                session = {
                    "id": session_id,
                    "activity_log_id": data.get("activity_log_id"),
                    "date": data.get("date", now.strftime('%Y-%m-%d')),
                    "activity": data.get("activity"),
                    "spot": data.get("spot"),
                    "duration_minutes": data.get("duration_minutes"),
                    "enjoyment": data.get("enjoyment"),
                    "conditions_summary": data.get("conditions_summary"),
                    "notes": data.get("notes"),
                    "focus_area": data.get("focus_area"),
                    "highlights": data.get("highlights"),
                    "strain": data.get("strain"),
                    "avg_hr": data.get("avg_hr"),
                    "max_hr": data.get("max_hr"),
                    "calories_kj": data.get("calories_kj"),
                }
                session = {k: v for k, v in session.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_sessions').insert(session).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(session).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/enrichment: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/play/spots":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                spot_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('name', '')}".encode()
                ).hexdigest()[:8]

                spot = {
                    "id": spot_id,
                    "sport": data.get("sport"),
                    "name": data.get("name"),
                    "description": data.get("description"),
                    "drive_time_minutes": data.get("drive_time_minutes", 0),
                    "best_conditions": data.get("best_conditions"),
                    "difficulty": data.get("difficulty", "intermediate"),
                    "risk_notes": data.get("risk_notes"),
                }
                spot = {k: v for k, v in spot.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_spots').insert(spot).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(spot).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/play/spots: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/play/goals":
            try:
                content_length = int(self.headers["Content-Length"])
                post_data = self.rfile.read(content_length)
                data = json.loads(post_data)

                now = get_brisbane_now()
                goal_id = hashlib.md5(
                    f"{now.isoformat()}{data.get('goal', '')}".encode()
                ).hexdigest()[:8]

                goal = {
                    "id": goal_id,
                    "activity": data.get("activity") or data.get("sport"),
                    "goal": data.get("goal"),
                    "status": data.get("status", "active"),
                    "notes": data.get("notes"),
                }
                goal = {k: v for k, v in goal.items() if v is not None}

                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_goals').insert(goal).execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps(goal).encode())

            except Exception as e:
                print(f"[ERROR] POST /api/play/goals: {e}")
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/recovery/protocols":
            self._post_recovery_protocol()
        elif self.path == "/api/training/programs":
            self._post_training_program()
        elif self.path.startswith("/api/photos/upload"):
            self._post_photo_upload()
        elif self.path == "/api/journal-entry":
            self._post_journal_entry()
        else:
            self.send_error(404)

    def do_DELETE(self):
        if self.path.startswith("/api/capture/"):
            capture_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_captures')\
                        .delete()\
                        .eq('id', capture_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/activity/"):
            activity_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_activity_logs')\
                        .delete()\
                        .eq('id', activity_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/journal-entry/"):
            entry_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_journal_entries')\
                        .delete()\
                        .eq('id', entry_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/research/"):
            research_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_health_research')\
                        .delete()\
                        .eq('id', research_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/diet/"):
            # DELETE /api/diet/{id}
            item_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_diet_items')\
                        .delete()\
                        .eq('id', item_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/supplements/"):
            # DELETE /api/supplements/{id} - Soft discontinue (not hard delete)
            supp_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    now_date = get_brisbane_now().strftime('%Y-%m-%d')
                    supabase.table('chl_supplements')\
                        .update({'status': 'discontinued', 'discontinued_date': now_date})\
                        .eq('id', supp_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/body-focus/"):
            # DELETE /api/body-focus/{id}
            item_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_body_focus')\
                        .delete()\
                        .eq('id', item_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/admin/"):
            # DELETE /api/admin/{id} - Delete contact
            contact_id = self.path.split("/")[3]
            try:
                admin_data = get_house_admin()
                admin_data["contacts"] = [c for c in admin_data["contacts"] if c["id"] != contact_id]
                save_house_admin(admin_data)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/budget/"):
            # DELETE /api/budget/{id}
            item_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_budget')\
                        .delete()\
                        .eq('id', item_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/investments/"):
            # DELETE /api/investments/{id}
            asset_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_investment_assets')\
                        .delete()\
                        .eq('id', asset_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/actions/"):
            # DELETE /api/bloodtests/actions/{id}
            action_id = self.path.split("/")[4]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_bloodtest_actions')\
                        .delete()\
                        .eq('id', action_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/"):
            # DELETE /api/bloodtests/{id} - Delete panel (cascade markers + actions)
            panel_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    # Delete actions first, then markers, then panel
                    supabase.table('chl_bloodtest_actions')\
                        .delete()\
                        .eq('panel_id', panel_id)\
                        .execute()
                    supabase.table('chl_bloodtest_markers')\
                        .delete()\
                        .eq('panel_id', panel_id)\
                        .execute()
                    supabase.table('chl_bloodtest_panels')\
                        .delete()\
                        .eq('id', panel_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Community DELETE endpoints
        elif self.path.startswith("/api/community-members/"):
            member_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_members')\
                        .delete()\
                        .eq('id', member_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/community-interactions/"):
            interaction_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_interactions')\
                        .delete()\
                        .eq('id', interaction_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/community-events/"):
            event_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_events')\
                        .delete()\
                        .eq('id', event_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/community-contacts/"):
            contact_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_community_contacts')\
                        .delete()\
                        .eq('id', contact_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Enrichment DELETE endpoint
        elif self.path.startswith("/api/enrichment/"):
            enrichment_id = self.path.split("/")[3]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_sessions')\
                        .delete()\
                        .eq('id', enrichment_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/play/spots/"):
            spot_id = self.path.split("/")[4]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_spots')\
                        .delete()\
                        .eq('id', spot_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/play/goals/"):
            goal_id = self.path.split("/")[4]
            try:
                supabase = get_supabase()
                if supabase:
                    supabase.table('chl_sport_goals')\
                        .delete()\
                        .eq('id', goal_id)\
                        .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        else:
            self.send_error(404)

    def do_PATCH(self):
        """Handle PATCH requests for partial updates"""
        if self.path.startswith("/api/journal-entry/"):
            entry_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    if 'date' in data:
                        update_data['date'] = data['date']
                    if 'time' in data:
                        update_data['time'] = data['time']

                    if update_data:
                        supabase.table('chl_journal_entries')\
                            .update(update_data)\
                            .eq('id', entry_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/research/"):
            research_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    # Only allow updating topic field for now
                    update_data = {}
                    if 'topic' in data:
                        update_data['topic'] = data['topic']

                    if update_data:
                        supabase.table('chl_health_research')\
                            .update(update_data)\
                            .eq('id', research_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/diet/"):
            # PATCH /api/diet/{id} - Update diet item
            item_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["meal_slot", "sort_order", "title", "description",
                                "frequency", "is_active", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_diet_items')\
                            .update(update_data)\
                            .eq('id', item_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/supplements/"):
            # PATCH /api/supplements/{id} - Update supplement
            supp_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["name", "dose", "timing", "purpose", "form", "brand",
                                "status", "sort_order", "experiment_id", "start_date",
                                "expected_duration",
                                "discontinued_date", "discontinued_reason", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_supplements')\
                            .update(update_data)\
                            .eq('id', supp_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/body-focus/"):
            # PATCH /api/body-focus/{id} - Update body focus area
            item_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["body_area", "title", "category", "severity",
                                "description", "protocol", "aggravated_by",
                                "helped_by", "status", "started_date",
                                "resolved_date", "sort_order", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_body_focus')\
                            .update(update_data)\
                            .eq('id', item_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/admin/"):
            # PATCH /api/admin/{id} - Update contact
            contact_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                admin_data = get_house_admin()
                for i, contact in enumerate(admin_data["contacts"]):
                    if contact["id"] == contact_id:
                        # Update fields
                        for key in ["category", "person", "phone", "place", "website", "notes"]:
                            if key in data:
                                admin_data["contacts"][i][key] = data[key]
                        break

                save_house_admin(admin_data)

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/budget/"):
            # PATCH /api/budget/{id} - Update budget item
            item_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["category", "display_name", "monthly_amount",
                                "budget_type", "sort_order", "is_active", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_budget')\
                            .update(update_data)\
                            .eq('id', item_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/transactions/"):
            # PATCH /api/transactions/{id} - Update transaction category
            txn_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["category", "category_notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_transactions')\
                            .update(update_data)\
                            .eq('id', txn_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/investments/"):
            # PATCH /api/investments/{id} - Update investment asset
            asset_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["asset_class", "name", "description", "currency",
                                "metadata", "sort_order", "is_active"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_investment_assets')\
                            .update(update_data)\
                            .eq('id', asset_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/actions/"):
            # PATCH /api/bloodtests/actions/{id} - Update action status
            action_id = self.path.split("/")[4]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["title", "description", "category", "priority",
                                "related_markers", "status", "due_date",
                                "completed_date", "outcome_notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_bloodtest_actions')\
                            .update(update_data)\
                            .eq('id', action_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/bloodtests/"):
            # PATCH /api/bloodtests/{id} - Update panel
            panel_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["date", "lab_name", "ordered_by", "panel_name",
                                "analysis_report", "analysis_summary", "health_score",
                                "markers_optimal", "markers_good", "markers_warning",
                                "markers_high", "markers_low", "context_snapshot",
                                "source", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_bloodtest_panels')\
                            .update(update_data)\
                            .eq('id', panel_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/activities/config/target":
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                group = data.get('group')
                weekly_target = data.get('weekly_target')

                config = get_activity_config()
                identity_groups = config.get('identity_groups', {})

                if not group or group not in identity_groups:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": f"Unknown group: {group}"}).encode())
                    return

                if not isinstance(weekly_target, int) or weekly_target < 1 or weekly_target > 14:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": "weekly_target must be int 1-14"}).encode())
                    return

                # Read, modify, write config file
                config_path = Path(__file__).parent / '.claude' / 'context' / 'activity-config.json'
                with open(config_path) as f:
                    file_config = json.load(f)
                file_config['identity_groups'][group]['weekly_target'] = weekly_target
                with open(config_path, 'w') as f:
                    json.dump(file_config, f, indent=2)
                    f.write('\n')

                reload_activity_config()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path == "/api/activities/config/groups":
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                group = data.get('group')
                activities = data.get('activities')

                config = get_activity_config()
                identity_groups = config.get('identity_groups', {})
                all_activities = list(config.get('activities', {}).keys())

                if not group or group not in identity_groups:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": f"Unknown group: {group}"}).encode())
                    return

                if not isinstance(activities, list):
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": "activities must be an array"}).encode())
                    return

                invalid = [a for a in activities if a not in all_activities]
                if invalid:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.send_header("Access-Control-Allow-Origin", "*")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": f"Unknown activities: {invalid}"}).encode())
                    return

                # Read, modify, write config file
                config_path = Path(__file__).parent / '.claude' / 'context' / 'activity-config.json'
                with open(config_path) as f:
                    file_config = json.load(f)

                # Remove incoming activities from all other groups (conflict resolution)
                for gk, gv in file_config['identity_groups'].items():
                    if gk != group:
                        gv['activities'] = [a for a in gv['activities'] if a not in activities]

                # Set this group's activities
                file_config['identity_groups'][group]['activities'] = activities

                with open(config_path, 'w') as f:
                    json.dump(file_config, f, indent=2)
                    f.write('\n')

                reload_activity_config()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Community PATCH endpoints
        elif self.path.startswith("/api/community-members/"):
            member_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["name", "unit_or_house", "phone", "email",
                                "partner_name", "kids", "interests", "skills",
                                "offers", "needs", "birthday", "photo_url",
                                "status", "last_interaction_date", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_community_members')\
                            .update(update_data)\
                            .eq('id', member_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/community-events/"):
            event_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["name", "date", "time", "description",
                                "attendee_ids", "status", "category",
                                "outcome_notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_community_events')\
                            .update(update_data)\
                            .eq('id', event_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/community-contacts/"):
            contact_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["category", "name", "phone", "website",
                                "description", "recommended_by"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_community_contacts')\
                            .update(update_data)\
                            .eq('id', contact_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        # Enrichment PATCH endpoint
        elif self.path.startswith("/api/enrichment/"):
            enrichment_id = self.path.split("/")[3]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["spot", "enjoyment", "conditions_summary", "notes",
                                "focus_area", "highlights", "duration_minutes",
                                "strain", "avg_hr", "max_hr", "calories_kj"]:
                        if key in data:
                            update_data[key] = data[key]

                    if update_data:
                        supabase.table('chl_sport_sessions')\
                            .update(update_data)\
                            .eq('id', enrichment_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        elif self.path.startswith("/api/play/goals/"):
            goal_id = self.path.split("/")[4]
            try:
                content_length = int(self.headers.get('Content-Length', 0))
                body = self.rfile.read(content_length)
                data = json.loads(body) if body else {}

                supabase = get_supabase()
                if supabase and data:
                    update_data = {}
                    for key in ["goal", "status", "notes"]:
                        if key in data:
                            update_data[key] = data[key]

                    if data.get('status') == 'achieved' and 'completed_at' not in data:
                        update_data['completed_at'] = get_brisbane_now().isoformat()

                    if update_data:
                        supabase.table('chl_sport_goals')\
                            .update(update_data)\
                            .eq('id', goal_id)\
                            .execute()

                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"ok": True}).encode())

            except Exception as e:
                self.send_response(500)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(json.dumps({"error": str(e)}).encode())

        else:
            self.send_error(404)

    # =========================================================================
    # JOURNAL & AWARENESS ENDPOINTS
    # =========================================================================

    def _get_journal_entries(self):
        """Get journal entries with optional filters"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            limit = int(params.get('limit', ['30'])[0])
            since = params.get('since', [None])[0]

            supabase = get_supabase()
            if supabase:
                query = supabase.table('chl_journal_entries')\
                    .select('*')\
                    .order('date', desc=True)\
                    .order('time', desc=True)\
                    .limit(limit)
                if since:
                    query = query.gte('date', since)
                result = query.execute()
                entries = result.data or []
            else:
                entries = []

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"entries": entries}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_awareness_stats(self):
        """Get awareness stats: presence trend, schema frequency, theme counts"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            days = int(params.get('days', ['30'])[0])

            now = get_brisbane_now()
            from datetime import timedelta
            start_date = (now - timedelta(days=days)).strftime('%Y-%m-%d')

            supabase = get_supabase()
            stats = {"presence_trend": [], "schema_counts": {}, "theme_counts": {}, "mood_counts": {}}

            if supabase:
                # Presence trend from daily logs
                try:
                    dl_result = supabase.table('chl_daily_logs')\
                        .select('date, presence_level, energy_level')\
                        .gte('date', start_date)\
                        .order('date', desc=False)\
                        .execute()
                    stats["presence_trend"] = dl_result.data or []
                except Exception:
                    pass

                # Schema/theme/mood counts from journal entries
                try:
                    je_result = supabase.table('chl_journal_entries')\
                        .select('date, schemas_detected, themes, mood_indicators')\
                        .gte('date', start_date)\
                        .order('date', desc=False)\
                        .execute()

                    schema_counts = {}
                    theme_counts = {}
                    mood_counts = {}
                    for entry in (je_result.data or []):
                        for s in (entry.get('schemas_detected') or []):
                            schema_counts[s] = schema_counts.get(s, 0) + 1
                        for t in (entry.get('themes') or []):
                            theme_counts[t] = theme_counts.get(t, 0) + 1
                        for m in (entry.get('mood_indicators') or []):
                            mood_counts[m] = mood_counts.get(m, 0) + 1

                    stats["schema_counts"] = schema_counts
                    stats["theme_counts"] = theme_counts
                    stats["mood_counts"] = mood_counts
                    stats["entry_count"] = len(je_result.data or [])
                except Exception:
                    pass

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(stats).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _get_pending_photos(self):
        """Get unprocessed photos from photos/journal/"""
        try:
            photos_dir = Path(__file__).parent / 'photos' / 'journal'
            pending = []
            if photos_dir.exists():
                # Check which photos already have journal entries
                supabase = get_supabase()
                existing_paths = set()
                if supabase:
                    try:
                        result = supabase.table('chl_journal_entries')\
                            .select('photo_path')\
                            .eq('source', 'photo')\
                            .execute()
                        existing_paths = {r['photo_path'] for r in (result.data or []) if r.get('photo_path')}
                    except Exception:
                        pass

                for f in sorted(photos_dir.iterdir()):
                    if f.suffix.lower() in ('.jpg', '.jpeg', '.png', '.heic', '.webp'):
                        rel_path = f"photos/journal/{f.name}"
                        if rel_path not in existing_paths:
                            pending.append({
                                "filename": f.name,
                                "path": rel_path,
                                "size": f.stat().st_size,
                                "modified": f.stat().st_mtime
                            })

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"photos": pending}).encode())

        except Exception as e:
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _post_journal_entry(self):
        """Create a journal entry from typed input (with AI metadata extraction)"""
        try:
            content_length = int(self.headers["Content-Length"])
            post_data = self.rfile.read(content_length)
            data = json.loads(post_data)

            raw_text = data.get("text", "").strip()
            if not raw_text:
                self.send_response(400)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(json.dumps({"error": "text is required"}).encode())
                return

            now = get_brisbane_now()
            entry_id = hashlib.md5(f"{now.isoformat()}{raw_text[:50]}".encode()).hexdigest()[:8]

            # Extract metadata using Claude API if available
            metadata = _extract_journal_metadata(raw_text)

            entry = {
                "id": entry_id,
                "date": now.strftime("%Y-%m-%d"),
                "time": now.strftime("%H:%M:%S"),
                "source": "typed",
                "raw_text": raw_text,
                "mood_indicators": metadata.get("mood_indicators", []),
                "schemas_detected": metadata.get("schemas_detected", []),
                "themes": metadata.get("themes", []),
                "presence_note": metadata.get("presence_note"),
                "metadata": {"word_count": len(raw_text.split())}
            }

            supabase = get_supabase()
            if supabase:
                supabase.table('chl_journal_entries').insert(entry).execute()

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"ok": True, "entry": entry}).encode())

        except Exception as e:
            print(f"[ERROR] POST /api/journal-entry: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def _post_photo_upload(self):
        """Handle photo upload from Android share or browser upload"""
        try:
            from urllib.parse import urlparse, parse_qs
            params = parse_qs(urlparse(self.path).query)
            photo_type = params.get('type', ['journal'])[0]

            content_length = int(self.headers.get("Content-Length", 0))
            content_type = self.headers.get("Content-Type", "")

            now = get_brisbane_now()
            timestamp = now.strftime("%Y-%m-%d_%H%M%S")

            # Create target directory
            photos_dir = Path(__file__).parent / 'photos' / photo_type
            photos_dir.mkdir(parents=True, exist_ok=True)

            if "multipart/form-data" in content_type:
                # Handle multipart form upload (browser)
                import cgi
                form = cgi.FieldStorage(
                    fp=self.rfile,
                    headers=self.headers,
                    environ={'REQUEST_METHOD': 'POST',
                             'CONTENT_TYPE': content_type}
                )
                # Find the uploaded file by checking for filename attribute
                file_item = None
                for key in form.keys():
                    item = form[key]
                    if hasattr(item, 'filename') and item.filename:
                        file_item = item
                        break
                if file_item is not None:
                    photo_data = file_item.value  # .value returns bytes directly
                    ext = Path(file_item.filename).suffix or '.jpg'
                    filename = f"{timestamp}{ext}"
                    filepath = photos_dir / filename
                    with open(filepath, 'wb') as f:
                        f.write(photo_data)
                else:
                    self.send_response(400)
                    self.send_header("Content-Type", "application/json")
                    self.end_headers()
                    self.wfile.write(json.dumps({"error": "No file in upload"}).encode())
                    return
            else:
                # Handle raw body or base64 JSON (Tasker)
                body = self.rfile.read(content_length)

                try:
                    data = json.loads(body)
                    # Base64-encoded photo from Tasker
                    import base64
                    photo_data = base64.b64decode(data.get("photo", data.get("image", "")))
                    ext = data.get("ext", ".jpg")
                    filename = f"{timestamp}{ext}"
                except (json.JSONDecodeError, ValueError):
                    # Raw binary photo data
                    photo_data = body
                    ext = ".jpg"
                    filename = f"{timestamp}{ext}"

                filepath = photos_dir / filename
                with open(filepath, 'wb') as f:
                    f.write(photo_data)

            rel_path = f"photos/{photo_type}/{filename}"
            print(f"[PHOTO] Saved {rel_path} ({filepath.stat().st_size} bytes)")

            result = {"ok": True, "path": rel_path, "type": photo_type, "timestamp": timestamp}

            # Auto-process journal photos
            if photo_type == "journal":
                try:
                    transcription = _transcribe_journal_photo(str(filepath))
                    if transcription:
                        entry_id = hashlib.md5(f"{now.isoformat()}{rel_path}".encode()).hexdigest()[:8]
                        entry = {
                            "id": entry_id,
                            "date": now.strftime("%Y-%m-%d"),
                            "time": now.strftime("%H:%M:%S"),
                            "source": "photo",
                            "raw_text": transcription["raw_text"],
                            "mood_indicators": transcription.get("mood_indicators", []),
                            "schemas_detected": transcription.get("schemas_detected", []),
                            "themes": transcription.get("themes", []),
                            "presence_note": transcription.get("presence_note"),
                            "photo_path": rel_path,
                            "metadata": {
                                "word_count": len(transcription["raw_text"].split()),
                                "processing_model": "claude-haiku-4-5"
                            }
                        }
                        supabase = get_supabase()
                        if supabase:
                            supabase.table('chl_journal_entries').insert(entry).execute()
                        result["journal_entry"] = entry
                        print(f"[JOURNAL] Auto-transcribed → {entry_id}")
                except Exception as te:
                    print(f"[WARN] Auto-transcription failed: {te}")
                    result["transcription_error"] = str(te)

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps(result).encode())

        except Exception as e:
            print(f"[ERROR] POST /api/photos/upload: {e}")
            self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({"error": str(e)}).encode())

    def do_OPTIONS(self):
        # Handle CORS preflight
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, DELETE, PATCH, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()

    def log_message(self, format, *args):
        # Suppress default logging for cleaner output
        try:
            msg = str(args[0]) if args else ""
            if "POST" in msg or "DELETE" in msg or "PATCH" in msg or "error" in str(args).lower():
                print(f"[{datetime.now().strftime('%H:%M:%S')}] {msg}")
        except (TypeError, IndexError):
            pass


if __name__ == "__main__":
    os.chdir(Path(__file__).parent)

    print(f"\n  Starting Dashboard Server...")

    # Initialize Supabase connection
    get_supabase()

    # Allow socket reuse to avoid "Address already in use" errors
    socketserver.TCPServer.allow_reuse_address = True
    with socketserver.TCPServer(("", PORT), DashboardHandler) as httpd:
        print(f"\n  Dashboard server running at http://localhost:{PORT}")
        print(f"  Open http://localhost:{PORT}/dashboard.html")
        print(f"\n  Press Ctrl+C to stop\n")
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("\n  Server stopped.")
