Add 12/24 hour time format option and clean up test files

This commit is contained in:
Chris Sewell
2025-06-13 18:14:15 -04:00
parent a2c25e603a
commit 8e62d94721
8 changed files with 61 additions and 365 deletions

View File

@ -0,0 +1,8 @@
---
description: ByteRover MCP Custom Instructions
alwaysApply: true
---
# important
always use search-memories tool to get the related context before any tasks
always use create-memories to store all the critical informations after sucessfull tasks

View File

@ -30,6 +30,9 @@ class RiskConfig:
enable_graduated_recovery: bool = True
enable_time_of_day_factor: bool = True
# Display preferences
use_24hr_time: bool = False # False for 12hr time format, True for 24hr
@classmethod
def from_env(cls) -> 'RiskConfig':
"""Create configuration from environment variables."""
@ -48,7 +51,9 @@ class RiskConfig:
surface_type=os.getenv('SURFACE_TYPE', 'asphalt'),
surface_max_recovery_score=float(os.getenv('SURFACE_MAX_RECOVERY_SCORE', 2.0)),
enable_graduated_recovery=os.getenv('ENABLE_GRADUATED_RECOVERY', 'true').lower() == 'true',
enable_time_of_day_factor=os.getenv('ENABLE_TIME_OF_DAY_FACTOR', 'true').lower() == 'true'
enable_time_of_day_factor=os.getenv('ENABLE_TIME_OF_DAY_FACTOR', 'true').lower() == 'true',
# Display preferences
use_24hr_time=os.getenv('USE_24HR_TIME', 'false').lower() == 'true'
)
@dataclass

View File

@ -33,3 +33,6 @@ SURFACE_TYPE=asphalt
SURFACE_MAX_RECOVERY_SCORE=2.0
ENABLE_GRADUATED_RECOVERY=true
ENABLE_TIME_OF_DAY_FACTOR=true
# Display preferences (Optional)
USE_24HR_TIME=false

14
main.py
View File

@ -71,6 +71,13 @@ class PawRiskApp:
logger.error(f"Error during analysis: {e}")
return {"error": str(e)}
def format_time(self, dt: datetime) -> str:
"""Format time based on user's preference."""
if self.config.risk_config.use_24hr_time:
return dt.strftime("%H:%M")
else:
return dt.strftime("%I:%M %p")
def print_summary(self, analysis_result: dict):
"""Print a formatted summary of the analysis."""
if "error" in analysis_result:
@ -117,11 +124,11 @@ class PawRiskApp:
print("\n🕐 HOURLY BREAKDOWN:")
print("-" * 80)
print(f"{'Time':>6} {'Temp':>6} {'UV':>4} {'Condition':>12} {'Risk':>6} {'Shoes':>7}")
print(f"{'Time':>8} {'Temp':>6} {'UV':>4} {'Condition':>12} {'Risk':>6} {'Shoes':>7}")
print("-" * 80)
for weather, risk in zip(weather_hours, risk_scores):
time_str = weather.datetime.strftime("%H:%M")
time_str = self.format_time(weather.datetime)
temp_str = f"{weather.temperature_f:.0f}°F"
uv_str = f"{weather.uv_index:.1f}" if weather.uv_index else "N/A"
condition_short = weather.condition[:12]
@ -129,7 +136,7 @@ class PawRiskApp:
shoes_str = "YES" if risk.recommend_shoes else "no"
shoes_color = "⚠️ " if risk.recommend_shoes else ""
print(f"{time_str:>6} {temp_str:>6} {uv_str:>4} {condition_short:>12} "
print(f"{time_str:>8} {temp_str:>6} {uv_str:>4} {condition_short:>12} "
f"{risk_str:>6} {shoes_color}{shoes_str:>5}")
def create_plots(self, analysis_result: dict, save_plots: bool = False):
@ -207,6 +214,7 @@ def main():
print(f"Default Location: {config.default_location}")
print(f"Database Path: {config.database_path}")
print(f"Risk Threshold: {config.risk_config.risk_threshold_shoes}")
print(f"Time Format: {'24-hour' if config.risk_config.use_24hr_time else '12-hour'}")
return
except Exception as e:
print(f"❌ Configuration error: {e}")

View File

@ -10,6 +10,7 @@ import warnings
import os
import shutil
from models import WeatherHour, RiskScore
from config import RiskConfig, get_config
# Suppress matplotlib UserWarnings and macOS GUI warnings
warnings.filterwarnings('ignore', category=UserWarning, module='matplotlib')
@ -18,16 +19,30 @@ warnings.filterwarnings('ignore', message='.*NSSavePanel.*')
class RiskPlotter:
"""Handles plotting and visualization of risk data."""
def __init__(self, figure_size: Tuple[int, int] = (12, 8)):
def __init__(self, figure_size: Tuple[int, int] = (12, 8), use_24hr_time: Optional[bool] = None):
self.figure_size = figure_size
self.plots_dir = "plots"
self._plots_dir_setup = False
# Get time format preference from config if not specified
if use_24hr_time is None:
config = get_config().risk_config
self.use_24hr_time = getattr(config, 'use_24hr_time', False)
else:
self.use_24hr_time = use_24hr_time
# Set up matplotlib style and suppress warnings
plt.style.use('default')
plt.rcParams['figure.figsize'] = figure_size
plt.rcParams['font.size'] = 10
def get_time_formatter(self):
"""Get the appropriate time formatter based on config."""
if self.use_24hr_time:
return mdates.DateFormatter('%H:%M')
else:
return mdates.DateFormatter('%I:%M %p')
def _setup_plots_directory(self):
"""Create and clear plots directory."""
if os.path.exists(self.plots_dir):
@ -116,8 +131,9 @@ class RiskPlotter:
ax3.grid(True, alpha=0.3)
# Format x-axis
time_formatter = self.get_time_formatter()
for ax in [ax1, ax2, ax3]:
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
ax.xaxis.set_major_formatter(time_formatter)
ax.xaxis.set_major_locator(mdates.HourLocator(interval=2))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
@ -166,7 +182,8 @@ class RiskPlotter:
ax.grid(True, alpha=0.3)
# Format x-axis
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
time_formatter = self.get_time_formatter()
ax.xaxis.set_major_formatter(time_formatter)
ax.xaxis.set_major_locator(mdates.HourLocator(interval=2))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
@ -334,8 +351,9 @@ class RiskPlotter:
ax4.set_title('Weather Conditions')
# Format x-axis for all subplots
time_formatter = self.get_time_formatter()
for ax in [ax1, ax4]:
ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
ax.xaxis.set_major_formatter(time_formatter)
ax.xaxis.set_major_locator(mdates.HourLocator(interval=2))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)

