mirror of
https://github.com/ferdzo/iotDashboard.git
synced 2026-04-05 17:16:26 +00:00
Introduced GPT/AI service, added dashboard widgets, drag and drop, export import.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
API_KEY=your_api_key_here
|
||||
PROVIDER_NAME=openai
|
||||
MODEL_NAME=gpt-4.1
|
||||
MODEL_NAME=gpt-4o-mini
|
||||
HOST_URL= http://localhost:8000
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
|
||||
136
services/gpt_service/METRICS_REFERENCE.md
Normal file
136
services/gpt_service/METRICS_REFERENCE.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Environmental Monitoring Standards Reference
|
||||
|
||||
This document provides the industry-standard optimal ranges used by the GPT service for environmental analysis.
|
||||
|
||||
## Supported Metrics
|
||||
|
||||
### Temperature (°C)
|
||||
- **Optimal Range**: 18-24°C
|
||||
- **Comfort Zone**: 20-22°C
|
||||
- **Critical Low**: <15°C
|
||||
- **Critical High**: >28°C
|
||||
- **Standards**: ASHRAE 55, ISO 7730
|
||||
- **Key Concerns**:
|
||||
- Worker comfort and productivity
|
||||
- Equipment operating conditions
|
||||
- Energy efficiency
|
||||
- HVAC system performance
|
||||
|
||||
### Humidity (%)
|
||||
- **Optimal Range**: 30-60%
|
||||
- **Comfort Zone**: 40-50%
|
||||
- **Critical Low**: <20%
|
||||
- **Critical High**: >70%
|
||||
- **Standards**: ASHRAE 55, WHO guidelines
|
||||
- **Key Concerns**:
|
||||
- Mold and mildew growth (>60%)
|
||||
- Static electricity and equipment damage (<30%)
|
||||
- Respiratory health and comfort
|
||||
- Material degradation
|
||||
|
||||
### CO2 (ppm)
|
||||
- **Optimal Range**: 400-1000ppm
|
||||
- **Comfort Zone**: 400-800ppm
|
||||
- **Critical Low**: <350ppm (unusual indoors)
|
||||
- **Critical High**: >1500ppm
|
||||
- **Standards**: ASHRAE 62.1, WHO Air Quality Guidelines
|
||||
- **Key Concerns**:
|
||||
- Air quality and ventilation effectiveness
|
||||
- Cognitive performance (>1000ppm affects decision-making)
|
||||
- Occupant health and alertness
|
||||
- HVAC system efficiency
|
||||
- **Impact**: Studies show 15% decline in cognitive function at 1400ppm
|
||||
|
||||
### Atmospheric Pressure (hPa)
|
||||
- **Optimal Range**: 1013-1023hPa
|
||||
- **Comfort Zone**: 1013-1020hPa
|
||||
- **Critical Low**: <980hPa
|
||||
- **Critical High**: >1050hPa
|
||||
- **Key Concerns**:
|
||||
- Weather changes and ventilation
|
||||
- Building pressurization
|
||||
- Equipment calibration
|
||||
- Occupant comfort
|
||||
|
||||
### Light / Illuminance (lux)
|
||||
- **Optimal Range**: 300-500 lux
|
||||
- **Comfort Zone**: 400-500 lux
|
||||
- **Critical Low**: <200 lux
|
||||
- **Critical High**: >1000 lux
|
||||
- **Standards**: EN 12464-1, IESNA recommendations
|
||||
- **Key Concerns**:
|
||||
- Visual comfort and eye strain
|
||||
- Productivity and task performance
|
||||
- Energy consumption
|
||||
- Circadian rhythm regulation
|
||||
- **Note**: Higher levels (750-1000 lux) for detailed work
|
||||
|
||||
### Noise (dB)
|
||||
- **Optimal Range**: 30-50dB
|
||||
- **Comfort Zone**: 35-45dB
|
||||
- **Critical Low**: <20dB (unusual indoors)
|
||||
- **Critical High**: >70dB
|
||||
- **Standards**: WHO Noise Guidelines, OSHA
|
||||
- **Key Concerns**:
|
||||
- Acoustic comfort and concentration
|
||||
- Speech intelligibility
|
||||
- Stress and productivity impact
|
||||
- Hearing protection requirements (>85dB)
|
||||
- **Impact**: 40-45dB ideal for office work, <35dB for focused tasks
|
||||
|
||||
### PM2.5 - Fine Particulate Matter (µg/m³)
|
||||
- **Optimal Range**: 0-12 µg/m³
|
||||
- **Comfort Zone**: 0-10 µg/m³
|
||||
- **Critical Low**: 0 µg/m³ (best)
|
||||
- **Critical High**: >35 µg/m³
|
||||
- **Standards**: EPA Air Quality Index, WHO guidelines
|
||||
- **Key Concerns**:
|
||||
- Air quality and health risk
|
||||
- Respiratory system impact
|
||||
- Filter maintenance requirements
|
||||
- Outdoor air quality correlation
|
||||
- **Impact**: >35 µg/m³ = Unhealthy for sensitive groups
|
||||
|
||||
### VOC - Volatile Organic Compounds (ppb)
|
||||
- **Optimal Range**: 0-220ppb
|
||||
- **Comfort Zone**: 0-150ppb
|
||||
- **Critical Low**: 0ppb (best)
|
||||
- **Critical High**: >500ppb
|
||||
- **Standards**: Various indoor air quality standards
|
||||
- **Key Concerns**:
|
||||
- Indoor air quality
|
||||
- Off-gassing from materials
|
||||
- Ventilation effectiveness
|
||||
- Occupant health symptoms (headaches, irritation)
|
||||
|
||||
## Analysis Approach
|
||||
|
||||
The GPT service uses these standards to:
|
||||
|
||||
1. **Assess Current Conditions**: Compare measurements against optimal ranges
|
||||
2. **Identify Issues**: Flag deviations with severity levels
|
||||
3. **Provide Context**: Explain health/productivity impacts
|
||||
4. **Recommend Actions**: Suggest specific interventions (HVAC, ventilation, etc.)
|
||||
5. **Predict Trends**: Forecast potential issues based on patterns
|
||||
|
||||
## Multi-Metric Correlation
|
||||
|
||||
When analyzing multiple metrics together, the service looks for:
|
||||
- **HVAC Performance**: Temperature + Humidity + CO2 trends
|
||||
- **Ventilation Effectiveness**: CO2 + PM2.5 + VOC levels
|
||||
- **Occupancy Impact**: CO2 rise + Temperature increase + Humidity changes
|
||||
- **Seasonal Patterns**: Pressure + Temperature + Humidity correlations
|
||||
|
||||
## References
|
||||
|
||||
- ASHRAE Standard 55: Thermal Environmental Conditions for Human Occupancy
|
||||
- ASHRAE Standard 62.1: Ventilation for Acceptable Indoor Air Quality
|
||||
- WHO Air Quality Guidelines
|
||||
- ISO 7730: Ergonomics of the thermal environment
|
||||
- EN 12464-1: Light and lighting of work places
|
||||
- EPA Air Quality Index
|
||||
- OSHA Occupational Noise Exposure Standards
|
||||
|
||||
## Usage in Prompts
|
||||
|
||||
The service automatically includes relevant standards in analysis prompts based on detected metrics. No manual configuration needed - just send your telemetry data!
|
||||
@@ -0,0 +1,240 @@
|
||||
# GPT Service
|
||||
|
||||
FastAPI microservice for AI-powered environmental telemetry analysis using OpenAI GPT models.
|
||||
|
||||
## Purpose
|
||||
|
||||
Provides intelligent analysis of IoT environmental sensor data to ensure optimal working conditions. The service specializes in monitoring indoor environmental quality metrics (temperature, humidity, CO2, etc.) with context-aware insights based on industry standards (ASHRAE, WHO, OSHA).
|
||||
|
||||
**Goal**: Smart IoT dashboard that maintains healthy, comfortable, and productive work environments through AI-driven insights.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **Input**: Telemetry data + device context from Django
|
||||
- **Processing**: OpenAI GPT API with specialized prompts
|
||||
- **Output**: Actionable insights and analysis
|
||||
- **State**: Stateless - no database, pure text processing
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Install dependencies**:
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
2. **Configure environment**:
|
||||
```bash
|
||||
cp .env.sample .env
|
||||
# Edit .env with your OpenAI API key
|
||||
```
|
||||
|
||||
3. **Run the service**:
|
||||
```bash
|
||||
uv run uvicorn main:app --reload --port 8001
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST /analyze
|
||||
|
||||
Analyze telemetry data with AI.
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"telemetry_data": [
|
||||
{
|
||||
"device_id": "abc123",
|
||||
"metric": "temperature",
|
||||
"value": 23.5,
|
||||
"timestamp": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
],
|
||||
"device_info": {
|
||||
"name": "Sensor-01",
|
||||
"location": "Lab A"
|
||||
},
|
||||
"prompt_type": "trend_summary",
|
||||
"custom_prompt": null
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Types**:
|
||||
- `trend_summary`: Environmental quality trends with comfort assessment
|
||||
- `anomaly_detection`: Identify deviations from optimal ranges with severity
|
||||
- `custom`: Use custom_prompt field
|
||||
|
||||
**Supported Metrics** (with industry-standard optimal ranges):
|
||||
- `temperature`: Indoor temperature (18-24°C optimal)
|
||||
- `humidity`: Relative humidity (30-60% optimal)
|
||||
- `co2`: Carbon dioxide concentration (400-1000ppm optimal)
|
||||
- `pressure`: Atmospheric pressure (1013-1023hPa optimal)
|
||||
- `light`: Illuminance level (300-500 lux optimal)
|
||||
- `noise`: Sound level (30-50dB optimal)
|
||||
- `pm25`: Fine particulate matter (0-12 µg/m³ optimal)
|
||||
- `voc`: Volatile organic compounds (0-220ppb optimal)
|
||||
|
||||
Each metric includes:
|
||||
- Optimal and comfort ranges
|
||||
- Critical thresholds
|
||||
- Health/productivity impact assessment
|
||||
- Specific concerns (e.g., mold growth for humidity, cognitive impact for CO2)
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"analysis": "The temperature data shows...",
|
||||
"prompt_type": "trend_summary",
|
||||
"data_points_analyzed": 100
|
||||
}
|
||||
```
|
||||
|
||||
### GET /health
|
||||
|
||||
Health check endpoint.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "gpt_service"
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `API_KEY`: OpenAI API key (required)
|
||||
- `PROVIDER_NAME`: AI provider (default: "openai")
|
||||
- `MODEL_NAME`: OpenAI model (default: "gpt-4o-mini")
|
||||
- `HOST_URL`: Service URL (default: "http://localhost:8001")
|
||||
- `LOG_LEVEL`: Logging level (default: "INFO")
|
||||
|
||||
## Integration with Django
|
||||
|
||||
Django backend should:
|
||||
1. Query telemetry from PostgreSQL/TimescaleDB
|
||||
2. Format data as array of `{device_id, metric, value, timestamp}`
|
||||
3. Add device context in `device_info`
|
||||
4. POST to `/analyze` endpoint
|
||||
5. Return analysis to frontend
|
||||
|
||||
Example Django integration:
|
||||
```python
|
||||
import httpx
|
||||
|
||||
async def get_telemetry_insights(device_id: str, metric: str = None):
|
||||
# Query telemetry
|
||||
telemetry = Telemetry.objects.filter(device_id=device_id)
|
||||
if metric:
|
||||
telemetry = telemetry.filter(metric=metric)
|
||||
|
||||
# Format data
|
||||
data = [
|
||||
{
|
||||
"device_id": t.device_id,
|
||||
"metric": t.metric,
|
||||
"value": t.value,
|
||||
"timestamp": t.timestamp.isoformat()
|
||||
}
|
||||
for t in telemetry[:100] # Limit to last 100 points
|
||||
]
|
||||
|
||||
# Get device info
|
||||
device = Device.objects.get(device_id=device_id)
|
||||
device_info = {
|
||||
"name": device.name,
|
||||
"location": device.location
|
||||
}
|
||||
|
||||
# Call GPT service
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8001/analyze",
|
||||
json={
|
||||
"telemetry_data": data,
|
||||
"device_info": device_info,
|
||||
"prompt_type": "trend_summary"
|
||||
}
|
||||
)
|
||||
return response.json()
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# Test analysis
|
||||
curl -X POST http://localhost:8001/analyze \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"telemetry_data": [
|
||||
{"device_id": "test", "metric": "temperature", "value": 23.5, "timestamp": "2024-01-15T10:30:00Z"}
|
||||
],
|
||||
"prompt_type": "trend_summary"
|
||||
}'
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
- **Stateless design** - No database required
|
||||
- **Metric-aware** - Specialized prompts for 8 environmental metrics
|
||||
- **Standards-based** - Uses ASHRAE, WHO, OSHA guidelines
|
||||
- **Context-rich** - Includes optimal ranges, thresholds, and impact assessments
|
||||
- **Async/await** - Non-blocking OpenAI API calls
|
||||
- **Error handling** - Proper HTTP exceptions with logging
|
||||
- **Type safety** - Pydantic models and type hints throughout
|
||||
|
||||
## Analysis Features
|
||||
|
||||
### Metric-Specific Intelligence
|
||||
|
||||
The service automatically detects which metrics are in your telemetry data and provides specialized analysis:
|
||||
|
||||
**Temperature Analysis**:
|
||||
- Optimal range: 18-24°C (comfort zone: 20-22°C)
|
||||
- Assesses impact on worker productivity and equipment
|
||||
- Identifies HVAC performance issues
|
||||
- Recommends energy efficiency improvements
|
||||
|
||||
**Humidity Analysis**:
|
||||
- Optimal range: 30-60% (comfort zone: 40-50%)
|
||||
- Warns about mold risk (>60%) and static electricity (<30%)
|
||||
- Evaluates respiratory health impact
|
||||
- Suggests ventilation adjustments
|
||||
|
||||
**CO2 Analysis**:
|
||||
- Optimal range: 400-1000ppm (comfort zone: 400-800ppm)
|
||||
- Links high CO2 to cognitive performance decline
|
||||
- Assesses ventilation effectiveness
|
||||
- Recommends occupancy adjustments
|
||||
|
||||
**And more** for pressure, light, noise, PM2.5, and VOC metrics.
|
||||
|
||||
### Analysis Types
|
||||
|
||||
1. **Trend Summary** (`prompt_type: "trend_summary"`):
|
||||
- Overall environmental quality rating
|
||||
- Time spent in optimal vs suboptimal ranges
|
||||
- Daily patterns and correlations
|
||||
- Predictive insights and optimization opportunities
|
||||
|
||||
2. **Anomaly Detection** (`prompt_type: "anomaly_detection"`):
|
||||
- Identifies deviations from optimal ranges
|
||||
- Severity assessment (low/medium/high/critical)
|
||||
- Root cause analysis (HVAC, occupancy, external factors)
|
||||
- Prioritized action items
|
||||
|
||||
3. **Custom Analysis** (`prompt_type: "custom"`):
|
||||
- Use your own prompt
|
||||
- Still includes metric context and standards
|
||||
- Flexible for specific use cases
|
||||
|
||||
## Notes
|
||||
|
||||
- Service is stateless by design
|
||||
- Django provides all data context
|
||||
- No direct database access
|
||||
- Focuses on text transformation only
|
||||
- Aligns with microservices architecture pattern
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from openai import OpenAI
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from config import API_KEY, MODEL_NAME, PROVIDER_NAME, HOST_URL, LOG_LEVEL
|
||||
import logging
|
||||
@@ -20,4 +21,354 @@ class GPTService:
|
||||
self.logger.error(f"Unsupported provider: {self.provider_name}")
|
||||
raise ValueError(f"Unsupported provider: {self.provider_name}")
|
||||
|
||||
|
||||
def _get_metric_specific_context(self, metric: str) -> Dict[str, Any]:
|
||||
"""Get metric-specific optimal ranges and context for environmental monitoring."""
|
||||
|
||||
metric_contexts = {
|
||||
"temperature": {
|
||||
"unit": "°C",
|
||||
"optimal_range": (18, 24),
|
||||
"comfort_range": (20, 22),
|
||||
"critical_low": 15,
|
||||
"critical_high": 28,
|
||||
"context": "indoor environment temperature",
|
||||
"concerns": [
|
||||
"Worker comfort and productivity",
|
||||
"Equipment operating conditions",
|
||||
"Energy efficiency",
|
||||
"HVAC system performance"
|
||||
]
|
||||
},
|
||||
"humidity": {
|
||||
"unit": "%",
|
||||
"optimal_range": (30, 60),
|
||||
"comfort_range": (40, 50),
|
||||
"critical_low": 20,
|
||||
"critical_high": 70,
|
||||
"context": "relative humidity",
|
||||
"concerns": [
|
||||
"Mold and mildew growth (>60%)",
|
||||
"Static electricity and equipment damage (<30%)",
|
||||
"Respiratory health and comfort",
|
||||
"Material degradation"
|
||||
]
|
||||
},
|
||||
"co2": {
|
||||
"unit": "ppm",
|
||||
"optimal_range": (400, 1000),
|
||||
"comfort_range": (400, 800),
|
||||
"critical_low": 350,
|
||||
"critical_high": 1500,
|
||||
"context": "carbon dioxide concentration",
|
||||
"concerns": [
|
||||
"Air quality and ventilation effectiveness",
|
||||
"Cognitive performance (>1000ppm affects decision-making)",
|
||||
"Occupant health and alertness",
|
||||
"HVAC system efficiency"
|
||||
]
|
||||
},
|
||||
"pressure": {
|
||||
"unit": "hPa",
|
||||
"optimal_range": (1013, 1023),
|
||||
"comfort_range": (1013, 1020),
|
||||
"critical_low": 980,
|
||||
"critical_high": 1050,
|
||||
"context": "atmospheric pressure",
|
||||
"concerns": [
|
||||
"Weather changes and ventilation",
|
||||
"Building pressurization",
|
||||
"Equipment calibration",
|
||||
"Occupant comfort"
|
||||
]
|
||||
},
|
||||
"light": {
|
||||
"unit": "lux",
|
||||
"optimal_range": (300, 500),
|
||||
"comfort_range": (400, 500),
|
||||
"critical_low": 200,
|
||||
"critical_high": 1000,
|
||||
"context": "illuminance level",
|
||||
"concerns": [
|
||||
"Visual comfort and eye strain",
|
||||
"Productivity and task performance",
|
||||
"Energy consumption",
|
||||
"Circadian rhythm regulation"
|
||||
]
|
||||
},
|
||||
"noise": {
|
||||
"unit": "dB",
|
||||
"optimal_range": (30, 50),
|
||||
"comfort_range": (35, 45),
|
||||
"critical_low": 20,
|
||||
"critical_high": 70,
|
||||
"context": "noise level",
|
||||
"concerns": [
|
||||
"Acoustic comfort and concentration",
|
||||
"Speech intelligibility",
|
||||
"Stress and productivity impact",
|
||||
"Hearing protection requirements (>85dB)"
|
||||
]
|
||||
},
|
||||
"pm25": {
|
||||
"unit": "µg/m³",
|
||||
"optimal_range": (0, 12),
|
||||
"comfort_range": (0, 10),
|
||||
"critical_low": 0,
|
||||
"critical_high": 35,
|
||||
"context": "fine particulate matter (PM2.5)",
|
||||
"concerns": [
|
||||
"Air quality and health risk",
|
||||
"Respiratory system impact",
|
||||
"Filter maintenance requirements",
|
||||
"Outdoor air quality correlation"
|
||||
]
|
||||
},
|
||||
"voc": {
|
||||
"unit": "ppb",
|
||||
"optimal_range": (0, 220),
|
||||
"comfort_range": (0, 150),
|
||||
"critical_low": 0,
|
||||
"critical_high": 500,
|
||||
"context": "volatile organic compounds",
|
||||
"concerns": [
|
||||
"Indoor air quality",
|
||||
"Off-gassing from materials",
|
||||
"Ventilation effectiveness",
|
||||
"Occupant health symptoms"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# Default for unknown metrics
|
||||
default = {
|
||||
"unit": "",
|
||||
"optimal_range": None,
|
||||
"comfort_range": None,
|
||||
"critical_low": None,
|
||||
"critical_high": None,
|
||||
"context": f"{metric} measurement",
|
||||
"concerns": ["Monitor for unexpected changes", "Verify sensor accuracy"]
|
||||
}
|
||||
|
||||
return metric_contexts.get(metric.lower(), default)
|
||||
|
||||
def _build_prompt(
|
||||
self,
|
||||
telemetry_data: List[Dict[str, Any]],
|
||||
device_info: Dict[str, Any],
|
||||
prompt_type: str,
|
||||
custom_prompt: str | None = None
|
||||
) -> str:
|
||||
"""Build analysis prompt based on type with metric-specific context."""
|
||||
|
||||
# Format telemetry data for prompt
|
||||
data_summary = self._format_telemetry_summary(telemetry_data)
|
||||
device_context = self._format_device_info(device_info)
|
||||
|
||||
# Get metrics present in data
|
||||
metrics_in_data = set(point.get("metric", "").lower() for point in telemetry_data)
|
||||
|
||||
# Build metric-specific context
|
||||
metric_context_lines = []
|
||||
for metric in metrics_in_data:
|
||||
if metric:
|
||||
ctx = self._get_metric_specific_context(metric)
|
||||
metric_context_lines.append(f"\n**{metric.upper()}** ({ctx['context']}):")
|
||||
if ctx['optimal_range']:
|
||||
metric_context_lines.append(f" - Optimal Range: {ctx['optimal_range'][0]}-{ctx['optimal_range'][1]} {ctx['unit']}")
|
||||
if ctx['comfort_range']:
|
||||
metric_context_lines.append(f" - Comfort Zone: {ctx['comfort_range'][0]}-{ctx['comfort_range'][1]} {ctx['unit']}")
|
||||
if ctx['critical_high']:
|
||||
metric_context_lines.append(f" - Critical Thresholds: <{ctx['critical_low']} or >{ctx['critical_high']} {ctx['unit']}")
|
||||
metric_context_lines.append(" - Key Concerns:")
|
||||
for concern in ctx['concerns']:
|
||||
metric_context_lines.append(f" • {concern}")
|
||||
|
||||
metric_context = "\n".join(metric_context_lines) if metric_context_lines else ""
|
||||
|
||||
prompts = {
|
||||
"anomaly_detection": f"""You are an IoT environmental monitoring specialist. Analyze the telemetry data to detect anomalies and issues.
|
||||
|
||||
{device_context}
|
||||
|
||||
Environmental Standards:
|
||||
{metric_context}
|
||||
|
||||
Recent Measurements:
|
||||
{data_summary}
|
||||
|
||||
Respond in this EXACT JSON format (no markdown, just valid JSON):
|
||||
{{
|
||||
"status": "normal|warning|critical",
|
||||
"summary": "Brief 1-2 sentence overview",
|
||||
"anomalies": [
|
||||
{{
|
||||
"metric": "metric name",
|
||||
"severity": "low|medium|high|critical",
|
||||
"description": "What's wrong",
|
||||
"value": "current value",
|
||||
"expected": "expected range"
|
||||
}}
|
||||
],
|
||||
"impacts": ["Impact 1", "Impact 2"],
|
||||
"actions": ["Action 1", "Action 2"],
|
||||
"root_causes": ["Cause 1", "Cause 2"]
|
||||
}}
|
||||
|
||||
Keep summary under 50 words, each item under 20 words.""",
|
||||
|
||||
"trend_summary": f"""You are an IoT environmental monitoring specialist. Analyze the measurement trends and patterns.
|
||||
|
||||
{device_context}
|
||||
|
||||
Environmental Standards:
|
||||
{metric_context}
|
||||
|
||||
Measurement History:
|
||||
{data_summary}
|
||||
|
||||
Respond in this EXACT JSON format (no markdown, just valid JSON):
|
||||
{{
|
||||
"status": "excellent|good|fair|poor",
|
||||
"summary": "Brief 1-2 sentence overview of conditions",
|
||||
"trends": [
|
||||
{{
|
||||
"metric": "metric name",
|
||||
"direction": "improving|stable|degrading",
|
||||
"description": "What's happening"
|
||||
}}
|
||||
],
|
||||
"comfort_score": {{
|
||||
"rating": 85,
|
||||
"description": "Brief assessment"
|
||||
}},
|
||||
"patterns": ["Pattern 1", "Pattern 2"],
|
||||
"recommendations": ["Recommendation 1", "Recommendation 2"],
|
||||
"forecast": "Brief prediction based on trends"
|
||||
}}
|
||||
|
||||
Keep all text concise: summary under 50 words, each item under 20 words.""",
|
||||
|
||||
"custom": custom_prompt or "Analyze the provided telemetry data."
|
||||
}
|
||||
|
||||
base_prompt = prompts.get(prompt_type, prompts["trend_summary"])
|
||||
|
||||
if prompt_type == "custom" and custom_prompt:
|
||||
base_prompt = f"{custom_prompt}\n\n{device_context}\n\n{metric_context}\n\nTelemetry Data:\n{data_summary}"
|
||||
|
||||
return base_prompt
|
||||
|
||||
def _format_telemetry_summary(self, telemetry_data: List[Dict[str, Any]]) -> str:
|
||||
"""Format telemetry data into readable summary with full data points."""
|
||||
if not telemetry_data:
|
||||
return "No data available"
|
||||
|
||||
# Group by metric
|
||||
metrics = {}
|
||||
for point in telemetry_data:
|
||||
metric = point.get("metric", "unknown")
|
||||
if metric not in metrics:
|
||||
metrics[metric] = []
|
||||
metrics[metric].append({
|
||||
"value": point.get("value"),
|
||||
"timestamp": point.get("timestamp"),
|
||||
"device_id": point.get("device_id")
|
||||
})
|
||||
|
||||
# Build summary with full data
|
||||
lines = []
|
||||
for metric, points in metrics.items():
|
||||
values = [p["value"] for p in points if p["value"] is not None]
|
||||
if values:
|
||||
lines.append(f"\n{metric}:")
|
||||
lines.append(f" Summary Statistics:")
|
||||
lines.append(f" - Data points: {len(values)}")
|
||||
lines.append(f" - Min: {min(values):.2f}")
|
||||
lines.append(f" - Max: {max(values):.2f}")
|
||||
lines.append(f" - Average: {sum(values)/len(values):.2f}")
|
||||
lines.append(f" - Latest: {points[-1]['value']:.2f} at {points[-1]['timestamp']}")
|
||||
|
||||
# Include all individual readings for AI analysis
|
||||
lines.append(f" Full Time Series Data:")
|
||||
for point in points:
|
||||
lines.append(f" - {point['timestamp']}: {point['value']:.2f}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_device_info(self, device_info: Dict[str, Any]) -> str:
|
||||
"""Format device information for prompt."""
|
||||
if not device_info:
|
||||
return "Device Context: Not provided"
|
||||
|
||||
lines = ["Device Context:"]
|
||||
for key, value in device_info.items():
|
||||
lines.append(f" - {key}: {value}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
async def analyze(
|
||||
self,
|
||||
telemetry_data: List[Dict[str, Any]],
|
||||
device_info: Dict[str, Any] = None,
|
||||
prompt_type: str = "trend_summary",
|
||||
custom_prompt: str | None = None
|
||||
) -> str:
|
||||
"""
|
||||
Analyze telemetry data using OpenAI GPT model.
|
||||
|
||||
Args:
|
||||
telemetry_data: List of telemetry data points with device_id, metric, value, timestamp
|
||||
device_info: Optional device metadata for context
|
||||
prompt_type: Type of analysis (anomaly_detection, trend_summary, custom)
|
||||
custom_prompt: Custom prompt for 'custom' type
|
||||
|
||||
Returns:
|
||||
Analysis result as string
|
||||
"""
|
||||
try:
|
||||
device_info = device_info or {}
|
||||
|
||||
# Build prompt
|
||||
prompt = self._build_prompt(telemetry_data, device_info, prompt_type, custom_prompt)
|
||||
|
||||
system_prompt = "You are an expert IoT environmental monitoring specialist with deep knowledge of indoor environmental quality standards (ASHRAE, WHO, OSHA guidelines). Your goal is to help maintain optimal working conditions for occupant health, comfort, and productivity. Provide clear, actionable insights with specific metric values and recommendations. Focus on environmental factors that impact human performance and wellbeing."
|
||||
|
||||
# Log the complete prompt for debugging/review
|
||||
self.logger.info("="*80)
|
||||
self.logger.info(f"PROMPT LOGGING - Analysis Type: {prompt_type}")
|
||||
self.logger.info("="*80)
|
||||
self.logger.info("\n[SYSTEM PROMPT]")
|
||||
self.logger.info(system_prompt)
|
||||
self.logger.info("\n" + "-"*80)
|
||||
self.logger.info("[USER PROMPT]")
|
||||
self.logger.info(prompt)
|
||||
self.logger.info("="*80)
|
||||
|
||||
self.logger.info(f"Sending analysis request to {self.model_name}")
|
||||
|
||||
# Call OpenAI API
|
||||
response = self.client.chat.completions.create(
|
||||
model=self.model_name,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_prompt
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": prompt
|
||||
}
|
||||
],
|
||||
temperature=0.7,
|
||||
max_tokens=1500
|
||||
)
|
||||
|
||||
analysis = response.choices[0].message.content
|
||||
self.logger.info(f"Analysis completed successfully. Tokens used: {response.usage.total_tokens}")
|
||||
|
||||
return analysis
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Analysis failed: {str(e)}")
|
||||
raise
|
||||
|
||||
@@ -1,10 +1,93 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Literal
|
||||
from contextlib import asynccontextmanager
|
||||
from gpt_service import GPTService
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
gpt_service = None
|
||||
|
||||
|
||||
def main():
|
||||
GPTService()
|
||||
print("Hello from gpt-service!")
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Lifespan event handler for startup and shutdown."""
|
||||
global gpt_service
|
||||
# Startup
|
||||
logger.info("Initializing GPT Service...")
|
||||
gpt_service = GPTService()
|
||||
logger.info("GPT Service initialized successfully")
|
||||
yield
|
||||
# Shutdown (cleanup if needed)
|
||||
logger.info("Shutting down GPT Service...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
class TelemetryDataPoint(BaseModel):
|
||||
device_id: str
|
||||
metric: str
|
||||
value: float
|
||||
timestamp: str
|
||||
|
||||
class AnalyzeRequest(BaseModel):
|
||||
telemetry_data: List[TelemetryDataPoint] = Field(..., description="Array of telemetry data points")
|
||||
device_info: Dict[str, Any] = Field(default_factory=dict, description="Device metadata")
|
||||
prompt_type: Literal["anomaly_detection", "trend_summary", "custom"] = Field(
|
||||
default="trend_summary",
|
||||
description="Type of analysis to perform"
|
||||
)
|
||||
custom_prompt: str | None = Field(None, description="Custom prompt for 'custom' type")
|
||||
|
||||
class AnalyzeResponse(BaseModel):
|
||||
analysis: str
|
||||
prompt_type: str
|
||||
data_points_analyzed: int
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy", "service": "gpt_service"}
|
||||
|
||||
|
||||
@app.post("/analyze", response_model=AnalyzeResponse)
|
||||
async def analyze_telemetry(request: AnalyzeRequest):
|
||||
"""
|
||||
Analyze telemetry data using GPT model.
|
||||
Receives formatted telemetry data from Django and returns AI insights.
|
||||
"""
|
||||
try:
|
||||
if not gpt_service:
|
||||
raise HTTPException(status_code=503, detail="GPT service not initialized")
|
||||
|
||||
if not request.telemetry_data:
|
||||
raise HTTPException(status_code=400, detail="No telemetry data provided")
|
||||
|
||||
logger.info(f"Analyzing {len(request.telemetry_data)} telemetry points with prompt_type={request.prompt_type}")
|
||||
|
||||
# Convert Pydantic models to dicts for GPTService
|
||||
telemetry_dicts = [point.model_dump() for point in request.telemetry_data]
|
||||
|
||||
# Call GPT service analysis
|
||||
analysis_result = await gpt_service.analyze(
|
||||
telemetry_data=telemetry_dicts,
|
||||
device_info=request.device_info,
|
||||
prompt_type=request.prompt_type,
|
||||
custom_prompt=request.custom_prompt
|
||||
)
|
||||
|
||||
return AnalyzeResponse(
|
||||
analysis=analysis_result,
|
||||
prompt_type=request.prompt_type,
|
||||
data_points_analyzed=len(request.telemetry_data)
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Validation error: {str(e)}")
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Analysis failed: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
||||
@@ -5,6 +5,8 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastapi>=0.121.0",
|
||||
"openai>=2.6.1",
|
||||
"pydantic>=2.12.3",
|
||||
"python-dotenv>=1.2.1",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user