Ali2206 commited on
Commit
b19e5b5
·
1 Parent(s): 693cb3d

Deploy FastAPI backend with MongoDB integration - Added app.py, requirements.txt, and updated Dockerfile for HF Spaces deployment

Browse files
Files changed (4) hide show
  1. Dockerfile +20 -0
  2. README.md +79 -4
  3. app.py +735 -0
  4. requirements.txt +20 -0
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy requirements first for better caching
6
+ COPY server/requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # Copy the rest of the application
10
+ COPY server/ .
11
+
12
+ # Expose the port that FastAPI will run on
13
+ EXPOSE 8000
14
+
15
+ # Set environment variables
16
+ ENV PORT=8000
17
+ ENV CORS_ORIGIN=https://ali2206-checklist.hf.space
18
+
19
+ # Run the FastAPI application
20
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,10 +1,85 @@
1
  ---
2
- title: Checklist
3
- emoji: 🚀
4
  colorFrom: blue
5
- colorTo: yellow
6
  sdk: docker
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Audit Checklist API
3
+ emoji: 📋
4
  colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
  pinned: false
8
+ license: mit
9
+ app_port: 8000
10
  ---
11
 
12
+ # Audit Checklist FastAPI Backend
13
+
14
+ This is the backend API for the Audit Checklist React Native application.
15
+
16
+ ## Features
17
+ - MongoDB Atlas integration
18
+ - REST API endpoints for audit checklists
19
+ - CORS support for mobile apps
20
+ - Health check endpoint
21
+ - User session management
22
+ - Comprehensive error handling
23
+
24
+ ## API Endpoints
25
+
26
+ ### Health Check
27
+ - `GET /` - Basic health check
28
+ - `GET /health` - Detailed health information
29
+
30
+ ### Checklist Management
31
+ - `GET /api/checklist/{user_id}` - Get checklist for user
32
+ - `PUT /api/checklist/{user_id}` - Save/update checklist
33
+ - `GET /api/checklists` - Get all checklists (admin)
34
+ - `GET /api/checklists/by-user/{user_name}` - Get checklists by user name
35
+
36
+ ### Server Information
37
+ - `GET /api/server-info` - Get server information
38
+
39
+ ## Environment Variables
40
+
41
+ Set these in your Hugging Face Space settings:
42
+
43
+ - `MONGODB_URI`: Your MongoDB Atlas connection string
44
+ - `CORS_ORIGIN`: Frontend URL (optional, defaults to allow all)
45
+
46
+ ## Data Structure
47
+
48
+ The API manages audit checklists with the following structure:
49
+
50
+ ```json
51
+ {
52
+ "userId": "user-1234567890",
53
+ "title": "Checklist di Audit Operativo (38 Controlli)",
54
+ "sections": [...],
55
+ "metadata": {
56
+ "userName": "John Doe",
57
+ "savedAt": "2024-01-15T14:30:25.123Z",
58
+ "savedAtFormatted": "15/01/2024, 14:30:25",
59
+ "userId": "user-1234567890",
60
+ "version": "1.0"
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ This API is designed to work with the Audit Checklist React Native mobile application. The mobile app sends audit data to this backend, which stores it in MongoDB Atlas with user information and timestamps.
68
+
69
+ ## Deployment
70
+
71
+ This FastAPI application is deployed on Hugging Face Spaces using Docker. The application runs on port 8000 and is accessible at:
72
+
73
+ `https://ali2206-checklist.hf.space`
74
+
75
+ ## Development
76
+
77
+ To run locally:
78
+
79
+ ```bash
80
+ cd server
81
+ pip install -r requirements.txt
82
+ python start_server.py
83
+ ```
84
+
85
+ The API will be available at `http://localhost:5000`
app.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI Server for Audit Checklist Application
3
+ =============================================
4
+
5
+ This server provides a REST API for managing audit checklists with MongoDB integration.
6
+ It supports CRUD operations for checklist data and user management.
7
+
8
+ Features:
9
+ - MongoDB Atlas integration with async Motor driver
10
+ - CORS support for React frontend
11
+ - Comprehensive error handling
12
+ - Health check endpoint
13
+ - Environment-based configuration
14
+
15
+ Author: Audit Checklist Team
16
+ Version: 1.0.0
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ from datetime import datetime
22
+ from typing import Optional, List, Dict, Any, Union
23
+ import logging
24
+
25
+ # FastAPI imports
26
+ from fastapi import FastAPI, HTTPException, status
27
+ from fastapi.middleware.cors import CORSMiddleware
28
+ from fastapi.responses import JSONResponse
29
+
30
+ # Pydantic for data validation
31
+ from pydantic import BaseModel, Field
32
+
33
+ # MongoDB async driver
34
+ import motor.motor_asyncio
35
+ from bson import ObjectId
36
+
37
+ # Environment variables
38
+ from dotenv import load_dotenv
39
+
40
+ # Load environment variables from .env file (if available)
41
+ # For Hugging Face Spaces, environment variables are set in the Space settings
42
+ try:
43
+ load_dotenv('mongodb.env')
44
+ except:
45
+ pass # Continue without .env file if not found
46
+
47
+ # Configure logging
48
+ logging.basicConfig(
49
+ level=logging.INFO,
50
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
51
+ )
52
+ logger = logging.getLogger(__name__)
53
+
54
+ # =============================================================================
55
+ # CONFIGURATION AND INITIALIZATION
56
+ # =============================================================================
57
+
58
+ # Environment variables with fallback defaults
59
+ MONGODB_URI = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/audit_checklist')
60
+ PORT = int(os.getenv('PORT', 8000)) # Hugging Face Spaces uses port 8000
61
+ CORS_ORIGIN = os.getenv('CORS_ORIGIN', '*') # Allow all origins by default for mobile apps
62
+
63
+ # Initialize FastAPI application
64
+ app = FastAPI(
65
+ title="Audit Checklist API",
66
+ description="REST API for managing audit checklists with MongoDB integration",
67
+ version="1.0.0",
68
+ docs_url="/docs", # Swagger UI documentation
69
+ redoc_url="/redoc" # ReDoc documentation
70
+ )
71
+
72
+ # Configure CORS middleware
73
+ # Allow all origins for Hugging Face Spaces deployment
74
+ cors_origins = [CORS_ORIGIN] if CORS_ORIGIN != "*" else ["*"]
75
+
76
+ app.add_middleware(
77
+ CORSMiddleware,
78
+ allow_origins=cors_origins,
79
+ allow_credentials=True,
80
+ allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
81
+ allow_headers=["*"],
82
+ )
83
+
84
+ # MongoDB connection setup
85
+ client = None
86
+ db = None
87
+
88
+ async def connect_to_mongodb():
89
+ """
90
+ Establish connection to MongoDB Atlas
91
+
92
+ This function creates an async connection to MongoDB using the Motor driver.
93
+ It handles connection errors gracefully and logs the status.
94
+ """
95
+ global client, db
96
+ try:
97
+ client = motor.motor_asyncio.AsyncIOMotorClient(MONGODB_URI)
98
+ db = client.audit_checklist
99
+
100
+ # Test the connection
101
+ await client.admin.command('ping')
102
+ logger.info("Successfully connected to MongoDB Atlas")
103
+
104
+ # Create indexes for better performance
105
+ await create_database_indexes()
106
+
107
+ except Exception as e:
108
+ logger.error(f"Failed to connect to MongoDB: {e}")
109
+ raise e
110
+
111
+ async def create_database_indexes():
112
+ """
113
+ Create database indexes for optimal query performance
114
+
115
+ This function creates indexes on frequently queried fields to improve
116
+ database performance and query speed.
117
+ """
118
+ try:
119
+ # Index on userId for faster user-specific queries
120
+ await db.checklists.create_index("userId", unique=False)
121
+
122
+ # Index on createdAt for time-based queries
123
+ await db.checklists.create_index("createdAt", unique=False)
124
+
125
+ logger.info("Database indexes created successfully")
126
+
127
+ except Exception as e:
128
+ logger.warning(f"Could not create indexes: {e}")
129
+
130
+ async def close_mongodb_connection():
131
+ """Close MongoDB connection gracefully"""
132
+ global client
133
+ if client:
134
+ client.close()
135
+ logger.info("MongoDB connection closed")
136
+
137
+ # =============================================================================
138
+ # PYDANTIC MODELS FOR DATA VALIDATION
139
+ # =============================================================================
140
+
141
+ class ChecklistItem(BaseModel):
142
+ """
143
+ Model for individual checklist items
144
+
145
+ Attributes:
146
+ id: Unique identifier for the item
147
+ requirement: Description of the requirement to be checked
148
+ compliance: Compliance status (N/A, Compliant, Non-Compliant)
149
+ deviation: Description of any deviation found
150
+ action: Action taken to address the issue
151
+ checkedAt: Timestamp when the item was checked
152
+ checkedBy: Name of the person who checked the item
153
+ """
154
+ id: str = Field(..., description="Unique identifier for the checklist item")
155
+ requirement: str = Field(..., description="Description of the requirement")
156
+ compliance: str = Field(default="N/A", description="Compliance status: N/A, Compliant, or Non-Compliant")
157
+ deviation: str = Field(default="", description="Description of any deviation found")
158
+ action: str = Field(default="", description="Action taken to address the issue")
159
+ checkedAt: Optional[datetime] = Field(default=None, description="Timestamp when item was checked")
160
+ checkedBy: str = Field(default="", description="Name of the person who checked the item")
161
+
162
+ class ChecklistSection(BaseModel):
163
+ """
164
+ Model for checklist sections containing multiple items
165
+
166
+ Attributes:
167
+ id: Unique identifier for the section
168
+ title: Display title of the section
169
+ icon: Icon identifier for UI display
170
+ items: List of checklist items in this section
171
+ """
172
+ id: str = Field(..., description="Unique identifier for the section")
173
+ title: str = Field(..., description="Display title of the section")
174
+ icon: str = Field(..., description="Icon identifier for UI display")
175
+ items: List[ChecklistItem] = Field(default=[], description="List of checklist items")
176
+
177
+ class Metadata(BaseModel):
178
+ """
179
+ Metadata model for checklist information
180
+ """
181
+ userName: Optional[str] = Field(default=None, description="Name of the user who saved the checklist")
182
+ savedAt: Optional[str] = Field(default=None, description="ISO timestamp when saved")
183
+ savedAtFormatted: Optional[str] = Field(default=None, description="Formatted timestamp when saved")
184
+ userId: Optional[str] = Field(default=None, description="User ID")
185
+ version: Optional[str] = Field(default="1.0", description="Version of the checklist")
186
+
187
+ class ChecklistData(BaseModel):
188
+ """
189
+ Complete checklist data model
190
+
191
+ Attributes:
192
+ userId: Unique identifier for the user
193
+ title: Title of the checklist
194
+ sections: List of sections in the checklist
195
+ totalItems: Total number of items across all sections
196
+ completedItems: Number of completed items
197
+ nonCompliantItems: Number of non-compliant items
198
+ complianceScore: Calculated compliance percentage
199
+ verificationDate: Date when the checklist was verified
200
+ createdAt: Timestamp when the checklist was created
201
+ updatedAt: Timestamp when the checklist was last updated
202
+ """
203
+ userId: str = Field(..., description="Unique identifier for the user")
204
+ title: str = Field(..., description="Title of the checklist")
205
+ sections: List[ChecklistSection] = Field(default=[], description="List of checklist sections")
206
+ totalItems: Optional[int] = Field(default=0, description="Total number of items")
207
+ completedItems: Optional[int] = Field(default=0, description="Number of completed items")
208
+ nonCompliantItems: Optional[int] = Field(default=0, description="Number of non-compliant items")
209
+ complianceScore: Optional[float] = Field(default=0.0, description="Compliance percentage score")
210
+ verificationDate: Optional[datetime] = Field(default=None, description="Date of verification")
211
+ createdAt: Optional[datetime] = Field(default=None, description="Creation timestamp")
212
+ updatedAt: Optional[datetime] = Field(default=None, description="Last update timestamp")
213
+ metadata: Optional[Metadata] = Field(default=None, description="Additional metadata including user information")
214
+
215
+ class Config:
216
+ # Allow extra fields that might be sent from frontend
217
+ extra = "allow"
218
+
219
+ class ChecklistResponse(BaseModel):
220
+ """
221
+ Standard API response model for checklist operations
222
+
223
+ Attributes:
224
+ success: Boolean indicating if the operation was successful
225
+ data: The checklist data (if successful)
226
+ message: Optional message describing the result
227
+ error: Error message (if unsuccessful)
228
+ """
229
+ success: bool = Field(..., description="Whether the operation was successful")
230
+ data: Optional[Union[ChecklistData, Dict[str, Any]]] = Field(default=None, description="Checklist data")
231
+ message: Optional[str] = Field(default=None, description="Success message")
232
+ error: Optional[str] = Field(default=None, description="Error message")
233
+
234
+ class HealthResponse(BaseModel):
235
+ """Health check response model"""
236
+ status: str = Field(..., description="Health status")
237
+ timestamp: datetime = Field(..., description="Current timestamp")
238
+ database: str = Field(..., description="Database connection status")
239
+ version: str = Field(default="1.0.0", description="API version")
240
+
241
+ # =============================================================================
242
+ # UTILITY FUNCTIONS
243
+ # =============================================================================
244
+
245
+ def calculate_checklist_metrics(checklist_data: Dict[str, Any]) -> Dict[str, Any]:
246
+ """
247
+ Calculate compliance metrics for a checklist
248
+
249
+ Args:
250
+ checklist_data: Dictionary containing checklist data
251
+
252
+ Returns:
253
+ Dictionary with calculated metrics (totalItems, completedItems, nonCompliantItems, complianceScore)
254
+ """
255
+ total_items = 0
256
+ completed_items = 0
257
+ non_compliant_items = 0
258
+
259
+ # Iterate through all sections and items
260
+ for section in checklist_data.get('sections', []):
261
+ for item in section.get('items', []):
262
+ total_items += 1
263
+
264
+ # Count completed items (those that are not N/A)
265
+ if item.get('compliance') != 'N/A':
266
+ completed_items += 1
267
+
268
+ # Count non-compliant items
269
+ if item.get('compliance') == 'Non-Compliant':
270
+ non_compliant_items += 1
271
+
272
+ # Calculate compliance score
273
+ compliance_score = (completed_items / total_items * 100) if total_items > 0 else 0
274
+
275
+ return {
276
+ 'totalItems': total_items,
277
+ 'completedItems': completed_items,
278
+ 'nonCompliantItems': non_compliant_items,
279
+ 'complianceScore': round(compliance_score, 2)
280
+ }
281
+
282
+ def serialize_checklist(checklist_doc: Dict[str, Any]) -> Dict[str, Any]:
283
+ """
284
+ Serialize MongoDB document to JSON-serializable format
285
+
286
+ Args:
287
+ checklist_doc: MongoDB document
288
+
289
+ Returns:
290
+ Dictionary with ObjectId converted to string
291
+ """
292
+ if checklist_doc and '_id' in checklist_doc:
293
+ checklist_doc['_id'] = str(checklist_doc['_id'])
294
+
295
+ # Convert datetime objects to ISO strings
296
+ for field in ['createdAt', 'updatedAt', 'verificationDate']:
297
+ if field in checklist_doc and checklist_doc[field]:
298
+ # Only convert if it's a datetime object, not a string
299
+ if hasattr(checklist_doc[field], 'isoformat'):
300
+ checklist_doc[field] = checklist_doc[field].isoformat()
301
+
302
+ # Convert datetime objects in items
303
+ for section in checklist_doc.get('sections', []):
304
+ for item in section.get('items', []):
305
+ if 'checkedAt' in item and item['checkedAt']:
306
+ # Only convert if it's a datetime object, not a string
307
+ if hasattr(item['checkedAt'], 'isoformat'):
308
+ item['checkedAt'] = item['checkedAt'].isoformat()
309
+
310
+ return checklist_doc
311
+
312
+ # =============================================================================
313
+ # API ENDPOINTS
314
+ # =============================================================================
315
+
316
+ @app.on_event("startup")
317
+ async def startup_event():
318
+ """
319
+ Application startup event handler
320
+
321
+ This function is called when the FastAPI application starts up.
322
+ It initializes the MongoDB connection and sets up the database.
323
+ """
324
+ logger.info("Starting Audit Checklist API server...")
325
+ await connect_to_mongodb()
326
+ logger.info(f"Server ready on port {PORT}")
327
+
328
+ @app.on_event("shutdown")
329
+ async def shutdown_event():
330
+ """
331
+ Application shutdown event handler
332
+
333
+ This function is called when the FastAPI application shuts down.
334
+ It gracefully closes the MongoDB connection.
335
+ """
336
+ logger.info("Shutting down Audit Checklist API server...")
337
+ await close_mongodb_connection()
338
+
339
+ @app.get("/", response_model=Dict[str, str])
340
+ async def root():
341
+ """
342
+ Root endpoint providing basic API information
343
+
344
+ Returns:
345
+ Dictionary with API name and version
346
+ """
347
+ return {
348
+ "message": "Audit Checklist API",
349
+ "version": "1.0.0",
350
+ "docs": "/docs",
351
+ "health": "/health"
352
+ }
353
+
354
+ @app.get("/health", response_model=HealthResponse)
355
+ async def health_check():
356
+ """
357
+ Health check endpoint for monitoring and load balancers
358
+
359
+ This endpoint checks the status of the API server and database connection.
360
+ It's useful for health monitoring, load balancers, and DevOps automation.
361
+
362
+ Returns:
363
+ HealthResponse with current status information
364
+ """
365
+ try:
366
+ # Test database connection
367
+ await client.admin.command('ping')
368
+ db_status = "connected"
369
+ except Exception as e:
370
+ logger.error(f"Database health check failed: {e}")
371
+ db_status = "disconnected"
372
+
373
+ return HealthResponse(
374
+ status="healthy" if db_status == "connected" else "unhealthy",
375
+ timestamp=datetime.utcnow(),
376
+ database=db_status,
377
+ version="1.0.0"
378
+ )
379
+
380
+ @app.get("/api/checklist/{user_id}", response_model=ChecklistResponse)
381
+ async def get_checklist(user_id: str):
382
+ """
383
+ Retrieve checklist data for a specific user
384
+
385
+ This endpoint fetches the checklist data for a given user ID. If no checklist
386
+ exists for the user, it creates a new one with the default structure.
387
+
388
+ Args:
389
+ user_id: Unique identifier for the user
390
+
391
+ Returns:
392
+ ChecklistResponse containing the checklist data
393
+
394
+ Raises:
395
+ HTTPException: If database operation fails
396
+ """
397
+ try:
398
+ logger.info(f"Fetching checklist for user: {user_id}")
399
+
400
+ # Query the database for the user's checklist
401
+ # Find the most recent checklist for the user
402
+ checklist_doc = await db.checklists.find_one(
403
+ {"userId": user_id},
404
+ sort=[("createdAt", -1)] # Get the most recent one
405
+ )
406
+
407
+ if not checklist_doc:
408
+ # Create new checklist if none exists
409
+ logger.info(f"No checklist found for user {user_id}, creating new one")
410
+
411
+ # Default checklist structure - Complete 38-item audit checklist
412
+ default_checklist = {
413
+ "userId": user_id,
414
+ "title": "Checklist di Audit Operativo (38 Controlli)",
415
+ "sections": [
416
+ {
417
+ "id": "S1",
418
+ "title": "1. PERSONALE E IGIENE (6 Controlli)",
419
+ "icon": "Users",
420
+ "items": [
421
+ {"id": "I1.1", "requirement": "Indumenti da lavoro puliti (divisa, grembiule).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
422
+ {"id": "I1.2", "requirement": "Scarpe antinfortunistiche / Calzature pulite.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
423
+ {"id": "I1.3", "requirement": "Cuffie e/o Retine per capelli indossate correttamente.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
424
+ {"id": "I1.4", "requirement": "Assenza di gioielli, piercing visibili, unghie lunghe.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
425
+ {"id": "I1.5", "requirement": "Lavaggio mani documentato all'ingresso/reingresso.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
426
+ {"id": "I1.6", "requirement": "Assenza di cibo/bevande non autorizzate nelle aree produttive.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
427
+ ]
428
+ },
429
+ {
430
+ "id": "S2",
431
+ "title": "2. STRUTTURE E IMPIANTI (6 Controlli)",
432
+ "icon": "Building",
433
+ "items": [
434
+ {"id": "I2.1", "requirement": "Illuminazione adeguata e funzionante in tutte le aree.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
435
+ {"id": "I2.2", "requirement": "Porte esterne/interne in buone condizioni e chiuse correttamente (sigillatura).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
436
+ {"id": "I2.3", "requirement": "Integrità di pavimenti, pareti e soffitti (assenza di crepe/danni).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
437
+ {"id": "I2.4", "requirement": "Controllo vetri / lampade protette o anti-frantumazione.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
438
+ {"id": "I2.5", "requirement": "Condizioni igieniche dei servizi igienici e spogliatoi (pulizia e dotazioni).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
439
+ {"id": "I2.6", "requirement": "Ventilazione e aspirazione funzionanti, pulite e non ostruite.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
440
+ ]
441
+ },
442
+ {
443
+ "id": "S3",
444
+ "title": "3. GESTIONE E IGIENE AMBIENTALE (4 Controlli)",
445
+ "icon": "Package",
446
+ "items": [
447
+ {"id": "I3.1", "requirement": "Contenitori dei rifiuti puliti, chiusi e identificati.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
448
+ {"id": "I3.2", "requirement": "Separazione corretta dei rifiuti (es. umido, secco, plastica).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
449
+ {"id": "I3.3", "requirement": "Area di stoccaggio rifiuti (interna ed esterna) ordinata e sanificata.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
450
+ {"id": "I3.4", "requirement": "Frequenza di rimozione dei rifiuti adeguata a prevenire accumuli.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
451
+ ]
452
+ },
453
+ {
454
+ "id": "S4",
455
+ "title": "4. CONTROLLO PROCESSO E QUALITÀ (6 Controlli)",
456
+ "icon": "Settings",
457
+ "items": [
458
+ {"id": "I4.1", "requirement": "Monitoraggio e registrazione dei Punti Critici di Controllo (CCP).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
459
+ {"id": "I4.2", "requirement": "Procedure di Buona Fabbricazione (GMP) rispettate (es. pulizia prima dell'avvio).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
460
+ {"id": "I4.3", "requirement": "Verifica e calibrazione del Metal Detector eseguita e registrata.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
461
+ {"id": "I4.4", "requirement": "Corretta identificazione dei lotti di materie prime e semilavorati.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
462
+ {"id": "I4.5", "requirement": "Rispettate le temperature di stoccaggio dei prodotti finiti e intermedi.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
463
+ {"id": "I4.6", "requirement": "Corretta gestione, etichettatura e isolamento dei prodotti non conformi.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
464
+ ]
465
+ },
466
+ {
467
+ "id": "S5",
468
+ "title": "5. CONTROLLO INFESTANTI (5 Controlli)",
469
+ "icon": "Shield",
470
+ "items": [
471
+ {"id": "I5.1", "requirement": "Assenza di tracce visibili di roditori, insetti o uccelli.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
472
+ {"id": "I5.2", "requirement": "Stazioni di monitoraggio/trappole numerate, integre e registrate.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
473
+ {"id": "I5.3", "requirement": "Mappe e registri delle trappole aggiornati e disponibili.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
474
+ {"id": "I5.4", "requirement": "Barriere fisiche (es. reti, spazzole) anti-intrusione in buone condizioni.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
475
+ {"id": "I5.5", "requirement": "Prodotti chimici di controllo infestanti stoccati in modo sicuro e separato.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
476
+ ]
477
+ },
478
+ {
479
+ "id": "S6",
480
+ "title": "6. MANUTENZIONE E VETRI (6 Controlli)",
481
+ "icon": "Settings",
482
+ "items": [
483
+ {"id": "I6.1", "requirement": "Piano di manutenzione preventiva e correttiva rispettato e registrato.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
484
+ {"id": "I6.2", "requirement": "Lubrificanti e oli utilizzati autorizzati per uso alimentare (se richiesto).", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
485
+ {"id": "I6.3", "requirement": "Registri di calibrazione/verifica degli strumenti di misura critici aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
486
+ {"id": "I6.4", "requirement": "Gestione dei vetri rotti (registri rotture) aggiornata e conforme alla procedura.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
487
+ {"id": "I6.5", "requirement": "Assenza di perdite, gocciolamenti o cavi scoperti da impianti/macchinari.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
488
+ {"id": "I6.6", "requirement": "Area di deposito utensili e pezzi di ricambio ordinata e pulita.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
489
+ ]
490
+ },
491
+ {
492
+ "id": "S7",
493
+ "title": "7. DOCUMENTAZIONE E FORMAZIONE (5 Controlli)",
494
+ "icon": "FileText",
495
+ "items": [
496
+ {"id": "I7.1", "requirement": "Documentazione di processo (procedure, istruzioni) aggiornata e disponibile.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
497
+ {"id": "I7.2", "requirement": "Registri di formazione del personale aggiornati e verificabili.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
498
+ {"id": "I7.3", "requirement": "Certificazioni e attestati del personale validi e aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
499
+ {"id": "I7.4", "requirement": "Controllo documenti (versioni, distribuzione, obsolescenza) conforme.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""},
500
+ {"id": "I7.5", "requirement": "Archiviazione e conservazione documenti secondo i requisiti normativi.", "compliance": "N/A", "deviation": "", "action": "", "checkedAt": None, "checkedBy": ""}
501
+ ]
502
+ }
503
+ ],
504
+ "totalItems": 38,
505
+ "completedItems": 0,
506
+ "nonCompliantItems": 0,
507
+ "complianceScore": 0.0,
508
+ "verificationDate": None,
509
+ "createdAt": datetime.utcnow(),
510
+ "updatedAt": datetime.utcnow()
511
+ }
512
+
513
+ # Insert the new checklist
514
+ result = await db.checklists.insert_one(default_checklist)
515
+ checklist_doc = await db.checklists.find_one({"_id": result.inserted_id})
516
+ logger.info(f"Created new checklist for user {user_id}")
517
+
518
+ # Serialize the document
519
+ serialized_checklist = serialize_checklist(checklist_doc)
520
+
521
+ return ChecklistResponse(
522
+ success=True,
523
+ data=ChecklistData(**serialized_checklist),
524
+ message="Checklist retrieved successfully"
525
+ )
526
+
527
+ except Exception as e:
528
+ logger.error(f"Error retrieving checklist for user {user_id}: {e}")
529
+ raise HTTPException(
530
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
531
+ detail=f"Failed to retrieve checklist: {str(e)}"
532
+ )
533
+
534
+ @app.put("/api/checklist/{user_id}", response_model=ChecklistResponse)
535
+ async def save_checklist(user_id: str, checklist_data: dict):
536
+ """
537
+ Save or update checklist data for a specific user
538
+
539
+ This endpoint saves the checklist data to the database. It calculates
540
+ compliance metrics and updates the timestamp.
541
+
542
+ Args:
543
+ user_id: Unique identifier for the user
544
+ checklist_data: The checklist data to save
545
+
546
+ Returns:
547
+ ChecklistResponse confirming the save operation
548
+
549
+ Raises:
550
+ HTTPException: If database operation fails
551
+ """
552
+ try:
553
+ logger.info(f"Saving checklist for user: {user_id}")
554
+
555
+ # Use the dictionary directly (already converted from JSON)
556
+ checklist_dict = checklist_data.copy()
557
+
558
+ # Calculate compliance metrics
559
+ metrics = calculate_checklist_metrics(checklist_dict)
560
+ checklist_dict.update(metrics)
561
+
562
+ # Update timestamps
563
+ checklist_dict['updatedAt'] = datetime.utcnow()
564
+
565
+ # Always create a new checklist session (don't update existing)
566
+ # Add a unique session ID to make each save unique
567
+ session_id = f"{user_id}-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}"
568
+ checklist_dict['sessionId'] = session_id
569
+
570
+ # Remove _id if it exists to let MongoDB generate a new one
571
+ if '_id' in checklist_dict:
572
+ del checklist_dict['_id']
573
+
574
+ # Insert as a new document (always create new session)
575
+ result = await db.checklists.insert_one(checklist_dict)
576
+
577
+ if result.inserted_id:
578
+ logger.info(f"Created new checklist session for user {user_id}")
579
+ message = "Checklist session created successfully"
580
+ else:
581
+ logger.error(f"Failed to create checklist for user {user_id}")
582
+ raise HTTPException(
583
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
584
+ detail="Failed to save checklist"
585
+ )
586
+
587
+ # Retrieve the newly created checklist
588
+ created_checklist = await db.checklists.find_one({"_id": result.inserted_id})
589
+ serialized_checklist = serialize_checklist(created_checklist)
590
+
591
+ return ChecklistResponse(
592
+ success=True,
593
+ data=serialized_checklist, # Return as dict instead of Pydantic model
594
+ message=message
595
+ )
596
+
597
+ except Exception as e:
598
+ logger.error(f"Error saving checklist for user {user_id}: {e}")
599
+ raise HTTPException(
600
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
601
+ detail=f"Failed to save checklist: {str(e)}"
602
+ )
603
+
604
+ @app.get("/api/checklists/by-user/{user_name}", response_model=Dict[str, Any])
605
+ async def get_checklists_by_user_name(user_name: str):
606
+ """
607
+ Retrieve all checklists for a specific user name
608
+
609
+ Args:
610
+ user_name: The name of the user to retrieve checklists for
611
+
612
+ Returns:
613
+ Dictionary containing list of checklists for the user
614
+
615
+ Raises:
616
+ HTTPException: If database operation fails
617
+ """
618
+ try:
619
+ logger.info(f"Retrieving checklists for user: {user_name}")
620
+
621
+ # Find all checklists where metadata.userName matches
622
+ cursor = db.checklists.find({"metadata.userName": user_name})
623
+ checklists = []
624
+
625
+ async for checklist_doc in cursor:
626
+ serialized_checklist = serialize_checklist(checklist_doc)
627
+ checklists.append(serialized_checklist)
628
+
629
+ return {
630
+ "success": True,
631
+ "data": checklists,
632
+ "count": len(checklists),
633
+ "user_name": user_name
634
+ }
635
+
636
+ except Exception as e:
637
+ logger.error(f"Error retrieving checklists for user {user_name}: {e}")
638
+ raise HTTPException(
639
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
640
+ detail=f"Failed to retrieve checklists for user: {str(e)}"
641
+ )
642
+
643
+ @app.get("/api/checklists", response_model=Dict[str, Any])
644
+ async def get_all_checklists():
645
+ """
646
+ Retrieve all checklists (admin endpoint)
647
+
648
+ This endpoint returns all checklists in the database. It's intended for
649
+ administrative purposes and should be protected with proper authentication
650
+ in a production environment.
651
+
652
+ Returns:
653
+ Dictionary containing list of all checklists
654
+
655
+ Raises:
656
+ HTTPException: If database operation fails
657
+ """
658
+ try:
659
+ logger.info("Retrieving all checklists")
660
+
661
+ # Find all checklists
662
+ cursor = db.checklists.find({})
663
+ checklists = []
664
+
665
+ async for checklist_doc in cursor:
666
+ serialized_checklist = serialize_checklist(checklist_doc)
667
+ checklists.append(serialized_checklist)
668
+
669
+ return {
670
+ "success": True,
671
+ "data": checklists,
672
+ "count": len(checklists),
673
+ "message": f"Retrieved {len(checklists)} checklists"
674
+ }
675
+
676
+ except Exception as e:
677
+ logger.error(f"Error retrieving all checklists: {e}")
678
+ raise HTTPException(
679
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
680
+ detail=f"Failed to retrieve checklists: {str(e)}"
681
+ )
682
+
683
+ # =============================================================================
684
+ # ERROR HANDLERS
685
+ # =============================================================================
686
+
687
+ @app.exception_handler(HTTPException)
688
+ async def http_exception_handler(request, exc):
689
+ """Handle HTTP exceptions with consistent error format"""
690
+ logger.error(f"HTTP Exception: {exc.detail}")
691
+ return JSONResponse(
692
+ status_code=exc.status_code,
693
+ content={
694
+ "success": False,
695
+ "error": exc.detail,
696
+ "status_code": exc.status_code
697
+ }
698
+ )
699
+
700
+ @app.exception_handler(Exception)
701
+ async def general_exception_handler(request, exc):
702
+ """Handle general exceptions with logging"""
703
+ logger.error(f"Unhandled exception: {exc}", exc_info=True)
704
+ return JSONResponse(
705
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
706
+ content={
707
+ "success": False,
708
+ "error": "Internal server error",
709
+ "detail": str(exc) if os.getenv("DEBUG", "false").lower() == "true" else "An unexpected error occurred"
710
+ }
711
+ )
712
+
713
+ # =============================================================================
714
+ # MAIN EXECUTION
715
+ # =============================================================================
716
+
717
+ if __name__ == "__main__":
718
+ """
719
+ Main execution block for running the server
720
+
721
+ This block is executed when the script is run directly (not imported).
722
+ It starts the Uvicorn ASGI server with the configured settings.
723
+ """
724
+ import uvicorn
725
+
726
+ logger.info("Starting FastAPI server...")
727
+
728
+ # Run the server
729
+ uvicorn.run(
730
+ "app:app", # Changed from "main:app" to "app:app" for HF Spaces
731
+ host="0.0.0.0",
732
+ port=PORT,
733
+ reload=False, # Disable auto-reload for production deployment
734
+ log_level="info"
735
+ )
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FastAPI and related dependencies
2
+ fastapi==0.104.1
3
+ uvicorn[standard]==0.24.0
4
+ pydantic==2.5.0
5
+
6
+ # MongoDB integration
7
+ motor==3.3.2 # Async MongoDB driver
8
+ pymongo==4.6.0 # MongoDB driver
9
+
10
+ # Environment variables
11
+ python-dotenv==1.0.0
12
+
13
+ # CORS middleware
14
+ python-multipart==0.0.6
15
+
16
+ # Date/time handling
17
+ python-dateutil==2.8.2
18
+
19
+ # Additional utilities
20
+ httpx==0.25.2 # For making HTTP requests if needed