View File

@ -303,6 +303,13 @@ class RiskCalculator:
return blocks
def format_time(self, dt: datetime) -> str:
"""Format time based on user's preference."""
if hasattr(self.config, 'use_24hr_time') and self.config.use_24hr_time:
return dt.strftime("%H:%M")
else:
return dt.strftime("%I:%M %p")
def generate_recommendations(self, risk_scores: List[RiskScore]) -> dict:
"""Generate comprehensive recommendations based on risk scores."""
if not risk_scores:
@ -326,13 +333,13 @@ class RiskCalculator:
"high_risk_hours": high_risk_hours,
"max_risk_score": round(max_score, 1),
"average_risk_score": round(avg_score, 1),
"peak_risk_time": peak_score.datetime.strftime("%H:%M"),
"peak_risk_time": self.format_time(peak_score.datetime),
"continuous_risk_blocks": len(risk_blocks)
},
"risk_periods": [
{
"start": start.strftime("%H:%M"),
"end": end.strftime("%H:%M"),
"start": self.format_time(start),
"end": self.format_time(end),
"duration_hours": round((end - start).total_seconds() / 3600, 1)
}
for start, end in risk_blocks

View File

@ -1,154 +0,0 @@
#!/usr/bin/env python3
"""
Test script for enhanced surface recovery logic with real data
"""
import os
import logging
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from models import WeatherHour, RiskScore
from config import RiskConfig, AppConfig, get_config
from risk_calculator import RiskCalculator
from constants import SURFACE_COOLING_COEFFICIENTS
from models import DatabaseManager
# Set up logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def load_db_data():
"""Load real weather data from the database."""
config = get_config()
db_manager = DatabaseManager(config.database_path)
# Get data from the past 24 hours
end_date = datetime.now()
start_date = end_date - timedelta(hours=24)
try:
return db_manager.get_weather_data(start_date, end_date)
except Exception as e:
logger.error(f"Error loading data from database: {e}")
return []
def compare_recovery_settings(weather_data):
"""Compare different recovery settings with real weather data."""
if not weather_data:
logger.error("No weather data available for testing")
return
print(f"Loaded {len(weather_data)} hours of weather data from database")
print(f"Temperature range: {min(h.temperature_f for h in weather_data):.1f}°F - {max(h.temperature_f for h in weather_data):.1f}°F")
# Test configs
configs = [
# Default settings
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=False,
enable_time_of_day_factor=False
),
# All enhanced features
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=True,
enable_time_of_day_factor=True,
surface_max_recovery_score=2.0,
surface_type="asphalt"
),
]
# Test each configuration
all_results = []
for i, config in enumerate(configs):
calculator = RiskCalculator(config)
risk_scores = calculator.calculate_risk_scores(weather_data)
all_results.append(risk_scores)
label = "Default" if i == 0 else "Enhanced"
print(f"\n=== {label} Recovery Settings ===")
high_risk = [s for s in risk_scores if s.recommend_shoes]
print(f"High Risk Hours: {len(high_risk)} out of {len(risk_scores)}")
if high_risk:
times = [s.datetime.strftime("%H:%M") for s in high_risk]
print(f"High Risk Times: {', '.join(times)}")
# Show recovery score impact
recovery_impact = sum(abs(s.surface_recovery_score) for s in risk_scores)
print(f"Total Recovery Impact: {recovery_impact:.2f}")
print(f"Average Recovery Score: {recovery_impact/len(risk_scores):.2f}")
# Visualize the results
visualize_comparison(weather_data, all_results)
def visualize_comparison(weather_data, result_sets):
"""Create visualization comparing different recovery strategies with real data."""
plt.figure(figsize=(14, 10))
# Extract time and temperatures for plotting
times = [hour.datetime for hour in weather_data]
temps = [hour.temperature_f for hour in weather_data]
# Plot temperature
ax1 = plt.subplot(3, 1, 1)
ax1.plot(times, temps, 'r-', linewidth=2)
ax1.set_ylabel('Temperature (°F)')
ax1.set_title('Temperature Profile')
ax1.axhline(y=90, color='r', linestyle='--', alpha=0.7)
ax1.text(times[0], 91, "Recovery Threshold (90°F)", color='r')
ax1.grid(True, alpha=0.3)
# Plot recovery scores
ax2 = plt.subplot(3, 1, 2, sharex=ax1)
# Add labels for legend
labels = ['Default Recovery', 'Enhanced Recovery']
linestyles = ['-', '--']
colors = ['blue', 'green']
for i, results in enumerate(result_sets):
recovery_scores = [score.surface_recovery_score for score in results]
ax2.plot(times, recovery_scores, linestyle=linestyles[i], color=colors[i], linewidth=2, label=labels[i])
ax2.set_ylabel('Recovery Score')
ax2.set_title('Surface Recovery Scores Comparison')
ax2.grid(True, alpha=0.3)
ax2.legend()
# Plot total risk scores
ax3 = plt.subplot(3, 1, 3, sharex=ax1)
for i, results in enumerate(result_sets):
total_scores = [score.total_score for score in results]
ax3.plot(times, total_scores, linestyle=linestyles[i], color=colors[i], linewidth=2, label=labels[i])
# Add threshold line
ax3.axhline(y=6.0, color='red', linestyle='--', alpha=0.7, label='Shoe Threshold (6.0)')
ax3.set_ylabel('Total Risk Score')
ax3.set_title('Total Risk Score Comparison (Real Weather Data)')
ax3.set_xlabel('Time')
ax3.grid(True, alpha=0.3)
ax3.legend()
# Format x-axis
for ax in [ax1, ax2, ax3]:
ax.set_xlim(times[0], times[-1])
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
plt.tight_layout()
plt.savefig('real_data_comparison.png')
plt.show()
if __name__ == "__main__":
print("Testing enhanced surface recovery logic with real data")
weather_data = load_db_data()
if weather_data:
compare_recovery_settings(weather_data)
else:
print("No data available. Please ensure you've run the app to collect weather data first.")

