Emmanuel Frimpong Asante commited on
Commit
be56c50
·
1 Parent(s): a7e3aed

update space

Browse files
.idea/workspace.xml CHANGED
@@ -5,7 +5,12 @@
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
- <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
 
 
 
 
 
9
  </list>
10
  <option name="SHOW_DIALOG" value="false" />
11
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -119,15 +124,7 @@
119
  <workItem from="1730305361670" duration="2425000" />
120
  <workItem from="1730378091154" duration="2435000" />
121
  <workItem from="1730381153348" duration="8786000" />
122
- <workItem from="1730397485849" duration="8040000" />
123
- </task>
124
- <task id="LOCAL-00033" summary="update space">
125
- <option name="closed" value="true" />
126
- <created>1730356036066</created>
127
- <option name="number" value="00033" />
128
- <option name="presentableId" value="LOCAL-00033" />
129
- <option name="project" value="LOCAL" />
130
- <updated>1730356036066</updated>
131
  </task>
132
  <task id="LOCAL-00034" summary="update space">
133
  <option name="closed" value="true" />
@@ -513,7 +510,15 @@
513
  <option name="project" value="LOCAL" />
514
  <updated>1730409232117</updated>
515
  </task>
516
- <option name="localTasksCounter" value="82" />
 
 
 
 
 
 
 
 
517
  <servers />
518
  </component>
519
  <component name="TypeScriptGeneratedFilesManager">
 
5
  </component>
6
  <component name="ChangeListManager">
7
  <list default="true" id="27c9ae1a-a6fa-4472-8bcd-a7087620894b" name="Changes" comment="update space">
8
+ <change afterPath="$PROJECT_DIR$/models/schemas/health_record_schema.py" afterDir="false" />
9
+ <change afterPath="$PROJECT_DIR$/services/health_alerts_service.py" afterDir="false" />
10
+ <change afterPath="$PROJECT_DIR$/templates/health_dashboard.html" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/routes/administration.py" beforeDir="false" afterPath="$PROJECT_DIR$/routes/administration.py" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/routes/disease_detection.py" beforeDir="false" afterPath="$PROJECT_DIR$/routes/disease_detection.py" afterDir="false" />
14
  </list>
15
  <option name="SHOW_DIALOG" value="false" />
16
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
 
124
  <workItem from="1730305361670" duration="2425000" />
125
  <workItem from="1730378091154" duration="2435000" />
126
  <workItem from="1730381153348" duration="8786000" />
127
+ <workItem from="1730397485849" duration="9041000" />
 
 
 
 
 
 
 
 
128
  </task>
129
  <task id="LOCAL-00034" summary="update space">
130
  <option name="closed" value="true" />
 
510
  <option name="project" value="LOCAL" />
511
  <updated>1730409232117</updated>
512
  </task>
513
+ <task id="LOCAL-00082" summary="update space">
514
+ <option name="closed" value="true" />
515
+ <created>1730433894107</created>
516
+ <option name="number" value="00082" />
517
+ <option name="presentableId" value="LOCAL-00082" />
518
+ <option name="project" value="LOCAL" />
519
+ <updated>1730433894107</updated>
520
+ </task>
521
+ <option name="localTasksCounter" value="83" />
522
  <servers />
523
  </component>
524
  <component name="TypeScriptGeneratedFilesManager">
