Untitled
unknown
plain_text
a month ago
15 kB
3
Indexable
import json from datetime import datetime, timedelta from typing import Dict, List, Set import os import threading import time class MetricsHandler: # Environmental impact constants IMPACT_FACTORS = { 'Paper': { 'co2_per_kg': 0.9, # kg CO2 saved per kg paper recycled 'trees_per_kg': 0.017, # trees saved per kg paper recycled 'avg_item_weight': 0.1 # kg per paper item (estimated average) }, 'Recycling': { 'co2_per_kg': 1.6, # kg CO2 saved per kg plastic recycled 'avg_item_weight': 0.05 # kg per plastic item (estimated average) }, 'Organic': { 'co2_per_kg': 0.5, # kg CO2 saved per kg organic waste composted 'avg_item_weight': 0.2 # kg per organic waste item (estimated average) }, 'Residual': { 'co2_per_kg': 0.0, # No direct savings for residual waste 'avg_item_weight': 0.15 # kg per residual item (estimated average) } } # Achievement definitions ACHIEVEMENTS = { 'first_sort': { 'id': 'first_sort', 'name': 'First Steps', 'description': 'Sort your first item', 'condition': lambda metrics: metrics['basic_metrics']['total_items_sorted'] >= 1 }, 'sorting_expert': { 'id': 'sorting_expert', 'tiers': ['bronze', 'silver', 'gold'], 'name': 'Sorting Expert', 'description': 'Sort items with high accuracy', 'thresholds': [100, 500, 1000], # Items sorted per tier 'condition': lambda metrics, tier_idx: metrics['basic_metrics']['total_items_sorted'] >= ACHIEVEMENTS['sorting_expert']['thresholds'][tier_idx] }, 'perfect_balance': { 'id': 'perfect_balance', 'name': 'Perfect Balance', 'description': 'Keep all bins within 20% fill level of each other for 7 days', 'condition': lambda metrics: metrics.get('balance_tracking', {}).get('days_within_threshold', 0) >= 7 }, 'time_lord': { 'id': 'time_lord', 'name': 'Time Lord', 'description': 'Sort items in every hour of the day', 'condition': lambda metrics: len(set(metrics['hour_coverage']['hours_sorted'])) == 24 }, 'weekend_warrior': { 'id': 'weekend_warrior', 'name': 'Weekend Warrior', 'description': 'Sort items on 4 consecutive weekends', 'condition': lambda metrics: metrics['hour_coverage']['weekend_streak']['count'] >= 4 }, 'seasonal_master': { 'id': 'seasonal_master', 'name': 'Seasonal Master', 'description': 'Sort items in both summer and winter', 'condition': lambda metrics: all(metrics['seasonal_tracking']['seasonal_counts'][season] > 0 for season in ['summer', 'winter']) }, 'eco_warrior': { 'id': 'eco_warrior', 'tiers': ['bronze', 'silver', 'gold'], 'name': 'Eco Warrior', 'description': 'Save CO2 through proper recycling', 'thresholds': [10, 50, 100], # kg CO2 saved per tier 'condition': lambda metrics, tier_idx: metrics['environmental_impact']['co2_saved'] >= ACHIEVEMENTS['eco_warrior']['thresholds'][tier_idx] } } def __init__(self, metrics_file='metrics.json'): self.metrics_file = metrics_file self.metrics = self._load_metrics() def _load_metrics(self) -> Dict: """Load metrics from JSON file or create default structure""" if os.path.exists(self.metrics_file): try: with open(self.metrics_file, 'r') as f: return json.load(f) except json.JSONDecodeError: return self._create_default_metrics() return self._create_default_metrics() def _create_default_metrics(self) -> Dict: """Create default metrics structure""" return { 'basic_metrics': { 'total_items_sorted': 0, 'items_sorted_per_bin': {}, 'classification_results': [], 'sort_timestamps': [], 'fill_levels_per_bin': {}, 'bin_emptying_counts': {}, 'api_vs_local_usage': {'api': 0, 'local': 0} }, 'time_metrics': { 'daily_usage_counts': {}, 'weekly_usage_counts': {}, 'monthly_usage_counts': {}, 'time_between_sorts': [], 'time_of_day_patterns': [0] * 24, 'daily_weekly_monthly_streaks': { 'daily': 0, 'weekly': 0, 'monthly': 0 }, 'time_to_empty_from_90': [], 'fill_rate_per_bin': {} }, 'short_term_tracking': { 'items_last_5_minutes': { 'count': 0, 'timestamps': [] }, 'current_sorting_streak': { 'start_date': None, 'days': 0, 'last_sort_date': None } }, 'hour_coverage': { 'hours_sorted': [], 'weekend_streak': { 'count': 0, 'dates': [] } }, 'seasonal_tracking': { 'seasonal_counts': { 'summer': 0, 'winter': 0, 'current_season': None, 'season_start': None } }, 'environmental_impact': { 'co2_saved': 0.0, 'trees_saved': 0.0, 'paper_weight_recycled': 0.0, 'plastic_weight_recycled': 0.0, 'organic_weight_processed': 0.0 }, 'fill_level_history': {}, 'achievements': {}, 'bin_specialization': {} } def record_sort(self, bin_id: str, classification_result: str, fill_level: float, classification_method: str): """Record a sorting event""" timestamp = datetime.now().isoformat() # Update basic metrics immediately self.metrics['basic_metrics']['total_items_sorted'] += 1 self.metrics['basic_metrics']['items_sorted_per_bin'][bin_id] = \ self.metrics['basic_metrics']['items_sorted_per_bin'].get(bin_id, 0) + 1 self.metrics['basic_metrics']['sort_timestamps'].append(timestamp) self.metrics['basic_metrics']['api_vs_local_usage'][classification_method] += 1 # Schedule delayed fill level recording def record_delayed_metrics(): try: # Read updated fill level after delay with open('fill_levels.json', 'r') as f: fill_levels = json.load(f) current_fill_level = fill_levels.get(bin_id, 0.0) # Record sort details sort_record = { 'timestamp': timestamp, 'bin_id': bin_id, 'classification_result': classification_result, 'fill_level': current_fill_level, 'method': classification_method } self.metrics['basic_metrics']['classification_results'].append(sort_record) self.metrics['basic_metrics']['fill_levels_per_bin'][bin_id] = current_fill_level # Update environmental impact self._update_environmental_impact(classification_result) # Update time-based metrics self._update_time_metrics(timestamp) # Update seasonal tracking self._update_seasonal_tracking(timestamp) # Update achievements self._check_achievements() # Save all updates self.save_metrics() except Exception as e: print(f"Error recording delayed metrics: {e}") # Start delayed recording thread thread = threading.Thread(target=lambda: [time.sleep(6), record_delayed_metrics()]) thread.daemon = True thread.start() def _update_environmental_impact(self, classification_result: str): """Update environmental impact calculations""" impact_factor = self.IMPACT_FACTORS.get(classification_result) if impact_factor: # Calculate environmental impact item_weight = impact_factor['avg_item_weight'] if classification_result == 'Paper': self.metrics['environmental_impact']['paper_weight_recycled'] += item_weight self.metrics['environmental_impact']['trees_saved'] += item_weight * impact_factor['trees_per_kg'] elif classification_result == 'Recycling': self.metrics['environmental_impact']['plastic_weight_recycled'] += item_weight elif classification_result == 'Organic': self.metrics['environmental_impact']['organic_weight_processed'] += item_weight # Calculate CO2 savings co2_saved = item_weight * impact_factor['co2_per_kg'] self.metrics['environmental_impact']['co2_saved'] += co2_saved def _update_time_metrics(self, timestamp: str): """Update time-based metrics""" current_time = datetime.fromisoformat(timestamp) # Update daily/weekly/monthly counts date_str = current_time.date().isoformat() week_str = f"{current_time.year}-W{current_time.isocalendar()[1]}" month_str = f"{current_time.year}-{current_time.month:02d}" self.metrics['time_metrics']['daily_usage_counts'][date_str] = \ self.metrics['time_metrics']['daily_usage_counts'].get(date_str, 0) + 1 self.metrics['time_metrics']['weekly_usage_counts'][week_str] = \ self.metrics['time_metrics']['weekly_usage_counts'].get(week_str, 0) + 1 self.metrics['time_metrics']['monthly_usage_counts'][month_str] = \ self.metrics['time_metrics']['monthly_usage_counts'].get(month_str, 0) + 1 # Update time of day patterns hour = current_time.hour self.metrics['time_metrics']['time_of_day_patterns'][hour] += 1 # Update hour coverage if hour not in self.metrics['hour_coverage']['hours_sorted']: self.metrics['hour_coverage']['hours_sorted'].append(hour) # Update weekend tracking if current_time.weekday() >= 5: # Saturday = 5, Sunday = 6 weekend_dates = self.metrics['hour_coverage']['weekend_streak']['dates'] if not weekend_dates or \ (current_time.date() - datetime.fromisoformat(weekend_dates[-1]).date()).days > 1: weekend_dates.append(timestamp) # Check for consecutive weekends if len(weekend_dates) >= 2: date1 = datetime.fromisoformat(weekend_dates[-2]).date() date2 = current_time.date() if (date2 - date1).days <= 8: # Within 8 days = consecutive weekends self.metrics['hour_coverage']['weekend_streak']['count'] += 1 else: self.metrics['hour_coverage']['weekend_streak']['count'] = 1 def _update_seasonal_tracking(self, timestamp: str): """Update seasonal tracking""" current_time = datetime.fromisoformat(timestamp) month = current_time.month # Simple season determination (Northern Hemisphere) current_season = 'summer' if 5 <= month <= 8 else 'winter' # Update season if changed if self.metrics['seasonal_tracking']['current_season'] != current_season: self.metrics['seasonal_tracking']['current_season'] = current_season self.metrics['seasonal_tracking']['season_start'] = timestamp # Increment seasonal count self.metrics['seasonal_tracking']['seasonal_counts'][current_season] += 1 def _check_achievements(self): """Check and update achievements""" if 'achievements' not in self.metrics: self.metrics['achievements'] = {} # Check each achievement for achievement_id, achievement in self.ACHIEVEMENTS.items(): if 'tiers' in achievement: # Multi-tier achievement for tier_idx, tier in enumerate(achievement['tiers']): achievement_key = f"{achievement_id}_{tier}" if achievement_key not in self.metrics['achievements']: if achievement['condition'](self.metrics, tier_idx): self.metrics['achievements'][achievement_key] = { 'status': 'completed', 'unlock_date': datetime.now().isoformat(), 'tier': tier } else: # Single-tier achievement if achievement_id not in self.metrics['achievements']: if achievement['condition'](self.metrics): self.metrics['achievements'][achievement_id] = { 'status': 'completed', 'unlock_date': datetime.now().isoformat() } def check_bin_balance(self): """Check if bins are balanced (within 20% of each other)""" fill_levels = [level for level in self.metrics['basic_metrics']['fill_levels_per_bin'].values()] if not fill_levels: return False max_level = max(fill_levels) min_level = min(fill_levels) return (max_level - min_level) <= 20.0 def save_metrics(self): """Save metrics to JSON file""" with open(self.metrics_file, 'w') as f: json.dump(self.metrics, f, indent=2) def get_metrics(self) -> Dict: """Get all metrics""" return self.metrics def get_metric(self, category: str, metric: str) -> any: """Get a specific metric""" return self.metrics.get(category, {}).get(metric, None)
Editor is loading...
Leave a Comment