Compare commits

...
2 Commits
Author SHA1 Message Date
Jeroen SchweitzerandClaude Opus 4.5 5ff4ba0a43 fix: environment data parsing for scheduler format
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 1m15s
- Forecast: Check 'daily' key first (scheduler stores count in 'days')
- Sun times: Use ISO fields, calculate daylight from various sources
- Air quality: Support aqi_us/aqi_european and nitrogen_dioxide fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 18:30:30 +01:00
Jeroen SchweitzerandClaude Opus 4.5 bb438e22d6 fix: strip email domain from user identifier for environment endpoint
Build and Push / release (push) Successful in 3s
Build and Push / build (push) Successful in 1m15s
If preferred_username is an email (user@domain.com), extract just the
username part to match Qdrant collection naming (volatile_user).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 15:46:16 +01:00
4 changed files with 63 additions and 13 deletions
+18
View File
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.10.10] - 2026-01-08
### Fixed
- **Environment data parsing** - Fix parsing of scheduler-generated Qdrant data
- Forecast: Check `daily` key first (scheduler stores day count in `days`, list in `daily`)
- Sun times: Use `sunrise_iso`/`sunset_iso` fields, handle time-only format fallback
- Sun times: Calculate daylight from `daylight_duration_seconds` or `daylight_hours`
- Air quality: Support `aqi_us`/`aqi_european` and `nitrogen_dioxide` field names
## [1.10.9] - 2026-01-08
### Fixed
- **Environment user ID cleanup** - Strip email domain from user identifier
- If `preferred_username` is an email, extract just the username part
- Ensures Qdrant collection name matches (e.g., `volatile_jpmschweitzer` not `volatile_jpmschweitzer@gmail.com`)
## [1.10.8] - 2026-01-08
### Fixed
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "core-api"
version = "1.10.8"
version = "1.10.10"
description = "Core Code API - Infrastructure management and tools API"
readme = "README.md"
requires-python = ">=3.12"
+3
View File
@@ -198,6 +198,9 @@ class ToolsController(BaseController):
user_id = "default"
if user:
user_id = user.get("preferred_username") or user.get("sub", "default")
# Strip email domain if present (e.g., "user@example.com" -> "user")
if "@" in user_id:
user_id = user_id.split("@")[0]
logger.info(f"Fetching environment data for user: {user_id}")
result = await self.environment_service.get_current(user_id)
+41 -12
View File
@@ -73,7 +73,15 @@ class EnvironmentService:
# Normalize to list
forecast_list = raw_data
if isinstance(raw_data, dict):
forecast_list = raw_data.get("days") or raw_data.get("forecast") or []
# Check 'daily' first (scheduler format), then 'forecast', then 'days'
# Note: 'days' might be an integer count, so check 'daily' first
forecast_list = raw_data.get("daily") or raw_data.get("forecast")
if forecast_list is None:
days_value = raw_data.get("days")
if isinstance(days_value, list):
forecast_list = days_value
else:
forecast_list = []
if not isinstance(forecast_list, list):
return None
@@ -83,8 +91,8 @@ class EnvironmentService:
if isinstance(day, dict):
days.append(ForecastDay(
date=day.get("date", ""),
high=day.get("high") or day.get("maxtemp") or day.get("temp_max"),
low=day.get("low") or day.get("mintemp") or day.get("temp_min"),
high=day.get("high") or day.get("temp_high") or day.get("maxtemp") or day.get("temp_max"),
low=day.get("low") or day.get("temp_low") or day.get("mintemp") or day.get("temp_min"),
conditions=day.get("conditions") or day.get("weather") or day.get("description"),
precipitation_chance=day.get("precipitation_chance") or day.get("pop") or day.get("precip"),
icon=day.get("icon"),
@@ -106,19 +114,39 @@ class EnvironmentService:
return None
try:
sunrise = raw_data.get("sunrise")
sunset = raw_data.get("sunset")
# Prefer ISO format fields (sunrise_iso, sunset_iso) over time-only fields
sunrise = raw_data.get("sunrise_iso") or raw_data.get("sunrise")
sunset = raw_data.get("sunset_iso") or raw_data.get("sunset")
# Parse datetime strings if needed
if isinstance(sunrise, str):
sunrise = datetime.fromisoformat(sunrise.replace("Z", "+00:00"))
# Handle time-only format (HH:MM) by combining with today's date
if len(sunrise) <= 5 and ":" in sunrise:
today = datetime.now().date()
sunrise = datetime.strptime(f"{today} {sunrise}", "%Y-%m-%d %H:%M")
else:
sunrise = datetime.fromisoformat(sunrise.replace("Z", "+00:00"))
if isinstance(sunset, str):
sunset = datetime.fromisoformat(sunset.replace("Z", "+00:00"))
# Handle time-only format (HH:MM) by combining with today's date
if len(sunset) <= 5 and ":" in sunset:
today = datetime.now().date()
sunset = datetime.strptime(f"{today} {sunset}", "%Y-%m-%d %H:%M")
else:
sunset = datetime.fromisoformat(sunset.replace("Z", "+00:00"))
# Calculate daylight minutes if not provided
# Get daylight from various field names
daylight_minutes = raw_data.get("daylight_minutes") or raw_data.get("daylight")
if daylight_minutes is None and sunrise and sunset:
daylight_minutes = int((sunset - sunrise).total_seconds() / 60)
if daylight_minutes is None:
# Try to calculate from daylight_duration_seconds or daylight_hours
daylight_seconds = raw_data.get("daylight_duration_seconds")
if daylight_seconds:
daylight_minutes = int(daylight_seconds / 60)
else:
daylight_hours = raw_data.get("daylight_hours")
if daylight_hours:
daylight_minutes = int(daylight_hours * 60)
elif sunrise and sunset:
daylight_minutes = int((sunset - sunrise).total_seconds() / 60)
# Parse optional fields
solar_noon = raw_data.get("solar_noon")
@@ -167,7 +195,8 @@ class EnvironmentService:
if not isinstance(raw_data, dict):
return None
aqi = raw_data.get("aqi") or raw_data.get("index")
# Try various AQI field names - prefer US AQI, then European, then generic
aqi = raw_data.get("aqi") or raw_data.get("aqi_us") or raw_data.get("aqi_european") or raw_data.get("index")
if isinstance(aqi, (int, float)):
aqi = int(aqi)
@@ -177,7 +206,7 @@ class EnvironmentService:
pm25=raw_data.get("pm25") or raw_data.get("pm2_5"),
pm10=raw_data.get("pm10"),
o3=raw_data.get("o3") or raw_data.get("ozone"),
no2=raw_data.get("no2"),
no2=raw_data.get("no2") or raw_data.get("nitrogen_dioxide"),
location=raw_data.get("location"),
)