Untitled

 avatar
unknown
plain_text
a month ago
12 kB
3
Indexable
import json
import os
import time
from datetime import datetime
from typing import Dict, Any, List, Optional
from filelock import FileLock

class StatsManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return

        # Define file paths
        self.stats_file = 'sorting_stats.json'
        self.impact_file = 'environmental_impact.json'
        self.time_file = 'time_tracking.json'
        self.fill_file = 'fill_history.json'
        self.achievements_file = 'achievements.json'
        
        # Initialize file locks
        self.locks = {}
        for file in [self.stats_file, self.impact_file, self.time_file, 
                    self.fill_file, self.achievements_file]:
            self.locks[file] = FileLock(f"{file}.lock")
        
        # Initialize all files with default structures
        self._initialize_files()
        self._initialized = True

    def _initialize_files(self):
        """Initialize all tracking files with default structures"""
        # Default sorting stats structure
        default_stats = {
            "total_items_sorted": 0,
            "items_per_bin": {
                "bin1": 0, "bin2": 0, "bin3": 0, "bin4": 0
            },
            "sort_events": [],
            "bin_emptying_counts": {
                "bin1": 0, "bin2": 0, "bin3": 0, "bin4": 0
            },
            "classification_counts": {
                "api": 0,
                "local": 0
            }
        }

        # Default environmental impact structure
        default_impact = {
            "co2_saved": 0.0,
            "trees_saved": 0.0,
            "recycled_weights": {
                "paper": 0.0,
                "plastic": 0.0,
                "organic": 0.0
            }
        }

        # Default time tracking structure
        default_time = {
            "hours_sorted": [],
            "last_sort_timestamp": "",
            "current_streak": {
                "start_date": "",
                "days": 0
            },
            "weekend_streak": {
                "count": 0,
                "last_weekend": ""
            },
            "seasonal_counts": {
                "summer": 0,
                "winter": 0
            },
            "daily_sorts": {},
            "weekly_sorts": {},
            "monthly_sorts": {}
        }

        # Default fill history structure
        default_fill = {
            "bin1": {"history": [], "emptying_events": []},
            "bin2": {"history": [], "emptying_events": []},
            "bin3": {"history": [], "emptying_events": []},
            "bin4": {"history": [], "emptying_events": []}
        }

        # Default achievements structure
        default_achievements = {
            "achievements": {
                "beginner_sorter": {
                    "id": "beginner_sorter",
                    "name": "Beginner Sorter",
                    "description": "Sort your first item",
                    "status": "locked",
                    "progress": 0,
                    "unlock_date": None
                },
                # Add all other achievements here
            }
        }

        # Initialize all files if they don't exist
        file_defaults = {
            self.stats_file: default_stats,
            self.impact_file: default_impact,
            self.time_file: default_time,
            self.fill_file: default_fill,
            self.achievements_file: default_achievements
        }

        for filename, default_data in file_defaults.items():
            if not os.path.exists(filename):
                self._safe_write(filename, default_data)

    def _safe_read(self, filename: str) -> Dict:
        """Safely read JSON file with lock"""
        with self.locks[filename]:
            try:
                with open(filename, 'r') as f:
                    return json.load(f)
            except FileNotFoundError:
                return {}

    def _safe_write(self, filename: str, data: Dict):
        """Safely write to JSON file with lock"""
        with self.locks[filename]:
            # Write to temporary file first
            temp_file = f"{filename}.tmp"
            with open(temp_file, 'w') as f:
                json.dump(data, f, indent=2)
            # Rename temporary file to actual file
            os.replace(temp_file, filename)

    def record_sort_event(self, bin_id: str, fill_level_before: float, 
                         fill_level_after: float, classification_method: str):
        """Record a sorting event with all related statistics"""
        current_time = datetime.now()
        
        # Update sorting stats
        stats = self._safe_read(self.stats_file)
        stats['total_items_sorted'] += 1
        stats['items_per_bin'][bin_id] += 1
        stats['classification_counts'][classification_method] += 1
        
        # Add sort event
        event = {
            'timestamp': current_time.isoformat(),
            'bin_id': bin_id,
            'fill_level_before': fill_level_before,
            'fill_level_after': fill_level_after,
            'classification_method': classification_method
        }
        stats['sort_events'].append(event)
        self._safe_write(self.stats_file, stats)

        # Update time tracking
        self._update_time_tracking(current_time, bin_id)
        
        # Update fill history
        self._update_fill_history(bin_id, fill_level_before, fill_level_after, current_time)
        
        # Update environmental impact
        self._update_environmental_impact(bin_id)
        
        # Check achievements
        self._check_achievements(bin_id, current_time)

    def record_bin_empty(self, bin_id: str, level_before: float):
        """Record when a bin is emptied"""
        current_time = datetime.now()
        
        # Update emptying counts
        stats = self._safe_read(self.stats_file)
        stats['bin_emptying_counts'][bin_id] += 1
        self._safe_write(self.stats_file, stats)
        
        # Update fill history
        fill_history = self._safe_read(self.fill_file)
        emptying_event = {
            "timestamp": current_time.isoformat(),
            "level_before": level_before,
            "level_after": 0.0
        }
        fill_history[bin_id]["emptying_events"].append(emptying_event)
        self._safe_write(self.fill_file, fill_history)

    def _update_time_tracking(self, current_time: datetime, bin_id: str):
        """Update all time-based tracking metrics"""
        time_data = self._safe_read(self.time_file)
        
        # Update hours sorted
        current_hour = current_time.hour
        if str(current_hour) not in time_data['hours_sorted']:
            time_data['hours_sorted'].append(str(current_hour))
        
        # Update streak tracking
        last_sort = datetime.fromisoformat(time_data['last_sort_timestamp']) if time_data['last_sort_timestamp'] else None
        if not last_sort or (current_time.date() - last_sort.date()).days > 1:
            time_data['current_streak']['start_date'] = current_time.isoformat()
            time_data['current_streak']['days'] = 1
        elif (current_time.date() - last_sort.date()).days == 1:
            time_data['current_streak']['days'] += 1
        
        # Update last sort timestamp
        time_data['last_sort_timestamp'] = current_time.isoformat()
        
        # Update seasonal tracking
        month = current_time.month
        if 6 <= month <= 8:  # Summer months
            time_data['seasonal_counts']['summer'] += 1
        elif month <= 2 or month == 12:  # Winter months
            time_data['seasonal_counts']['winter'] += 1
        
        self._safe_write(self.time_file, time_data)

    def _update_fill_history(self, bin_id: str, level_before: float, 
                           level_after: float, timestamp: datetime):
        """Update fill level history"""
        fill_data = self._safe_read(self.fill_file)
        
        event = {
            "timestamp": timestamp.isoformat(),
            "level": level_after,
            "event_type": "sort"
        }
        fill_data[bin_id]["history"].append(event)
        self._safe_write(self.fill_file, fill_data)

    def _update_environmental_impact(self, bin_id: str):
        """Update environmental impact calculations"""
        impact_data = self._safe_read(self.impact_file)
        
        # Example calculations (adjust these based on your actual impact metrics)
        if bin_id == "bin3":  # Paper bin
            impact_data['trees_saved'] += 0.1
            impact_data['co2_saved'] += 2.5
        elif bin_id == "bin2":  # Recycling bin
            impact_data['co2_saved'] += 1.8
        
        self._safe_write(self.impact_file, impact_data)

    def _check_achievements(self, bin_id: str, current_time: datetime):
        """Check and update achievements"""
        achievements = self._safe_read(self.achievements_file)
        stats = self._safe_read(self.stats_file)
        
        # Check sorting milestones
        total_sorted = stats['total_items_sorted']
        milestone_achievements = {
            "beginner_sorter": 1,
            "dedicated_recycler": 100,
            "waste_warrior": 1000,
            "sorting_legend": 10000
        }
        
        for achievement_id, required_count in milestone_achievements.items():
            if (total_sorted >= required_count and 
                achievements['achievements'][achievement_id]['status'] == 'locked'):
                achievements['achievements'][achievement_id].update({
                    'status': 'completed',
                    'progress': 100,
                    'unlock_date': current_time.isoformat()
                })
        
        # Update bin specialization progress
        items_in_bin = stats['items_per_bin'][bin_id]
        bin_achievement_map = {
            "bin1": "organic_expert",
            "bin2": "recycling_rockstar",
            "bin3": "paper_champion",
            "bin4": "residual_master"
        }
        
        if bin_id in bin_achievement_map:
            achievement_id = bin_achievement_map[bin_id]
            if items_in_bin >= 100 and achievements['achievements'][achievement_id]['status'] == 'locked':
                achievements['achievements'][achievement_id].update({
                    'status': 'completed',
                    'progress': 100,
                    'unlock_date': current_time.isoformat()
                })
        
        self._safe_write(self.achievements_file, achievements)

    def get_statistics(self) -> Dict:
        """Get all statistics for display"""
        stats = self._safe_read(self.stats_file)
        impact = self._safe_read(self.impact_file)
        time_data = self._safe_read(self.time_file)
        
        return {
            "total_items": stats['total_items_sorted'],
            "items_per_bin": stats['items_per_bin'],
            "environmental_impact": {
                "co2_saved": impact['co2_saved'],
                "trees_saved": impact['trees_saved']
            },
            "current_streak": time_data['current_streak'],
            "seasonal_counts": time_data['seasonal_counts'],
            "classification_methods": stats['classification_counts']
        }

    def get_achievements(self) -> Dict:
        """Get all achievements and their status"""
        return self._safe_read(self.achievements_file)
Editor is loading...
Leave a Comment