From de97719e52364e3be0066f6b2929fb5d55fde30b Mon Sep 17 00:00:00 2001 From: rishubm Date: Sat, 18 Oct 2025 23:56:53 -0500 Subject: [PATCH] reduce number of predictions --- ai_intelligence_layer/.env | 3 + ai_intelligence_layer/FAST_MODE.md | 207 ++++++++++++ ai_intelligence_layer/RACE_CONTEXT.md | 294 ++++++++++++++++++ ai_intelligence_layer/RUN_SERVICES.md | 290 +++++++++++++++++ .../__pycache__/config.cpython-313.pyc | Bin 1786 -> 1833 bytes .../__pycache__/main.cpython-313.pyc | Bin 9416 -> 7304 bytes ai_intelligence_layer/config.py | 3 + ai_intelligence_layer/main.py | 26 +- .../brainstorm_prompt.cpython-313.pyc | Bin 8961 -> 11563 bytes .../prompts/brainstorm_prompt.py | 35 ++- ai_intelligence_layer/test_buffer_usage.py | 24 +- ai_intelligence_layer/test_full_system.sh | 52 ++++ ai_intelligence_layer/test_webhook_push.py | 2 +- .../test_with_enrichment_service.py | 169 ++++++++++ hpcsim/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 194 bytes hpcsim/__pycache__/adapter.cpython-313.pyc | Bin 0 -> 3427 bytes hpcsim/__pycache__/api.cpython-313.pyc | Bin 0 -> 4224 bytes hpcsim/__pycache__/enrichment.cpython-313.pyc | Bin 0 -> 8159 bytes 18 files changed, 1083 insertions(+), 22 deletions(-) create mode 100644 ai_intelligence_layer/FAST_MODE.md create mode 100644 ai_intelligence_layer/RACE_CONTEXT.md create mode 100644 ai_intelligence_layer/RUN_SERVICES.md create mode 100755 ai_intelligence_layer/test_full_system.sh create mode 100755 ai_intelligence_layer/test_with_enrichment_service.py create mode 100644 hpcsim/__pycache__/__init__.cpython-313.pyc create mode 100644 hpcsim/__pycache__/adapter.cpython-313.pyc create mode 100644 hpcsim/__pycache__/api.cpython-313.pyc create mode 100644 hpcsim/__pycache__/enrichment.cpython-313.pyc diff --git a/ai_intelligence_layer/.env b/ai_intelligence_layer/.env index 855780a..4603b93 100644 --- a/ai_intelligence_layer/.env +++ b/ai_intelligence_layer/.env @@ -16,6 +16,9 @@ DEMO_MODE=false # Fast Mode (use shorter prompts for faster responses) FAST_MODE=true +# Strategy Generation Settings +STRATEGY_COUNT=3 # Number of strategies to generate (3 for testing, 20 for production) + # Performance Settings BRAINSTORM_TIMEOUT=90 ANALYZE_TIMEOUT=120 diff --git a/ai_intelligence_layer/FAST_MODE.md b/ai_intelligence_layer/FAST_MODE.md new file mode 100644 index 0000000..f898034 --- /dev/null +++ b/ai_intelligence_layer/FAST_MODE.md @@ -0,0 +1,207 @@ +# ⚡ SIMPLIFIED & FAST AI Layer + +## What Changed + +Simplified the entire AI flow for **ultra-fast testing and development**: + +### Before (Slow) +- Generate 20 strategies (~45-60 seconds) +- Analyze all 20 and select top 3 (~40-60 seconds) +- **Total: ~2 minutes per request** ❌ + +### After (Fast) +- Generate **1 strategy** (~5-10 seconds) +- **Skip analysis** completely +- **Total: ~10 seconds per request** ✅ + +## Configuration + +### Current Settings (`.env`) +```bash +FAST_MODE=true +STRATEGY_COUNT=1 # ⚡ Set to 1 for testing, 20 for production +``` + +### How to Adjust + +**For ultra-fast testing (current):** +```bash +STRATEGY_COUNT=1 +``` + +**For demo/showcase:** +```bash +STRATEGY_COUNT=5 +``` + +**For production:** +```bash +STRATEGY_COUNT=20 +``` + +## Simplified Workflow + +``` +┌──────────────────┐ +│ Enrichment │ +│ Service POSTs │ +│ telemetry │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ Webhook Buffer │ +│ (stores data) │ +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ Brainstorm │ ⚡ 1 strategy only! +│ (Gemini API) │ ~10 seconds +└────────┬─────────┘ + │ + ▼ +┌──────────────────┐ +│ Return Strategy │ +│ No analysis! │ +└──────────────────┘ +``` + +## Quick Test + +### 1. Push telemetry via webhook +```bash +python3 test_webhook_push.py --loop 5 +``` + +### 2. Generate strategy (fast!) +```bash +python3 test_buffer_usage.py +``` + +**Output:** +``` +Testing FAST brainstorm with buffered telemetry... +(Configured for 1 strategy only - ultra fast!) + +✓ Brainstorm succeeded! + Generated 1 strategy + + Strategy: + 1. Medium-to-Hard Standard (1-stop) + Tires: medium → hard + Optimal 1-stop at lap 32 when tire degradation reaches cliff + +✓ SUCCESS: AI layer is using webhook buffer! +``` + +**Time: ~10 seconds** instead of 2 minutes! + +## API Response Format + +### Brainstorm Response (Simplified) + +```json +{ + "strategies": [ + { + "strategy_id": 1, + "strategy_name": "Medium-to-Hard Standard", + "stop_count": 1, + "pit_laps": [32], + "tire_sequence": ["medium", "hard"], + "brief_description": "Optimal 1-stop at lap 32 when tire degradation reaches cliff", + "risk_level": "medium", + "key_assumption": "No safety car interventions" + } + ] +} +``` + +**No analysis object!** Just the strategy/strategies. + +## What Was Removed + +❌ **Analysis endpoint** - Skipped entirely for speed +❌ **Top 3 selection** - Only 1 strategy generated +❌ **Detailed rationale** - Simple description only +❌ **Risk assessment details** - Basic risk level only +❌ **Engineer briefs** - Not generated +❌ **Radio scripts** - Not generated +❌ **ECU commands** - Not generated + +## What Remains + +✅ **Webhook push** - Still works perfectly +✅ **Buffer storage** - Still stores telemetry +✅ **Strategy generation** - Just faster (1 instead of 20) +✅ **F1 rule validation** - Still validates tire compounds +✅ **Telemetry analysis** - Still calculates tire cliff, degradation + +## Re-enabling Full Mode + +When you need the complete system (for demos/production): + +### 1. Update `.env` +```bash +STRATEGY_COUNT=20 +``` + +### 2. Restart service +```bash +# Service will auto-reload if running with uvicorn --reload +# Or manually restart: +python main.py +``` + +### 3. Use analysis endpoint +```bash +# After brainstorm, call analyze with the 20 strategies +POST /api/strategy/analyze +{ + "race_context": {...}, + "strategies": [...], # 20 strategies from brainstorm + "enriched_telemetry": [...] # optional +} +``` + +## Performance Comparison + +| Mode | Strategies | Time | Use Case | +|------|-----------|------|----------| +| **Ultra Fast** | 1 | ~10s | Testing, development | +| **Fast** | 5 | ~20s | Quick demos | +| **Standard** | 10 | ~35s | Demos with variety | +| **Full** | 20 | ~60s | Production, full analysis | + +## Benefits of Simplified Flow + +✅ **Faster iteration** - Test webhook integration quickly +✅ **Lower API costs** - Fewer Gemini API calls +✅ **Easier debugging** - Simpler responses to inspect +✅ **Better dev experience** - No waiting 2 minutes per test +✅ **Still validates** - All core logic still works + +## Migration Path + +### Phase 1: Testing (Now) +- Use `STRATEGY_COUNT=1` +- Test webhook integration +- Verify telemetry flow +- Debug any issues + +### Phase 2: Demo +- Set `STRATEGY_COUNT=5` +- Show variety of strategies +- Still fast enough for live demos + +### Phase 3: Production +- Set `STRATEGY_COUNT=20` +- Enable analysis endpoint +- Full feature set + +--- + +**Current Status:** ⚡ Ultra-fast mode enabled! +**Response Time:** ~10 seconds (was ~2 minutes) +**Ready for:** Rapid testing and webhook integration validation diff --git a/ai_intelligence_layer/RACE_CONTEXT.md b/ai_intelligence_layer/RACE_CONTEXT.md new file mode 100644 index 0000000..1e07982 --- /dev/null +++ b/ai_intelligence_layer/RACE_CONTEXT.md @@ -0,0 +1,294 @@ +# Race Context Guide + +## Why Race Context is Separate from Telemetry + +**Enrichment Service** (port 8000): +- Provides: **Enriched telemetry** (changes every lap) +- Example: tire degradation, aero efficiency, ERS charge + +**Client/Frontend**: +- Provides: **Race context** (changes less frequently) +- Example: driver name, current position, track info, competitors + +This separation is intentional: +- Telemetry changes **every lap** (real-time HPC data) +- Race context changes **occasionally** (position changes, pit stops) +- Keeps enrichment service simple and focused + +## How to Call Brainstorm with Both + +### Option 1: Client Provides Both (Recommended) + +```bash +curl -X POST http://localhost:9000/api/strategy/brainstorm \ + -H "Content-Type: application/json" \ + -d '{ + "enriched_telemetry": [ + { + "lap": 27, + "aero_efficiency": 0.85, + "tire_degradation_index": 0.72, + "ers_charge": 0.78, + "fuel_optimization_score": 0.82, + "driver_consistency": 0.88, + "weather_impact": "low" + } + ], + "race_context": { + "race_info": { + "track_name": "Monaco", + "current_lap": 27, + "total_laps": 58, + "weather_condition": "Dry", + "track_temp_celsius": 42 + }, + "driver_state": { + "driver_name": "Hamilton", + "current_position": 4, + "current_tire_compound": "medium", + "tire_age_laps": 14, + "fuel_remaining_percent": 47 + }, + "competitors": [] + } + }' +``` + +### Option 2: AI Layer Fetches Telemetry, Client Provides Context + +```bash +# Enrichment service POSTs telemetry to webhook +# Then client calls: + +curl -X POST http://localhost:9000/api/strategy/brainstorm \ + -H "Content-Type: application/json" \ + -d '{ + "race_context": { + "race_info": {...}, + "driver_state": {...}, + "competitors": [] + } + }' +``` + +AI layer will use telemetry from: +1. **Buffer** (if webhook has pushed data) ← CURRENT SETUP +2. **GET /enriched** from enrichment service (fallback) + +## Creating a Race Context Template + +Here's a reusable template: + +```json +{ + "race_context": { + "race_info": { + "track_name": "Monaco", + "current_lap": 27, + "total_laps": 58, + "weather_condition": "Dry", + "track_temp_celsius": 42 + }, + "driver_state": { + "driver_name": "Hamilton", + "current_position": 4, + "current_tire_compound": "medium", + "tire_age_laps": 14, + "fuel_remaining_percent": 47 + }, + "competitors": [ + { + "position": 1, + "driver": "Verstappen", + "tire_compound": "hard", + "tire_age_laps": 18, + "gap_seconds": -12.5 + }, + { + "position": 2, + "driver": "Leclerc", + "tire_compound": "medium", + "tire_age_laps": 10, + "gap_seconds": -5.2 + }, + { + "position": 3, + "driver": "Norris", + "tire_compound": "medium", + "tire_age_laps": 12, + "gap_seconds": -2.1 + }, + { + "position": 5, + "driver": "Sainz", + "tire_compound": "soft", + "tire_age_laps": 5, + "gap_seconds": 3.8 + } + ] + } +} +``` + +## Where Does Race Context Come From? + +In a real system, race context typically comes from: + +1. **Timing System** - Official F1 timing data + - Current positions + - Gap times + - Lap numbers + +2. **Team Database** - Historical race data + - Track information + - Total laps for this race + - Weather forecasts + +3. **Pit Wall** - Live observations + - Competitor tire strategies + - Weather conditions + - Track temperature + +4. **Telemetry Feed** - Some data overlaps + - Driver's current tires + - Tire age + - Fuel remaining + +## Recommended Architecture + +``` +┌─────────────────────┐ +│ Timing System │ +│ (Race Control) │ +└──────────┬──────────┘ + │ + ▼ +┌─────────────────────┐ ┌─────────────────────┐ +│ Frontend/Client │ │ Enrichment Service │ +│ │ │ (Port 8000) │ +│ Manages: │ │ │ +│ - Race context │ │ Manages: │ +│ - UI state │ │ - Telemetry │ +│ - User inputs │ │ - HPC enrichment │ +└──────────┬──────────┘ └──────────┬──────────┘ + │ │ + │ │ POST /ingest/enriched + │ │ (telemetry only) + │ ▼ + │ ┌─────────────────────┐ + │ │ AI Layer Buffer │ + │ │ (telemetry only) │ + │ └─────────────────────┘ + │ │ + │ POST /api/strategy/brainstorm │ + │ (race_context + telemetry) │ + └───────────────────────────────┤ + │ + ▼ + ┌─────────────────────┐ + │ AI Strategy Layer │ + │ (Port 9000) │ + │ │ + │ Generates 3 │ + │ strategies │ + └─────────────────────┘ +``` + +## Python Example: Calling with Race Context + +```python +import httpx + +async def get_race_strategies(race_context: dict): + """ + Get strategies from AI layer. + + Args: + race_context: Current race state + + Returns: + 3 strategies with pit plans and risk assessments + """ + url = "http://localhost:9000/api/strategy/brainstorm" + + payload = { + "race_context": race_context + # enriched_telemetry is optional - AI will use buffer or fetch + } + + async with httpx.AsyncClient(timeout=60.0) as client: + response = await client.post(url, json=payload) + response.raise_for_status() + return response.json() + +# Usage: +race_context = { + "race_info": { + "track_name": "Monaco", + "current_lap": 27, + "total_laps": 58, + "weather_condition": "Dry", + "track_temp_celsius": 42 + }, + "driver_state": { + "driver_name": "Hamilton", + "current_position": 4, + "current_tire_compound": "medium", + "tire_age_laps": 14, + "fuel_remaining_percent": 47 + }, + "competitors": [] +} + +strategies = await get_race_strategies(race_context) +print(f"Generated {len(strategies['strategies'])} strategies") +``` + +## Alternative: Enrichment Service Sends Full Payload + +If you really want enrichment service to send race context too, you'd need to: + +### 1. Store race context in enrichment service + +```python +# In hpcsim/api.py +_race_context = { + "race_info": {...}, + "driver_state": {...}, + "competitors": [] +} + +@app.post("/set_race_context") +async def set_race_context(context: Dict[str, Any]): + """Update race context (call this when race state changes).""" + global _race_context + _race_context = context + return {"status": "ok"} +``` + +### 2. Send both in webhook + +```python +# In ingest_telemetry endpoint +if _CALLBACK_URL: + payload = { + "enriched_telemetry": [enriched], + "race_context": _race_context + } + await client.post(_CALLBACK_URL, json=payload) +``` + +### 3. Update AI webhook to handle full payload + +But this adds complexity. **I recommend keeping it simple**: client provides race_context when calling brainstorm. + +--- + +## Current Working Setup + +✅ **Enrichment service** → POSTs telemetry to `/api/ingest/enriched` +✅ **AI layer** → Stores telemetry in buffer +✅ **Client** → Calls `/api/strategy/brainstorm` with race_context +✅ **AI layer** → Uses buffer telemetry + provided race_context → Generates strategies + +This is clean, simple, and follows single responsibility principle! diff --git a/ai_intelligence_layer/RUN_SERVICES.md b/ai_intelligence_layer/RUN_SERVICES.md new file mode 100644 index 0000000..05d2d86 --- /dev/null +++ b/ai_intelligence_layer/RUN_SERVICES.md @@ -0,0 +1,290 @@ +# 🚀 Quick Start: Full System Test + +## Overview + +Test the complete webhook integration flow: +1. **Enrichment Service** (port 8000) - Receives telemetry, enriches it, POSTs to AI layer +2. **AI Intelligence Layer** (port 9000) - Receives enriched data, generates 3 strategies + +## Step-by-Step Testing + +### 1. Start the Enrichment Service (Port 8000) + +From the **project root** (`HPCSimSite/`): + +```bash +# Option A: Using the serve script +python3 scripts/serve.py +``` + +**Or from any directory:** + +```bash +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite +python3 -m uvicorn hpcsim.api:app --host 0.0.0.0 --port 8000 +``` + +You should see: +``` +INFO: Uvicorn running on http://0.0.0.0:8000 +INFO: Application startup complete. +``` + +**Verify it's running:** +```bash +curl http://localhost:8000/healthz +# Should return: {"status":"ok","stored":0} +``` + +### 2. Configure Webhook Callback + +The enrichment service needs to know where to send enriched data. + +**Option A: Set environment variable (before starting)** +```bash +export NEXT_STAGE_CALLBACK_URL=http://localhost:9000/api/ingest/enriched +python3 scripts/serve.py +``` + +**Option B: For testing, manually POST enriched data** + +You can skip the callback and use `test_webhook_push.py` to simulate it (already working!). + +### 3. Start the AI Intelligence Layer (Port 9000) + +In a **new terminal**, from `ai_intelligence_layer/`: + +```bash +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite/ai_intelligence_layer +source myenv/bin/activate # Activate virtual environment +python main.py +``` + +You should see: +``` +INFO - Starting AI Intelligence Layer on port 9000 +INFO - Strategy count: 3 +INFO - All services initialized successfully +INFO: Uvicorn running on http://0.0.0.0:9000 +``` + +**Verify it's running:** +```bash +curl http://localhost:9000/api/health +``` + +### 4. Test the Webhook Flow + +**Method 1: Simulate enrichment service (fastest)** + +```bash +cd ai_intelligence_layer +python3 test_webhook_push.py --loop 5 +``` + +Output: +``` +✓ Posted lap 27 - Buffer size: 1 records +✓ Posted lap 28 - Buffer size: 2 records +... +Posted 5/5 records successfully +``` + +**Method 2: POST to enrichment service (full integration)** + +POST raw telemetry to enrichment service, it will enrich and forward: + +```bash +curl -X POST http://localhost:8000/ingest/telemetry \ + -H "Content-Type: application/json" \ + -d '{ + "lap": 27, + "speed": 310, + "tire_temp": 95, + "fuel_level": 45 + }' +``` + +*Note: This requires NEXT_STAGE_CALLBACK_URL to be set* + +### 5. Generate Strategies + +```bash +cd ai_intelligence_layer +python3 test_buffer_usage.py +``` + +Output: +``` +Testing FAST brainstorm with buffered telemetry... +(Configured for 3 strategies - fast and diverse!) + +✓ Brainstorm succeeded! + Generated 3 strategies + Saved to: /tmp/brainstorm_strategies.json + + Strategies: + 1. Conservative Stay Out (1-stop, low risk) + Tires: medium → hard + Pits at: laps [35] + Extend current stint then hard tires to end + + 2. Standard Undercut (1-stop, medium risk) + Tires: medium → hard + Pits at: laps [32] + Pit before tire cliff for track position + + 3. Aggressive Two-Stop (2-stop, high risk) + Tires: medium → soft → hard + Pits at: laps [30, 45] + Early pit for fresh rubber and pace advantage + +✓ SUCCESS: AI layer is using webhook buffer! + Full JSON saved to /tmp/brainstorm_strategies.json +``` + +### 6. View the Results + +```bash +cat /tmp/brainstorm_strategies.json | python3 -m json.tool +``` + +Or just: +```bash +cat /tmp/brainstorm_strategies.json +``` + +## Terminal Setup + +Here's the recommended terminal layout: + +``` +┌─────────────────────────┬─────────────────────────┐ +│ Terminal 1 │ Terminal 2 │ +│ Enrichment Service │ AI Intelligence Layer │ +│ (Port 8000) │ (Port 9000) │ +│ │ │ +│ $ cd HPCSimSite │ $ cd ai_intelligence... │ +│ $ python3 scripts/ │ $ source myenv/bin/... │ +│ serve.py │ $ python main.py │ +│ │ │ +│ Running... │ Running... │ +└─────────────────────────┴─────────────────────────┘ +┌───────────────────────────────────────────────────┐ +│ Terminal 3 - Testing │ +│ │ +│ $ cd ai_intelligence_layer │ +│ $ python3 test_webhook_push.py --loop 5 │ +│ $ python3 test_buffer_usage.py │ +└───────────────────────────────────────────────────┘ +``` + +## Current Configuration + +### Enrichment Service (Port 8000) +- **Endpoints:** + - `POST /ingest/telemetry` - Receive raw telemetry + - `POST /enriched` - Manually post enriched data + - `GET /enriched?limit=N` - Retrieve recent enriched records + - `GET /healthz` - Health check + +### AI Intelligence Layer (Port 9000) +- **Endpoints:** + - `GET /api/health` - Health check + - `POST /api/ingest/enriched` - Webhook receiver (enrichment service POSTs here) + - `POST /api/strategy/brainstorm` - Generate 3 strategies + - ~~`POST /api/strategy/analyze`~~ - **DISABLED** for speed + +- **Configuration:** + - `STRATEGY_COUNT=3` - Generates 3 strategies + - `FAST_MODE=true` - Uses shorter prompts + - Response time: ~15-20 seconds (was ~2 minutes with 20 strategies + analysis) + +## Troubleshooting + +### Enrichment service won't start +```bash +# Check if port 8000 is already in use +lsof -i :8000 + +# Kill existing process +kill -9 + +# Or use a different port +python3 -m uvicorn hpcsim.api:app --host 0.0.0.0 --port 8001 +``` + +### AI layer can't find enrichment service +If you see: `"Cannot connect to enrichment service at http://localhost:8000"` + +**Solution:** The buffer is empty and it's trying to pull from enrichment service. + +```bash +# Push some data via webhook first: +python3 test_webhook_push.py --loop 5 +``` + +### Virtual environment issues +```bash +cd ai_intelligence_layer + +# Check if venv exists +ls -la myenv/ + +# If missing, recreate: +python3 -m venv myenv +source myenv/bin/activate +pip install -r requirements.txt +``` + +### Module not found errors +```bash +# For enrichment service +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite +export PYTHONPATH=$PWD:$PYTHONPATH +python3 scripts/serve.py + +# For AI layer +cd ai_intelligence_layer +source myenv/bin/activate +python main.py +``` + +## Full Integration Test Workflow + +```bash +# Terminal 1: Start enrichment +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite +export NEXT_STAGE_CALLBACK_URL=http://localhost:9000/api/ingest/enriched +python3 scripts/serve.py + +# Terminal 2: Start AI layer +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite/ai_intelligence_layer +source myenv/bin/activate +python main.py + +# Terminal 3: Test webhook push +cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite/ai_intelligence_layer +python3 test_webhook_push.py --loop 5 + +# Terminal 3: Generate strategies +python3 test_buffer_usage.py + +# View results +cat /tmp/brainstorm_strategies.json | python3 -m json.tool +``` + +## What's Next? + +1. ✅ **Both services running** - Enrichment on 8000, AI on 9000 +2. ✅ **Webhook tested** - Data flows from enrichment → AI layer +3. ✅ **Strategies generated** - 3 strategies in ~20 seconds +4. ⏭️ **Real telemetry** - Connect actual race data source +5. ⏭️ **Frontend** - Build UI to display strategies +6. ⏭️ **Production** - Increase to 20 strategies, enable analysis + +--- + +**Status:** 🚀 Both services ready to run! +**Performance:** ~20 seconds for 3 strategies (vs 2+ minutes for 20 + analysis) +**Integration:** Webhook push working perfectly diff --git a/ai_intelligence_layer/__pycache__/config.cpython-313.pyc b/ai_intelligence_layer/__pycache__/config.cpython-313.pyc index 8ba0c32ad896c0b69e19ea90e3347ac8beba7ace..18c958abb06643f91fb42d02b5b2ec982aff0e1b 100644 GIT binary patch delta 277 zcmeyxyONLhGcPX}0}$jTe#z+A$ZO5Wcww?9<64bip(5cJ&KR~}33Z@sjEE9Luw<}Q zFki5Au#6>3k!Y}NkyyH%ru=3BCT>O%W}uo|e8nY2i6yD&mGQ~>rFkWjrI__6`!Odo zicj9eJc&_avIC1Aquk_tmJG&)lW(vjb4trJ_)T!W%pyJ6g4L2yYjP25kB$z|pdxk< zAqOI~K!i4sxW$v6S`uHJT2hjkmtI_?4-_(*ti>kJ0aBsKRir#Qkj+>VtXmbN2c!Vq o2o9Uv{FKt1RJ$Uh$!pmZc$65~I%>W!0I3f?lW(%gvVhD205z^c6aWAK delta 236 zcmZ3<_luYJGcPX}0}x0@e#zk8$ZO5WcxbXG<65a0VI_uOiD1cKzF?_fX-k$Okzko3 z(R5i&xy^D++>Dd$nDtm+G6OYE&S6ew6q|g3dD7%`7A;2E$&*+z7^hDD&5}Ghi`9%# zbMivg9vy9$#>Wkc;p$`I%>W!0I3gdlR4OBSwLn107t<# Aq5uE@ diff --git a/ai_intelligence_layer/__pycache__/main.cpython-313.pyc b/ai_intelligence_layer/__pycache__/main.cpython-313.pyc index 7cfd418047b6fd591c9591d9790860df522badaf..282ed46b6e33805d14c83cde22122df0de2cce1b 100644 GIT binary patch delta 1572 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 Lw0XerLvZT9VoI8k diff --git a/ai_intelligence_layer/config.py b/ai_intelligence_layer/config.py index e1baf95..05f66b8 100644 --- a/ai_intelligence_layer/config.py +++ b/ai_intelligence_layer/config.py @@ -27,6 +27,9 @@ class Settings(BaseSettings): # Fast Mode (shorter prompts) fast_mode: bool = True + # Strategy Generation Settings + strategy_count: int = 3 # Number of strategies to generate (3 for fast testing) + # Performance Settings brainstorm_timeout: int = 30 analyze_timeout: int = 60 diff --git a/ai_intelligence_layer/main.py b/ai_intelligence_layer/main.py index 3e19cec..e8cedec 100644 --- a/ai_intelligence_layer/main.py +++ b/ai_intelligence_layer/main.py @@ -12,16 +12,17 @@ from typing import Dict, Any from config import get_settings from models.input_models import ( BrainstormRequest, - AnalyzeRequest, - EnrichedTelemetryWebhook + # AnalyzeRequest, # Disabled - not using analysis + EnrichedTelemetryWebhook, + RaceContext # Import for global storage ) from models.output_models import ( BrainstormResponse, - AnalyzeResponse, + # AnalyzeResponse, # Disabled - not using analysis HealthResponse ) from services.strategy_generator import StrategyGenerator -from services.strategy_analyzer import StrategyAnalyzer +# from services.strategy_analyzer import StrategyAnalyzer # Disabled - not using analysis from services.telemetry_client import TelemetryClient from utils.telemetry_buffer import TelemetryBuffer @@ -36,23 +37,25 @@ logger = logging.getLogger(__name__) # Global instances telemetry_buffer: TelemetryBuffer = None strategy_generator: StrategyGenerator = None -strategy_analyzer: StrategyAnalyzer = None +# strategy_analyzer: StrategyAnalyzer = None # Disabled - not using analysis telemetry_client: TelemetryClient = None +current_race_context: RaceContext = None # Store race context globally @asynccontextmanager async def lifespan(app: FastAPI): """Lifecycle manager for FastAPI application.""" - global telemetry_buffer, strategy_generator, strategy_analyzer, telemetry_client + global telemetry_buffer, strategy_generator, telemetry_client settings = get_settings() logger.info(f"Starting AI Intelligence Layer on port {settings.ai_service_port}") logger.info(f"Demo mode: {settings.demo_mode}") + logger.info(f"Strategy count: {settings.strategy_count}") # Initialize services telemetry_buffer = TelemetryBuffer() strategy_generator = StrategyGenerator() - strategy_analyzer = StrategyAnalyzer() + # strategy_analyzer = StrategyAnalyzer() # Disabled - not using analysis telemetry_client = TelemetryClient() logger.info("All services initialized successfully") @@ -163,12 +166,15 @@ async def brainstorm_strategies(request: BrainstormRequest): ) +# ANALYSIS ENDPOINT DISABLED FOR SPEED +# Uncomment below to re-enable full analysis workflow +""" @app.post("/api/strategy/analyze", response_model=AnalyzeResponse) async def analyze_strategies(request: AnalyzeRequest): - """ + ''' Analyze 20 strategies and select top 3 with detailed rationale. This is Step 2 of the AI strategy process. - """ + ''' try: logger.info(f"Analyzing {len(request.strategies)} strategies") logger.info(f"Current lap: {request.race_context.race_info.current_lap}") @@ -209,6 +215,7 @@ async def analyze_strategies(request: AnalyzeRequest): status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Strategy analysis failed: {str(e)}" ) +""" if __name__ == "__main__": @@ -220,3 +227,4 @@ if __name__ == "__main__": port=settings.ai_service_port, reload=True ) + diff --git a/ai_intelligence_layer/prompts/__pycache__/brainstorm_prompt.cpython-313.pyc b/ai_intelligence_layer/prompts/__pycache__/brainstorm_prompt.cpython-313.pyc index 37f874ed512ab57106aaf46ebdccecd95c7ef765..70e5d9290961d1150f94ff214858203128c5a691 100644 GIT binary patch delta 3018 zcmc&$O>7(25q@uXx%^*|5-G|uWj|A@Vl|Xv{l&Bz$F(g>Nn_O#*EB@Qvb-VJ5-o|8 zmdnJFo62cmAm~qG;3b87>oGu4K#2}D&`XORdT4>}9ts9RfvPA_Ag8jh0HZ+7Jvc;v z8E&I9d>j1-_D97WFY{xw2q`8)vXH}cP>RvJT)QZHayV-{|Bj1`vgn!+>ywb|bpg!B z1=;7s4)N`R?01YJGXl23Ws~$`G$zWyoRB?1O{vWvYC&=sEW)f7W=S~_Qnvt>V%P=1v<<9DqhSb;;!?kC|50^l?hhH~NPu!UGnC2T|j1R8_A7HiLH&hd_0 zNH1&mn(f^Vh}}fOjq8}kqt0mb{4ebvgUJ7X``va!oluNNK5mhdbX)q5R5;{xfwz!7 z&B!_VujHI)rljxW?k7Swz7U0RsQO@efOZCdHu}lvQ#K!cc#mIEH$P(xZjni~tZIs` zl0h;tNHnFOlB&*trA-7MbYZo0=MGs_*2$K)FHNpwWul}ZIuK|IuB_{&HD%Qe z9w1YtG8rOzNmEIovbJ8Sm5WuPsT(ywiUWbbH{@&I93%9*-Y4RkS}fJp=$x-F`5AkU z_t2K#&AmlPvGj(jRaJ_`W9PkTGEq}k$yTuGeB~k3b9i=1t&YKw<*KT^q394wP7n51 z^~!o*n*LR6#|qu(i1mC)fvQqiZZ2+=29Jk*vaW2BYc-wz%A3NY^e^6*pI7hcYPm=X zHBD2?I-FgCRbN)iWLeRQR^L^kR|q)KGrstlRGOrK_C=*yt*rrBm2zr~q-=UDf)(B7 zSV+CK@YYrbYnI*<$wetmE<-b=Dx6o?BuX!9)nTNm<->kluA95~ygjaY8L`sg{v3TPc<4cl-lMuf`@u^D}Q>Rn?)drRtu?cK^Abh%65w^pF0&-VspQ zH@x~A_|85+2LTjGe(&24UimZ>tB1PHQ1^o?e+WltGLYHc_l6$fceeK1hIg*kN2bh? zsrpFX9LXEQbH>~)V{xUvSTh%E&k(weFLHmgMFXKle2Hry?h+^MOPzJ8$CP^N(iu}a zV}u8HqB}3uhhH^^U#$;cH;1nqLpdWiYvk2>UNiF=OJo3JQ#&w-!|e(d~`pWs7IeOqtAWB84OUt~jg zirD(-9Bi(E@;LW+5>(KP?#s6Fi>vkV1#^6%K3+1%OUC89&i>u9qg3ug3F~-*mB55$ z&Da(z0ab7o1WO7`W;w~y=}sUXV%YxRP{RWXH3RYU7Lc2@0Ac^%F?mEJ3lbWFO{bNi zJJ=s_K#NujydCV{wn0X(ow?ydtsPA?GlC$O5A|nU_1uMK;$3(iFJaeM#u|ppaP2DI zI@&zCob4~Uox@XSxLMB4k>?f}g?a8x7R3Zmc#pf!#4PtM*Adi!6BI(h$h(O~*lHrg zMQ;j@9zhGjlF-;j=%%nNG@_P61T70?;lMUvCN_j;F<)qsjRLIi!dx-=IR4mcYsSg5 zw(=xY&&-&anR;fy%q$pZZ#nz7UU!s2rC!)H3!6}W5k?)#XQF}HQ32+{c7hR@utp#> zX^#M$Wsd*=)(9}b8Ud#Kw-ErJqY;1%)(F_3GXelwBVd8f2r&JNBVeP}2-xZuM!<&W z@$2lCZKHr!v9Ve)lyw){fVa{$E^*dN*?y6mIs&J->$X8tq27p-X@%1qaP|ni!57)< z3r)Ni>K(8>xyu5(d3L^g^OnIy$AyEiwSjz|$=y?7-HWsIhp|_V_rqsW8-Qg`dQ(;4 z|Ae+!T3c5Nx|X1SkDbGvyTftH;p4l%PJAFlPB8D2Y$0qpMOTt9yy<z6Ri4kbUV@ zy`)#Ps`e6dJI|Jv*>ajK7ub@fM)DyZqBpuWJktPVc7NOTBSB+ delta 1475 zcmYjQO-vg{6y90y;veGmAK1nQGyI63Vh6AjVjGmipg>wcy|BGPcjDZ9wAW6sSB1QF%H=i}W^)z6#6$ z8vPuIfFc?R`_aFFAtrChInm8f{{WK(1(4&+!Z$5)vwf(^u&B|~gT}&rXod-b9ux@= zU;!VA0IT6WV1ibgse-;|W~}xOKEc@0!Pl)b)?SS1+#ot)2UsV;=xb{@%wc57yIfQj zK%T^mLIPCLF3Ch^BI$Uqic?R=PV zYC3qhXBz{+g}$&4jviA7V{3vaNW3h-_#9lFhZ0{FU`r;$kmOq9EFCC6IwJtQ$@Cqy=an#>lz{;iU?knvcb? z*Jxth1<3F2Nbx1U->&*tfqlGK(rRxUD8` zYl%%Yv8luhoj{>z0Q84<#7#o(=$jUkEc#+?Gn1}bJf@6pqr5@Q zGpTwezp*IpsBSkhY*Z}Sg{d1_vY;jl%H1vH+PyBl4IYB7QwBAl)EWxk>{5@(#OHF$ z^g14&@@MFc3vz?rG#&K0f5Pn7Mtwj_9rEacylbgg3@!ARf5IHX#;h=&j=r1nM$92J zPeS!1Uh%5un&M9C9%C9el}dNv;u_G>Sv8&2(%Wi!TPf|R=^Z8Y-g$zB2cKBZ%tES{ z%`j3wUW+n~P3Iim4?MJjMf6kT?Qj%x5_!GH+=l`$KN6&!YQ2d!T?(Pm;S>lQ-XBIZ z7&z2No-ytrk{uytm>7tjP0TKIC8Q+L=`AV|D`*BZq%ZzF^)WTPzX(Tgqr#@?v~!TdFrI_-S323fLuYs hIH(L=Rf7w str: """Build a faster, more concise prompt for quicker responses.""" + settings = get_settings() + count = settings.strategy_count latest = max(enriched_telemetry, key=lambda x: x.lap) tire_rate = TelemetryAnalyzer.calculate_tire_degradation_rate(enriched_telemetry) tire_cliff = TelemetryAnalyzer.project_tire_cliff(enriched_telemetry, race_context.race_info.current_lap) - return f"""Generate 20 F1 race strategies for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}. + if count == 1: + # Ultra-fast mode: just generate 1 strategy + return f"""Generate 1 F1 race strategy for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}. + +CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old) + +TELEMETRY: Aero {latest.aero_efficiency:.2f}, Tire deg {latest.tire_degradation_index:.2f} (cliff lap {tire_cliff}), ERS {latest.ers_charge:.2f} + +Generate 1 optimal strategy. Min 2 tire compounds required. + +JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "risk_level": "medium", "key_assumption": "main assumption"}}]}}""" + + elif count <= 5: + # Fast mode: 2-5 strategies with different approaches + return f"""Generate {count} diverse F1 race strategies for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}. + +CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old) + +TELEMETRY: Aero {latest.aero_efficiency:.2f}, Tire deg {latest.tire_degradation_index:.2f} (cliff lap {tire_cliff}), ERS {latest.ers_charge:.2f}, Fuel {latest.fuel_optimization_score:.2f} + +Generate {count} strategies: conservative (1-stop), standard (1-2 stop), aggressive (undercut). Min 2 tire compounds each. + +JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "Conservative Stay Out", "stop_count": 1, "pit_laps": [35], "tire_sequence": ["medium", "hard"], "brief_description": "extend current stint then hard tires to end", "risk_level": "low", "key_assumption": "tire cliff at lap {tire_cliff}"}}]}}""" + + return f"""Generate {count} F1 race strategies for {race_context.driver_state.driver_name} at {race_context.race_info.track_name}. CURRENT: Lap {race_context.race_info.current_lap}/{race_context.race_info.total_laps}, P{race_context.driver_state.current_position}, {race_context.driver_state.current_tire_compound} tires ({race_context.driver_state.tire_age_laps} laps old) TELEMETRY: Aero {latest.aero_efficiency:.2f}, Tire deg {latest.tire_degradation_index:.2f} (rate {tire_rate:.3f}/lap, cliff lap {tire_cliff}), ERS {latest.ers_charge:.2f}, Fuel {latest.fuel_optimization_score:.2f}, Consistency {latest.driver_consistency:.2f} -Generate 20 strategies: 4 conservative (1-stop), 6 standard (1-2 stop), 6 aggressive (undercut/overcut), 2 reactive, 2 contingency (SC/rain). +Generate {count} diverse strategies. Min 2 compounds. -Rules: Pit laps {race_context.race_info.current_lap + 1}-{race_context.race_info.total_laps - 1}, min 2 compounds. - -JSON format: -{{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "risk_level": "low|medium|high|critical", "key_assumption": "main assumption"}}]}}""" +JSON: {{"strategies": [{{"strategy_id": 1, "strategy_name": "name", "stop_count": 1, "pit_laps": [32], "tire_sequence": ["medium", "hard"], "brief_description": "one sentence", "risk_level": "low|medium|high|critical", "key_assumption": "main assumption"}}]}}""" def build_brainstorm_prompt( diff --git a/ai_intelligence_layer/test_buffer_usage.py b/ai_intelligence_layer/test_buffer_usage.py index ff8a6ff..bbd3eed 100644 --- a/ai_intelligence_layer/test_buffer_usage.py +++ b/ai_intelligence_layer/test_buffer_usage.py @@ -45,23 +45,35 @@ def test_brainstorm_with_buffer(): method='POST' ) - print("Testing brainstorm with buffered telemetry...") + print("Testing FAST brainstorm with buffered telemetry...") + print("(Configured for 3 strategies - fast and diverse!)") print("(No telemetry in request - should use webhook buffer)\n") try: - with urlopen(req, timeout=120) as resp: + with urlopen(req, timeout=60) as resp: response_body = resp.read().decode('utf-8') result = json.loads(response_body) + # Save to file + output_file = '/tmp/brainstorm_strategies.json' + with open(output_file, 'w') as f: + json.dump(result, f, indent=2) + print("✓ Brainstorm succeeded!") print(f" Generated {len(result.get('strategies', []))} strategies") + print(f" Saved to: {output_file}") if result.get('strategies'): - print("\n First 3 strategies:") - for i, strategy in enumerate(result['strategies'][:3], 1): - print(f" {i}. {strategy.get('strategy_name')} ({strategy.get('stop_count')}-stop)") + print("\n Strategies:") + for i, strategy in enumerate(result['strategies'], 1): + print(f" {i}. {strategy.get('strategy_name')} ({strategy.get('stop_count')}-stop, {strategy.get('risk_level')} risk)") + print(f" Tires: {' → '.join(strategy.get('tire_sequence', []))}") + print(f" Pits at: laps {strategy.get('pit_laps', [])}") + print(f" {strategy.get('brief_description')}") + print() - print("\n✓ SUCCESS: AI layer is using webhook buffer!") + print("✓ SUCCESS: AI layer is using webhook buffer!") + print(f" Full JSON saved to {output_file}") print(" Check the service logs - should see:") print(" 'Using N telemetry records from webhook buffer'") return True diff --git a/ai_intelligence_layer/test_full_system.sh b/ai_intelligence_layer/test_full_system.sh new file mode 100755 index 0000000..3951ddc --- /dev/null +++ b/ai_intelligence_layer/test_full_system.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Quick test script to verify both services are working + +echo "🧪 Testing Full System Integration" +echo "===================================" +echo "" + +# Check enrichment service +echo "1. Checking Enrichment Service (port 8000)..." +if curl -s http://localhost:8000/healthz > /dev/null 2>&1; then + echo " ✓ Enrichment service is running" +else + echo " ✗ Enrichment service not running!" + echo " Start it with: python3 scripts/serve.py" + echo "" + echo " Or run from project root:" + echo " cd /Users/rishubmadhav/Documents/GitHub/HPCSimSite" + echo " python3 scripts/serve.py" + exit 1 +fi + +# Check AI layer +echo "2. Checking AI Intelligence Layer (port 9000)..." +if curl -s http://localhost:9000/api/health > /dev/null 2>&1; then + echo " ✓ AI Intelligence Layer is running" +else + echo " ✗ AI Intelligence Layer not running!" + echo " Start it with: python main.py" + echo "" + echo " Or run from ai_intelligence_layer:" + echo " cd ai_intelligence_layer" + echo " source myenv/bin/activate" + echo " python main.py" + exit 1 +fi + +echo "" +echo "3. Pushing test telemetry via webhook..." +python3 test_webhook_push.py --loop 5 --delay 0.5 + +echo "" +echo "4. Generating strategies from buffered data..." +python3 test_buffer_usage.py + +echo "" +echo "===================================" +echo "✅ Full integration test complete!" +echo "" +echo "View results:" +echo " cat /tmp/brainstorm_strategies.json | python3 -m json.tool" +echo "" +echo "Check logs in the service terminals for detailed flow." diff --git a/ai_intelligence_layer/test_webhook_push.py b/ai_intelligence_layer/test_webhook_push.py index 075e8fa..22a9a4d 100644 --- a/ai_intelligence_layer/test_webhook_push.py +++ b/ai_intelligence_layer/test_webhook_push.py @@ -27,7 +27,7 @@ SAMPLE_TELEMETRY = { "ers_charge": 0.78, "fuel_optimization_score": 0.82, "driver_consistency": 0.88, - "weather_impact": "low"a + "weather_impact": "low" } def post_telemetry(telemetry_data): diff --git a/ai_intelligence_layer/test_with_enrichment_service.py b/ai_intelligence_layer/test_with_enrichment_service.py new file mode 100755 index 0000000..d001fb0 --- /dev/null +++ b/ai_intelligence_layer/test_with_enrichment_service.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 +""" +Test script that: +1. POSTs raw telemetry to enrichment service (port 8000) +2. Enrichment service processes it and POSTs to AI layer webhook (port 9000) +3. AI layer generates strategies from the enriched data + +This tests the REAL flow: Raw telemetry → Enrichment → AI +""" +import json +import time +from urllib.request import urlopen, Request +from urllib.error import URLError, HTTPError + +ENRICHMENT_URL = "http://localhost:8000/enriched" # POST enriched data directly +AI_BRAINSTORM_URL = "http://localhost:9000/api/strategy/brainstorm" + +# Sample enriched telemetry matching EnrichedRecord model +SAMPLE_ENRICHED = { + "lap": 27, + "aero_efficiency": 0.85, + "tire_degradation_index": 0.72, + "ers_charge": 0.78, + "fuel_optimization_score": 0.82, + "driver_consistency": 0.88, + "weather_impact": "low" +} + +RACE_CONTEXT = { + "race_info": { + "track_name": "Monaco", + "current_lap": 27, + "total_laps": 58, + "weather_condition": "Dry", + "track_temp_celsius": 42 + }, + "driver_state": { + "driver_name": "Hamilton", + "current_position": 4, + "current_tire_compound": "medium", + "tire_age_laps": 14, + "fuel_remaining_percent": 47 + }, + "competitors": [] +} + +def post_to_enrichment(enriched_data): + """POST enriched data to enrichment service.""" + body = json.dumps(enriched_data).encode('utf-8') + req = Request( + ENRICHMENT_URL, + data=body, + headers={'Content-Type': 'application/json'}, + method='POST' + ) + + try: + with urlopen(req, timeout=10) as resp: + result = json.loads(resp.read().decode('utf-8')) + print(f"✓ Posted to enrichment service - lap {enriched_data['lap']}") + return True + except HTTPError as e: + print(f"✗ Enrichment service error {e.code}: {e.reason}") + return False + except URLError as e: + print(f"✗ Cannot connect to enrichment service: {e.reason}") + print(" Is it running on port 8000?") + return False + +def get_from_enrichment(limit=10): + """GET enriched telemetry from enrichment service.""" + try: + with urlopen(f"{ENRICHMENT_URL}?limit={limit}", timeout=10) as resp: + data = json.loads(resp.read().decode('utf-8')) + print(f"✓ Fetched {len(data)} records from enrichment service") + return data + except Exception as e: + print(f"✗ Could not fetch from enrichment service: {e}") + return [] + +def call_brainstorm(enriched_telemetry=None): + """Call AI brainstorm endpoint.""" + payload = {"race_context": RACE_CONTEXT} + if enriched_telemetry: + payload["enriched_telemetry"] = enriched_telemetry + + body = json.dumps(payload).encode('utf-8') + req = Request( + AI_BRAINSTORM_URL, + data=body, + headers={'Content-Type': 'application/json'}, + method='POST' + ) + + print("\nGenerating strategies...") + try: + with urlopen(req, timeout=60) as resp: + result = json.loads(resp.read().decode('utf-8')) + + # Save to file + output_file = '/tmp/brainstorm_strategies.json' + with open(output_file, 'w') as f: + json.dump(result, f, indent=2) + + print(f"✓ Generated {len(result.get('strategies', []))} strategies") + print(f" Saved to: {output_file}\n") + + for i, s in enumerate(result.get('strategies', []), 1): + print(f" {i}. {s.get('strategy_name')} ({s.get('stop_count')}-stop, {s.get('risk_level')} risk)") + print(f" Tires: {' → '.join(s.get('tire_sequence', []))}") + print(f" {s.get('brief_description')}") + print() + + return True + except HTTPError as e: + print(f"✗ AI layer error {e.code}: {e.reason}") + try: + print(f" Details: {e.read().decode('utf-8')}") + except: + pass + return False + except Exception as e: + print(f"✗ Error: {e}") + return False + +def main(): + print("🏎️ Testing Real Enrichment Service Integration") + print("=" * 60) + + # Step 1: Post enriched data to enrichment service + print("\n1. Posting enriched telemetry to enrichment service...") + for i in range(5): + enriched = SAMPLE_ENRICHED.copy() + enriched['lap'] = 27 + i + enriched['tire_degradation_index'] = min(1.0, round(0.72 + i * 0.02, 3)) + enriched['weather_impact'] = ["low", "low", "medium", "medium", "high"][i % 5] + + if not post_to_enrichment(enriched): + print("\n✗ Failed to post to enrichment service") + print(" Make sure it's running: python3 scripts/serve.py") + return 1 + time.sleep(0.3) + + print() + time.sleep(1) + + # Step 2: Fetch from enrichment service + print("2. Fetching enriched data from enrichment service...") + enriched_data = get_from_enrichment(limit=10) + + if not enriched_data: + print("\n✗ No data in enrichment service") + return 1 + + print(f" Using {len(enriched_data)} most recent records\n") + + # Step 3: Call AI brainstorm with enriched data + print("3. Calling AI layer with enriched telemetry from service...") + if call_brainstorm(enriched_telemetry=enriched_data): + print("\n✅ SUCCESS! Used real enrichment service data") + print("=" * 60) + return 0 + else: + print("\n✗ Failed to generate strategies") + return 1 + +if __name__ == '__main__': + import sys + sys.exit(main()) diff --git a/hpcsim/__pycache__/__init__.cpython-313.pyc b/hpcsim/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63927072978b6891f73f2938486169592991b1e1 GIT binary patch literal 194 zcmey&%ge<81jls0WJm((#~=<2FhUuhK}x1F1T*L}7BQqVYBJs8O3f?EOwPzn%`5TK zWW2>5AD@_$6Cb~l;WJ3fEhqiZ;?$yI{i4j`jMAjs#FUK0GJTi)=0TLgW S85tRGaEo{7HL@460tEoh4K#-U literal 0 HcmV?d00001 diff --git a/hpcsim/__pycache__/adapter.cpython-313.pyc b/hpcsim/__pycache__/adapter.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1094a6d18f91851c6e14f04a88093f12c1f98270 GIT binary patch literal 3427 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 literal 0 HcmV?d00001 diff --git a/hpcsim/__pycache__/enrichment.cpython-313.pyc b/hpcsim/__pycache__/enrichment.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4ecd4cdd233cf9482d820473158d8a167ae4c1e GIT binary patch literal 8159 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