Emmanuel Frimpong Asante
commited on
Commit
·
c36974c
1
Parent(s):
6dbfc52
update space
Browse files- .idea/workspace.xml +15 -16
- README.md +2 -2
- models/schemas/log_schema.py +28 -0
- requirements.txt +2 -0
- routes/report_dashboard.py +53 -0
- services/utils.py +15 -0
- templates/report_dashboard.html +97 -0
.idea/workspace.xml
CHANGED
@@ -5,13 +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 afterPath="$PROJECT_DIR$/models/schemas/
|
9 |
-
<change afterPath="$PROJECT_DIR$/routes/
|
10 |
-
<change afterPath="$PROJECT_DIR$/templates/
|
11 |
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
12 |
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
13 |
-
<change beforePath="$PROJECT_DIR$/
|
14 |
-
<change beforePath="$PROJECT_DIR$/services/auth_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/services/auth_service.py" afterDir="false" />
|
15 |
</list>
|
16 |
<option name="SHOW_DIALOG" value="false" />
|
17 |
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
@@ -24,8 +23,8 @@
|
|
24 |
<option value="Dockerfile" />
|
25 |
<option value="JavaScript File" />
|
26 |
<option value="CSS File" />
|
27 |
-
<option value="Python Script" />
|
28 |
<option value="HTML File" />
|
|
|
29 |
</list>
|
30 |
</option>
|
31 |
</component>
|
@@ -124,15 +123,7 @@
|
|
124 |
<workItem from="1730338434025" duration="11075000" />
|
125 |
<workItem from="1730305361670" duration="2425000" />
|
126 |
<workItem from="1730378091154" duration="2435000" />
|
127 |
-
<workItem from="1730381153348" duration="
|
128 |
-
</task>
|
129 |
-
<task id="LOCAL-00022" summary="update space">
|
130 |
-
<option name="closed" value="true" />
|
131 |
-
<created>1730325791987</created>
|
132 |
-
<option name="number" value="00022" />
|
133 |
-
<option name="presentableId" value="LOCAL-00022" />
|
134 |
-
<option name="project" value="LOCAL" />
|
135 |
-
<updated>1730325791987</updated>
|
136 |
</task>
|
137 |
<task id="LOCAL-00023" summary="update space">
|
138 |
<option name="closed" value="true" />
|
@@ -518,7 +509,15 @@
|
|
518 |
<option name="project" value="LOCAL" />
|
519 |
<updated>1730387397425</updated>
|
520 |
</task>
|
521 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
522 |
<servers />
|
523 |
</component>
|
524 |
<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/log_schema.py" afterDir="false" />
|
9 |
+
<change afterPath="$PROJECT_DIR$/routes/report_dashboard.py" afterDir="false" />
|
10 |
+
<change afterPath="$PROJECT_DIR$/templates/report_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$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
13 |
+
<change beforePath="$PROJECT_DIR$/services/utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/services/utils.py" afterDir="false" />
|
|
|
14 |
</list>
|
15 |
<option name="SHOW_DIALOG" value="false" />
|
16 |
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
|
23 |
<option value="Dockerfile" />
|
24 |
<option value="JavaScript File" />
|
25 |
<option value="CSS File" />
|
|
|
26 |
<option value="HTML File" />
|
27 |
+
<option value="Python Script" />
|
28 |
</list>
|
29 |
</option>
|
30 |
</component>
|
|
|
123 |
<workItem from="1730338434025" duration="11075000" />
|
124 |
<workItem from="1730305361670" duration="2425000" />
|
125 |
<workItem from="1730378091154" duration="2435000" />
|
126 |
+
<workItem from="1730381153348" duration="8786000" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
</task>
|
128 |
<task id="LOCAL-00023" summary="update space">
|
129 |
<option name="closed" value="true" />
|
|
|
509 |
<option name="project" value="LOCAL" />
|
510 |
<updated>1730387397425</updated>
|
511 |
</task>
|
512 |
+
<task id="LOCAL-00071" summary="update space">
|
513 |
+
<option name="closed" value="true" />
|
514 |
+
<created>1730389031505</created>
|
515 |
+
<option name="number" value="00071" />
|
516 |
+
<option name="presentableId" value="LOCAL-00071" />
|
517 |
+
<option name="project" value="LOCAL" />
|
518 |
+
<updated>1730389031505</updated>
|
519 |
+
</task>
|
520 |
+
<option name="localTasksCounter" value="72" />
|
521 |
<servers />
|
522 |
</component>
|
523 |
<component name="TypeScriptGeneratedFilesManager">
|
README.md
CHANGED
@@ -69,8 +69,8 @@ This project combines a **Poultry Farming Assistance System** and a **Poultry Ma
|
|
69 |
### 4. To-Do List Management
|
70 |
- [x] Design MongoDB schema for to-do lists.
|
71 |
- [x] Create routes for farmers to view and mark to-do items.
|
72 |
-
- [
|
73 |
-
- [
|
74 |
|
75 |
### 5. Data Logging and Reporting
|
76 |
- [ ] Implement data logging for activities and health records.
|
|
|
69 |
### 4. To-Do List Management
|
70 |
- [x] Design MongoDB schema for to-do lists.
|
71 |
- [x] Create routes for farmers to view and mark to-do items.
|
72 |
+
- [x] Build functionality for admins to assign tasks.
|
73 |
+
- [x] Develop an **AdminLTE 4**-based UI for managing tasks.
|
74 |
|
75 |
### 5. Data Logging and Reporting
|
76 |
- [ ] Implement data logging for activities and health records.
|
models/schemas/log_schema.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# models/schemas/log_schema.py
|
2 |
+
|
3 |
+
from pydantic import BaseModel, Field
|
4 |
+
from datetime import datetime
|
5 |
+
from typing import Optional
|
6 |
+
from bson import ObjectId
|
7 |
+
|
8 |
+
class PyObjectId(ObjectId):
|
9 |
+
"""Custom ObjectId class for Pydantic validation."""
|
10 |
+
@classmethod
|
11 |
+
def __get_validators__(cls):
|
12 |
+
yield cls.validate
|
13 |
+
|
14 |
+
@classmethod
|
15 |
+
def validate(cls, v):
|
16 |
+
if not ObjectId.is_valid(v):
|
17 |
+
raise ValueError("Invalid ObjectId")
|
18 |
+
return ObjectId(v)
|
19 |
+
|
20 |
+
class ActivityLog(BaseModel):
|
21 |
+
id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
|
22 |
+
activity_type: str # e.g., "health_check", "feeding", "medication"
|
23 |
+
description: str
|
24 |
+
user_id: str
|
25 |
+
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
26 |
+
|
27 |
+
class Config:
|
28 |
+
json_encoders = {ObjectId: str}
|
requirements.txt
CHANGED
@@ -17,3 +17,5 @@ transformers
|
|
17 |
keras
|
18 |
torch
|
19 |
bcrypt>=3.2.0
|
|
|
|
|
|
17 |
keras
|
18 |
torch
|
19 |
bcrypt>=3.2.0
|
20 |
+
reportlab
|
21 |
+
pandas
|
routes/report_dashboard.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Request, Depends
|
2 |
+
from fastapi.templating import Jinja2Templates
|
3 |
+
from services.utils import db, log_activity
|
4 |
+
from services.auth_service import get_current_user
|
5 |
+
from starlette.responses import HTMLResponse, JSONResponse
|
6 |
+
from fastapi.responses import StreamingResponse
|
7 |
+
from reportlab.lib.pagesizes import letter
|
8 |
+
from reportlab.pdfgen import canvas
|
9 |
+
import io
|
10 |
+
import pandas as pd
|
11 |
+
from fastapi.responses import FileResponse
|
12 |
+
|
13 |
+
dashboard_router = APIRouter()
|
14 |
+
templates = Jinja2Templates(directory="templates")
|
15 |
+
|
16 |
+
@dashboard_router.get("/reports", response_class=HTMLResponse)
|
17 |
+
async def report_dashboard(request: Request):
|
18 |
+
return templates.TemplateResponse("report_dashboard.html", {"request": request})
|
19 |
+
|
20 |
+
@dashboard_router.get("/reports/health_data")
|
21 |
+
async def get_health_data():
|
22 |
+
"""
|
23 |
+
Retrieve health data for dashboard visualization.
|
24 |
+
"""
|
25 |
+
health_collection = db.get_collection("health_records")
|
26 |
+
health_data = list(health_collection.find())
|
27 |
+
return JSONResponse(health_data)
|
28 |
+
|
29 |
+
@dashboard_router.get("/reports/activity_data")
|
30 |
+
async def get_activity_data():
|
31 |
+
"""
|
32 |
+
Retrieve activity logs for dashboard visualization.
|
33 |
+
"""
|
34 |
+
activity_collection = db.get_collection("activity_logs")
|
35 |
+
activity_logs = list(activity_collection.find())
|
36 |
+
return JSONResponse(activity_logs)
|
37 |
+
|
38 |
+
@dashboard_router.get("/export/health/pdf")
|
39 |
+
async def export_health_pdf():
|
40 |
+
buffer = io.BytesIO()
|
41 |
+
pdf = canvas.Canvas(buffer, pagesize=letter)
|
42 |
+
pdf.drawString(100, 750, "Health Records Report")
|
43 |
+
# Add more data...
|
44 |
+
pdf.save()
|
45 |
+
buffer.seek(0)
|
46 |
+
return StreamingResponse(buffer, media_type="application/pdf", headers={"Content-Disposition": "attachment;filename=health_records.pdf"})
|
47 |
+
|
48 |
+
@dashboard_router.get("/export/health/xls")
|
49 |
+
async def export_health_xls():
|
50 |
+
health_data = list(db.get_collection("health_records").find())
|
51 |
+
df = pd.DataFrame(health_data)
|
52 |
+
df.to_excel("health_records.xlsx", index=False)
|
53 |
+
return FileResponse("health_records.xlsx", media_type="application/vnd.ms-excel", headers={"Content-Disposition": "attachment;filename=health_records.xlsx"})
|
services/utils.py
CHANGED
@@ -178,3 +178,18 @@ def test_db_connection():
|
|
178 |
"""
|
179 |
result = mongo_instance.test_connection()
|
180 |
print(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
178 |
"""
|
179 |
result = mongo_instance.test_connection()
|
180 |
print(result)
|
181 |
+
|
182 |
+
|
183 |
+
log_collection = db.get_collection("activity_logs")
|
184 |
+
|
185 |
+
def log_activity(activity_type: str, description: str, user_id: str):
|
186 |
+
"""
|
187 |
+
Logs an activity with type, description, and user details.
|
188 |
+
"""
|
189 |
+
log_entry = {
|
190 |
+
"activity_type": activity_type,
|
191 |
+
"description": description,
|
192 |
+
"user_id": user_id,
|
193 |
+
"timestamp": datetime.utcnow()
|
194 |
+
}
|
195 |
+
log_collection.insert_one(log_entry)
|
templates/report_dashboard.html
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "base.html" %}
|
2 |
+
|
3 |
+
{% block content %}
|
4 |
+
<div class="content-header">
|
5 |
+
<div class="container-fluid">
|
6 |
+
<h1>Health and Activity Logs</h1>
|
7 |
+
</div>
|
8 |
+
</div>
|
9 |
+
|
10 |
+
<div class="content">
|
11 |
+
<div class="container-fluid">
|
12 |
+
<!-- Health Records Table -->
|
13 |
+
<div class="card">
|
14 |
+
<div class="card-header">
|
15 |
+
<h3 class="card-title">Health Records</h3>
|
16 |
+
<div class="card-tools">
|
17 |
+
<button onclick="exportHealthData('pdf')" class="btn btn-danger btn-sm">Export PDF</button>
|
18 |
+
<button onclick="exportHealthData('xls')" class="btn btn-success btn-sm">Export XLS</button>
|
19 |
+
</div>
|
20 |
+
</div>
|
21 |
+
<div class="card-body table-responsive">
|
22 |
+
<table class="table table-hover">
|
23 |
+
<thead>
|
24 |
+
<tr>
|
25 |
+
<th>Record Date</th>
|
26 |
+
<th>Metric</th>
|
27 |
+
<th>Value</th>
|
28 |
+
<th>Status</th>
|
29 |
+
</tr>
|
30 |
+
</thead>
|
31 |
+
<tbody id="health-records">
|
32 |
+
<!-- Populate rows via JavaScript -->
|
33 |
+
</tbody>
|
34 |
+
</table>
|
35 |
+
</div>
|
36 |
+
</div>
|
37 |
+
|
38 |
+
<!-- Activity Logs Table -->
|
39 |
+
<div class="card mt-4">
|
40 |
+
<div class="card-header">
|
41 |
+
<h3 class="card-title">Activity Logs</h3>
|
42 |
+
<div class="card-tools">
|
43 |
+
<button onclick="exportActivityData('pdf')" class="btn btn-danger btn-sm">Export PDF</button>
|
44 |
+
<button onclick="exportActivityData('xls')" class="btn btn-success btn-sm">Export XLS</button>
|
45 |
+
</div>
|
46 |
+
</div>
|
47 |
+
<div class="card-body table-responsive">
|
48 |
+
<table class="table table-hover">
|
49 |
+
<thead>
|
50 |
+
<tr>
|
51 |
+
<th>Timestamp</th>
|
52 |
+
<th>Type</th>
|
53 |
+
<th>Description</th>
|
54 |
+
<th>User</th>
|
55 |
+
</tr>
|
56 |
+
</thead>
|
57 |
+
<tbody id="activity-logs">
|
58 |
+
<!-- Populate rows via JavaScript -->
|
59 |
+
</tbody>
|
60 |
+
</table>
|
61 |
+
</div>
|
62 |
+
</div>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
<!-- Add in report_dashboard.html -->
|
66 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
67 |
+
<script>
|
68 |
+
async function loadHealthData() {
|
69 |
+
const response = await fetch("/reports/health_data");
|
70 |
+
const data = await response.json();
|
71 |
+
// Process data for Chart.js format
|
72 |
+
|
73 |
+
const ctx = document.getElementById("healthChart").getContext("2d");
|
74 |
+
new Chart(ctx, {
|
75 |
+
type: 'line',
|
76 |
+
data: {
|
77 |
+
labels: data.map(entry => entry.date),
|
78 |
+
datasets: [{
|
79 |
+
label: 'Mortality Rate',
|
80 |
+
data: data.map(entry => entry.mortality_rate),
|
81 |
+
borderColor: 'rgb(255, 99, 132)',
|
82 |
+
fill: false
|
83 |
+
}]
|
84 |
+
},
|
85 |
+
options: {
|
86 |
+
scales: {
|
87 |
+
x: { type: 'time', time: { unit: 'day' } },
|
88 |
+
y: { beginAtZero: true }
|
89 |
+
}
|
90 |
+
}
|
91 |
+
});
|
92 |
+
}
|
93 |
+
document.addEventListener("DOMContentLoaded", loadHealthData);
|
94 |
+
</script>
|
95 |
+
|
96 |
+
|
97 |
+
{% endblock %}
|