models/schemas/health_record_schema.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/schemas/health_record_schema.py
2
+
3
+ from pydantic import BaseModel, Field, validator
4
+ from datetime import datetime
5
+ from bson import ObjectId
6
+ from typing import Optional
7
+
8
+ class PyObjectId(ObjectId):
9
+ """Custom ObjectId type for Pydantic to enable validation and proper MongoDB support."""
10
+ @classmethod
11
+ def __get_validators__(cls):
12
+ yield cls.validate
13
+
14
+ @classmethod
15
+ def validate(cls, value):
16
+ if not ObjectId.is_valid(value):
17
+ raise ValueError("Invalid ObjectId format")
18
+ return ObjectId(value)
19
+
20
+ class HealthRecord(BaseModel):
21
+ id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
22
+ bird_id: str = Field(..., description="Unique identifier for a specific bird or batch of birds")
23
+ date: datetime = Field(default_factory=datetime.utcnow, description="Timestamp of the health record entry")
24
+ weight: float = Field(..., gt=0, description="Weight measurement in kilograms")
25
+ mortality_rate: float = Field(..., ge=0, le=100, description="Mortality rate as a percentage")
26
+ feed_intake: float = Field(..., ge=0, description="Daily feed intake in kilograms")
27
+ disease_detected: Optional[str] = Field(None, description="Detected disease name, if any")
28
+ status: str = Field(default="Healthy", description="Health status determined by metrics")
29
+ treatment_recommendation: Optional[str] = Field(None, description="Suggested treatment based on diagnosis")
30
+
31
+ @validator('status')
32
+ def validate_status(cls, value):
33
+ allowed_statuses = {"Healthy", "At Risk", "Critical"}
34
+ if value not in allowed_statuses:
35
+ raise ValueError(f"Status must be one of {allowed_statuses}")
36
+ return value
37
+
38
+ class Config:
39
+ json_encoders = {ObjectId: str}
40
+ schema_extra = {
41
+ "example": {
42
+ "bird_id": "batch_123",
43
+ "date": "2024-11-01T14:30:00Z",
44
+ "weight": 1.5,
45
+ "mortality_rate": 2.0,
46
+ "feed_intake": 0.4,
47
+ "disease_detected": "Coccidiosis",
48
+ "status": "Critical",
49
+ "treatment_recommendation": "Administer anti-coccidial medication and improve hygiene"
50
+ }
51
+ }
routes/administration.py CHANGED
@@ -1,7 +1,8 @@
 
 
1
  import io
2
  from datetime import datetime
3
- from typing import Optional, List
4
-
5
  import pandas as pd
6
  from bson import ObjectId
7
  from fastapi import APIRouter, Depends, Request, HTTPException, status
@@ -9,7 +10,6 @@ from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, Fil
9
  from fastapi.templating import Jinja2Templates
10
  from reportlab.lib.pagesizes import letter
11
  from reportlab.pdfgen import canvas
12
-
13
  from models.schemas.todo_schema import ToDoItem, ToDoCreate, ToDoUpdate
14
  from services.auth_service import get_current_user, is_admin_user
15
  from services.health_monitoring_service import evaluate_health_data
