diff --git a/.cursor/rules/byterover-rules.mdc b/.cursor/rules/byterover-rules.mdc new file mode 100644 index 0000000..bbcc03c --- /dev/null +++ b/.cursor/rules/byterover-rules.mdc @@ -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 \ No newline at end of file diff --git a/config.py b/config.py index b1b9d30..b31df54 100644 --- a/config.py +++ b/config.py @@ -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 diff --git a/env_template.txt b/env_template.txt index c3fbf77..6857d16 100644 --- a/env_template.txt +++ b/env_template.txt @@ -32,4 +32,7 @@ SURFACE_RECOVERY_TEMP_THRESHOLD=90 SURFACE_TYPE=asphalt SURFACE_MAX_RECOVERY_SCORE=2.0 ENABLE_GRADUATED_RECOVERY=true -ENABLE_TIME_OF_DAY_FACTOR=true \ No newline at end of file +ENABLE_TIME_OF_DAY_FACTOR=true + +# Display preferences (Optional) +USE_24HR_TIME=false \ No newline at end of file diff --git a/main.py b/main.py index dd7e803..7db89eb 100644 --- a/main.py +++ b/main.py @@ -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}") diff --git a/plotting.py b/plotting.py index c9dcb88..18c66de 100644 --- a/plotting.py +++ b/plotting.py @@ -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) diff --git a/risk_calculator.py b/risk_calculator.py index 9506496..94125f2 100644 --- a/risk_calculator.py +++ b/risk_calculator.py @@ -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 diff --git a/test_real_data.py b/test_real_data.py deleted file mode 100644 index 642fffd..0000000 --- a/test_real_data.py +++ /dev/null @@ -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.") \ No newline at end of file diff --git a/test_recovery.py b/test_recovery.py deleted file mode 100644 index ac26521..0000000 --- a/test_recovery.py +++ /dev/null @@ -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() \ No newline at end of file