Ali2206 commited on
Commit
623a0a4
·
1 Parent(s): 1bb5c06

taw bch yekhdem

Browse files
Files changed (1) hide show
  1. app.py +1034 -83
app.py CHANGED
@@ -18,12 +18,13 @@ Version: 1.0.0
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
 
@@ -37,17 +38,8 @@ from bson import ObjectId
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
- print("✅ Loaded environment variables from mongodb.env")
45
- except:
46
- try:
47
- load_dotenv('.env')
48
- print("✅ Loaded environment variables from .env")
49
- except:
50
- print("ℹ️ No .env file found, using system environment variables")
51
 
52
  # Configure logging
53
  logging.basicConfig(
@@ -65,16 +57,6 @@ MONGODB_URI = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/audit_checklis
65
  PORT = int(os.getenv('PORT', 8000)) # Hugging Face Spaces uses port 8000
66
  CORS_ORIGIN = os.getenv('CORS_ORIGIN', '*') # Allow all origins by default for mobile apps
67
 
68
- # Debug logging for environment variables
69
- logger.info(f"MONGODB_URI: {'*' * 50 if MONGODB_URI.startswith('mongodb+srv://') else MONGODB_URI}")
70
- logger.info(f"PORT: {PORT}")
71
- logger.info(f"CORS_ORIGIN: {CORS_ORIGIN}")
72
-
73
- # Check if we're using the default MongoDB URI (which means env var is not set)
74
- if MONGODB_URI == 'mongodb://localhost:27017/audit_checklist':
75
- logger.warning("⚠️ MONGODB_URI environment variable not set! Using default localhost connection.")
76
- logger.warning("⚠️ Please set MONGODB_URI environment variable in Hugging Face Space settings.")
77
-
78
  # Initialize FastAPI application
79
  app = FastAPI(
80
  title="Audit Checklist API",
@@ -214,6 +196,8 @@ class ChecklistData(BaseModel):
214
  verificationDate: Date when the checklist was verified
215
  createdAt: Timestamp when the checklist was created
216
  updatedAt: Timestamp when the checklist was last updated
 
 
217
  """
218
  userId: str = Field(..., description="Unique identifier for the user")
219
  title: str = Field(..., description="Title of the checklist")
@@ -226,6 +210,8 @@ class ChecklistData(BaseModel):
226
  createdAt: Optional[datetime] = Field(default=None, description="Creation timestamp")
227
  updatedAt: Optional[datetime] = Field(default=None, description="Last update timestamp")
228
  metadata: Optional[Metadata] = Field(default=None, description="Additional metadata including user information")
 
 
229
 
230
  class Config:
231
  # Allow extra fields that might be sent from frontend
@@ -304,30 +290,25 @@ def serialize_checklist(checklist_doc: Dict[str, Any]) -> Dict[str, Any]:
304
  Returns:
305
  Dictionary with ObjectId converted to string
306
  """
307
- import copy
308
- import json
309
- from bson import ObjectId
310
-
311
- # Make a deep copy to avoid modifying the original document
312
- serialized = copy.deepcopy(checklist_doc)
313
-
314
- # Convert ObjectId to string recursively
315
- def convert_objectid(obj):
316
- if isinstance(obj, ObjectId):
317
- return str(obj)
318
- elif isinstance(obj, dict):
319
- return {key: convert_objectid(value) for key, value in obj.items()}
320
- elif isinstance(obj, list):
321
- return [convert_objectid(item) for item in obj]
322
- elif hasattr(obj, 'isoformat'): # datetime objects
323
- return obj.isoformat()
324
- else:
325
- return obj
326
 
327
- # Apply ObjectId conversion to the entire document
328
- serialized = convert_objectid(serialized)
 
 
 
 
329
 
330
- return serialized
 
 
 
 
 
 
 
 
331
 
332
  # =============================================================================
333
  # API ENDPOINTS
@@ -425,8 +406,8 @@ async def get_checklist(user_id: str):
425
  )
426
 
427
  if not checklist_doc:
428
- # Return default checklist structure without saving to database
429
- logger.info(f"No checklist found for user {user_id}, returning default structure")
430
 
431
  # Default checklist structure - Complete 38-item audit checklist
432
  default_checklist = {
@@ -530,8 +511,10 @@ async def get_checklist(user_id: str):
530
  "updatedAt": datetime.utcnow()
531
  }
532
 
533
- # Return the default checklist without saving to database
534
- checklist_doc = default_checklist
 
 
535
 
536
  # Serialize the document
537
  serialized_checklist = serialize_checklist(checklist_doc)
@@ -577,45 +560,76 @@ async def save_checklist(user_id: str, checklist_data: dict):
577
  metrics = calculate_checklist_metrics(checklist_dict)
578
  checklist_dict.update(metrics)
579
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  # Update timestamps
581
  checklist_dict['updatedAt'] = datetime.utcnow()
582
 
583
- # Always create a new checklist session (don't update existing)
584
- # Add a unique session ID to make each save unique
585
- session_id = f"{user_id}-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}"
586
- checklist_dict['sessionId'] = session_id
587
 
588
- # Ensure metadata exists and contains user information
589
- if 'metadata' not in checklist_dict:
590
- checklist_dict['metadata'] = {}
591
-
592
- # Preserve existing userName or set default
593
- if 'userName' not in checklist_dict['metadata']:
594
- checklist_dict['metadata']['userName'] = 'Unknown User'
595
- checklist_dict['metadata']['userId'] = user_id
596
- checklist_dict['metadata']['savedAt'] = datetime.utcnow().isoformat()
597
- checklist_dict['metadata']['savedAtFormatted'] = datetime.utcnow().strftime('%d/%m/%Y, %H:%M:%S')
598
-
599
- # Remove _id if it exists to let MongoDB generate a new one
600
- if '_id' in checklist_dict:
601
- del checklist_dict['_id']
602
-
603
- # Insert as a new document (always create new session)
604
- result = await db.checklists.insert_one(checklist_dict)
605
-
606
- if result.inserted_id:
607
- logger.info(f"Created new checklist session for user {user_id}")
608
- message = "Checklist session created successfully"
609
- else:
610
- logger.error(f"Failed to create checklist for user {user_id}")
611
- raise HTTPException(
612
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
613
- detail="Failed to save checklist"
614
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
615
 
616
- # Retrieve the newly created checklist
617
- created_checklist = await db.checklists.find_one({"_id": result.inserted_id})
618
- serialized_checklist = serialize_checklist(created_checklist)
 
 
 
 
 
 
619
 
620
  return ChecklistResponse(
621
  success=True,
@@ -669,6 +683,943 @@ async def get_checklists_by_user_name(user_name: str):
669
  detail=f"Failed to retrieve checklists for user: {str(e)}"
670
  )
671
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  @app.get("/api/checklists", response_model=Dict[str, Any])
673
  async def get_all_checklists():
674
  """
@@ -756,9 +1707,9 @@ if __name__ == "__main__":
756
 
757
  # Run the server
758
  uvicorn.run(
759
- "app:app", # Changed from "main:app" to "app:app" for HF Spaces
760
  host="0.0.0.0",
761
  port=PORT,
762
- reload=False, # Disable auto-reload for production deployment
763
  log_level="info"
764
  )
 
18
 
19
  import os
20
  import sys
21
+ import base64
22
  from datetime import datetime
23
  from typing import Optional, List, Dict, Any, Union
24
  import logging
25
 
26
  # FastAPI imports
27
+ from fastapi import FastAPI, HTTPException, status, UploadFile, File
28
  from fastapi.middleware.cors import CORSMiddleware
29
  from fastapi.responses import JSONResponse
30
 
 
38
  # Environment variables
39
  from dotenv import load_dotenv
40
 
41
+ # Load environment variables from .env file
42
+ load_dotenv('mongodb.env')
 
 
 
 
 
 
 
 
 
43
 
44
  # Configure logging
45
  logging.basicConfig(
 
57
  PORT = int(os.getenv('PORT', 8000)) # Hugging Face Spaces uses port 8000
58
  CORS_ORIGIN = os.getenv('CORS_ORIGIN', '*') # Allow all origins by default for mobile apps
59
 
 
 
 
 
 
 
 
 
 
 
60
  # Initialize FastAPI application
61
  app = FastAPI(
62
  title="Audit Checklist API",
 
196
  verificationDate: Date when the checklist was verified
197
  createdAt: Timestamp when the checklist was created
198
  updatedAt: Timestamp when the checklist was last updated
199
+ imageData: Base64 encoded image data (stored when compliance < 100%)
200
+ imageType: MIME type of the image (e.g., image/jpeg, image/png)
201
  """
202
  userId: str = Field(..., description="Unique identifier for the user")
203
  title: str = Field(..., description="Title of the checklist")
 
210
  createdAt: Optional[datetime] = Field(default=None, description="Creation timestamp")
211
  updatedAt: Optional[datetime] = Field(default=None, description="Last update timestamp")
212
  metadata: Optional[Metadata] = Field(default=None, description="Additional metadata including user information")
213
+ imageData: Optional[str] = Field(default=None, description="Base64 encoded image data for non-compliant checklists")
214
+ imageType: Optional[str] = Field(default=None, description="MIME type of the image")
215
 
216
  class Config:
217
  # Allow extra fields that might be sent from frontend
 
290
  Returns:
291
  Dictionary with ObjectId converted to string
292
  """
293
+ if checklist_doc and '_id' in checklist_doc:
294
+ checklist_doc['_id'] = str(checklist_doc['_id'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
+ # Convert datetime objects to ISO strings
297
+ for field in ['createdAt', 'updatedAt', 'verificationDate']:
298
+ if field in checklist_doc and checklist_doc[field]:
299
+ # Only convert if it's a datetime object, not a string
300
+ if hasattr(checklist_doc[field], 'isoformat'):
301
+ checklist_doc[field] = checklist_doc[field].isoformat()
302
 
303
+ # Convert datetime objects in items
304
+ for section in checklist_doc.get('sections', []):
305
+ for item in section.get('items', []):
306
+ if 'checkedAt' in item and item['checkedAt']:
307
+ # Only convert if it's a datetime object, not a string
308
+ if hasattr(item['checkedAt'], 'isoformat'):
309
+ item['checkedAt'] = item['checkedAt'].isoformat()
310
+
311
+ return checklist_doc
312
 
313
  # =============================================================================
314
  # API ENDPOINTS
 
406
  )
407
 
408
  if not checklist_doc:
409
+ # Create new checklist if none exists
410
+ logger.info(f"No checklist found for user {user_id}, creating new one")
411
 
412
  # Default checklist structure - Complete 38-item audit checklist
413
  default_checklist = {
 
511
  "updatedAt": datetime.utcnow()
512
  }
513
 
514
+ # Insert the new checklist
515
+ result = await db.checklists.insert_one(default_checklist)
516
+ checklist_doc = await db.checklists.find_one({"_id": result.inserted_id})
517
+ logger.info(f"Created new checklist for user {user_id}")
518
 
519
  # Serialize the document
520
  serialized_checklist = serialize_checklist(checklist_doc)
 
560
  metrics = calculate_checklist_metrics(checklist_dict)
561
  checklist_dict.update(metrics)
562
 
563
+ # Handle image data for non-compliant checklists
564
+ compliance_score = metrics.get('complianceScore', 0.0)
565
+ if compliance_score < 100.0 and 'collectedImages' in checklist_dict and checklist_dict['collectedImages']:
566
+ # Process collected images from individual items
567
+ collected_images = checklist_dict['collectedImages']
568
+ logger.info(f"Storing {len(collected_images)} images for non-compliant checklist (score: {compliance_score}%)")
569
+
570
+ # Store the collected images in the checklist data
571
+ checklist_dict['imageData'] = collected_images
572
+ checklist_dict['imageType'] = 'multiple_items'
573
+ elif compliance_score < 100.0:
574
+ logger.info(f"No image data provided for non-compliant checklist (score: {compliance_score}%)")
575
+
576
  # Update timestamps
577
  checklist_dict['updatedAt'] = datetime.utcnow()
578
 
579
+ # Check if this is an update to existing checklist or new save
580
+ existing_checklist = await db.checklists.find_one({"userId": user_id})
 
 
581
 
582
+ if existing_checklist:
583
+ # Update existing checklist
584
+ checklist_dict['createdAt'] = existing_checklist.get('createdAt', datetime.utcnow())
585
+
586
+ # Remove _id if it exists to prevent immutable field error
587
+ if '_id' in checklist_dict:
588
+ del checklist_dict['_id']
589
+
590
+ result = await db.checklists.update_one(
591
+ {"userId": user_id},
592
+ {"$set": checklist_dict}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  )
594
+
595
+ if result.modified_count > 0:
596
+ logger.info(f"Updated existing checklist for user {user_id}")
597
+ message = "Checklist updated successfully"
598
+ else:
599
+ logger.error(f"Failed to update checklist for user {user_id}")
600
+ raise HTTPException(
601
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
602
+ detail="Failed to update checklist"
603
+ )
604
+ else:
605
+ # Create new checklist
606
+ checklist_dict['createdAt'] = datetime.utcnow()
607
+
608
+ # Remove _id if it exists to let MongoDB generate a new one
609
+ if '_id' in checklist_dict:
610
+ del checklist_dict['_id']
611
+
612
+ result = await db.checklists.insert_one(checklist_dict)
613
+
614
+ if result.inserted_id:
615
+ logger.info(f"Created new checklist for user {user_id}")
616
+ message = "Checklist created successfully"
617
+ else:
618
+ logger.error(f"Failed to create checklist for user {user_id}")
619
+ raise HTTPException(
620
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
621
+ detail="Failed to save checklist"
622
+ )
623
 
624
+ # Retrieve the updated/created checklist
625
+ if existing_checklist:
626
+ # For updates, get the updated document
627
+ updated_checklist = await db.checklists.find_one({"userId": user_id})
628
+ serialized_checklist = serialize_checklist(updated_checklist)
629
+ else:
630
+ # For new checklists, get the newly created document
631
+ created_checklist = await db.checklists.find_one({"_id": result.inserted_id})
632
+ serialized_checklist = serialize_checklist(created_checklist)
633
 
634
  return ChecklistResponse(
635
  success=True,
 
683
  detail=f"Failed to retrieve checklists for user: {str(e)}"
684
  )
685
 
686
+ @app.post("/api/checklist/{user_id}/image")
687
+ async def upload_checklist_image(user_id: str, image: UploadFile = File(...)):
688
+ """
689
+ Upload an image for a checklist (for non-compliant cases)
690
+
691
+ Args:
692
+ user_id: Unique identifier for the user
693
+ image: The image file to upload
694
+
695
+ Returns:
696
+ Dictionary containing the base64 encoded image data and metadata
697
+ """
698
+ try:
699
+ logger.info(f"Uploading image for user: {user_id}")
700
+
701
+ # Validate image file type
702
+ allowed_types = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"]
703
+ if image.content_type not in allowed_types:
704
+ raise HTTPException(
705
+ status_code=status.HTTP_400_BAD_REQUEST,
706
+ detail=f"Invalid image type. Allowed types: {', '.join(allowed_types)}"
707
+ )
708
+
709
+ # Read image data
710
+ image_data = await image.read()
711
+
712
+ # Encode to base64
713
+ base64_data = base64.b64encode(image_data).decode('utf-8')
714
+
715
+ # Create data URL
716
+ data_url = f"data:{image.content_type};base64,{base64_data}"
717
+
718
+ logger.info(f"Image uploaded successfully for user {user_id}, size: {len(image_data)} bytes")
719
+
720
+ return {
721
+ "success": True,
722
+ "data": {
723
+ "imageData": base64_data,
724
+ "imageType": image.content_type,
725
+ "dataUrl": data_url,
726
+ "size": len(image_data),
727
+ "filename": image.filename
728
+ },
729
+ "message": "Image uploaded successfully"
730
+ }
731
+
732
+ except HTTPException:
733
+ raise
734
+ except Exception as e:
735
+ logger.error(f"Error uploading image for user {user_id}: {e}")
736
+ raise HTTPException(
737
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
738
+ detail=f"Failed to upload image: {str(e)}"
739
+ )
740
+
741
+ async def ensure_default_template_exists():
742
+ """
743
+ Ensure the default template exists in the database.
744
+ Creates it if it doesn't exist.
745
+ """
746
+ try:
747
+ logger.info("Checking if default template exists...")
748
+ # Check if template exists (check both old and new IDs)
749
+ template = await db.checklist_templates.find_one({
750
+ "$or": [
751
+ {"templateId": "default"},
752
+ {"templateId": "default-audit-checklist"}
753
+ ]
754
+ })
755
+
756
+ if not template:
757
+ logger.info("Default template not found, creating it now...")
758
+
759
+ # Create the default template
760
+ default_template = {
761
+ "templateId": "default",
762
+ "title": "Checklist di Audit Operativo",
763
+ "sections": [
764
+ {
765
+ "id": "S1",
766
+ "title": "1. PERSONALE E IGIENE",
767
+ "icon": "Users",
768
+ "items": [
769
+ {"id": "I1.1", "requirement": "Indumenti da lavoro puliti (divisa, grembiule)."},
770
+ {"id": "I1.2", "requirement": "Scarpe antinfortunistiche / Calzature pulite."},
771
+ {"id": "I1.3", "requirement": "Cuffie e/o Retine per capelli indossate correttamente."},
772
+ {"id": "I1.4", "requirement": "Assenza di gioielli, piercing visibili, unghie lunghe."},
773
+ {"id": "I1.5", "requirement": "Lavaggio mani documentato all'ingresso/reingresso."},
774
+ {"id": "I1.6", "requirement": "Assenza di cibo/bevande non autorizzate nelle aree produttive."},
775
+ ],
776
+ },
777
+ {
778
+ "id": "S2",
779
+ "title": "2. STRUTTURE E IMPIANTI",
780
+ "icon": "Building",
781
+ "items": [
782
+ {"id": "I2.1", "requirement": "Illuminazione adeguata e funzionante in tutte le aree."},
783
+ {"id": "I2.2", "requirement": "Porte esterne/interne in buone condizioni e chiuse correttamente (sigillatura)."},
784
+ {"id": "I2.3", "requirement": "Integrità di pavimenti, pareti e soffitti (assenza di crepe/danni)."},
785
+ {"id": "I2.4", "requirement": "Controllo vetri / lampade protette o anti-frantumazione."},
786
+ {"id": "I2.5", "requirement": "Condizioni igieniche dei servizi igienici e spogliatoi (pulizia e dotazioni)."},
787
+ {"id": "I2.6", "requirement": "Ventilazione e aspirazione funzionanti, pulite e non ostruite."},
788
+ ],
789
+ },
790
+ {
791
+ "id": "S3",
792
+ "title": "3. GESTIONE E IGIENE AMBIENTALE",
793
+ "icon": "Package",
794
+ "items": [
795
+ {"id": "I3.1", "requirement": "Contenitori dei rifiuti puliti, chiusi e identificati."},
796
+ {"id": "I3.2", "requirement": "Separazione corretta dei rifiuti (es. umido, secco, plastica)."},
797
+ {"id": "I3.3", "requirement": "Area di stoccaggio rifiuti (interna ed esterna) ordinata e sanificata."},
798
+ {"id": "I3.4", "requirement": "Frequenza di rimozione dei rifiuti adeguata a prevenire accumuli."},
799
+ ],
800
+ },
801
+ {
802
+ "id": "S4",
803
+ "title": "4. CONTROLLO PROCESSO E QUALITÀ",
804
+ "icon": "Settings",
805
+ "items": [
806
+ {"id": "I4.1", "requirement": "Monitoraggio e registrazione dei Punti Critici di Controllo (CCP)."},
807
+ {"id": "I4.2", "requirement": "Procedure di Buona Fabbricazione (GMP) rispettate (es. pulizia prima dell'avvio)."},
808
+ {"id": "I4.3", "requirement": "Verifica e calibrazione del Metal Detector eseguita e registrata."},
809
+ {"id": "I4.4", "requirement": "Corretta identificazione dei lotti di materie prime e semilavorati."},
810
+ {"id": "I4.5", "requirement": "Rispettate le temperature di stoccaggio dei prodotti finiti e intermedi."},
811
+ {"id": "I4.6", "requirement": "Corretta gestione, etichettatura e isolamento dei prodotti non conformi."},
812
+ ],
813
+ },
814
+ {
815
+ "id": "S5",
816
+ "title": "5. CONTROLLO INFESTANTI",
817
+ "icon": "Shield",
818
+ "items": [
819
+ {"id": "I5.1", "requirement": "Assenza di tracce visibili di roditori, insetti o uccelli."},
820
+ {"id": "I5.2", "requirement": "Stazioni di monitoraggio/trappole numerate, integre e registrate."},
821
+ {"id": "I5.3", "requirement": "Mappe e registri delle trappole aggiornati e disponibili."},
822
+ {"id": "I5.4", "requirement": "Barriere fisiche (es. reti, spazzole) anti-intrusione in buone condizioni."},
823
+ {"id": "I5.5", "requirement": "Prodotti chimici di controllo infestanti stoccati in modo sicuro e separato."},
824
+ ],
825
+ },
826
+ {
827
+ "id": "S6",
828
+ "title": "6. MANUTENZIONE E VETRI",
829
+ "icon": "Settings",
830
+ "items": [
831
+ {"id": "I6.1", "requirement": "Piano di manutenzione preventiva e correttiva rispettato e registrato."},
832
+ {"id": "I6.2", "requirement": "Lubrificanti e oli utilizzati autorizzati per uso alimentare (se richiesto)."},
833
+ {"id": "I6.3", "requirement": "Registri di calibrazione/verifica degli strumenti di misura critici aggiornati."},
834
+ {"id": "I6.4", "requirement": "Gestione dei vetri rotti (registri rotture) aggiornata e conforme alla procedura."},
835
+ {"id": "I6.5", "requirement": "Assenza di perdite, gocciolamenti o cavi scoperti da impianti/macchinari."},
836
+ {"id": "I6.6", "requirement": "Area di deposito utensili e pezzi di ricambio ordinata e pulita."},
837
+ ],
838
+ },
839
+ {
840
+ "id": "S7",
841
+ "title": "7. DOCUMENTAZIONE E FORMAZIONE",
842
+ "icon": "FileText",
843
+ "items": [
844
+ {"id": "I7.1", "requirement": "Documentazione di processo (procedure, istruzioni) aggiornata e disponibile."},
845
+ {"id": "I7.2", "requirement": "Registri di formazione del personale aggiornati e verificabili."},
846
+ {"id": "I7.3", "requirement": "Certificazioni e attestati del personale validi e aggiornati."},
847
+ {"id": "I7.4", "requirement": "Controllo documenti (versioni, distribuzione, obsolescenza) conforme."},
848
+ {"id": "I7.5", "requirement": "Archiviazione e conservazione documenti secondo i requisiti normativi."},
849
+ ],
850
+ },
851
+ ],
852
+ "metadata": {
853
+ "createdAt": datetime.utcnow().isoformat(),
854
+ "updatedAt": datetime.utcnow().isoformat(),
855
+ }
856
+ }
857
+
858
+ result = await db.checklist_templates.insert_one(default_template)
859
+ logger.info(f"✅ Successfully created default template with ID: {result.inserted_id}")
860
+ # Fetch the newly created template to return with _id
861
+ template = await db.checklist_templates.find_one({"templateId": "default"})
862
+ return template
863
+ else:
864
+ logger.info("✅ Default template already exists in database")
865
+ return template
866
+
867
+ except Exception as e:
868
+ logger.error(f"❌ Error ensuring default template exists: {e}", exc_info=True)
869
+ return None
870
+
871
+ @app.get("/api/checklist-template/default", response_model=Dict[str, Any])
872
+ async def get_default_checklist_template():
873
+ """
874
+ Get the default checklist template
875
+
876
+ Returns:
877
+ Dictionary containing the default checklist template structure
878
+
879
+ Raises:
880
+ HTTPException: If template not found or database operation fails
881
+ """
882
+ try:
883
+ logger.info("📋 GET /api/checklist-template/default - Retrieving default checklist template")
884
+
885
+ # Ensure template exists (creates if not found)
886
+ template_doc = await ensure_default_template_exists()
887
+ logger.info(f"Template check result: {'Found' if template_doc else 'Not Found'}")
888
+
889
+ if not template_doc:
890
+ # Fallback to looking for old template ID
891
+ logger.info("Checking for old template ID: default-audit-checklist")
892
+ template_doc = await db.checklist_templates.find_one({"templateId": "default-audit-checklist"})
893
+
894
+ if not template_doc:
895
+ # If template doesn't exist, create it from the hardcoded structure
896
+ logger.info("Default template not found, creating from hardcoded structure")
897
+
898
+ # Use the same structure as in get_checklist but mark as template
899
+ default_template = {
900
+ "templateId": "default-audit-checklist",
901
+ "title": "Checklist di Audit Operativo (38 Controlli)",
902
+ "description": "Template per audit operativo in ambiente di produzione alimentare",
903
+ "version": "1.0",
904
+ "isTemplate": True,
905
+ "createdAt": datetime.utcnow(),
906
+ "updatedAt": datetime.utcnow(),
907
+ "sections": [
908
+ {
909
+ "id": "S1",
910
+ "title": "1. PERSONALE E IGIENE",
911
+ "icon": "Users",
912
+ "items": [
913
+ {"id": "I1.1", "requirement": "Indumenti da lavoro puliti (divisa, grembiule).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
914
+ {"id": "I1.2", "requirement": "Scarpe antinfortunistiche / Calzature pulite.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
915
+ {"id": "I1.3", "requirement": "Cuffie e/o Retine per capelli indossate correttamente.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
916
+ {"id": "I1.4", "requirement": "Assenza di gioielli, piercing visibili, unghie lunghe.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
917
+ {"id": "I1.5", "requirement": "Lavaggio mani documentato all'ingresso/reingresso.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
918
+ {"id": "I1.6", "requirement": "Assenza di cibo/bevande non autorizzate nelle aree produttive.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
919
+ ]
920
+ },
921
+ {
922
+ "id": "S2",
923
+ "title": "2. STRUTTURE E IMPIANTI",
924
+ "icon": "Building",
925
+ "items": [
926
+ {"id": "I2.1", "requirement": "Illuminazione adeguata e funzionante in tutte le aree.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
927
+ {"id": "I2.2", "requirement": "Porte esterne/interne in buone condizioni e chiuse correttamente (sigillatura).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
928
+ {"id": "I2.3", "requirement": "Integrità di pavimenti, pareti e soffitti (assenza di crepe/danni).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
929
+ {"id": "I2.4", "requirement": "Controllo vetri / lampade protette o anti-frantumazione.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
930
+ {"id": "I2.5", "requirement": "Condizioni igieniche dei servizi igienici e spogliatoi (pulizia e dotazioni).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
931
+ {"id": "I2.6", "requirement": "Ventilazione e aspirazione funzionanti, pulite e non ostruite.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
932
+ ]
933
+ },
934
+ {
935
+ "id": "S3",
936
+ "title": "3. GESTIONE E IGIENE AMBIENTALE",
937
+ "icon": "Package",
938
+ "items": [
939
+ {"id": "I3.1", "requirement": "Contenitori dei rifiuti puliti, chiusi e identificati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
940
+ {"id": "I3.2", "requirement": "Separazione corretta dei rifiuti (es. umido, secco, plastica).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
941
+ {"id": "I3.3", "requirement": "Area di stoccaggio rifiuti (interna ed esterna) ordinata e sanificata.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
942
+ {"id": "I3.4", "requirement": "Frequenza di rimozione dei rifiuti adeguata a prevenire accumuli.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
943
+ ]
944
+ },
945
+ {
946
+ "id": "S4",
947
+ "title": "4. CONTROLLO PROCESSO E QUALITÀ",
948
+ "icon": "Settings",
949
+ "items": [
950
+ {"id": "I4.1", "requirement": "Monitoraggio e registrazione dei Punti Critici di Controllo (CCP).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
951
+ {"id": "I4.2", "requirement": "Procedure di Buona Fabbricazione (GMP) rispettate (es. pulizia prima dell'avvio).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
952
+ {"id": "I4.3", "requirement": "Verifica e calibrazione del Metal Detector eseguita e registrata.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
953
+ {"id": "I4.4", "requirement": "Corretta identificazione dei lotti di materie prime e semilavorati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
954
+ {"id": "I4.5", "requirement": "Rispettate le temperature di stoccaggio dei prodotti finiti e intermedi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
955
+ {"id": "I4.6", "requirement": "Corretta gestione, etichettatura e isolamento dei prodotti non conformi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
956
+ ]
957
+ },
958
+ {
959
+ "id": "S5",
960
+ "title": "5. CONTROLLO INFESTANTI",
961
+ "icon": "Shield",
962
+ "items": [
963
+ {"id": "I5.1", "requirement": "Assenza di tracce visibili di roditori, insetti o uccelli.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
964
+ {"id": "I5.2", "requirement": "Stazioni di monitoraggio/trappole numerate, integre e registrate.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
965
+ {"id": "I5.3", "requirement": "Mappe e registri delle trappole aggiornati e disponibili.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
966
+ {"id": "I5.4", "requirement": "Barriere fisiche (es. reti, spazzole) anti-intrusione in buone condizioni.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
967
+ {"id": "I5.5", "requirement": "Prodotti chimici di controllo infestanti stoccati in modo sicuro e separato.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
968
+ ]
969
+ },
970
+ {
971
+ "id": "S6",
972
+ "title": "6. MANUTENZIONE E VETRI",
973
+ "icon": "Settings",
974
+ "items": [
975
+ {"id": "I6.1", "requirement": "Piano di manutenzione preventiva e correttiva rispettato e registrato.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
976
+ {"id": "I6.2", "requirement": "Lubrificanti e oli utilizzati autorizzati per uso alimentare (se richiesto).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
977
+ {"id": "I6.3", "requirement": "Registri di calibrazione/verifica degli strumenti di misura critici aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
978
+ {"id": "I6.4", "requirement": "Gestione dei vetri rotti (registri rotture) aggiornata e conforme alla procedura.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
979
+ {"id": "I6.5", "requirement": "Assenza di perdite, gocciolamenti o cavi scoperti da impianti/macchinari.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
980
+ {"id": "I6.6", "requirement": "Area di deposito utensili e pezzi di ricambio ordinata e pulita.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
981
+ ]
982
+ },
983
+ {
984
+ "id": "S7",
985
+ "title": "7. DOCUMENTAZIONE E FORMAZIONE",
986
+ "icon": "FileText",
987
+ "items": [
988
+ {"id": "I7.1", "requirement": "Documentazione di processo (procedure, istruzioni) aggiornata e disponibile.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
989
+ {"id": "I7.2", "requirement": "Registri di formazione del personale aggiornati e verificabili.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
990
+ {"id": "I7.3", "requirement": "Certificazioni e attestati del personale validi e aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
991
+ {"id": "I7.4", "requirement": "Controllo documenti (versioni, distribuzione, obsolescenza) conforme.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
992
+ {"id": "I7.5", "requirement": "Archiviazione e conservazione documenti secondo i requisiti normativi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
993
+ ]
994
+ }
995
+ ],
996
+ "totalItems": 38,
997
+ "completedItems": 0,
998
+ "nonCompliantItems": 0,
999
+ "complianceScore": 0.0
1000
+ }
1001
+
1002
+ # Insert the template
1003
+ await db.checklist_templates.insert_one(default_template)
1004
+ template_doc = default_template
1005
+
1006
+ # Serialize the document
1007
+ serialized_template = serialize_checklist(template_doc)
1008
+
1009
+ return {
1010
+ "success": True,
1011
+ "data": serialized_template,
1012
+ "message": "Default checklist template retrieved successfully"
1013
+ }
1014
+
1015
+ except Exception as e:
1016
+ logger.error(f"Error retrieving default checklist template: {e}")
1017
+ raise HTTPException(
1018
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1019
+ detail=f"Failed to retrieve checklist template: {str(e)}"
1020
+ )
1021
+
1022
+ @app.post("/api/checklist-template/item", response_model=Dict[str, Any])
1023
+ async def add_template_item(item_data: dict):
1024
+ """
1025
+ Add a new item to the shared checklist template
1026
+
1027
+ Args:
1028
+ item_data: Dictionary containing item information (sectionId, requirement, etc.)
1029
+
1030
+ Returns:
1031
+ Dictionary confirming the item was added
1032
+
1033
+ Raises:
1034
+ HTTPException: If database operation fails
1035
+ """
1036
+ try:
1037
+ logger.info(f"Adding new item to shared template")
1038
+
1039
+ # Ensure template exists first and get it
1040
+ template = await ensure_default_template_exists()
1041
+
1042
+ if not template:
1043
+ raise HTTPException(
1044
+ status_code=status.HTTP_404_NOT_FOUND,
1045
+ detail="Template not found"
1046
+ )
1047
+
1048
+ template_id = template.get("templateId", "default")
1049
+
1050
+ # Find the section and add the new item
1051
+ section_id = item_data.get('sectionId')
1052
+ section_index = next((i for i, s in enumerate(template['sections']) if s['id'] == section_id), None)
1053
+
1054
+ if section_index is None:
1055
+ raise HTTPException(
1056
+ status_code=status.HTTP_404_NOT_FOUND,
1057
+ detail=f"Section {section_id} not found"
1058
+ )
1059
+
1060
+ new_item = {
1061
+ "id": item_data.get('id', f"I{section_id[-1]}.{len(template['sections'][section_index]['items'])+1}"),
1062
+ "requirement": item_data.get('requirement', ''),
1063
+ }
1064
+
1065
+ # Update the template
1066
+ result = await db.checklist_templates.update_one(
1067
+ {"templateId": template_id, "sections.id": section_id},
1068
+ {"$push": {"sections.$.items": new_item}}
1069
+ )
1070
+
1071
+ if result.modified_count > 0:
1072
+ logger.info(f"Successfully added item to template")
1073
+ return {
1074
+ "success": True,
1075
+ "message": "Item added successfully",
1076
+ "data": new_item
1077
+ }
1078
+ else:
1079
+ raise HTTPException(
1080
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1081
+ detail="Failed to add item"
1082
+ )
1083
+
1084
+ except HTTPException:
1085
+ raise
1086
+ except Exception as e:
1087
+ logger.error(f"Error adding item to template: {e}")
1088
+ raise HTTPException(
1089
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1090
+ detail=f"Failed to add item: {str(e)}"
1091
+ )
1092
+
1093
+ @app.put("/api/checklist-template/item/{item_id}", response_model=Dict[str, Any])
1094
+ async def update_template_item(item_id: str, item_data: dict):
1095
+ """
1096
+ Update an existing item in the shared checklist template
1097
+
1098
+ Args:
1099
+ item_id: ID of the item to update
1100
+ item_data: Dictionary containing updated item information
1101
+
1102
+ Returns:
1103
+ Dictionary confirming the item was updated
1104
+
1105
+ Raises:
1106
+ HTTPException: If database operation fails
1107
+ """
1108
+ try:
1109
+ logger.info(f"Updating item {item_id} in shared template")
1110
+
1111
+ # Ensure template exists first and get it
1112
+ template = await ensure_default_template_exists()
1113
+ template_id = template.get("templateId") if template else "default"
1114
+
1115
+ # Prepare update fields
1116
+ update_fields = {}
1117
+ if 'requirement' in item_data:
1118
+ update_fields["sections.$[].items.$[item].requirement"] = item_data['requirement']
1119
+
1120
+ if not update_fields:
1121
+ raise HTTPException(
1122
+ status_code=status.HTTP_400_BAD_REQUEST,
1123
+ detail="No valid fields to update"
1124
+ )
1125
+
1126
+ # Update the item in template (use the template ID we found)
1127
+ result = await db.checklist_templates.update_one(
1128
+ {"templateId": template_id},
1129
+ {"$set": update_fields},
1130
+ array_filters=[{"item.id": item_id}]
1131
+ )
1132
+
1133
+ if result.modified_count > 0:
1134
+ logger.info(f"Successfully updated item {item_id} in template")
1135
+ return {
1136
+ "success": True,
1137
+ "message": "Item updated successfully",
1138
+ "data": item_data
1139
+ }
1140
+ else:
1141
+ raise HTTPException(
1142
+ status_code=status.HTTP_404_NOT_FOUND,
1143
+ detail="Item not found or no changes made"
1144
+ )
1145
+
1146
+ except HTTPException:
1147
+ raise
1148
+ except Exception as e:
1149
+ logger.error(f"Error updating item {item_id} in template: {e}")
1150
+ raise HTTPException(
1151
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1152
+ detail=f"Failed to update item: {str(e)}"
1153
+ )
1154
+
1155
+ @app.delete("/api/checklist-template/item/{item_id}", response_model=Dict[str, Any])
1156
+ async def delete_template_item(item_id: str):
1157
+ """
1158
+ Delete an item from the shared checklist template
1159
+
1160
+ Args:
1161
+ item_id: ID of the item to delete
1162
+
1163
+ Returns:
1164
+ Dictionary confirming the item was deleted
1165
+
1166
+ Raises:
1167
+ HTTPException: If database operation fails
1168
+ """
1169
+ try:
1170
+ logger.info(f"Deleting item {item_id} from shared template")
1171
+
1172
+ # Ensure template exists first and get it
1173
+ template = await ensure_default_template_exists()
1174
+ template_id = template.get("templateId") if template else "default"
1175
+
1176
+ # Remove the item from the template
1177
+ result = await db.checklist_templates.update_one(
1178
+ {"templateId": template_id},
1179
+ {"$pull": {"sections.$[].items": {"id": item_id}}}
1180
+ )
1181
+
1182
+ if result.modified_count > 0:
1183
+ logger.info(f"Successfully deleted item {item_id} from template")
1184
+ return {
1185
+ "success": True,
1186
+ "message": "Item deleted successfully"
1187
+ }
1188
+ else:
1189
+ raise HTTPException(
1190
+ status_code=status.HTTP_404_NOT_FOUND,
1191
+ detail="Item not found"
1192
+ )
1193
+
1194
+ except HTTPException:
1195
+ raise
1196
+ except Exception as e:
1197
+ logger.error(f"Error deleting item {item_id} from template: {e}")
1198
+ raise HTTPException(
1199
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1200
+ detail=f"Failed to delete item: {str(e)}"
1201
+ )
1202
+
1203
+ @app.post("/api/checklist-template/section", response_model=Dict[str, Any])
1204
+ async def add_template_section(section_data: dict):
1205
+ """
1206
+ Add a new section to the shared checklist template
1207
+
1208
+ Args:
1209
+ section_data: Dictionary containing section information
1210
+
1211
+ Returns:
1212
+ Dictionary confirming the section was added
1213
+
1214
+ Raises:
1215
+ HTTPException: If database operation fails
1216
+ """
1217
+ try:
1218
+ logger.info(f"Adding new section to shared template")
1219
+
1220
+ # Ensure template exists first and get it
1221
+ template = await ensure_default_template_exists()
1222
+
1223
+ if not template:
1224
+ raise HTTPException(
1225
+ status_code=status.HTTP_404_NOT_FOUND,
1226
+ detail="Template not found"
1227
+ )
1228
+
1229
+ template_id = template.get("templateId", "default")
1230
+
1231
+ # Create new section
1232
+ new_section = {
1233
+ "id": section_data.get('id', f"S{len(template['sections'])+1}"),
1234
+ "title": section_data.get('title', 'New Section'),
1235
+ "icon": section_data.get('icon', 'Settings'),
1236
+ "items": []
1237
+ }
1238
+
1239
+ # Add the new section
1240
+ result = await db.checklist_templates.update_one(
1241
+ {"templateId": template_id},
1242
+ {"$push": {"sections": new_section}}
1243
+ )
1244
+
1245
+ if result.modified_count > 0:
1246
+ logger.info(f"Successfully added section to template")
1247
+ return {
1248
+ "success": True,
1249
+ "message": "Section added successfully",
1250
+ "data": new_section
1251
+ }
1252
+ else:
1253
+ raise HTTPException(
1254
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1255
+ detail="Failed to add section"
1256
+ )
1257
+
1258
+ except HTTPException:
1259
+ raise
1260
+ except Exception as e:
1261
+ logger.error(f"Error adding section to template: {e}")
1262
+ raise HTTPException(
1263
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1264
+ detail=f"Failed to add section: {str(e)}"
1265
+ )
1266
+
1267
+ @app.delete("/api/checklist-template/section/{section_id}", response_model=Dict[str, Any])
1268
+ async def delete_template_section(section_id: str):
1269
+ """
1270
+ Delete a section from the shared checklist template
1271
+
1272
+ Args:
1273
+ section_id: ID of the section to delete
1274
+
1275
+ Returns:
1276
+ Dictionary confirming the section was deleted
1277
+
1278
+ Raises:
1279
+ HTTPException: If database operation fails
1280
+ """
1281
+ try:
1282
+ logger.info(f"Deleting section {section_id} from shared template")
1283
+
1284
+ # Ensure template exists first and get it
1285
+ template = await ensure_default_template_exists()
1286
+ template_id = template.get("templateId") if template else "default"
1287
+
1288
+ # Remove the section from the template
1289
+ result = await db.checklist_templates.update_one(
1290
+ {"templateId": template_id},
1291
+ {"$pull": {"sections": {"id": section_id}}}
1292
+ )
1293
+
1294
+ if result.modified_count > 0:
1295
+ logger.info(f"Successfully deleted section {section_id} from template")
1296
+ return {
1297
+ "success": True,
1298
+ "message": "Section deleted successfully"
1299
+ }
1300
+ else:
1301
+ raise HTTPException(
1302
+ status_code=status.HTTP_404_NOT_FOUND,
1303
+ detail="Section not found"
1304
+ )
1305
+
1306
+ except HTTPException:
1307
+ raise
1308
+ except Exception as e:
1309
+ logger.error(f"Error deleting section {section_id} from template: {e}")
1310
+ raise HTTPException(
1311
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1312
+ detail=f"Failed to delete section: {str(e)}"
1313
+ )
1314
+
1315
+ @app.post("/api/checklist/{user_id}/item", response_model=Dict[str, Any])
1316
+ async def add_checklist_item(user_id: str, item_data: dict):
1317
+ """
1318
+ Add a new item to a checklist
1319
+
1320
+ Args:
1321
+ user_id: Unique identifier for the user
1322
+ item_data: Dictionary containing item information (sectionId, requirement, etc.)
1323
+
1324
+ Returns:
1325
+ Dictionary confirming the item was added
1326
+
1327
+ Raises:
1328
+ HTTPException: If database operation fails
1329
+ """
1330
+ try:
1331
+ logger.info(f"Adding new item to checklist for user: {user_id}")
1332
+
1333
+ # Get the user's checklist
1334
+ checklist_doc = await db.checklists.find_one({"userId": user_id})
1335
+
1336
+ if not checklist_doc:
1337
+ raise HTTPException(
1338
+ status_code=status.HTTP_404_NOT_FOUND,
1339
+ detail="Checklist not found for user"
1340
+ )
1341
+
1342
+ # Find the section and add the new item
1343
+ section_id = item_data.get('sectionId')
1344
+ new_item = {
1345
+ "id": item_data.get('id', f"I{section_id}.{len(checklist_doc['sections'][int(section_id[-1])-1]['items'])+1}"),
1346
+ "requirement": item_data.get('requirement', ''),
1347
+ "compliance": "N/A",
1348
+ "deviation": "",
1349
+ "action": "",
1350
+ "imageData": None,
1351
+ "checkedAt": None,
1352
+ "checkedBy": ""
1353
+ }
1354
+
1355
+ # Update the checklist
1356
+ result = await db.checklists.update_one(
1357
+ {"userId": user_id, "sections.id": section_id},
1358
+ {"$push": {f"sections.$.items": new_item}}
1359
+ )
1360
+
1361
+ if result.modified_count > 0:
1362
+ logger.info(f"Successfully added item to checklist for user {user_id}")
1363
+ return {
1364
+ "success": True,
1365
+ "message": "Item added successfully",
1366
+ "data": new_item
1367
+ }
1368
+ else:
1369
+ raise HTTPException(
1370
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1371
+ detail="Failed to add item"
1372
+ )
1373
+
1374
+ except HTTPException:
1375
+ raise
1376
+ except Exception as e:
1377
+ logger.error(f"Error adding item for user {user_id}: {e}")
1378
+ raise HTTPException(
1379
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1380
+ detail=f"Failed to add item: {str(e)}"
1381
+ )
1382
+
1383
+ @app.put("/api/checklist/{user_id}/item/{item_id}", response_model=Dict[str, Any])
1384
+ async def update_checklist_item(user_id: str, item_id: str, item_data: dict):
1385
+ """
1386
+ Update an existing checklist item
1387
+
1388
+ Args:
1389
+ user_id: Unique identifier for the user
1390
+ item_id: ID of the item to update
1391
+ item_data: Dictionary containing updated item information
1392
+
1393
+ Returns:
1394
+ Dictionary confirming the item was updated
1395
+
1396
+ Raises:
1397
+ HTTPException: If database operation fails
1398
+ """
1399
+ try:
1400
+ logger.info(f"Updating item {item_id} for user: {user_id}")
1401
+
1402
+ # Check if checklist exists
1403
+ checklist = await db.checklists.find_one({"userId": user_id})
1404
+ if not checklist:
1405
+ logger.warning(f"No checklist found for user {user_id}")
1406
+ raise HTTPException(
1407
+ status_code=status.HTTP_404_NOT_FOUND,
1408
+ detail=f"Checklist not found for user {user_id}. Please create a checklist first."
1409
+ )
1410
+
1411
+ # Prepare update fields
1412
+ update_fields = {}
1413
+ for field in ['requirement', 'compliance', 'deviation', 'action']:
1414
+ if field in item_data:
1415
+ update_fields[f"sections.$[].items.$[item].{field}"] = item_data[field]
1416
+
1417
+ if not update_fields:
1418
+ raise HTTPException(
1419
+ status_code=status.HTTP_400_BAD_REQUEST,
1420
+ detail="No valid fields to update"
1421
+ )
1422
+
1423
+ # Update the item
1424
+ result = await db.checklists.update_one(
1425
+ {"userId": user_id},
1426
+ {"$set": update_fields},
1427
+ array_filters=[{"item.id": item_id}]
1428
+ )
1429
+
1430
+ if result.modified_count > 0:
1431
+ logger.info(f"Successfully updated item {item_id} for user {user_id}")
1432
+ return {
1433
+ "success": True,
1434
+ "message": "Item updated successfully",
1435
+ "data": item_data
1436
+ }
1437
+ else:
1438
+ raise HTTPException(
1439
+ status_code=status.HTTP_404_NOT_FOUND,
1440
+ detail="Item not found or no changes made"
1441
+ )
1442
+
1443
+ except HTTPException:
1444
+ raise
1445
+ except Exception as e:
1446
+ logger.error(f"Error updating item {item_id} for user {user_id}: {e}")
1447
+ raise HTTPException(
1448
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1449
+ detail=f"Failed to update item: {str(e)}"
1450
+ )
1451
+
1452
+ @app.delete("/api/checklist/{user_id}/item/{item_id}", response_model=Dict[str, Any])
1453
+ async def delete_checklist_item(user_id: str, item_id: str):
1454
+ """
1455
+ Delete a checklist item
1456
+
1457
+ Args:
1458
+ user_id: Unique identifier for the user
1459
+ item_id: ID of the item to delete
1460
+
1461
+ Returns:
1462
+ Dictionary confirming the item was deleted
1463
+
1464
+ Raises:
1465
+ HTTPException: If database operation fails
1466
+ """
1467
+ try:
1468
+ logger.info(f"Deleting item {item_id} for user: {user_id}")
1469
+
1470
+ # Check if checklist exists
1471
+ checklist = await db.checklists.find_one({"userId": user_id})
1472
+ if not checklist:
1473
+ logger.warning(f"No checklist found for user {user_id}")
1474
+ raise HTTPException(
1475
+ status_code=status.HTTP_404_NOT_FOUND,
1476
+ detail=f"Checklist not found for user {user_id}. Please create a checklist first."
1477
+ )
1478
+
1479
+ # Remove the item from the checklist
1480
+ result = await db.checklists.update_one(
1481
+ {"userId": user_id},
1482
+ {"$pull": {"sections.$[].items": {"id": item_id}}}
1483
+ )
1484
+
1485
+ if result.modified_count > 0:
1486
+ logger.info(f"Successfully deleted item {item_id} for user {user_id}")
1487
+ return {
1488
+ "success": True,
1489
+ "message": "Item deleted successfully"
1490
+ }
1491
+ else:
1492
+ raise HTTPException(
1493
+ status_code=status.HTTP_404_NOT_FOUND,
1494
+ detail="Item not found"
1495
+ )
1496
+
1497
+ except HTTPException:
1498
+ raise
1499
+ except Exception as e:
1500
+ logger.error(f"Error deleting item {item_id} for user {user_id}: {e}")
1501
+ raise HTTPException(
1502
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1503
+ detail=f"Failed to delete item: {str(e)}"
1504
+ )
1505
+
1506
+ @app.post("/api/checklist/{user_id}/section", response_model=Dict[str, Any])
1507
+ async def add_checklist_section(user_id: str, section_data: dict):
1508
+ """
1509
+ Add a new section to a checklist
1510
+
1511
+ Args:
1512
+ user_id: Unique identifier for the user
1513
+ section_data: Dictionary containing section information
1514
+
1515
+ Returns:
1516
+ Dictionary confirming the section was added
1517
+
1518
+ Raises:
1519
+ HTTPException: If database operation fails
1520
+ """
1521
+ try:
1522
+ logger.info(f"Adding new section to checklist for user: {user_id}")
1523
+
1524
+ # Get the user's checklist
1525
+ checklist_doc = await db.checklists.find_one({"userId": user_id})
1526
+
1527
+ if not checklist_doc:
1528
+ raise HTTPException(
1529
+ status_code=status.HTTP_404_NOT_FOUND,
1530
+ detail="Checklist not found for user"
1531
+ )
1532
+
1533
+ # Create new section
1534
+ new_section = {
1535
+ "id": section_data.get('id', f"S{len(checklist_doc['sections'])+1}"),
1536
+ "title": section_data.get('title', 'New Section'),
1537
+ "icon": section_data.get('icon', 'Settings'),
1538
+ "items": []
1539
+ }
1540
+
1541
+ # Add the new section
1542
+ result = await db.checklists.update_one(
1543
+ {"userId": user_id},
1544
+ {"$push": {"sections": new_section}}
1545
+ )
1546
+
1547
+ if result.modified_count > 0:
1548
+ logger.info(f"Successfully added section to checklist for user {user_id}")
1549
+ return {
1550
+ "success": True,
1551
+ "message": "Section added successfully",
1552
+ "data": new_section
1553
+ }
1554
+ else:
1555
+ raise HTTPException(
1556
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1557
+ detail="Failed to add section"
1558
+ )
1559
+
1560
+ except HTTPException:
1561
+ raise
1562
+ except Exception as e:
1563
+ logger.error(f"Error adding section for user {user_id}: {e}")
1564
+ raise HTTPException(
1565
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1566
+ detail=f"Failed to add section: {str(e)}"
1567
+ )
1568
+
1569
+ @app.delete("/api/checklist/{user_id}/section/{section_id}", response_model=Dict[str, Any])
1570
+ async def delete_checklist_section(user_id: str, section_id: str):
1571
+ """
1572
+ Delete a checklist section and all its items
1573
+
1574
+ Args:
1575
+ user_id: Unique identifier for the user
1576
+ section_id: ID of the section to delete
1577
+
1578
+ Returns:
1579
+ Dictionary confirming the section was deleted
1580
+
1581
+ Raises:
1582
+ HTTPException: If database operation fails
1583
+ """
1584
+ try:
1585
+ logger.info(f"Deleting section {section_id} for user: {user_id}")
1586
+
1587
+ # Check if checklist exists
1588
+ checklist = await db.checklists.find_one({"userId": user_id})
1589
+ if not checklist:
1590
+ logger.warning(f"No checklist found for user {user_id}")
1591
+ raise HTTPException(
1592
+ status_code=status.HTTP_404_NOT_FOUND,
1593
+ detail=f"Checklist not found for user {user_id}. Please create a checklist first."
1594
+ )
1595
+
1596
+ # Remove the section from the checklist
1597
+ result = await db.checklists.update_one(
1598
+ {"userId": user_id},
1599
+ {"$pull": {"sections": {"id": section_id}}}
1600
+ )
1601
+
1602
+ if result.modified_count > 0:
1603
+ logger.info(f"Successfully deleted section {section_id} for user {user_id}")
1604
+ return {
1605
+ "success": True,
1606
+ "message": "Section deleted successfully"
1607
+ }
1608
+ else:
1609
+ raise HTTPException(
1610
+ status_code=status.HTTP_404_NOT_FOUND,
1611
+ detail="Section not found"
1612
+ )
1613
+
1614
+ except HTTPException:
1615
+ raise
1616
+ except Exception as e:
1617
+ logger.error(f"Error deleting section {section_id} for user {user_id}: {e}")
1618
+ raise HTTPException(
1619
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
1620
+ detail=f"Failed to delete section: {str(e)}"
1621
+ )
1622
+
1623
  @app.get("/api/checklists", response_model=Dict[str, Any])
1624
  async def get_all_checklists():
1625
  """
 
1707
 
1708
  # Run the server
1709
  uvicorn.run(
1710
+ "main:app",
1711
  host="0.0.0.0",
1712
  port=PORT,
1713
+ reload=True, # Enable auto-reload for development
1714
  log_level="info"
1715
  )