Initial commit: Complete Paw Burn Risk Assessment Tool with WeatherAPI integration, comprehensive risk scoring, SQLite storage, rich visualizations, configurable parameters, and full documentation with examples

This commit is contained in:
Chris Sewell
2025-06-07 15:42:40 -04:00
commit fcb902dea6
14 changed files with 1979 additions and 0 deletions

258
models.py Normal file
View File

@ -0,0 +1,258 @@
"""Data models for weather and risk assessment."""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, List
import sqlite3
import json
import logging
logger = logging.getLogger(__name__)
@dataclass
class WeatherHour:
"""Represents weather data for a single hour."""
datetime: datetime
temperature_f: float
uv_index: Optional[float]
condition: str
is_forecast: bool = False
def to_dict(self) -> dict:
"""Convert to dictionary for JSON serialization."""
return {
'datetime': self.datetime.isoformat(),
'temperature_f': self.temperature_f,
'uv_index': self.uv_index,
'condition': self.condition,
'is_forecast': self.is_forecast
}
@classmethod
def from_dict(cls, data: dict) -> 'WeatherHour':
"""Create from dictionary."""
return cls(
datetime=datetime.fromisoformat(data['datetime']),
temperature_f=data['temperature_f'],
uv_index=data.get('uv_index'),
condition=data['condition'],
is_forecast=data.get('is_forecast', False)
)
@dataclass
class RiskScore:
"""Represents a risk score for a specific hour."""
datetime: datetime
temperature_score: float
uv_score: float
condition_score: float
accumulated_heat_score: float
surface_recovery_score: float
total_score: float
recommend_shoes: bool
def to_dict(self) -> dict:
"""Convert to dictionary for JSON serialization."""
return {
'datetime': self.datetime.isoformat(),
'temperature_score': self.temperature_score,
'uv_score': self.uv_score,
'condition_score': self.condition_score,
'accumulated_heat_score': self.accumulated_heat_score,
'surface_recovery_score': self.surface_recovery_score,
'total_score': self.total_score,
'recommend_shoes': self.recommend_shoes
}
@classmethod
def from_dict(cls, data: dict) -> 'RiskScore':
"""Create from dictionary."""
return cls(
datetime=datetime.fromisoformat(data['datetime']),
temperature_score=data['temperature_score'],
uv_score=data['uv_score'],
condition_score=data['condition_score'],
accumulated_heat_score=data['accumulated_heat_score'],
surface_recovery_score=data['surface_recovery_score'],
total_score=data['total_score'],
recommend_shoes=data['recommend_shoes']
)
class DatabaseManager:
"""Manages database operations for weather and risk data."""
def __init__(self, db_path: str):
self.db_path = db_path
self.init_database()
def init_database(self):
"""Initialize database tables."""
conn = sqlite3.connect(self.db_path)
try:
cursor = conn.cursor()
# Weather data table
cursor.execute('''
CREATE TABLE IF NOT EXISTS weather_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT UNIQUE,
temperature_f REAL,
uv_index REAL,
condition TEXT,
is_forecast BOOLEAN,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Risk scores table
cursor.execute('''
CREATE TABLE IF NOT EXISTS risk_scores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT UNIQUE,
temperature_score REAL,
uv_score REAL,
condition_score REAL,
accumulated_heat_score REAL,
surface_recovery_score REAL,
total_score REAL,
recommend_shoes BOOLEAN,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Create indexes for better performance
cursor.execute('CREATE INDEX IF NOT EXISTS idx_weather_datetime ON weather_data(datetime)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_risk_datetime ON risk_scores(datetime)')
conn.commit()
logger.info("Database initialized successfully")
except Exception as e:
logger.error(f"Error initializing database: {e}")
raise
finally:
conn.close()
def save_weather_data(self, weather_hours: List[WeatherHour]):
"""Save weather data to database."""
conn = sqlite3.connect(self.db_path)
try:
cursor = conn.cursor()
for hour in weather_hours:
cursor.execute('''
INSERT OR REPLACE INTO weather_data
(datetime, temperature_f, uv_index, condition, is_forecast)
VALUES (?, ?, ?, ?, ?)
''', (
hour.datetime.isoformat(),
hour.temperature_f,
hour.uv_index,
hour.condition,
hour.is_forecast
))
conn.commit()
logger.info(f"Saved {len(weather_hours)} weather records")
except Exception as e:
logger.error(f"Error saving weather data: {e}")
raise
finally:
conn.close()
def save_risk_scores(self, risk_scores: List[RiskScore]):
"""Save risk scores to database."""
conn = sqlite3.connect(self.db_path)
try:
cursor = conn.cursor()
for score in risk_scores:
cursor.execute('''
INSERT OR REPLACE INTO risk_scores
(datetime, temperature_score, uv_score, condition_score,
accumulated_heat_score, surface_recovery_score, total_score, recommend_shoes)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
score.datetime.isoformat(),
score.temperature_score,
score.uv_score,
score.condition_score,
score.accumulated_heat_score,
score.surface_recovery_score,
score.total_score,
score.recommend_shoes
))
conn.commit()
logger.info(f"Saved {len(risk_scores)} risk score records")
except Exception as e:
logger.error(f"Error saving risk scores: {e}")
raise
finally:
conn.close()
def get_weather_data(self, start_date: datetime, end_date: datetime) -> List[WeatherHour]:
"""Retrieve weather data for a date range."""
conn = sqlite3.connect(self.db_path)
try:
cursor = conn.cursor()
cursor.execute('''
SELECT datetime, temperature_f, uv_index, condition, is_forecast
FROM weather_data
WHERE datetime BETWEEN ? AND ?
ORDER BY datetime
''', (start_date.isoformat(), end_date.isoformat()))
results = []
for row in cursor.fetchall():
results.append(WeatherHour(
datetime=datetime.fromisoformat(row[0]),
temperature_f=row[1],
uv_index=row[2],
condition=row[3],
is_forecast=bool(row[4])
))
return results
except Exception as e:
logger.error(f"Error retrieving weather data: {e}")
raise
finally:
conn.close()
def get_risk_scores(self, start_date: datetime, end_date: datetime) -> List[RiskScore]:
"""Retrieve risk scores for a date range."""
conn = sqlite3.connect(self.db_path)
try:
cursor = conn.cursor()
cursor.execute('''
SELECT datetime, temperature_score, uv_score, condition_score,
accumulated_heat_score, surface_recovery_score, total_score, recommend_shoes
FROM risk_scores
WHERE datetime BETWEEN ? AND ?
ORDER BY datetime
''', (start_date.isoformat(), end_date.isoformat()))
results = []
for row in cursor.fetchall():
results.append(RiskScore(
datetime=datetime.fromisoformat(row[0]),
temperature_score=row[1],
uv_score=row[2],
condition_score=row[3],
accumulated_heat_score=row[4],
surface_recovery_score=row[5],
total_score=row[6],
recommend_shoes=bool(row[7])
))
return results
except Exception as e:
logger.error(f"Error retrieving risk scores: {e}")
raise
finally:
conn.close()