@@ -33,11 +33,8 @@ async def health_dashboard(request: Request, current_user: str = Depends(get_cur
33
  """
34
  Display health metrics with real-time and historical alerts on the dashboard.
35
  """
36
- current_health_metrics = {
37
- "weight_loss_percentage": 4,
38
- "mortality_rate": 1,
39
- "reduced_feed_intake_percentage": 8
40
- }
41
  real_time_alerts = evaluate_health_data(current_health_metrics).get("notifications", [])
42
  historical_alerts = list(health_collection.find().sort("timestamp", -1).limit(20))
43
 
@@ -50,6 +47,15 @@ async def health_dashboard(request: Request, current_user: str = Depends(get_cur
50
  return templates.TemplateResponse("dashboard.html", context)
51
 
52
 
 
 
 
 
 
 
 
 
 
53
  # --- Admin Task Management Routes ---
54
 
55
  @dashboard_router.get("/tasks", response_class=HTMLResponse)
@@ -62,12 +68,7 @@ async def manage_tasks(request: Request, current_user: str = Depends(get_current
62
 
63
  tasks = list(todo_collection.find({}))
64
  farmers = list(user_collection.find({"role": "farmer"}))
65
-
66
- context = {
67
- "request": request,
68
- "tasks": tasks,
69
- "farmers": farmers,
70
- }
71
  return templates.TemplateResponse("admin_tasks.html", context)
72
 
73
 
@@ -80,12 +81,7 @@ async def assign_task(todo_data: ToDoCreate, assigned_to: str, current_user: str
80
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
81
 
82
  task = todo_data.dict()
83
- task.update({
84
- "assigned_to": assigned_to,
85
- "created_by": current_user,
86
- "is_completed": False,
87
- "created_at": datetime.utcnow()
88
- })
89
 
90
  result = todo_collection.insert_one(task)
91
  if result.inserted_id:
@@ -103,15 +99,10 @@ async def create_todo_item(todo_data: ToDoCreate, current_user: str = Depends(ge
103
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
104
 
105
  todo_item = todo_data.dict()
106
- todo_item.update({
107
- "is_completed": False,
108
- "created_by": current_user,
109
- "created_at": datetime.utcnow(),
110
- })
111
  result = todo_collection.insert_one(todo_item)
112
  if result.inserted_id:
113
- return JSONResponse(status_code=status.HTTP_201_CREATED,
114
- content={"message": "To-do item created successfully."})
115
  else:
116
  raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create to-do item.")
117
 
@@ -123,7 +114,6 @@ async def get_all_todo_items(current_user: str = Depends(get_current_user)):
123
  """
124
  if not is_admin_user(current_user):
125
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
126
-
127
  return list(todo_collection.find({}))
128
 
129
 
@@ -175,11 +165,9 @@ async def complete_assigned_todo_item(todo_id: str, current_user: str = Depends(
175
  """
176
  todo = todo_collection.find_one({"_id": ObjectId(todo_id), "assigned_to": current_user})
177
  if not todo:
178
- raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
179
- detail="To-do item not found or not assigned to you.")
180
 
181
- todo_collection.update_one({"_id": ObjectId(todo_id)},
182
- {"$set": {"is_completed": True, "completed_at": datetime.utcnow()}})
183
  return {"message": f"To-do item '{todo.get('title')}' marked as completed."}
184
 
185
 
@@ -242,8 +230,7 @@ async def export_health_pdf(current_user: str = Depends(get_current_user)):
242
  buffer.seek(0)
243
 
244
  log_activity("Exported health records as PDF", current_user)
245
- return StreamingResponse(buffer, media_type="application/pdf",
246
- headers={"Content-Disposition": "attachment;filename=health_records.pdf"})
247
 
248
 
249
  @dashboard_router.get("/export/health/xls")
@@ -260,6 +247,4 @@ async def export_health_xls(current_user: str = Depends(get_current_user)):
260
  df.to_excel(file_path, index=False)
261
 
262
  log_activity("Exported health records as XLS", current_user)
263
- return FileResponse(file_path, media_type="application/vnd.ms-excel",
264
- headers={"Content-Disposition": "attachment;filename=health_records.xlsx"})
265
-
 
1
+ # routes/administration.py
2
+
3
  import io
4
  from datetime import datetime
5
+ from typing import List, Optional
 
6
  import pandas as pd
7
  from bson import ObjectId
8
  from fastapi import APIRouter, Depends, Request, HTTPException, status
 
10
  from fastapi.templating import Jinja2Templates
11
  from reportlab.lib.pagesizes import letter
12
  from reportlab.pdfgen import canvas
 
13
  from models.schemas.todo_schema import ToDoItem, ToDoCreate, ToDoUpdate
14
  from services.auth_service import get_current_user, is_admin_user
15
  from services.health_monitoring_service import evaluate_health_data
 
33
  """
34
  Display health metrics with real-time and historical alerts on the dashboard.
35
  """
36
+ # Retrieve current metrics and alerts
37
+ current_health_metrics = {"weight_loss_percentage": 4, "mortality_rate": 1, "reduced_feed_intake_percentage": 8}
 
 
 
38
  real_time_alerts = evaluate_health_data(current_health_metrics).get("notifications", [])
39
  historical_alerts = list(health_collection.find().sort("timestamp", -1).limit(20))
40
 
 
47
  return templates.TemplateResponse("dashboard.html", context)
48
 
49
 
50
+ @dashboard_router.get("/health_dashboard", response_class=HTMLResponse)
51
+ async def health_dashboard_view(request: Request):
52
+ """
53
+ Render the health dashboard with the latest 10 health records.
54
+ """
55
+ health_records = list(health_collection.find().sort("date", -1).limit(10))
56
+ return templates.TemplateResponse("health_dashboard.html", {"request": request, "health_records": health_records})
57
+
58
+
59
  # --- Admin Task Management Routes ---
60
 
61
  @dashboard_router.get("/tasks", response_class=HTMLResponse)
 
68
 
69
  tasks = list(todo_collection.find({}))
70
  farmers = list(user_collection.find({"role": "farmer"}))
71
+ context = {"request": request, "tasks": tasks, "farmers": farmers}
 
 
 
 
 
72
  return templates.TemplateResponse("admin_tasks.html", context)
73
 
74
 
 
81
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
82
 
83
  task = todo_data.dict()
84
+ task.update({"assigned_to": assigned_to, "created_by": current_user, "is_completed": False, "created_at": datetime.utcnow()})
 
 
 
 
 
85
 
86
  result = todo_collection.insert_one(task)
87
  if result.inserted_id:
 
99
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
100
 
101
  todo_item = todo_data.dict()
102
+ todo_item.update({"is_completed": False, "created_by": current_user, "created_at": datetime.utcnow()})
 
 
 
 
103
  result = todo_collection.insert_one(todo_item)
104
  if result.inserted_id:
105
+ return JSONResponse(status_code=status.HTTP_201_CREATED, content={"message": "To-do item created successfully."})
 
106
  else:
107
  raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create to-do item.")
108
 
 
114
  """
115
  if not is_admin_user(current_user):
116
  raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin privileges required.")
 
117
  return list(todo_collection.find({}))
118
 
119
 
 
165
  """
166
  todo = todo_collection.find_one({"_id": ObjectId(todo_id), "assigned_to": current_user})
167
  if not todo:
168
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="To-do item not found or not assigned to you.")
 
169
 
170
+ todo_collection.update_one({"_id": ObjectId(todo_id)}, {"$set": {"is_completed": True, "completed_at": datetime.utcnow()}})
 
171
  return {"message": f"To-do item '{todo.get('title')}' marked as completed."}
172
 
173
 
 
230
  buffer.seek(0)
231
 
232
  log_activity("Exported health records as PDF", current_user)
233
+ return StreamingResponse(buffer, media_type="application/pdf", headers={"Content-Disposition": "attachment;filename=health_records.pdf"})
 
234
 
235
 
236
  @dashboard_router.get("/export/health/xls")
 
247
  df.to_excel(file_path, index=False)
248
 
249
  log_activity("Exported health records as XLS", current_user)
250
+ return FileResponse(file_path, media_type="application/vnd.ms-excel", headers={"Content-Disposition": "attachment;filename=health_records.xlsx"})
 
 
routes/disease_detection.py CHANGED
@@ -1,67 +1,87 @@
1
  # routes/disease_detection.py
2
 
 
3
  import logging
 
4
  from fastapi import APIRouter, UploadFile, File, HTTPException
5
  from fastapi.responses import JSONResponse
6
  from PIL import Image
7
  import numpy as np
8
- import io
 
9
  from services.disease_detection_service import bot
 
10
 
11
- # Set up logging for this module
12
  logger = logging.getLogger(__name__)
13
 
14
  # Initialize the disease detection router
15
  disease_router = APIRouter()
16
 
 
17
  @disease_router.post("/detect_disease", response_class=JSONResponse)
18
  async def detect_disease(file: UploadFile = File(...)):
19
  """
20
- Detect disease from an uploaded poultry fecal image and provide treatment suggestions.
21
 
22
  Parameters:
23
  file (UploadFile): The image file to analyze.
24
 
25
  Returns:
26
- JSONResponse: A JSON response with disease prediction details, including:
27
- - disease: Name of the detected disease
28
- - status: Severity of the detected disease
29
- - confidence: Confidence level of the prediction
30
- - recommendation: Suggested treatment based on the diagnosis
31
- - details: Additional information about the disease
32
  """
33
- logger.info("Disease detection request received.")
34
 
35
- # Validate file type
36
  if file.content_type not in ["image/jpeg", "image/png"]:
37
- logger.warning(f"Invalid file type received: {file.content_type}")
38
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a JPEG or PNG image.")
39
 
40
  try:
41
- # Attempt to read and process the image
42
  image_data = await file.read()
43
  image = Image.open(io.BytesIO(image_data)).convert("RGB")
44
  image_np = np.array(image)
45
- logger.info("Image successfully loaded and converted to RGB.")
46
  except Exception as e:
47
- error_message = f"Error loading image: {e}"
48
- logger.error(error_message)
49
- raise HTTPException(status_code=500, detail=error_message)
50
 
51
- # Use the bot to diagnose disease and generate a detailed response
52
  try:
53
  detailed_response, disease_name, status, recommendation, confidence = bot.diagnose_and_respond(image_np)
54
- logger.info(f"Disease detection completed: {disease_name} with {confidence:.2f}% confidence.")
55
  except Exception as e:
56
- logger.error(f"Error during disease detection: {e}")
57
  raise HTTPException(status_code=500, detail="Error processing the image for disease detection.")
58
 
59
- # Verify if a valid disease was detected
60
- if disease_name is None:
61
- logger.warning("Disease detection failed; no disease identified in the image.")
62
- return JSONResponse(content={"error": "Unable to detect disease. Please try again."}, status_code=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
- # Return the response as JSON
65
  response_content = {
66
  "disease": disease_name,
67
  "status": status,
@@ -69,5 +89,5 @@ async def detect_disease(file: UploadFile = File(...)):
69
  "recommendation": recommendation,
70
  "details": detailed_response
71
  }
72
- logger.info(f"Disease detection response: {response_content}")
73
  return JSONResponse(content=response_content)
 
1
  # routes/disease_detection.py
2
 
3
+ import datetime
4
  import logging
5
+ import io
6
  from fastapi import APIRouter, UploadFile, File, HTTPException
7
  from fastapi.responses import JSONResponse
8
  from PIL import Image
9
  import numpy as np
10
+
11
+ from routes.administration import health_collection
12
  from services.disease_detection_service import bot
13
+ from services.health_alerts_service import check_health_thresholds
14
 
15
+ # Configure logging for this module
16
  logger = logging.getLogger(__name__)
17
 
18
  # Initialize the disease detection router
19
  disease_router = APIRouter()
20
 
21
+
22
  @disease_router.post("/detect_disease", response_class=JSONResponse)
23
  async def detect_disease(file: UploadFile = File(...)):
24
  """
25
+ Detect disease from an uploaded poultry image and provide treatment recommendations.
26
 
27
  Parameters:
28
  file (UploadFile): The image file to analyze.
29
 
30
  Returns:
31
+ JSONResponse: JSON with disease prediction, confidence, treatment recommendation,
32
+ and other details.
 
 
 
 
33
  """
34
+ logger.info("Received disease detection request.")
35
 
36
+ # Step 1: Validate the uploaded file type
37
  if file.content_type not in ["image/jpeg", "image/png"]:
38
+ logger.warning(f"Invalid file type: {file.content_type}")
39
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a JPEG or PNG image.")
40
 
41
  try:
42
+ # Step 2: Read and process the image
43
  image_data = await file.read()
44
  image = Image.open(io.BytesIO(image_data)).convert("RGB")
45
  image_np = np.array(image)
46
+ logger.info("Image successfully loaded and converted to RGB format.")
47
  except Exception as e:
48
+ logger.error(f"Failed to load image: {e}")
49
+ raise HTTPException(status_code=500, detail="Error processing the uploaded image.")
 
50
 
51
+ # Step 3: Perform disease detection
52
  try:
53
  detailed_response, disease_name, status, recommendation, confidence = bot.diagnose_and_respond(image_np)
54
+ logger.info(f"Disease detected: {disease_name} with {confidence:.2f}% confidence.")
55
  except Exception as e:
56
+ logger.error(f"Error in disease detection: {e}")
57
  raise HTTPException(status_code=500, detail="Error processing the image for disease detection.")
58
 
59
+ # Step 4: Check if a valid disease was detected
60
+ if not disease_name:
61
+ logger.warning("No disease detected in the image.")
62
+ return JSONResponse(content={"error": "Unable to detect disease. Please try a different image."},
63
+ status_code=500)
64
+
65
+ # Step 5: Log the detected disease and recommended treatment in health records
66
+ record = {
67
+ "bird_id": "batch_123", # Placeholder, replace with actual bird/batch ID
68
+ "date": datetime.datetime.utcnow(),
69
+ "disease_detected": disease_name,
70
+ "status": status,
71
+ "confidence": confidence,
72
+ "treatment_recommendation": recommendation,
73
+ "weight": 1.4, # Placeholder, replace with actual weight measurement
74
+ "mortality_rate": 0.5, # Placeholder, replace with actual mortality rate
75
+ "feed_intake": 0.35 # Placeholder, replace with actual feed intake
76
+ }
77
+ health_collection.insert_one(record)
78
+ logger.info("Disease and treatment recommendation logged in health records.")
79
+
80
+ # Step 6: Check for health alerts based on recorded data
81
+ check_health_thresholds(record)
82
+ logger.info("Health thresholds checked for potential alerts.")
83
 
84
+ # Step 7: Construct and return response
85
  response_content = {
86
  "disease": disease_name,
87
  "status": status,
 
89
  "recommendation": recommendation,
90
  "details": detailed_response
91
  }
92
+ logger.info(f"Response content prepared: {response_content}")
93
  return JSONResponse(content=response_content)
services/health_alerts_service.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/health_alerts_service.py
2
+
3
+ import os
4
+ from services.utils import db
5
+ from datetime import datetime
6
+
7
+ health_collection = db.get_collection("health_records")
8
+ alert_collection = db.get_collection("alerts")
9
+
10
+ # Define thresholds for automated alerts
11
+ THRESHOLDS = {
12
+ "weight": 1.0, # Example: Weight below 1.0 kg triggers alert
13
+ "mortality_rate": 2.0, # Mortality rate over 2% triggers alert
14
+ "feed_intake": 0.3 # Feed intake below 0.3 kg triggers alert
15
+ }
16
+
17
+ def check_health_thresholds(record):
18
+ alerts = []
19
+ if record["weight"] < THRESHOLDS["weight"]:
20
+ alerts.append("Low weight detected")
21
+
22
+ if record["mortality_rate"] > THRESHOLDS["mortality_rate"]:
23
+ alerts.append("High mortality rate detected")
24
+
25
+ if record["feed_intake"] < THRESHOLDS["feed_intake"]:
26
+ alerts.append("Reduced feed intake detected")
27
+
28
+ if alerts:
29
+ alert_record = {
30
+ "bird_id": record["bird_id"],
31
+ "date": datetime.utcnow(),
32
+ "alerts": alerts,
33
+ "status": record["status"],
34
+ "treatment_recommendation": record["treatment_recommendation"]
35
+ }
36
+ alert_collection.insert_one(alert_record)
37
+ # Send alert notification (e.g., email or SMS)
38
+ send_alert_notification(alert_record)
39
+
40
+ def send_alert_notification(alert):
41
+ farmer_email = os.getenv("FARMER_EMAIL")
42
+ # Logic to send notification via email or SMS
templates/health_dashboard.html ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="container">
5
+ <h1>Health & Disease Dashboard</h1>
6
+
7
+ <table class="table table-striped table-bordered">
8
+ <thead>
9
+ <tr>
10
+ <th>Bird ID</th>
11
+ <th>Date</th>
12
+ <th>Weight (kg)</th>
13
+ <th>Mortality Rate (%)</th>
14
+ <th>Feed Intake (kg)</th>
15
+ <th>Disease Detected</th>
16
+ <th>Status</th>
17
+ <th>Treatment Recommendation</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ {% for record in health_records %}
22
+ <tr>
23
+ <td>{{ record.bird_id }}</td>
24
+ <td>{{ record.date.strftime('%Y-%m-%d') }}</td>
25
+ <td>{{ record.weight }}</td>
26
+ <td>{{ record.mortality_rate }}</td>
27
+ <td>{{ record.feed_intake }}</td>
28
+ <td>{{ record.disease_detected or 'N/A' }}</td>
29
+ <td>{{ record.status }}</td>
30
+ <td>{{ record.treatment_recommendation or 'N/A' }}</td>
31
+ </tr>
32
+ {% endfor %}
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+ {% endblock %}