View File

@ -1,199 +0,0 @@
#!/usr/bin/env python3
"""
Test script for enhanced surface recovery logic
"""
import os
import logging
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from models import WeatherHour
from config import RiskConfig
from risk_calculator import RiskCalculator
from constants import SURFACE_COOLING_COEFFICIENTS
# Set up logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def create_test_data():
"""Create synthetic test data with a temperature peak followed by cooling."""
now = datetime.now()
hours = []
# Create a 24-hour dataset
for i in range(24):
# Time starting from 24 hours ago to now
time = now - timedelta(hours=24-i)
# Temperature pattern: starts low, peaks in afternoon, drops at night
if i < 8: # Early morning (cold)
temp = 75.0 + i * 1.5
condition = "Clear"
uv = 0.0
elif i < 14: # Mid-day heating (peak at 2pm / hour 14)
temp = 85.0 + (i - 8) * 3.0
condition = "Sunny"
uv = 8.0
else: # Afternoon cooling
temp = 95.0 - (i - 14) * 2.0
condition = "Partly Cloudy" if i < 18 else "Clear"
uv = max(0.0, 8.0 - (i - 14) * 1.5)
# Create a peak temperature that exceeds threshold
if i == 13: # 1pm
temp = 95.0 # Peak temperature
hours.append(WeatherHour(
datetime=time,
temperature_f=temp,
uv_index=uv,
condition=condition,
is_forecast=False
))
return hours
def visualize_results(test_data, all_results):
"""Create visualization comparing different recovery strategies."""
plt.figure(figsize=(14, 10))
# Extract time and temperatures for plotting
times = [hour.datetime for hour in test_data]
temps = [hour.temperature_f for hour in test_data]
# Plot temperature
ax1 = plt.subplot(3, 1, 1)
ax1.plot(times, temps, 'r-', linewidth=2)
ax1.set_ylabel('Temperature (°F)')
ax1.set_title('Temperature Profile')
ax1.axhline(y=90, color='r', linestyle='--', alpha=0.7)
ax1.text(times[0], 91, "Recovery Threshold (90°F)", color='r')
ax1.grid(True, alpha=0.3)
# Plot recovery scores
ax2 = plt.subplot(3, 1, 2, sharex=ax1)
# Add labels for legend
labels = ['Default', 'Graduated', 'Time-of-day', 'All Features', 'Concrete', 'Grass']
linestyles = ['-', '--', ':', '-.', '--', ':']
colors = ['blue', 'green', 'purple', 'orange', 'brown', 'magenta']
for i, results in enumerate(all_results):
recovery_scores = [score.surface_recovery_score for score in results]
ax2.plot(times, recovery_scores, linestyle=linestyles[i], color=colors[i], linewidth=2, label=labels[i])
ax2.set_ylabel('Recovery Score')
ax2.set_title('Surface Recovery Scores Comparison')
ax2.grid(True, alpha=0.3)
ax2.legend()
# Plot total risk scores
ax3 = plt.subplot(3, 1, 3, sharex=ax1)
for i, results in enumerate(all_results):
total_scores = [score.total_score for score in results]
ax3.plot(times, total_scores, linestyle=linestyles[i], color=colors[i], linewidth=2, label=labels[i])
# Add threshold line
ax3.axhline(y=6.0, color='red', linestyle='--', alpha=0.7, label='Shoe Threshold (6.0)')
ax3.set_ylabel('Total Risk Score')
ax3.set_title('Total Risk Score Comparison')
ax3.set_xlabel('Time')
ax3.grid(True, alpha=0.3)
ax3.legend()
# Format x-axis
for ax in [ax1, ax2, ax3]:
ax.set_xlim(times[0], times[-1])
plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
plt.tight_layout()
plt.savefig('recovery_comparison.png')
plt.show()
def test_recovery_settings():
"""Test different recovery settings and compare results."""
test_data = create_test_data()
# Test configs
configs = [
# Default settings
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=False,
enable_time_of_day_factor=False
),
# Graduated recovery
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=True,
enable_time_of_day_factor=False,
surface_max_recovery_score=2.0
),
# Time-of-day factor
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=False,
enable_time_of_day_factor=True
),
# All features
RiskConfig(
surface_recovery_temp_threshold=90.0,
enable_graduated_recovery=True,
enable_time_of_day_factor=True,
surface_max_recovery_score=2.0
),
# Different surface types
RiskConfig(
surface_recovery_temp_threshold=90.0,
surface_type="concrete",
enable_graduated_recovery=True,
enable_time_of_day_factor=True
),
RiskConfig(
surface_recovery_temp_threshold=90.0,
surface_type="grass",
enable_graduated_recovery=True,
enable_time_of_day_factor=True
),
]
all_results = []
# Test each configuration
for i, config in enumerate(configs):
calculator = RiskCalculator(config)
risk_scores = calculator.calculate_risk_scores(test_data)
all_results.append(risk_scores)
print(f"\n=== Test Config {i+1} ===")
print(f"Surface Type: {config.surface_type}")
print(f"Graduated Recovery: {config.enable_graduated_recovery}")
print(f"Time-of-Day Factor: {config.enable_time_of_day_factor}")
print("\nHourly Surface Recovery Scores:")
print("Hour | Temp | Recovery Score")
print("-" * 30)
for hour, score in enumerate(risk_scores):
temp = test_data[hour].temperature_f
time = test_data[hour].datetime.strftime("%H:%M")
print(f"{time} | {temp:4.1f}°F | {score.surface_recovery_score:5.2f}")
# Show the highest risk hours
high_risk = [s for s in risk_scores if s.recommend_shoes]
print(f"\nHigh Risk Hours: {len(high_risk)} out of {len(risk_scores)}")
if high_risk:
times = [s.datetime.strftime("%H:%M") for s in high_risk]
print(f"Times: {', '.join(times)}")
# Visualize comparison
visualize_results(test_data, all_results)
if __name__ == "__main__":
print("Testing enhanced surface recovery logic")
test_recovery_settings()