Spaces:
Runtime error
Runtime error
invincible-jha
commited on
Commit
·
f83b968
1
Parent(s):
76166e3
Add comprehensive logging system and analytics with log rotation
Browse files- .gitignore +2 -0
- README.md +105 -1
- requirements.txt +3 -1
- utils/analytics_logger.py +170 -0
- utils/log_manager.py +169 -0
.gitignore
CHANGED
@@ -32,7 +32,9 @@ ENV/
|
|
32 |
*.swo
|
33 |
|
34 |
# Logs
|
|
|
35 |
*.log
|
|
|
36 |
|
37 |
# Local configuration
|
38 |
.env
|
|
|
32 |
*.swo
|
33 |
|
34 |
# Logs
|
35 |
+
logs/
|
36 |
*.log
|
37 |
+
*.log.*
|
38 |
|
39 |
# Local configuration
|
40 |
.env
|
README.md
CHANGED
@@ -9,4 +9,108 @@ app_file: app.py
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
# Mental Wellness Platform
|
13 |
+
|
14 |
+
A comprehensive mental wellness support system powered by AI, deployed on Hugging Face Spaces.
|
15 |
+
|
16 |
+
## Features
|
17 |
+
- 🤖 Multi-agent AI support system
|
18 |
+
- 💬 Natural therapeutic conversations
|
19 |
+
- 📊 Mental health assessments
|
20 |
+
- 🧘♀️ Guided meditation and mindfulness
|
21 |
+
- 🚨 Crisis intervention support
|
22 |
+
- 📱 Multi-modal interaction (text, voice, image)
|
23 |
+
- 📈 Comprehensive logging and analytics
|
24 |
+
- 🔄 Automatic log rotation and management
|
25 |
+
|
26 |
+
## System Components
|
27 |
+
- Therapeutic Conversation Agent
|
28 |
+
- Assessment Agent
|
29 |
+
- Mindfulness Agent
|
30 |
+
- Crisis Intervention Agent
|
31 |
+
- Analytics and Logging System
|
32 |
+
|
33 |
+
## Logging System
|
34 |
+
The platform includes a comprehensive logging system with:
|
35 |
+
- Agent-specific logs
|
36 |
+
- System health monitoring
|
37 |
+
- Analytics tracking
|
38 |
+
- Performance metrics
|
39 |
+
- Security event logging
|
40 |
+
- Automatic log rotation
|
41 |
+
- Log cleanup for older files
|
42 |
+
|
43 |
+
### Log Categories
|
44 |
+
1. Agent Logs (`logs/agents/`)
|
45 |
+
- Individual agent activities
|
46 |
+
- Performance metrics
|
47 |
+
- Error tracking
|
48 |
+
|
49 |
+
2. System Logs (`logs/system/`)
|
50 |
+
- System health
|
51 |
+
- Resource usage
|
52 |
+
- General operations
|
53 |
+
|
54 |
+
3. Analytics Logs (`logs/analytics/`)
|
55 |
+
- User interactions
|
56 |
+
- Session metrics
|
57 |
+
- Model performance
|
58 |
+
- Usage statistics
|
59 |
+
|
60 |
+
## Usage
|
61 |
+
1. Open the chat interface
|
62 |
+
2. Type your message or use voice/image input
|
63 |
+
3. Choose from available tools:
|
64 |
+
- Mental Health Check
|
65 |
+
- Start Meditation
|
66 |
+
- View Resources
|
67 |
+
|
68 |
+
## Important Note
|
69 |
+
This is an AI-powered support tool and not a substitute for professional mental health care. In case of emergency:
|
70 |
+
- Call emergency services: 911 (US)
|
71 |
+
- National Crisis Hotline: 988
|
72 |
+
- Crisis Text Line: Text HOME to 741741
|
73 |
+
|
74 |
+
## Technical Details
|
75 |
+
Built with:
|
76 |
+
- Gradio for interface
|
77 |
+
- Hugging Face Transformers for AI models
|
78 |
+
- CrewAI for multi-agent orchestration
|
79 |
+
- Advanced voice and image processing
|
80 |
+
- Comprehensive logging system
|
81 |
+
|
82 |
+
## Development
|
83 |
+
To run locally:
|
84 |
+
1. Clone the repository
|
85 |
+
2. Install dependencies:
|
86 |
+
```bash
|
87 |
+
pip install -r requirements.txt
|
88 |
+
```
|
89 |
+
3. Run the application:
|
90 |
+
```bash
|
91 |
+
python app.py
|
92 |
+
```
|
93 |
+
|
94 |
+
## Monitoring
|
95 |
+
The system includes:
|
96 |
+
- Real-time performance monitoring
|
97 |
+
- Error tracking and alerts
|
98 |
+
- Usage analytics
|
99 |
+
- System health metrics
|
100 |
+
|
101 |
+
## Security
|
102 |
+
- All conversations are encrypted
|
103 |
+
- Comprehensive security logging
|
104 |
+
- Access control and monitoring
|
105 |
+
- Data retention policies
|
106 |
+
|
107 |
+
## License
|
108 |
+
MIT License
|
109 |
+
|
110 |
+
## Authors
|
111 |
+
Created by Invincible Jha
|
112 |
+
|
113 |
+
## Acknowledgments
|
114 |
+
- CrewAI for the multi-agent framework
|
115 |
+
- Hugging Face for AI models and hosting
|
116 |
+
- Gradio for the interface framework
|
requirements.txt
CHANGED
@@ -15,4 +15,6 @@ pillow>=10.1.0
|
|
15 |
redis>=5.0.1
|
16 |
pytest>=7.4.3
|
17 |
crewai>=0.11.0
|
18 |
-
huggingface_hub>=0.26.5
|
|
|
|
|
|
15 |
redis>=5.0.1
|
16 |
pytest>=7.4.3
|
17 |
crewai>=0.11.0
|
18 |
+
huggingface_hub>=0.26.5
|
19 |
+
psutil>=5.9.0
|
20 |
+
python-json-logger>=2.0.7
|
utils/analytics_logger.py
ADDED
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Dict, Any, Optional
|
2 |
+
import json
|
3 |
+
import time
|
4 |
+
from datetime import datetime
|
5 |
+
from utils.log_manager import LogManager
|
6 |
+
|
7 |
+
class AnalyticsLogger:
|
8 |
+
"""Handles logging of analytics events and metrics"""
|
9 |
+
|
10 |
+
def __init__(self):
|
11 |
+
self.log_manager = LogManager()
|
12 |
+
self.logger = self.log_manager.get_analytics_logger("events")
|
13 |
+
self.metrics_logger = self.log_manager.get_analytics_logger("metrics")
|
14 |
+
|
15 |
+
def log_user_interaction(self,
|
16 |
+
user_id: str,
|
17 |
+
interaction_type: str,
|
18 |
+
agent_type: str,
|
19 |
+
duration: float,
|
20 |
+
success: bool,
|
21 |
+
details: Optional[Dict] = None):
|
22 |
+
"""Log user interaction events"""
|
23 |
+
event = {
|
24 |
+
"event_type": "user_interaction",
|
25 |
+
"user_id": user_id,
|
26 |
+
"interaction_type": interaction_type,
|
27 |
+
"agent_type": agent_type,
|
28 |
+
"duration": duration,
|
29 |
+
"success": success,
|
30 |
+
"timestamp": datetime.now().isoformat(),
|
31 |
+
"details": details or {}
|
32 |
+
}
|
33 |
+
|
34 |
+
self.logger.info(f"User Interaction: {json.dumps(event, indent=2)}")
|
35 |
+
|
36 |
+
def log_agent_performance(self,
|
37 |
+
agent_type: str,
|
38 |
+
operation: str,
|
39 |
+
response_time: float,
|
40 |
+
success: bool,
|
41 |
+
error: Optional[str] = None):
|
42 |
+
"""Log agent performance metrics"""
|
43 |
+
metric = {
|
44 |
+
"metric_type": "agent_performance",
|
45 |
+
"agent_type": agent_type,
|
46 |
+
"operation": operation,
|
47 |
+
"response_time": response_time,
|
48 |
+
"success": success,
|
49 |
+
"error": error,
|
50 |
+
"timestamp": datetime.now().isoformat()
|
51 |
+
}
|
52 |
+
|
53 |
+
self.metrics_logger.info(f"Agent Performance: {json.dumps(metric, indent=2)}")
|
54 |
+
|
55 |
+
def log_system_health(self,
|
56 |
+
cpu_usage: float,
|
57 |
+
memory_usage: float,
|
58 |
+
active_users: int,
|
59 |
+
active_sessions: int):
|
60 |
+
"""Log system health metrics"""
|
61 |
+
metric = {
|
62 |
+
"metric_type": "system_health",
|
63 |
+
"cpu_usage": cpu_usage,
|
64 |
+
"memory_usage": memory_usage,
|
65 |
+
"active_users": active_users,
|
66 |
+
"active_sessions": active_sessions,
|
67 |
+
"timestamp": datetime.now().isoformat()
|
68 |
+
}
|
69 |
+
|
70 |
+
self.metrics_logger.info(f"System Health: {json.dumps(metric, indent=2)}")
|
71 |
+
|
72 |
+
def log_error(self,
|
73 |
+
error_type: str,
|
74 |
+
error_message: str,
|
75 |
+
severity: str,
|
76 |
+
context: Optional[Dict] = None):
|
77 |
+
"""Log error events"""
|
78 |
+
event = {
|
79 |
+
"event_type": "error",
|
80 |
+
"error_type": error_type,
|
81 |
+
"error_message": error_message,
|
82 |
+
"severity": severity,
|
83 |
+
"context": context or {},
|
84 |
+
"timestamp": datetime.now().isoformat()
|
85 |
+
}
|
86 |
+
|
87 |
+
self.logger.error(f"Error Event: {json.dumps(event, indent=2)}")
|
88 |
+
|
89 |
+
def log_security_event(self,
|
90 |
+
event_type: str,
|
91 |
+
user_id: str,
|
92 |
+
success: bool,
|
93 |
+
details: Optional[Dict] = None):
|
94 |
+
"""Log security-related events"""
|
95 |
+
event = {
|
96 |
+
"event_type": "security",
|
97 |
+
"security_event_type": event_type,
|
98 |
+
"user_id": user_id,
|
99 |
+
"success": success,
|
100 |
+
"details": details or {},
|
101 |
+
"timestamp": datetime.now().isoformat()
|
102 |
+
}
|
103 |
+
|
104 |
+
self.logger.info(f"Security Event: {json.dumps(event, indent=2)}")
|
105 |
+
|
106 |
+
def log_model_performance(self,
|
107 |
+
model_name: str,
|
108 |
+
operation: str,
|
109 |
+
input_tokens: int,
|
110 |
+
output_tokens: int,
|
111 |
+
response_time: float,
|
112 |
+
success: bool):
|
113 |
+
"""Log AI model performance metrics"""
|
114 |
+
metric = {
|
115 |
+
"metric_type": "model_performance",
|
116 |
+
"model_name": model_name,
|
117 |
+
"operation": operation,
|
118 |
+
"input_tokens": input_tokens,
|
119 |
+
"output_tokens": output_tokens,
|
120 |
+
"response_time": response_time,
|
121 |
+
"success": success,
|
122 |
+
"timestamp": datetime.now().isoformat()
|
123 |
+
}
|
124 |
+
|
125 |
+
self.metrics_logger.info(f"Model Performance: {json.dumps(metric, indent=2)}")
|
126 |
+
|
127 |
+
def log_user_feedback(self,
|
128 |
+
user_id: str,
|
129 |
+
interaction_id: str,
|
130 |
+
rating: int,
|
131 |
+
feedback_text: Optional[str] = None):
|
132 |
+
"""Log user feedback"""
|
133 |
+
event = {
|
134 |
+
"event_type": "user_feedback",
|
135 |
+
"user_id": user_id,
|
136 |
+
"interaction_id": interaction_id,
|
137 |
+
"rating": rating,
|
138 |
+
"feedback_text": feedback_text,
|
139 |
+
"timestamp": datetime.now().isoformat()
|
140 |
+
}
|
141 |
+
|
142 |
+
self.logger.info(f"User Feedback: {json.dumps(event, indent=2)}")
|
143 |
+
|
144 |
+
def log_session_metrics(self,
|
145 |
+
session_id: str,
|
146 |
+
user_id: str,
|
147 |
+
session_type: str,
|
148 |
+
start_time: str,
|
149 |
+
end_time: str,
|
150 |
+
metrics: Dict[str, Any]):
|
151 |
+
"""Log session-specific metrics"""
|
152 |
+
session_data = {
|
153 |
+
"metric_type": "session_metrics",
|
154 |
+
"session_id": session_id,
|
155 |
+
"user_id": user_id,
|
156 |
+
"session_type": session_type,
|
157 |
+
"start_time": start_time,
|
158 |
+
"end_time": end_time,
|
159 |
+
"duration": self._calculate_duration(start_time, end_time),
|
160 |
+
"metrics": metrics,
|
161 |
+
"timestamp": datetime.now().isoformat()
|
162 |
+
}
|
163 |
+
|
164 |
+
self.metrics_logger.info(f"Session Metrics: {json.dumps(session_data, indent=2)}")
|
165 |
+
|
166 |
+
def _calculate_duration(self, start_time: str, end_time: str) -> float:
|
167 |
+
"""Calculate duration between two ISO format timestamps"""
|
168 |
+
start = datetime.fromisoformat(start_time)
|
169 |
+
end = datetime.fromisoformat(end_time)
|
170 |
+
return (end - start).total_seconds()
|
utils/log_manager.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
from logging.handlers import RotatingFileHandler
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
from typing import Optional
|
6 |
+
|
7 |
+
class LogManager:
|
8 |
+
"""Manages logging configuration and rotation for all agents"""
|
9 |
+
|
10 |
+
def __init__(self, log_dir: str = "logs", max_bytes: int = 5_000_000, backup_count: int = 5):
|
11 |
+
"""Initialize log manager
|
12 |
+
|
13 |
+
Args:
|
14 |
+
log_dir: Directory to store log files
|
15 |
+
max_bytes: Maximum size of each log file before rotation
|
16 |
+
backup_count: Number of backup files to keep
|
17 |
+
"""
|
18 |
+
self.log_dir = Path(log_dir)
|
19 |
+
self.max_bytes = max_bytes
|
20 |
+
self.backup_count = backup_count
|
21 |
+
|
22 |
+
# Create log directories
|
23 |
+
self._create_log_dirs()
|
24 |
+
|
25 |
+
# Configure root logger
|
26 |
+
self._configure_root_logger()
|
27 |
+
|
28 |
+
def _create_log_dirs(self):
|
29 |
+
"""Create necessary log directories"""
|
30 |
+
dirs = [
|
31 |
+
self.log_dir,
|
32 |
+
self.log_dir / "agents",
|
33 |
+
self.log_dir / "system",
|
34 |
+
self.log_dir / "analytics"
|
35 |
+
]
|
36 |
+
|
37 |
+
for dir_path in dirs:
|
38 |
+
dir_path.mkdir(parents=True, exist_ok=True)
|
39 |
+
|
40 |
+
def _configure_root_logger(self):
|
41 |
+
"""Configure the root logger"""
|
42 |
+
root_logger = logging.getLogger()
|
43 |
+
root_logger.setLevel(logging.INFO)
|
44 |
+
|
45 |
+
# System log handler
|
46 |
+
system_handler = RotatingFileHandler(
|
47 |
+
self.log_dir / "system" / "system.log",
|
48 |
+
maxBytes=self.max_bytes,
|
49 |
+
backupCount=self.backup_count
|
50 |
+
)
|
51 |
+
system_handler.setLevel(logging.INFO)
|
52 |
+
formatter = logging.Formatter(
|
53 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
54 |
+
)
|
55 |
+
system_handler.setFormatter(formatter)
|
56 |
+
root_logger.addHandler(system_handler)
|
57 |
+
|
58 |
+
def get_agent_logger(self, agent_name: str, log_level: int = logging.INFO) -> logging.Logger:
|
59 |
+
"""Get a configured logger for an agent
|
60 |
+
|
61 |
+
Args:
|
62 |
+
agent_name: Name of the agent (used in log file name)
|
63 |
+
log_level: Logging level for this logger
|
64 |
+
|
65 |
+
Returns:
|
66 |
+
Configured logger instance
|
67 |
+
"""
|
68 |
+
logger = logging.getLogger(f"agent.{agent_name}")
|
69 |
+
logger.setLevel(log_level)
|
70 |
+
|
71 |
+
# Remove any existing handlers
|
72 |
+
logger.handlers = []
|
73 |
+
|
74 |
+
# Add rotating file handler
|
75 |
+
handler = RotatingFileHandler(
|
76 |
+
self.log_dir / "agents" / f"{agent_name}.log",
|
77 |
+
maxBytes=self.max_bytes,
|
78 |
+
backupCount=self.backup_count
|
79 |
+
)
|
80 |
+
handler.setLevel(log_level)
|
81 |
+
formatter = logging.Formatter(
|
82 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
83 |
+
)
|
84 |
+
handler.setFormatter(formatter)
|
85 |
+
logger.addHandler(handler)
|
86 |
+
|
87 |
+
return logger
|
88 |
+
|
89 |
+
def get_analytics_logger(self, name: str, log_level: int = logging.INFO) -> logging.Logger:
|
90 |
+
"""Get a configured logger for analytics
|
91 |
+
|
92 |
+
Args:
|
93 |
+
name: Analytics category name
|
94 |
+
log_level: Logging level for this logger
|
95 |
+
|
96 |
+
Returns:
|
97 |
+
Configured logger instance
|
98 |
+
"""
|
99 |
+
logger = logging.getLogger(f"analytics.{name}")
|
100 |
+
logger.setLevel(log_level)
|
101 |
+
|
102 |
+
# Remove any existing handlers
|
103 |
+
logger.handlers = []
|
104 |
+
|
105 |
+
# Add rotating file handler
|
106 |
+
handler = RotatingFileHandler(
|
107 |
+
self.log_dir / "analytics" / f"{name}.log",
|
108 |
+
maxBytes=self.max_bytes,
|
109 |
+
backupCount=self.backup_count
|
110 |
+
)
|
111 |
+
handler.setLevel(log_level)
|
112 |
+
formatter = logging.Formatter(
|
113 |
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
114 |
+
)
|
115 |
+
handler.setFormatter(formatter)
|
116 |
+
logger.addHandler(handler)
|
117 |
+
|
118 |
+
return logger
|
119 |
+
|
120 |
+
def cleanup_old_logs(self, max_age_days: int = 30):
|
121 |
+
"""Clean up log files older than specified days
|
122 |
+
|
123 |
+
Args:
|
124 |
+
max_age_days: Maximum age of log files in days
|
125 |
+
"""
|
126 |
+
import time
|
127 |
+
current_time = time.time()
|
128 |
+
max_age_seconds = max_age_days * 24 * 60 * 60
|
129 |
+
|
130 |
+
for root, _, files in os.walk(self.log_dir):
|
131 |
+
for file in files:
|
132 |
+
file_path = os.path.join(root, file)
|
133 |
+
if os.path.getmtime(file_path) < (current_time - max_age_seconds):
|
134 |
+
try:
|
135 |
+
os.remove(file_path)
|
136 |
+
logging.info(f"Removed old log file: {file_path}")
|
137 |
+
except Exception as e:
|
138 |
+
logging.error(f"Error removing old log file {file_path}: {str(e)}")
|
139 |
+
|
140 |
+
def get_log_stats(self) -> dict:
|
141 |
+
"""Get statistics about log files
|
142 |
+
|
143 |
+
Returns:
|
144 |
+
Dictionary containing log statistics
|
145 |
+
"""
|
146 |
+
stats = {
|
147 |
+
"total_size": 0,
|
148 |
+
"file_count": 0,
|
149 |
+
"categories": {}
|
150 |
+
}
|
151 |
+
|
152 |
+
for root, _, files in os.walk(self.log_dir):
|
153 |
+
category = os.path.basename(root)
|
154 |
+
if category not in stats["categories"]:
|
155 |
+
stats["categories"][category] = {
|
156 |
+
"size": 0,
|
157 |
+
"file_count": 0
|
158 |
+
}
|
159 |
+
|
160 |
+
for file in files:
|
161 |
+
file_path = os.path.join(root, file)
|
162 |
+
size = os.path.getsize(file_path)
|
163 |
+
|
164 |
+
stats["total_size"] += size
|
165 |
+
stats["file_count"] += 1
|
166 |
+
stats["categories"][category]["size"] += size
|
167 |
+
stats["categories"][category]["file_count"] += 1
|
168 |
+
|
169 |
+
return stats
|