mirror of
https://github.com/ferdzo/iotDashboard.git
synced 2026-04-05 17:16:26 +00:00
Updated Dockerfiles and small changes
This commit is contained in:
7
services/gpt_service/.dockerignore
Normal file
7
services/gpt_service/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
.env*
|
||||
.md
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.log
|
||||
11
services/gpt_service/Dockerfile
Normal file
11
services/gpt_service/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM ghcr.io/astral-sh/uv:python3.13-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY pyproject.toml ./
|
||||
|
||||
RUN uv sync
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uv", "run", "uvicorn","main:app", "--host", "0.0.0.0" ,"--port", "8001"]
|
||||
@@ -1,5 +1,6 @@
|
||||
from openai import OpenAI
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
from config import API_KEY, MODEL_NAME, PROVIDER_NAME, HOST_URL, LOG_LEVEL
|
||||
import logging
|
||||
@@ -372,3 +373,246 @@ Keep all text concise: summary under 50 words, each item under 20 words.""",
|
||||
except Exception as e:
|
||||
self.logger.error(f"Analysis failed: {str(e)}")
|
||||
raise
|
||||
|
||||
async def generate_daily_briefing(
|
||||
self,
|
||||
briefing_type: str,
|
||||
current_time: str,
|
||||
indoor_data: Dict[str, Any] | None = None,
|
||||
outdoor_data: Dict[str, Any] | None = None,
|
||||
health_data: Dict[str, Any] | None = None,
|
||||
calendar_events: List[Dict[str, Any]] | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a daily briefing for office workers.
|
||||
|
||||
Args:
|
||||
briefing_type: 'schedule', 'environment', or 'full'
|
||||
current_time: Current time in ISO format
|
||||
indoor_data: Indoor environment readings (temp, humidity, co2, etc.)
|
||||
outdoor_data: Weather and air quality data
|
||||
health_data: Health/fitness metrics (steps, heart rate, etc.)
|
||||
calendar_events: List of upcoming calendar events
|
||||
|
||||
Returns:
|
||||
Dict with status_line, status_emoji, insights, recommendations, context
|
||||
"""
|
||||
try:
|
||||
# Build context sections
|
||||
context_sections = []
|
||||
|
||||
# Current time context
|
||||
try:
|
||||
dt = datetime.fromisoformat(current_time.replace('Z', '+00:00'))
|
||||
time_str = dt.strftime("%A, %B %d at %I:%M %p")
|
||||
except:
|
||||
time_str = current_time
|
||||
context_sections.append(f"Current Time: {time_str}")
|
||||
|
||||
# Indoor environment
|
||||
if indoor_data:
|
||||
indoor_lines = ["Indoor Environment:"]
|
||||
for key, value in indoor_data.items():
|
||||
if value is not None:
|
||||
indoor_lines.append(f" - {key}: {value}")
|
||||
context_sections.append("\n".join(indoor_lines))
|
||||
|
||||
# Outdoor conditions
|
||||
if outdoor_data:
|
||||
outdoor_lines = ["Outdoor Conditions:"]
|
||||
for key, value in outdoor_data.items():
|
||||
if value is not None:
|
||||
outdoor_lines.append(f" - {key}: {value}")
|
||||
context_sections.append("\n".join(outdoor_lines))
|
||||
|
||||
# Health metrics
|
||||
if health_data:
|
||||
health_lines = ["Health & Activity:"]
|
||||
for key, value in health_data.items():
|
||||
if value is not None:
|
||||
health_lines.append(f" - {key}: {value}")
|
||||
context_sections.append("\n".join(health_lines))
|
||||
|
||||
# Calendar events
|
||||
if calendar_events:
|
||||
cal_lines = ["Upcoming Schedule:"]
|
||||
for event in calendar_events[:10]: # Limit to 10 events
|
||||
start = event.get('start', 'TBD')
|
||||
summary = event.get('summary', 'Untitled')
|
||||
location = event.get('location', '')
|
||||
loc_str = f" at {location}" if location else ""
|
||||
cal_lines.append(f" - {start}: {summary}{loc_str}")
|
||||
context_sections.append("\n".join(cal_lines))
|
||||
|
||||
context = "\n\n".join(context_sections)
|
||||
|
||||
# Build briefing-specific prompts
|
||||
prompts = {
|
||||
"schedule": f"""You are a smart wellness coach for office workers (software engineers, tech/finance professionals).
|
||||
Generate a Schedule Briefing focused on calendar and activity optimization.
|
||||
|
||||
{context}
|
||||
|
||||
Environmental thresholds for reference:
|
||||
- CO2 > 1000ppm: cognitive performance drops, ventilate before focused work
|
||||
- PM2.5 > 35: poor outdoor air, avoid outdoor activities
|
||||
- Temperature 20-24°C: optimal for focus work
|
||||
- Steps goal: 10,000/day, take breaks every 2 hours
|
||||
|
||||
Respond in this EXACT JSON format (no markdown, just valid JSON):
|
||||
{{
|
||||
"status_emoji": "🟢|🟡|🔴",
|
||||
"status_line": "One sentence summary of schedule/activity outlook",
|
||||
"insights": [
|
||||
"Actionable insight tied to specific time or event",
|
||||
"Another insight about activity or scheduling",
|
||||
"Insight about outdoor activity timing based on conditions"
|
||||
],
|
||||
"recommendations": [
|
||||
"Primary recommendation with specific timing",
|
||||
"Secondary recommendation"
|
||||
]
|
||||
}}
|
||||
|
||||
Guidelines:
|
||||
- Tie insights to SPECIFIC upcoming events or time slots
|
||||
- Suggest activity breaks during schedule gaps
|
||||
- Recommend outdoor activity timing based on air quality
|
||||
- Keep each insight under 25 words
|
||||
- Be specific about times (e.g., "before your 2pm meeting" not "later today")
|
||||
- Focus on productivity, movement, and work-life balance""",
|
||||
|
||||
"environment": f"""You are a smart wellness coach for office workers (software engineers, tech/finance professionals).
|
||||
Generate an Environment Briefing focused on indoor workspace conditions.
|
||||
|
||||
{context}
|
||||
|
||||
Environmental thresholds and their impacts:
|
||||
- CO2 400-800ppm: optimal | 800-1000ppm: acceptable | >1000ppm: ventilate (affects cognition)
|
||||
- Temperature 20-22°C: optimal | 18-24°C: acceptable | outside: adjust
|
||||
- Humidity 40-60%: optimal | <30%: too dry | >70%: too humid
|
||||
- PM2.5 0-12: good | 12-35: moderate | >35: unhealthy
|
||||
- Light 400-500 lux: optimal for desk work
|
||||
|
||||
Respond in this EXACT JSON format (no markdown, just valid JSON):
|
||||
{{
|
||||
"status_emoji": "🟢|🟡|🔴",
|
||||
"status_line": "One sentence summary of workspace conditions",
|
||||
"insights": [
|
||||
"Insight about most important environmental factor with action",
|
||||
"Insight about another condition affecting work",
|
||||
"Insight about ventilation or air quality"
|
||||
],
|
||||
"recommendations": [
|
||||
"Primary action to improve workspace",
|
||||
"Secondary recommendation"
|
||||
]
|
||||
}}
|
||||
|
||||
Guidelines:
|
||||
- If CO2 is high, mention opening windows BEFORE the next meeting/focused work
|
||||
- Tie environmental actions to upcoming activities when possible
|
||||
- Be specific about what to do (e.g., "open window" not "improve ventilation")
|
||||
- Mention how conditions affect cognitive performance
|
||||
- Keep each insight under 25 words""",
|
||||
|
||||
"full": f"""You are a smart wellness coach for office workers (software engineers, tech/finance professionals).
|
||||
Generate a comprehensive Daily Briefing combining schedule, environment, and health.
|
||||
|
||||
{context}
|
||||
|
||||
Environmental thresholds:
|
||||
- CO2 > 1000ppm: cognitive performance drops
|
||||
- PM2.5 > 35: poor outdoor air quality
|
||||
- Temperature 20-24°C: optimal for focus
|
||||
|
||||
Health targets:
|
||||
- 10,000 steps/day
|
||||
- Movement break every 2 hours
|
||||
- Heart rate zones for activity planning
|
||||
|
||||
Respond in this EXACT JSON format (no markdown, just valid JSON):
|
||||
{{
|
||||
"status_emoji": "🟢|🟡|🔴",
|
||||
"status_line": "One sentence overall summary",
|
||||
"insights": [
|
||||
"Most important cross-domain insight (environment + schedule)",
|
||||
"Health/activity insight tied to schedule",
|
||||
"Environmental condition affecting upcoming work",
|
||||
"Outdoor activity timing recommendation"
|
||||
],
|
||||
"recommendations": [
|
||||
"Primary recommendation combining multiple factors",
|
||||
"Secondary recommendation"
|
||||
]
|
||||
}}
|
||||
|
||||
Guidelines:
|
||||
- Connect environment to schedule (e.g., "ventilate before your debugging session")
|
||||
- Suggest activity timing based on air quality AND schedule gaps
|
||||
- Prioritize actionable insights over status reports
|
||||
- Be specific about times and actions
|
||||
- Keep each insight under 25 words"""
|
||||
}
|
||||
|
||||
prompt = prompts.get(briefing_type, prompts["full"])
|
||||
|
||||
system_prompt = """You are an expert wellness coach for knowledge workers. You help office workers (especially software engineers and tech/finance professionals) maintain optimal productivity and health by providing actionable, timing-specific advice.
|
||||
|
||||
Your insights should:
|
||||
1. Be tied to specific times or upcoming events
|
||||
2. Explain WHY something matters (e.g., "CO2 affects focus")
|
||||
3. Give specific actions (e.g., "open the window now" not "improve air")
|
||||
4. Consider work patterns (deep work, meetings, breaks)
|
||||
|
||||
Always respond with valid JSON only, no markdown formatting."""
|
||||
|
||||
self.logger.info(f"Generating {briefing_type} briefing")
|
||||
|
||||
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=1000
|
||||
)
|
||||
|
||||
result_text = response.choices[0].message.content
|
||||
self.logger.info(f"Briefing generated. Tokens used: {response.usage.total_tokens}")
|
||||
|
||||
# Parse JSON response
|
||||
import json
|
||||
# Clean potential markdown wrapping
|
||||
if result_text.startswith("```"):
|
||||
result_text = result_text.split("```")[1]
|
||||
if result_text.startswith("json"):
|
||||
result_text = result_text[4:]
|
||||
result_text = result_text.strip()
|
||||
|
||||
result = json.loads(result_text)
|
||||
|
||||
return {
|
||||
"status_emoji": result.get("status_emoji", "🟢"),
|
||||
"status_line": result.get("status_line", "Briefing generated"),
|
||||
"insights": result.get("insights", []),
|
||||
"recommendations": result.get("recommendations", []),
|
||||
"briefing_type": briefing_type,
|
||||
"generated_at": current_time,
|
||||
}
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
self.logger.error(f"Failed to parse briefing JSON: {e}")
|
||||
self.logger.error(f"Raw response: {result_text}")
|
||||
return {
|
||||
"status_emoji": "⚠️",
|
||||
"status_line": "Briefing generated with formatting issues",
|
||||
"insights": [result_text[:200] if result_text else "Unable to generate insights"],
|
||||
"recommendations": [],
|
||||
"briefing_type": briefing_type,
|
||||
"generated_at": current_time,
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Briefing generation failed: {str(e)}")
|
||||
raise
|
||||
|
||||
@@ -48,6 +48,34 @@ class AnalyzeResponse(BaseModel):
|
||||
data_points_analyzed: int
|
||||
|
||||
|
||||
class CalendarEvent(BaseModel):
|
||||
summary: str
|
||||
start: str
|
||||
end: str | None = None
|
||||
location: str | None = None
|
||||
|
||||
|
||||
class DailyBriefingRequest(BaseModel):
|
||||
briefing_type: Literal["schedule", "environment", "full"] = Field(
|
||||
default="full",
|
||||
description="Type of briefing to generate"
|
||||
)
|
||||
current_time: str = Field(..., description="Current time in ISO format")
|
||||
indoor_data: Dict[str, Any] | None = Field(None, description="Indoor environment readings")
|
||||
outdoor_data: Dict[str, Any] | None = Field(None, description="Weather and air quality data")
|
||||
health_data: Dict[str, Any] | None = Field(None, description="Health/fitness metrics")
|
||||
calendar_events: List[CalendarEvent] | None = Field(None, description="Upcoming calendar events")
|
||||
|
||||
|
||||
class DailyBriefingResponse(BaseModel):
|
||||
status_emoji: str
|
||||
status_line: str
|
||||
insights: List[str]
|
||||
recommendations: List[str]
|
||||
briefing_type: str
|
||||
generated_at: str
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
return {"status": "healthy", "service": "gpt_service"}
|
||||
@@ -90,4 +118,40 @@ async def analyze_telemetry(request: AnalyzeRequest):
|
||||
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)}")
|
||||
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
||||
|
||||
|
||||
@app.post("/daily-briefing", response_model=DailyBriefingResponse)
|
||||
async def generate_daily_briefing(request: DailyBriefingRequest):
|
||||
"""
|
||||
Generate a daily briefing for office workers.
|
||||
Combines environment, schedule, and health data into actionable insights.
|
||||
"""
|
||||
try:
|
||||
if not gpt_service:
|
||||
raise HTTPException(status_code=503, detail="GPT service not initialized")
|
||||
|
||||
logger.info(f"Generating {request.briefing_type} briefing")
|
||||
|
||||
# Convert calendar events to dicts
|
||||
calendar_events = None
|
||||
if request.calendar_events:
|
||||
calendar_events = [event.model_dump() for event in request.calendar_events]
|
||||
|
||||
result = await gpt_service.generate_daily_briefing(
|
||||
briefing_type=request.briefing_type,
|
||||
current_time=request.current_time,
|
||||
indoor_data=request.indoor_data,
|
||||
outdoor_data=request.outdoor_data,
|
||||
health_data=request.health_data,
|
||||
calendar_events=calendar_events,
|
||||
)
|
||||
|
||||
return DailyBriefingResponse(**result)
|
||||
|
||||
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"Briefing generation failed: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Briefing generation failed: {str(e)}")
|
||||
@@ -8,5 +8,6 @@ dependencies = [
|
||||
"fastapi>=0.121.0",
|
||||
"openai>=2.6.1",
|
||||
"pydantic>=2.12.3",
|
||||
"uvicorn[standard]>=0.24.0",
|
||||
"python-dotenv>=1.2.1",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user