Ani14 commited on
Commit
5bacd22
Β·
verified Β·
1 Parent(s): 09e2b29

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +117 -10
  2. main.py +2000 -0
  3. requirements.txt +12 -0
  4. test_app.py +105 -0
README.md CHANGED
@@ -1,10 +1,117 @@
1
- ---
2
- title: Wound App
3
- emoji: πŸŒ–
4
- colorFrom: blue
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SmartHeal FastAPI Application
2
+
3
+ This is a FastAPI conversion of the original Flask SmartHeal application, maintaining all the same endpoints and functionality.
4
+
5
+ ## Setup Instructions
6
+
7
+ 1. **Install Dependencies**
8
+ ```bash
9
+ pip install -r requirements.txt
10
+ ```
11
+
12
+ 2. **Environment Configuration**
13
+ - Copy `.env.example` to `.env`
14
+ - Fill in your database and Twilio credentials in the `.env` file
15
+
16
+ 3. **Run the Application**
17
+ ```bash
18
+ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
19
+ ```
20
+
21
+ ## API Endpoints
22
+
23
+ The application maintains all the original endpoints from the Flask version:
24
+
25
+ ### Organization Endpoints
26
+ - `POST /send_email` - Send license key via email
27
+ - `POST /verify_license_key` - Verify license key
28
+ - `POST /create_pin` - Create PIN for organization
29
+ - `POST /fetch_data` - Fetch organization data
30
+ - `POST /save_additional_data` - Save department and location
31
+ - `POST /verify_pin` - Verify PIN
32
+ - `POST /send_otp` - Send OTP
33
+ - `POST /change_pin_org` - Change organization PIN
34
+ - `POST /forgot_pin_org` - Reset organization PIN
35
+ - `GET /organisation_details` - Get organization details
36
+ - `POST /store_org_image` - Store organization image
37
+ - `GET /get_org_image` - Get organization image
38
+ - `POST /update_org_profile` - Update organization profile
39
+ - `POST /org/forgot/pin/otp` - Send OTP for PIN reset
40
+
41
+ ### Medical User Endpoints
42
+ - `POST /med_send_email` - Send license key for medical users
43
+ - `POST /med_verify_license_key` - Verify medical user license key
44
+ - `POST /med_create_pin` - Create PIN for medical user
45
+ - `POST /med_fetch_data` - Fetch medical user data
46
+ - `POST /save_med_data` - Save medical user data
47
+ - `POST /med_verify_pin` - Verify medical user PIN
48
+ - `POST /med_send_otp` - Send OTP for medical user
49
+ - `POST /change_pin_med` - Change medical user PIN
50
+ - `POST /forgot_pin_med` - Reset medical user PIN
51
+ - `GET /med_details` - Get medical user details
52
+ - `POST /store_med_image` - Store medical user image
53
+ - `GET /get_med_image` - Get medical user image
54
+ - `POST /update_med_profile` - Update medical user profile
55
+ - `POST /med/forgot/pin/otp` - Send OTP for PIN reset
56
+
57
+ ### Patient Management Endpoints
58
+ - `POST /add_patient` - Add new patient
59
+ - `POST /add_patient_v2` - Add new patient (v2)
60
+ - `GET /get_all_patient_details` - Get all patient details
61
+ - `GET /search_patient` - Search for patient
62
+ - `GET /patient_details` - Get specific patient details
63
+ - `POST /update_patient_details` - Update patient details
64
+ - `POST /store_image` - Store patient image
65
+ - `GET /get_image` - Get patient image
66
+ - `POST /save_notes` - Save patient notes
67
+ - `POST /save_notes_v2` - Save patient notes (v2)
68
+ - `POST /sort_patients` - Sort patients by criteria
69
+
70
+ ### Wound Management Endpoints
71
+ - `POST /add_wound_details` - Add wound details
72
+ - `POST /add_wound_details_v2` - Add wound details with image (v2)
73
+ - `POST /add_wound_details_v3` - Add wound details with multiple images (v3)
74
+ - `POST /store_wound_image` - Store wound image
75
+ - `GET /get_wound_image` - Get wound image
76
+ - `GET /wound_progress_timeline` - Get wound progress timeline
77
+
78
+ ### Appointment Management Endpoints
79
+ - `POST /update_scheduled_date` - Update appointment date
80
+ - `POST /update_scheduled_date_v2` - Update appointment date (v2)
81
+ - `GET /total_appointments_till_date` - Get total appointments till date
82
+ - `GET /total_appointments_till_month` - Get total appointments till month
83
+ - `POST /total_appointments` - Get total appointments in date range
84
+ - `GET /total_appointments_v2` - Get total appointments in date range (v2)
85
+ - `POST /get_appointment_count` - Get appointment count for specific date
86
+
87
+ ### Administrative Endpoints
88
+ - `POST /admin_add_practitioner` - Add practitioner (admin)
89
+ - `POST /admin_add_practitioner_v2` - Add practitioner (admin v2)
90
+ - `GET /generate_prescription` - Generate prescription
91
+
92
+ ## Key Changes from Flask to FastAPI
93
+
94
+ 1. **Request Handling**: Replaced Flask's `request.json` with Pydantic models for request validation
95
+ 2. **Response Handling**: Replaced Flask's `jsonify()` with FastAPI's `JSONResponse`
96
+ 3. **File Uploads**: Replaced Flask's file handling with FastAPI's `UploadFile`
97
+ 4. **Dependency Injection**: Used FastAPI's dependency injection for database sessions and JWT verification
98
+ 5. **Error Handling**: Replaced Flask's error handling with FastAPI's `HTTPException`
99
+ 6. **Static Files**: Replaced Flask's static file serving with FastAPI's `StaticFiles`
100
+
101
+ ## Authentication
102
+
103
+ The application uses JWT tokens for authentication. Include the token in the Authorization header:
104
+ ```
105
+ Authorization: Bearer <your_jwt_token>
106
+ ```
107
+
108
+ ## File Uploads
109
+
110
+ The application supports file uploads for:
111
+ - Patient images
112
+ - Wound images
113
+ - Organization images
114
+ - Medical practitioner images
115
+
116
+ Files are stored in the `uploads/` directory with organized subdirectories.
117
+
main.py ADDED
@@ -0,0 +1,2000 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Depends, HTTPException, status, File, UploadFile, Form
2
+ from fastapi.responses import JSONResponse, FileResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from sqlalchemy import create_engine, text
5
+ from sqlalchemy.orm import sessionmaker
6
+ import pymysql
7
+ import jwt
8
+ import random
9
+ import string
10
+ import datetime
11
+ import os
12
+ import uuid
13
+ from dotenv import load_dotenv
14
+ from twilio.rest import Client
15
+ import requests
16
+ from werkzeug.utils import secure_filename
17
+ from pydantic import BaseModel
18
+ from typing import Optional
19
+
20
+ load_dotenv()
21
+
22
+ app = FastAPI()
23
+
24
+ # Database credentials
25
+ DB_HOST = os.getenv('host')
26
+ DB_DATABASE = os.getenv('db')
27
+ DB_USERNAME = os.getenv('username')
28
+ DB_PASSWORD = os.getenv('psswd')
29
+ DATABASE_URL = f"mysql+pymysql://{DB_USERNAME}:{DB_PASSWORD}@{DB_HOST}/{DB_DATABASE}"
30
+ engine = create_engine(DATABASE_URL, pool_pre_ping=True)
31
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
32
+
33
+ account_sid = os.getenv('acc_sid')
34
+ auth_token = os.getenv('auth_token')
35
+ twilio_number = os.getenv('tn')
36
+
37
+ JWT_SECRET_KEY = 'CkOPcOppyh31sQcisbyOM3RKD4C2G7SzQmuG5LePt9XBarsxgjm0fc7uOECcqoGm'
38
+
39
+ # Configuration for file uploads
40
+ UPLOAD_FOLDER = 'uploads'
41
+ if not os.path.exists(UPLOAD_FOLDER):
42
+ os.makedirs(UPLOAD_FOLDER)
43
+
44
+ app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads")
45
+
46
+ BASE_URL = 'https://api.smartheal.waysdatalabs.com' # This might need to be updated based on deployment
47
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
48
+
49
+ # Dependency to get DB session
50
+ def get_db():
51
+ db = SessionLocal()
52
+ try:
53
+ yield db
54
+ finally:
55
+ db.close()
56
+
57
+ # Utility function to check allowed file extensions
58
+ def allowed_file(filename):
59
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
60
+
61
+ def generate_session_id():
62
+ return str(uuid.uuid4())
63
+
64
+ def generate_license_key():
65
+ return ''.join(random.choices(string.ascii_uppercase + string.digits, k=12))
66
+
67
+ def generate_patient_id():
68
+ with SessionLocal() as session:
69
+ result = session.execute(text("SELECT MAX(id) FROM patients")).fetchone()
70
+ last_id = result[0] if result[0] is not None else 0
71
+ prefix = "AB"
72
+ formatted_id = f"{prefix}000{last_id + 1}"
73
+ return formatted_id
74
+
75
+ def send_sms(phone, otp):
76
+ client = Client(account_sid, auth_token)
77
+ message = client.messages.create(
78
+ body=f"Your verification code is: {otp}. Don't share this code with anyone; our employees will never ask for the code.",
79
+ from_=twilio_number,
80
+ to=phone
81
+ )
82
+
83
+ def generate_otp():
84
+ return str(random.randint(1000, 9999))
85
+
86
+ def update_otp_in_database(session, phone, otp, expiry_time, updated_at):
87
+ try:
88
+ query = text("UPDATE organisations SET otp= :otp, otp_expiry= :expiry_time, updated_at = :updated_at WHERE phone= :phone")
89
+ session.execute(query, {'otp': otp, 'expiry_time': expiry_time, 'phone': phone, 'updated_at': updated_at})
90
+ session.commit()
91
+ except Exception as e:
92
+ raise HTTPException(status_code=500, detail=str(e))
93
+
94
+ def update_otp_in_med_database(session, phone, otp, expiry_time, updated_at):
95
+ try:
96
+ query = text("UPDATE users SET otp= :otp, otp_expiry= :expiry_time, updated_at = :updated_at WHERE phone= :phone")
97
+ session.execute(query, {'otp': otp, 'expiry_time': expiry_time, 'phone': phone, 'updated_at': updated_at})
98
+ session.commit()
99
+ except Exception as e:
100
+ raise HTTPException(status_code=500, detail=str(e))
101
+
102
+ # JWT Authentication Dependency
103
+ def verify_jwt_token(request: Request):
104
+ auth_header = request.headers.get('Authorization')
105
+ if not auth_header or not auth_header.startswith('Bearer '):
106
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid Authorization header')
107
+ token = auth_header.split(' ')[1]
108
+ try:
109
+ payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
110
+ return payload
111
+ except jwt.ExpiredSignatureError:
112
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Token has expired')
113
+ except jwt.InvalidTokenError:
114
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
115
+
116
+ # Pydantic Models for Request Body Validation
117
+ class SendEmailRequest(BaseModel):
118
+ name: str
119
+ email: str
120
+ c_code: str
121
+ phone: str
122
+
123
+ class VerifyLicenseKeyRequest(BaseModel):
124
+ email: str
125
+ license_key: str
126
+
127
+ class CreatePinRequest(BaseModel):
128
+ license_key: str
129
+ email: str
130
+ pin: str
131
+ name: Optional[str] = None
132
+ c_code: Optional[str] = None
133
+ phone: Optional[str] = None
134
+
135
+ class FetchDataRequest(BaseModel):
136
+ email: str
137
+
138
+ class SaveAdditionalDataRequest(BaseModel):
139
+ department: str
140
+ location: str
141
+ email: str
142
+ latitude: float
143
+ longitude: float
144
+
145
+ class AddWoundDetailsRequest(BaseModel):
146
+ length: float
147
+ breadth: float
148
+ depth: float
149
+ area: float
150
+ moisture: str
151
+ wound_location: Optional[str] = None
152
+ tissue: Optional[str] = None
153
+ exudate: Optional[str] = None
154
+ periwound: Optional[str] = None
155
+ periwound_type: Optional[str] = None
156
+ patient_id: str
157
+ type: Optional[str] = None
158
+ category: Optional[str] = None
159
+ edge: Optional[str] = None
160
+ infection: Optional[str] = None
161
+ last_dressing_date: Optional[str] = None
162
+
163
+ class AddPatientRequest(BaseModel):
164
+ name: str
165
+ dob: str
166
+ gender: str
167
+ age: int
168
+ height: float
169
+ weight: float
170
+ email: str
171
+ doctor: str
172
+
173
+ class SearchPatientRequest(BaseModel):
174
+ name: str
175
+
176
+ class GeneratePrescriptionRequest(BaseModel):
177
+ patient_id: str
178
+
179
+ class VerifyPinRequest(BaseModel):
180
+ email: str
181
+ pin: str
182
+
183
+ class SendOtpRequest(BaseModel):
184
+ phone: str
185
+
186
+ class UpdateScheduledDateRequest(BaseModel):
187
+ email: str
188
+ patient_id: str
189
+ doctor: str
190
+ scheduled_date: str
191
+
192
+ class TotalAppointmentsTillDateRequest(BaseModel):
193
+ date: str
194
+
195
+ class TotalAppointmentsTillMonthRequest(BaseModel):
196
+ year: int
197
+ month: int
198
+
199
+ class ChangePinRequest(BaseModel):
200
+ email: str
201
+ current_pin: str
202
+ new_pin: str
203
+
204
+ class ForgotPinRequest(BaseModel):
205
+ email: str
206
+ otp: int
207
+ new_pin: str
208
+
209
+ class OrganisationDetailsRequest(BaseModel):
210
+ email: str
211
+
212
+ class UpdatePatientDetailsRequest(BaseModel):
213
+ patient_id: str
214
+ allergies: Optional[str] = None
215
+ past_history: Optional[str] = None
216
+
217
+ class PatientDetailsRequest(BaseModel):
218
+ patient_id: str
219
+
220
+ class StoreImageRequest(BaseModel):
221
+ patient_id: str
222
+
223
+ class GetImageRequest(BaseModel):
224
+ patient_id: str
225
+
226
+ class StoreWoundImageRequest(BaseModel):
227
+ patient_id: str
228
+
229
+ class GetWoundImageRequest(BaseModel):
230
+ patient_id: str
231
+
232
+ class StoreMedImageRequest(BaseModel):
233
+ email: str
234
+
235
+ class GetMedImageRequest(BaseModel):
236
+ email: str
237
+
238
+ class StoreOrgImageRequest(BaseModel):
239
+ email: str
240
+
241
+ class GetOrgImageRequest(BaseModel):
242
+ email: str
243
+
244
+ class SaveNotesRequest(BaseModel):
245
+ patient_id: str
246
+ notes: str
247
+
248
+ class TotalAppointmentsRequest(BaseModel):
249
+ start_date: str
250
+ end_date: str
251
+ doctor: str
252
+
253
+ class AdminAddPractitionerRequest(BaseModel):
254
+ name: str
255
+ email: str
256
+ c_code: str
257
+ phone: str
258
+ org: str
259
+
260
+ class UpdateOrgProfileRequest(BaseModel):
261
+ email: str
262
+ name: Optional[str] = None
263
+ department: Optional[str] = None
264
+ about: Optional[str] = None
265
+ location: Optional[str] = None
266
+ latitude: Optional[float] = None
267
+ longitude: Optional[float] = None
268
+
269
+ class UpdateMedProfileRequest(BaseModel):
270
+ email: str
271
+ name: Optional[str] = None
272
+ department: Optional[str] = None
273
+ about: Optional[str] = None
274
+ location: Optional[str] = None
275
+ latitude: Optional[float] = None
276
+ longitude: Optional[float] = None
277
+
278
+ class AddWoundDetailsV2Request(BaseModel):
279
+ length: float
280
+ breadth: float
281
+ depth: float
282
+ area: float
283
+ moisture: str
284
+ wound_location: Optional[str] = None
285
+ tissue: Optional[str] = None
286
+ exudate: Optional[str] = None
287
+ periwound: Optional[str] = None
288
+ periwound_type: Optional[str] = None
289
+ patient_id: str
290
+ type: Optional[str] = None
291
+ category: Optional[str] = None
292
+ edge: Optional[str] = None
293
+ infection: Optional[str] = None
294
+ last_dressing_date: Optional[str] = None
295
+
296
+ class WoundProgressTimelineRequest(BaseModel):
297
+ patient_id: str
298
+
299
+ class AddPatientV2Request(BaseModel):
300
+ name: str
301
+ dob: str
302
+ gender: str
303
+ age: int
304
+ height: float
305
+ weight: float
306
+ email: str
307
+ doctor: str
308
+ role: str
309
+
310
+ class SaveNotesV2Request(BaseModel):
311
+ patient_id: str
312
+ notes: Optional[str] = None
313
+ remarks: Optional[str] = None
314
+
315
+ class AdminAddPractitionerV2Request(BaseModel):
316
+ name: str
317
+ email: str
318
+ c_code: str
319
+ phone: str
320
+ org_email: str
321
+
322
+ class SortPatientsRequest(BaseModel):
323
+ email: str
324
+ date: Optional[str] = None
325
+
326
+ class GetAppointmentCountRequest(BaseModel):
327
+ email: str
328
+ date: str
329
+
330
+ class AddWoundDetailsV3Request(BaseModel):
331
+ length: float
332
+ breadth: float
333
+ depth: float
334
+ area: float
335
+ moisture: str
336
+ wound_location: Optional[str] = None
337
+ tissue: Optional[str] = None
338
+ exudate: Optional[str] = None
339
+ periwound: Optional[str] = None
340
+ periwound_type: Optional[str] = None
341
+ patient_id: str
342
+ type: Optional[str] = None
343
+ category: Optional[str] = None
344
+ edge: Optional[str] = None
345
+ infection: Optional[str] = None
346
+ last_dressing_date: Optional[str] = None
347
+
348
+
349
+ # Endpoints
350
+
351
+ @app.post("/send_email")
352
+ async def send_email(request_data: SendEmailRequest, db: SessionLocal = Depends(get_db)):
353
+ name = request_data.name
354
+ email = request_data.email
355
+ c_code = request_data.c_code
356
+ phone = request_data.phone
357
+
358
+ try:
359
+ with db as session:
360
+ query = text("SELECT email, phone FROM organisations WHERE email = :email OR phone = :phone")
361
+ existing_user = session.execute(query, {'email': email, 'phone': phone}).fetchone()
362
+
363
+ if existing_user:
364
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Email or phone already exists. Please login.')
365
+ else:
366
+ license_key = generate_license_key()
367
+
368
+ email_payload = {
369
+ 'Recipient': email,
370
+ 'Subject': 'License key for SmartHeal',
371
+ 'Body': f'Your license key is: {license_key}',
372
+ 'ApiKey': '6A7339A3-E70B-4A8D-AA23-0264125F4959'
373
+ }
374
+
375
+ email_response = requests.post(
376
+ 'https://api.waysdatalabs.com/api/EmailSender/SendMail',
377
+ headers={},
378
+ data=email_payload
379
+ )
380
+
381
+ if email_response.status_code == 200:
382
+ return JSONResponse(content={'message': 'License key sent to email successfully', 'license_key': license_key}, status_code=status.HTTP_200_OK)
383
+ else:
384
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Failed to send email')
385
+ except Exception as e:
386
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
387
+
388
+ @app.post("/verify_license_key")
389
+ async def verify_license_key_endpoint(request_data: VerifyLicenseKeyRequest, db: SessionLocal = Depends(get_db)):
390
+ email = request_data.email
391
+ license_key = request_data.license_key
392
+
393
+ try:
394
+ with db as session:
395
+ query = text("SELECT licence_key FROM organisations WHERE email = :email")
396
+ result = session.execute(query, {'email': email}).fetchone()
397
+ if result and result.licence_key == license_key:
398
+ token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
399
+ return JSONResponse(content={'message': 'License key verified successfully', 'token': token}, status_code=status.HTTP_200_OK)
400
+ else:
401
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid license key')
402
+ except Exception as e:
403
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
404
+
405
+ @app.post("/create_pin")
406
+ async def create_pin_endpoint(request_data: CreatePinRequest, db: SessionLocal = Depends(get_db)):
407
+ license_key = request_data.license_key
408
+ email = request_data.email
409
+ pin = request_data.pin
410
+ name = request_data.name
411
+ c_code = request_data.c_code
412
+ phone = request_data.phone
413
+
414
+ created_at = datetime.datetime.utcnow()
415
+ updated_at = datetime.datetime.utcnow()
416
+
417
+ try:
418
+ with db as session:
419
+ token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
420
+ query = text("INSERT INTO organisations (name, email, c_code, phone, uuid, licence_key, pin, created_at, updated_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :pin, :created_at, :updated_at)")
421
+ session.execute(query, {
422
+ 'name': name,
423
+ 'email': email,
424
+ 'c_code': c_code,
425
+ 'phone': phone,
426
+ 'uuid': generate_session_id(),
427
+ 'license_key': license_key,
428
+ 'pin': pin,
429
+ 'created_at': created_at,
430
+ 'updated_at': updated_at
431
+ })
432
+ session.commit()
433
+ return JSONResponse(content={'message': 'PIN created and data saved successfully', 'token': token}, status_code=status.HTTP_200_OK)
434
+ except Exception as e:
435
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
436
+
437
+ @app.post("/fetch_data")
438
+ async def fetch_name_phone(request_data: FetchDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
439
+ email = request_data.email
440
+
441
+ try:
442
+ with db as session:
443
+ query = text("SELECT name, phone FROM organisations WHERE email = :email")
444
+ result = session.execute(query, {'email': email}).fetchall()
445
+ result_dicts = [{'name': row[0], 'phone': row[1]} for row in result]
446
+ return JSONResponse(content=result_dicts, status_code=status.HTTP_200_OK)
447
+ except Exception as e:
448
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
449
+
450
+ @app.post("/save_additional_data")
451
+ async def save_department_location(request_data: SaveAdditionalDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
452
+ department = request_data.department
453
+ location = request_data.location
454
+ email = request_data.email
455
+ latitude = request_data.latitude
456
+ longitude = request_data.longitude
457
+
458
+ try:
459
+ with db as session:
460
+ query = text("UPDATE organisations SET departments = :department, location = :location, latitude = :latitude, longitude = :longitude WHERE email = :email;")
461
+ session.execute(query, {'department': department, 'location': location, 'latitude': latitude, 'longitude': longitude, 'email': email})
462
+ session.commit()
463
+ return JSONResponse(content={'message': 'Department, location, latitude, and longitude saved successfully'}, status_code=status.HTTP_200_OK)
464
+ except Exception as e:
465
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
466
+
467
+ @app.post("/add_wound_details")
468
+ async def add_wound_details(request_data: AddWoundDetailsRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
469
+ length = request_data.length
470
+ breadth = request_data.breadth
471
+ depth = request_data.depth
472
+ area = request_data.area
473
+ moisture = request_data.moisture
474
+ wound_location = request_data.wound_location
475
+ tissue = request_data.tissue
476
+ exudate = request_data.exudate
477
+ periwound = request_data.periwound
478
+ periwound_type = request_data.periwound_type
479
+ patient_id = request_data.patient_id
480
+ type = request_data.type
481
+ category = request_data.category
482
+ edge = request_data.edge
483
+ infection = request_data.infection
484
+ last_dressing_date = request_data.last_dressing_date
485
+
486
+ try:
487
+ with db as session:
488
+ query = text("UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area, moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate, periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category, edges = :edge, infection = :infection, last_dressing = :last_dressing_date WHERE patient_id = :patient_id;")
489
+ session.execute(query, {'length': length, 'breadth': breadth, 'depth': depth, 'area': area, 'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue, 'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type, 'type': type, 'category': category, 'edge': edge, 'infection': infection, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id})
490
+ session.commit()
491
+ return JSONResponse(content={'message': 'Wound details added successfully'}, status_code=status.HTTP_200_OK)
492
+ except Exception as e:
493
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
494
+
495
+ @app.get("/get_all_patient_details")
496
+ async def get_all_patient_details(email: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
497
+ try:
498
+ with db as session:
499
+ query = text("SELECT * FROM patients WHERE email = :email")
500
+ patient_details = session.execute(query, {'email': email}).fetchall()
501
+ patient_dicts = [dict(row._mapping) for row in patient_details]
502
+ return JSONResponse(content={'patient_details': patient_dicts}, status_code=status.HTTP_200_OK)
503
+ except Exception as e:
504
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
505
+
506
+ @app.post("/add_patient")
507
+ async def add_patient_endpoint(request_data: AddPatientRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
508
+ name = request_data.name
509
+ dob = request_data.dob
510
+ gender = request_data.gender
511
+ age = request_data.age
512
+ height = request_data.height
513
+ weight = request_data.weight
514
+ email = request_data.email
515
+ doctor = request_data.doctor
516
+ patient_id = generate_patient_id()
517
+
518
+ created_at = datetime.datetime.utcnow()
519
+ updated_at = datetime.datetime.utcnow()
520
+ scheduled_date = datetime.datetime.utcnow()
521
+
522
+ try:
523
+ with db as session:
524
+ uuid_val = generate_session_id()
525
+ pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, doctor, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
526
+ session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
527
+ wound_query = text("INSERT INTO wounds ( uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
528
+ session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
529
+ session.commit()
530
+ return JSONResponse(content={'message': 'Patient added successfully', 'patient_id': patient_id}, status_code=status.HTTP_200_OK)
531
+ except Exception as e:
532
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
533
+
534
+ @app.get("/search_patient")
535
+ async def search_patient_endpoint(name: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
536
+ if not name:
537
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
538
+
539
+ try:
540
+ with db as session:
541
+ query = text("SELECT * FROM patients WHERE name = :name")
542
+ patients = session.execute(query, {'name': name}).fetchall()
543
+
544
+ if not patients:
545
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No patients found with this name')
546
+
547
+ patients_dicts = [dict(row._mapping) for row in patients]
548
+ return JSONResponse(content={'patients': patients_dicts}, status_code=status.HTTP_200_OK)
549
+ except Exception as e:
550
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
551
+
552
+ @app.get("/generate_prescription")
553
+ async def generate_prescription_endpoint(patient_id: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
554
+ if not patient_id:
555
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
556
+
557
+ try:
558
+ with db as session:
559
+ patient_query = text("SELECT * FROM patients WHERE patient_id = :patient_id ")
560
+ patient = session.execute(patient_query, {'patient_id': patient_id}).fetchone()
561
+ if not patient:
562
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No patient found with this ID')
563
+
564
+ wound_query = text("SELECT * FROM patients WHERE patient_id = :patient_id ")
565
+ wounds = session.execute(wound_query, {'patient_id': patient_id}).fetchall()
566
+
567
+ wound_category = []
568
+ for wound in wounds:
569
+ area = wound.height * wound.width
570
+ if area <= 5:
571
+ dimension = 'Small'
572
+ elif 5 < area <= 20:
573
+ dimension = 'Medium'
574
+ else:
575
+ dimension = 'Large'
576
+ wound_category.append((wound.wound_type, dimension))
577
+
578
+ medication_details = []
579
+ for wound_type, dimension in wound_category:
580
+ medication_query = text("""
581
+ SELECT * FROM WoundMedications
582
+ WHERE WoundType = :wound_type AND WoundDimensions = :dimension
583
+ """)
584
+ medications = session.execute(medication_query, {'wound_type': wound_type, 'dimension': dimension}).fetchall()
585
+ medication_details.extend(medications)
586
+
587
+ prescription = {
588
+ 'patient_details': dict(patient._mapping),
589
+ 'wound_details': [dict(w._mapping) for w in wounds],
590
+ 'medication_details': [dict(m._mapping) for m in medication_details]
591
+ }
592
+
593
+ return JSONResponse(content={'prescription': prescription}, status_code=status.HTTP_200_OK)
594
+ except Exception as e:
595
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
596
+
597
+ @app.post("/verify_pin")
598
+ async def verify_pin_endpoint(request_data: VerifyPinRequest, db: SessionLocal = Depends(get_db)):
599
+ email = request_data.email
600
+ pin = request_data.pin
601
+
602
+ try:
603
+ with db as session:
604
+ query = text("SELECT pin FROM organisations WHERE email = :email")
605
+ result = session.execute(query, {'email': email}).fetchone()
606
+
607
+ if result:
608
+ if result.pin == pin:
609
+ return JSONResponse(content={'message': 'Pin verified successfully'}, status_code=status.HTTP_200_OK)
610
+ else:
611
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pin')
612
+ else:
613
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
614
+ except Exception as e:
615
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
616
+
617
+ @app.post("/send_otp")
618
+ async def send_otp_endpoint(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db)):
619
+ phone = request_data.phone
620
+
621
+ try:
622
+ with db as session:
623
+ query = text("SELECT * FROM organisations WHERE phone = :phone")
624
+ organisation = session.execute(query, {'phone': phone}).fetchone()
625
+
626
+ if organisation:
627
+ phone_with_code = organisation.c_code + organisation.phone
628
+ otp = "1234" # generate_otp()
629
+ # send_sms(phone_with_code, otp)
630
+
631
+ updated_at = datetime.datetime.utcnow()
632
+ expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
633
+ update_otp_in_database(session, phone, otp, expiry_time, updated_at)
634
+
635
+ token = jwt.encode({'email': organisation.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
636
+ return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'token': token, 'otp': otp, 'email': organisation.email, 'name': organisation.name, 'pin': organisation.pin}, status_code=status.HTTP_200_OK)
637
+ else:
638
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
639
+ except Exception as e:
640
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
641
+
642
+ @app.post("/med_send_email")
643
+ async def med_send_email(request_data: SendEmailRequest, db: SessionLocal = Depends(get_db)):
644
+ name = request_data.name
645
+ email = request_data.email
646
+ c_code = request_data.c_code
647
+ phone = request_data.phone
648
+
649
+ try:
650
+ with db as session:
651
+ query = text("SELECT email, phone FROM users WHERE email = :email OR phone = :phone")
652
+ existing_user = session.execute(query, {'email': email, 'phone': phone}).fetchone()
653
+ if existing_user:
654
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Email or phone already exists. Please login.')
655
+ else:
656
+ license_key = generate_license_key()
657
+
658
+ email_payload = {
659
+ 'Recipient': email,
660
+ 'Subject': 'License key for SmartHeal',
661
+ 'Body': f'Your license key is: {license_key}',
662
+ 'ApiKey': '6A7339A3-E70B-4A8D-AA23-0264125F4959'
663
+ }
664
+
665
+ email_response = requests.post(
666
+ 'https://api.waysdatalabs.com/api/EmailSender/SendMail',
667
+ headers={},
668
+ data=email_payload
669
+ )
670
+
671
+ if email_response.status_code == 200:
672
+ return JSONResponse(content={'message': 'License key sent to email successfully', 'license_key': license_key}, status_code=status.HTTP_200_OK)
673
+ else:
674
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Failed to send email')
675
+ except Exception as e:
676
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
677
+
678
+ @app.post("/med_verify_license_key")
679
+ async def med_verify_license_key(request_data: VerifyLicenseKeyRequest, db: SessionLocal = Depends(get_db)):
680
+ email = request_data.email
681
+ license_key = request_data.license_key
682
+
683
+ try:
684
+ with db as session:
685
+ query = text("SELECT licence_key FROM users WHERE email = :email")
686
+ result = session.execute(query, {'email': email}).fetchone()
687
+ if result and result.licence_key == license_key:
688
+ token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)}, JWT_SECRET_KEY, algorithm='HS256')
689
+ return JSONResponse(content={'message': 'License key verified successfully', 'token': token}, status_code=status.HTTP_200_OK)
690
+ else:
691
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid license key')
692
+ except Exception as e:
693
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
694
+
695
+ @app.post("/med_create_pin")
696
+ async def med_create_pin(request_data: CreatePinRequest, db: SessionLocal = Depends(get_db)):
697
+ license_key = request_data.license_key
698
+ email = request_data.email
699
+ pin = request_data.pin
700
+ name = request_data.name
701
+ c_code = request_data.c_code
702
+ phone = request_data.phone
703
+
704
+ created_at = datetime.datetime.utcnow()
705
+ updated_at = datetime.datetime.utcnow()
706
+
707
+ try:
708
+ with db as session:
709
+ org = "0"
710
+ token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
711
+ query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, pin, org, created_at, updated_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :pin, :org, :created_at, :updated_at)")
712
+ session.execute(query, {
713
+ 'name': name,
714
+ 'email': email,
715
+ 'c_code': c_code,
716
+ 'phone': phone,
717
+ 'uuid': generate_session_id(),
718
+ 'license_key': license_key,
719
+ 'pin': pin,
720
+ 'org': org,
721
+ 'created_at': created_at,
722
+ 'updated_at': updated_at
723
+ })
724
+ session.commit()
725
+ return JSONResponse(content={'message': 'PIN created and data saved successfully', 'token': token}, status_code=status.HTTP_200_OK)
726
+ except Exception as e:
727
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
728
+
729
+ @app.post("/med_fetch_data")
730
+ async def med_fetch_name_phone(request_data: FetchDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
731
+ email = request_data.email
732
+
733
+ try:
734
+ with db as session:
735
+ query = text("SELECT name, phone FROM users WHERE email = :email")
736
+ result = session.execute(query, {'email': email}).fetchall()
737
+ result_dicts = [{'name': row[0], 'phone': row[1]} for row in result]
738
+ return JSONResponse(content=result_dicts, status_code=status.HTTP_200_OK)
739
+ except Exception as e:
740
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
741
+
742
+ @app.post("/save_med_data")
743
+ async def med_save_department_location(request_data: Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
744
+ data = await request_data.json()
745
+ speciality = data.get('speciality')
746
+ location = data.get('location')
747
+ email = data.get('email')
748
+ latitude = data.get('latitude')
749
+ longitude = data.get('longitude')
750
+ org = data.get('org')
751
+ designation = data.get('designation')
752
+
753
+ if not (speciality and location and latitude and longitude and org and email):
754
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
755
+
756
+ try:
757
+ with db as session:
758
+ query = text("UPDATE users SET speciality = :speciality, location = :location, latitude = :latitude, longitude = :longitude, org = :org, designation = :designation WHERE email = :email;")
759
+ session.execute(query, {'speciality': speciality, 'location': location, 'latitude': latitude, 'longitude': longitude, 'org': org, 'designation': designation, 'email': email})
760
+ session.commit()
761
+ return JSONResponse(content={'message': 'Speciality, location, organisation and designation saved successfully'}, status_code=status.HTTP_200_OK)
762
+ except Exception as e:
763
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
764
+
765
+ @app.post("/med_verify_pin")
766
+ async def med_verify_pin(request_data: VerifyPinRequest, db: SessionLocal = Depends(get_db)):
767
+ email = request_data.email
768
+ pin = request_data.pin
769
+
770
+ try:
771
+ with db as session:
772
+ query = text("SELECT pin FROM users WHERE email = :email")
773
+ result = session.execute(query, {'email': email}).fetchone()
774
+
775
+ if result:
776
+ if result.pin == pin:
777
+ return JSONResponse(content={'message': 'Pin verified successfully'}, status_code=status.HTTP_200_OK)
778
+ else:
779
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pin')
780
+ else:
781
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
782
+ except Exception as e:
783
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
784
+
785
+ @app.post("/med_send_otp")
786
+ async def med_send_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db)):
787
+ phone = request_data.phone
788
+
789
+ try:
790
+ with db as session:
791
+ query = text("SELECT * FROM users WHERE phone = :phone")
792
+ user = session.execute(query, {'phone': phone}).fetchone()
793
+
794
+ if user:
795
+ phone_with_code = user.c_code + user.phone
796
+ otp = "1234" # generate_otp()
797
+ # send_sms(phone_with_code, otp)
798
+
799
+ updated_at = datetime.datetime.utcnow()
800
+ expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
801
+ update_otp_in_med_database(session, phone, otp, expiry_time, updated_at)
802
+
803
+ token = jwt.encode({'email': user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)}, JWT_SECRET_KEY, algorithm='HS256')
804
+ return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'token': token, 'otp': otp, 'email': user.email, 'name': user.name, 'pin': user.pin}, status_code=status.HTTP_200_OK)
805
+ else:
806
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
807
+ except Exception as e:
808
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
809
+
810
+ @app.post("/update_scheduled_date")
811
+ async def update_scheduled_date_endpoint(request_data: UpdateScheduledDateRequest, db: SessionLocal = Depends(get_db)):
812
+ email = request_data.email
813
+ patient_id = request_data.patient_id
814
+ doctor = request_data.doctor
815
+ scheduled_date = request_data.scheduled_date
816
+
817
+ try:
818
+ with db as session:
819
+ query = text("UPDATE patients SET scheduled_date = :scheduled_date WHERE email = :email AND patient_id = :patient_id AND doctor = :doctor")
820
+ result = session.execute(query, {'scheduled_date': scheduled_date, 'email': email, 'patient_id': patient_id, 'doctor': doctor})
821
+ session.commit()
822
+
823
+ if result.rowcount == 0:
824
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No matching record found')
825
+
826
+ return JSONResponse(content={'message': 'Scheduled date updated successfully'}, status_code=status.HTTP_200_OK)
827
+ except Exception as e:
828
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
829
+
830
+ @app.get("/total_appointments_till_date")
831
+ async def total_appointments_till_date_endpoint(date: str, db: SessionLocal = Depends(get_db)):
832
+ if not date:
833
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Date parameter is required')
834
+
835
+ try:
836
+ with db as session:
837
+ query = text("SELECT COUNT(*) as total_appointments FROM patients WHERE scheduled_date <= :date")
838
+ result = session.execute(query, {'date': date}).fetchone()
839
+
840
+ total_appointments = result[0]
841
+
842
+ return JSONResponse(content={'total_appointments': total_appointments}, status_code=status.HTTP_200_OK)
843
+ except Exception as e:
844
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
845
+
846
+ @app.get("/total_appointments_till_month")
847
+ async def total_appointments_till_month_endpoint(year: int, month: int, db: SessionLocal = Depends(get_db)):
848
+ if not (year and month):
849
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Year and month parameters are required')
850
+
851
+ try:
852
+ with db as session:
853
+ query = text("""
854
+ SELECT COUNT(*) as total_appointments
855
+ FROM patients
856
+ WHERE YEAR(scheduled_date) = :year AND MONTH(scheduled_date) = :month
857
+ """)
858
+ result = session.execute(query, {'year': year, 'month': month}).fetchone()
859
+
860
+ total_appointments = result[0]
861
+
862
+ return JSONResponse(content={'total_appointments': total_appointments}, status_code=status.HTTP_200_OK)
863
+ except Exception as e:
864
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
865
+
866
+ @app.post("/change_pin_org")
867
+ async def change_pin_org(request_data: ChangePinRequest, db: SessionLocal = Depends(get_db)):
868
+ email = request_data.email
869
+ current_pin = request_data.current_pin
870
+ new_pin = request_data.new_pin
871
+
872
+ try:
873
+ with db as session:
874
+ query = text("SELECT pin FROM organisations WHERE email = :email")
875
+ result = session.execute(query, {'email': email}).fetchone()
876
+
877
+ if result:
878
+ if result.pin == current_pin:
879
+ update_query = text("UPDATE organisations SET pin = :new_pin WHERE email = :email")
880
+ session.execute(update_query, {'new_pin': new_pin, 'email': email})
881
+ session.commit()
882
+ return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
883
+ else:
884
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid current pin')
885
+ else:
886
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
887
+ except Exception as e:
888
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
889
+
890
+ @app.post("/forgot_pin_org")
891
+ async def forgot_pin_org(request_data: ForgotPinRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
892
+ email = request_data.email
893
+ otp = request_data.otp
894
+ new_pin = request_data.new_pin
895
+
896
+ try:
897
+ with db as session:
898
+ query = text("SELECT otp FROM organisations WHERE email = :email")
899
+ result = session.execute(query, {'email': email}).fetchone()
900
+
901
+ if result:
902
+ if result.otp == otp:
903
+ update_query = text("UPDATE organisations SET pin = :new_pin WHERE email = :email")
904
+ session.execute(update_query, {'new_pin': new_pin, 'email': email})
905
+ session.commit()
906
+ return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
907
+ else:
908
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid OTP')
909
+ else:
910
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
911
+ except Exception as e:
912
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
913
+
914
+ @app.get("/organisation_details")
915
+ async def organisation_details(email: str, db: SessionLocal = Depends(get_db)):
916
+ if not email:
917
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
918
+
919
+ try:
920
+ with db as session:
921
+ query = text("""
922
+ SELECT o.name, o.departments, o.location, o.latitude, o.longitude, o.about,
923
+ o.profile_photo_path, COUNT(p.email) as patient_count
924
+ FROM organisations o
925
+ LEFT JOIN patients p ON o.email = p.email
926
+ WHERE o.email = :email
927
+ """)
928
+ result = session.execute(query, {'email': email}).fetchone()
929
+
930
+ if result:
931
+ response = {
932
+ 'name': result.name,
933
+ 'departments': result.departments,
934
+ 'location': result.location,
935
+ 'latitude': result.latitude,
936
+ 'longitude': result.longitude,
937
+ 'about': result.about,
938
+ 'profile_photo_path': result.profile_photo_path,
939
+ 'patient_count': result.patient_count
940
+ }
941
+ return JSONResponse(content=response, status_code=status.HTTP_200_OK)
942
+ else:
943
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
944
+ except Exception as e:
945
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
946
+
947
+ @app.post("/change_pin_med")
948
+ async def change_pin_med(request_data: ChangePinRequest, db: SessionLocal = Depends(get_db)):
949
+ email = request_data.email
950
+ current_pin = request_data.current_pin
951
+ new_pin = request_data.new_pin
952
+
953
+ try:
954
+ with db as session:
955
+ query = text("SELECT pin FROM users WHERE email = :email")
956
+ result = session.execute(query, {'email': email}).fetchone()
957
+
958
+ if result:
959
+ if result.pin == current_pin:
960
+ update_query = text("UPDATE users SET pin = :new_pin WHERE email = :email")
961
+ session.execute(update_query, {'new_pin': new_pin, 'email': email})
962
+ session.commit()
963
+ return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
964
+ else:
965
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid current pin')
966
+ else:
967
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
968
+ except Exception as e:
969
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
970
+
971
+ @app.post("/forgot_pin_med")
972
+ async def forgot_pin_med(request_data: ForgotPinRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
973
+ email = request_data.email
974
+ otp = request_data.otp
975
+ new_pin = request_data.new_pin
976
+
977
+ try:
978
+ with db as session:
979
+ query = text("SELECT otp FROM users WHERE email = :email")
980
+ result = session.execute(query, {'email': email}).fetchone()
981
+
982
+ if result:
983
+ if result.otp == otp:
984
+ update_query = text("UPDATE users SET pin = :new_pin WHERE email = :email")
985
+ session.execute(update_query, {'new_pin': new_pin, 'email': email})
986
+ session.commit()
987
+ return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
988
+ else:
989
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid OTP')
990
+ else:
991
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
992
+ except Exception as e:
993
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
994
+
995
+ @app.get("/med_details")
996
+ async def med_details(email: str, db: SessionLocal = Depends(get_db)):
997
+ if not email:
998
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
999
+
1000
+ try:
1001
+ with db as session:
1002
+ query = text("""
1003
+ SELECT o.name, o.departments, o.location, o.latitude, o.longitude, o.about,
1004
+ o.profile_photo_path, COUNT(p.email) as patient_count
1005
+ FROM users o
1006
+ LEFT JOIN patients p ON o.email = p.email
1007
+ WHERE o.email = :email
1008
+ """)
1009
+ result = session.execute(query, {'email': email}).fetchone()
1010
+
1011
+ if result:
1012
+ response = {
1013
+ 'name': result.name,
1014
+ 'departments': result.departments,
1015
+ 'location': result.location,
1016
+ 'latitude': result.latitude,
1017
+ 'longitude': result.longitude,
1018
+ 'about': result.about,
1019
+ 'profile_photo_path': result.profile_photo_path,
1020
+ 'patient_count': result.patient_count
1021
+ }
1022
+ return JSONResponse(content=response, status_code=status.HTTP_200_OK)
1023
+ else:
1024
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
1025
+ except Exception as e:
1026
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1027
+
1028
+ @app.post("/update_patient_details")
1029
+ async def update_patient_details(request_data: UpdatePatientDetailsRequest, db: SessionLocal = Depends(get_db)):
1030
+ patient_id = request_data.patient_id
1031
+ allergies = request_data.allergies
1032
+ past_history = request_data.past_history
1033
+
1034
+ try:
1035
+ with db as session:
1036
+ query = text("""
1037
+ UPDATE patients
1038
+ SET allergy = :allergies, illness = :past_history
1039
+ WHERE patient_id = :patient_id
1040
+ """)
1041
+ session.execute(query, {
1042
+ 'allergies': allergies,
1043
+ 'past_history': past_history,
1044
+ 'patient_id': patient_id
1045
+ })
1046
+ session.commit()
1047
+
1048
+ return JSONResponse(content={'message': 'Patient details updated successfully'}, status_code=status.HTTP_200_OK)
1049
+ except Exception as e:
1050
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1051
+
1052
+ @app.get("/patient_details")
1053
+ async def get_patient_details(patient_id: str, db: SessionLocal = Depends(get_db)):
1054
+ if not patient_id:
1055
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
1056
+
1057
+ try:
1058
+ with db as session:
1059
+ patient_query = text("""
1060
+ SELECT *
1061
+ FROM patients
1062
+ WHERE patient_id = :patient_id
1063
+ """)
1064
+ patient_result = session.execute(patient_query, {'patient_id': patient_id}).fetchone()
1065
+
1066
+ if patient_result:
1067
+ wound_query = text("""
1068
+ SELECT *
1069
+ FROM wounds
1070
+ WHERE patient_id = :patient_id
1071
+ """)
1072
+ wound_results = session.execute(wound_query, {'patient_id': patient_id}).fetchall()
1073
+
1074
+ wound_details = [
1075
+ {
1076
+ 'length': wound.height,
1077
+ 'breadth': wound.width,
1078
+ 'depth': wound.depth,
1079
+ 'area': wound.area,
1080
+ 'wound_location': wound.position,
1081
+ 'tissue': wound.tissue,
1082
+ 'exudate': wound.exudate,
1083
+ 'periwound':wound.periwound,
1084
+ 'periwound_type': wound.periwound_type,
1085
+ 'image': wound.image,
1086
+ 'moisture': wound.moisture
1087
+ } for wound in wound_results
1088
+ ]
1089
+
1090
+ patient_details = {
1091
+ 'patient_id': patient_result.patient_id,
1092
+ 'name': patient_result.name,
1093
+ 'age': patient_result.age,
1094
+ 'gender': patient_result.gender,
1095
+ 'dob': patient_result.dob,
1096
+ 'profile_photo_path': patient_result.profile_photo_path,
1097
+ 'allergies': patient_result.allergy,
1098
+ 'past_history': patient_result.illness,
1099
+ 'doctor_name': patient_result.added_by,
1100
+ 'register_date': patient_result.created_at,
1101
+ 'wound_details': wound_details
1102
+ }
1103
+ return JSONResponse(content=patient_details, status_code=status.HTTP_200_OK)
1104
+ else:
1105
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
1106
+ except Exception as e:
1107
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1108
+
1109
+ @app.post("/store_image")
1110
+ async def store_image(patient_id: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
1111
+ if not allowed_file(image.filename):
1112
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
1113
+
1114
+ filename = secure_filename(image.filename)
1115
+
1116
+ try:
1117
+ patient_folder = os.path.join(UPLOAD_FOLDER, 'patients', patient_id)
1118
+ os.makedirs(patient_folder, exist_ok=True)
1119
+
1120
+ image_path = os.path.join(patient_folder, filename)
1121
+ with open(image_path, "wb") as buffer:
1122
+ buffer.write(await image.read())
1123
+
1124
+ image_url = f"{BASE_URL}/uploads/patients/{patient_id}/{filename}"
1125
+
1126
+ with db as session:
1127
+ query = text("UPDATE patients SET profile_photo_path = :image_url WHERE patient_id = :patient_id")
1128
+ session.execute(query, {'image_url': image_url, 'patient_id': patient_id})
1129
+ session.commit()
1130
+
1131
+ return JSONResponse(content={
1132
+ 'message': 'Image stored successfully',
1133
+ 'image_url': image_url
1134
+ }, status_code=status.HTTP_200_OK)
1135
+
1136
+ except Exception as e:
1137
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1138
+
1139
+ @app.get("/get_image")
1140
+ async def get_image(patient_id: str, db: SessionLocal = Depends(get_db)):
1141
+ if not patient_id:
1142
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
1143
+
1144
+ try:
1145
+ with db as session:
1146
+ query = text("SELECT profile_photo_path FROM patients WHERE patient_id = :patient_id")
1147
+ result = session.execute(query, {'patient_id': patient_id}).fetchone()
1148
+
1149
+ if result and result.profile_photo_path:
1150
+ image_url = result.profile_photo_path
1151
+ filename = image_url.split('/')[-1]
1152
+
1153
+ file_path = os.path.join(UPLOAD_FOLDER, 'patients', patient_id, filename)
1154
+ if os.path.exists(file_path):
1155
+ return FileResponse(file_path)
1156
+ else:
1157
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
1158
+ else:
1159
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given patient ID')
1160
+ except Exception as e:
1161
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1162
+
1163
+ @app.post("/store_wound_image")
1164
+ async def store_wound_image(patient_id: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
1165
+ if not allowed_file(image.filename):
1166
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
1167
+
1168
+ filename = secure_filename(image.filename)
1169
+
1170
+ try:
1171
+ patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
1172
+ os.makedirs(patient_folder, exist_ok=True)
1173
+
1174
+ image_path = os.path.join(patient_folder, filename)
1175
+ with open(image_path, "wb") as buffer:
1176
+ buffer.write(await image.read())
1177
+
1178
+ image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{filename}"
1179
+
1180
+ with db as session:
1181
+ query = text("UPDATE wounds SET image = :image_url WHERE patient_id = :patient_id")
1182
+ session.execute(query, {'image_url': image_url, 'patient_id': patient_id})
1183
+ session.commit()
1184
+
1185
+ return JSONResponse(content={
1186
+ 'message': 'Image stored successfully',
1187
+ 'image_url': image_url
1188
+ }, status_code=status.HTTP_200_OK)
1189
+
1190
+ except Exception as e:
1191
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1192
+
1193
+ @app.get("/get_wound_image")
1194
+ async def get_wound_image(patient_id: str, db: SessionLocal = Depends(get_db)):
1195
+ if not patient_id:
1196
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
1197
+
1198
+ try:
1199
+ with db as session:
1200
+ query = text("SELECT image FROM wounds WHERE patient_id = :patient_id")
1201
+ result = session.execute(query, {'patient_id': patient_id}).fetchone()
1202
+
1203
+ if result and result.image:
1204
+ image_url = result.image
1205
+ filename = image_url.split('/')[-1]
1206
+
1207
+ file_path = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id, filename)
1208
+ if os.path.exists(file_path):
1209
+ return FileResponse(file_path)
1210
+ else:
1211
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
1212
+ else:
1213
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given patient ID')
1214
+ except Exception as e:
1215
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1216
+
1217
+ @app.post("/store_med_image")
1218
+ async def store_med_image(email: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
1219
+ if not allowed_file(image.filename):
1220
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
1221
+
1222
+ filename = secure_filename(image.filename)
1223
+
1224
+ try:
1225
+ med_practitioner_folder = os.path.join(UPLOAD_FOLDER, 'medical_practitioners', email)
1226
+ os.makedirs(med_practitioner_folder, exist_ok=True)
1227
+
1228
+ image_path = os.path.join(med_practitioner_folder, filename)
1229
+ with open(image_path, "wb") as buffer:
1230
+ buffer.write(await image.read())
1231
+
1232
+ image_url = f"{BASE_URL}/uploads/medical_practitioners/{email}/{filename}"
1233
+
1234
+ with db as session:
1235
+ query = text("UPDATE users SET profile_photo_path = :image_url WHERE email = :email")
1236
+ session.execute(query, {'image_url': image_url, 'email': email})
1237
+ session.commit()
1238
+
1239
+ return JSONResponse(content={'message': 'Image stored and path updated successfully', 'image_url': image_url}, status_code=status.HTTP_200_OK)
1240
+ except Exception as e:
1241
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1242
+
1243
+ @app.get("/get_med_image")
1244
+ async def get_med_image(email: str, db: SessionLocal = Depends(get_db)):
1245
+ if not email:
1246
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
1247
+
1248
+ try:
1249
+ with db as session:
1250
+ query = text("SELECT profile_photo_path FROM users WHERE email = :email")
1251
+ result = session.execute(query, {'email': email}).fetchone()
1252
+
1253
+ if result and result.profile_photo_path:
1254
+ image_url = result.profile_photo_path
1255
+ filename = image_url.split('/')[-1]
1256
+
1257
+ file_path = os.path.join(UPLOAD_FOLDER, 'medical_practitioners', email, filename)
1258
+ if os.path.exists(file_path):
1259
+ return FileResponse(file_path)
1260
+ else:
1261
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
1262
+ else:
1263
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given email')
1264
+ except Exception as e:
1265
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1266
+
1267
+ @app.post("/store_org_image")
1268
+ async def store_org_image(email: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
1269
+ if not allowed_file(image.filename):
1270
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
1271
+
1272
+ filename = secure_filename(image.filename)
1273
+
1274
+ try:
1275
+ org_folder = os.path.join(UPLOAD_FOLDER, 'organisations', email)
1276
+ os.makedirs(org_folder, exist_ok=True)
1277
+
1278
+ image_path = os.path.join(org_folder, filename)
1279
+ with open(image_path, "wb") as buffer:
1280
+ buffer.write(await image.read())
1281
+
1282
+ image_url = f"{BASE_URL}/uploads/organisations/{email}/{filename}"
1283
+
1284
+ with db as session:
1285
+ query = text("UPDATE organisations SET profile_photo_path = :image_url WHERE email = :email")
1286
+ session.execute(query, {'image_url': image_url, 'email': email})
1287
+ session.commit()
1288
+
1289
+ return JSONResponse(content={'message': 'Image stored and path updated successfully', 'image_url': image_url}, status_code=status.HTTP_200_OK)
1290
+ except Exception as e:
1291
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1292
+
1293
+ @app.get("/get_org_image")
1294
+ async def get_org_image(email: str, db: SessionLocal = Depends(get_db)):
1295
+ if not email:
1296
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
1297
+
1298
+ try:
1299
+ with db as session:
1300
+ query = text("SELECT profile_photo_path FROM organisations WHERE email = :email")
1301
+ result = session.execute(query, {'email': email}).fetchone()
1302
+
1303
+ if result and result.profile_photo_path:
1304
+ image_url = result.profile_photo_path
1305
+ filename = image_url.split('/')[-1]
1306
+
1307
+ file_path = os.path.join(UPLOAD_FOLDER, 'organisations', email, filename)
1308
+ if os.path.exists(file_path):
1309
+ return FileResponse(file_path)
1310
+ else:
1311
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
1312
+ else:
1313
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given email')
1314
+ except Exception as e:
1315
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1316
+
1317
+ @app.post("/save_notes")
1318
+ async def save_notes(request_data: SaveNotesRequest, db: SessionLocal = Depends(get_db)):
1319
+ patient_id = request_data.patient_id
1320
+ notes = request_data.notes
1321
+
1322
+ try:
1323
+ with db as session:
1324
+ query = text("UPDATE patients SET notes = :notes WHERE patient_id = :patient_id")
1325
+ session.execute(query, {'notes': notes, 'patient_id': patient_id})
1326
+ session.commit()
1327
+
1328
+ return JSONResponse(content={'message': 'Notes saved successfully'}, status_code=status.HTTP_200_OK)
1329
+ except Exception as e:
1330
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1331
+
1332
+ @app.post("/total_appointments")
1333
+ async def total_appointments(request_data: TotalAppointmentsRequest, db: SessionLocal = Depends(get_db)):
1334
+ start_date_str = request_data.start_date
1335
+ end_date_str = request_data.end_date
1336
+ doctor = request_data.doctor
1337
+
1338
+ try:
1339
+ start_date = datetime.datetime.strptime(start_date_str, '%Y-%m-%d')
1340
+ end_date = datetime.datetime.strptime(end_date_str, '%Y-%m-%d')
1341
+
1342
+ with db as session:
1343
+ query = text("""
1344
+ SELECT DATE(scheduled_date) as date, COUNT(*) as total_appointments
1345
+ FROM patients
1346
+ WHERE scheduled_date BETWEEN :start_date AND :end_date
1347
+ AND doctor = :doctor
1348
+ GROUP BY DATE(scheduled_date)
1349
+ ORDER BY DATE(scheduled_date)
1350
+ """)
1351
+
1352
+ results = session.execute(query, {
1353
+ 'start_date': start_date,
1354
+ 'end_date': end_date,
1355
+ 'doctor': doctor
1356
+ }).fetchall()
1357
+
1358
+ appointments_by_day = {}
1359
+
1360
+ for row in results:
1361
+ date_str = row.date.strftime('%Y-%m-%d')
1362
+ appointments_by_day[date_str] = row.total_appointments
1363
+
1364
+ current_date = start_date
1365
+ while current_date <= end_date:
1366
+ date_str = current_date.strftime('%Y-%m-%d')
1367
+ if date_str not in appointments_by_day:
1368
+ appointments_by_day[date_str] = 0
1369
+ current_date += datetime.timedelta(days=1)
1370
+
1371
+ return JSONResponse(content={'appointments_by_day': appointments_by_day}, status_code=status.HTTP_200_OK)
1372
+
1373
+ except Exception as e:
1374
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1375
+
1376
+ @app.post("/admin_add_practitioner")
1377
+ async def admin_add_practitioner(request_data: AdminAddPractitionerRequest, db: SessionLocal = Depends(get_db)):
1378
+ name = request_data.name
1379
+ email = request_data.email
1380
+ c_code = request_data.c_code
1381
+ phone = request_data.phone
1382
+ org = request_data.org
1383
+
1384
+ try:
1385
+ with db as session:
1386
+ query = text("SELECT email FROM users WHERE email = :email")
1387
+ existing_email = session.execute(query, {'email': email}).fetchone()
1388
+
1389
+ query = text("SELECT phone FROM users WHERE phone = :phone")
1390
+ existing_phone = session.execute(query, {'phone': phone}).fetchone()
1391
+
1392
+ if existing_email:
1393
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email already exists. Please login.')
1394
+ elif existing_phone:
1395
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Phone number already exists. Please use another phone number.')
1396
+ else:
1397
+ uuid_val = generate_session_id()
1398
+ license_key = generate_license_key()
1399
+
1400
+ query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, org) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :org)")
1401
+ session.execute(query, {
1402
+ 'name': name,
1403
+ 'email': email,
1404
+ 'c_code': c_code,
1405
+ 'phone': phone,
1406
+ 'uuid': uuid_val,
1407
+ 'license_key': license_key,
1408
+ 'org': org
1409
+ })
1410
+ session.commit()
1411
+ return JSONResponse(content={'message': 'Data added successfully'}, status_code=status.HTTP_200_OK)
1412
+ except Exception as e:
1413
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1414
+
1415
+ @app.post("/update_org_profile")
1416
+ async def update_org_profile(request_data: UpdateOrgProfileRequest, db: SessionLocal = Depends(get_db)):
1417
+ name = request_data.name
1418
+ department = request_data.department
1419
+ about = request_data.about
1420
+ location = request_data.location
1421
+ latitude = request_data.latitude
1422
+ longitude = request_data.longitude
1423
+ email = request_data.email
1424
+
1425
+ if email is None:
1426
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email is required')
1427
+
1428
+ try:
1429
+ with db as session:
1430
+ query = text("SELECT email FROM organisations WHERE email = :email")
1431
+ existing_org = session.execute(query, {'email': email}).fetchone()
1432
+
1433
+ if not existing_org:
1434
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
1435
+
1436
+ update_query = text("""
1437
+ UPDATE organisations
1438
+ SET name = :name,
1439
+ departments = :department,
1440
+ about = :about,
1441
+ location = :location,
1442
+ latitude = :latitude,
1443
+ longitude = :longitude
1444
+ WHERE email = :email
1445
+ """)
1446
+ session.execute(update_query, {
1447
+ 'name': name,
1448
+ 'department': department,
1449
+ 'about': about,
1450
+ 'location': location,
1451
+ 'latitude': latitude,
1452
+ 'longitude': longitude,
1453
+ 'email': email
1454
+ })
1455
+ session.commit()
1456
+ return JSONResponse(content={'message': 'Organisation profile updated successfully'}, status_code=status.HTTP_200_OK)
1457
+ except Exception as e:
1458
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1459
+
1460
+ @app.post("/update_med_profile")
1461
+ async def update_med_profile(request_data: UpdateMedProfileRequest, db: SessionLocal = Depends(get_db)):
1462
+ name = request_data.name
1463
+ department = request_data.department
1464
+ about = request_data.about
1465
+ location = request_data.location
1466
+ latitude = request_data.latitude
1467
+ longitude = request_data.longitude
1468
+ email = request_data.email
1469
+
1470
+ if email is None:
1471
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email is required')
1472
+
1473
+ try:
1474
+ with db as session:
1475
+ query = text("SELECT email FROM users WHERE email = :email")
1476
+ existing_user = session.execute(query, {'email': email}).fetchone()
1477
+
1478
+ if not existing_user:
1479
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Medical user not found')
1480
+
1481
+ update_query = text("""
1482
+ UPDATE users
1483
+ SET name = :name,
1484
+ departments = :department,
1485
+ about = :about,
1486
+ location = :location,
1487
+ latitude = :latitude,
1488
+ longitude = :longitude
1489
+ WHERE email = :email
1490
+ """)
1491
+ session.execute(update_query, {
1492
+ 'name': name,
1493
+ 'department': department,
1494
+ 'about': about,
1495
+ 'location': location,
1496
+ 'latitude': latitude,
1497
+ 'longitude': longitude,
1498
+ 'email': email
1499
+ })
1500
+ session.commit()
1501
+ return JSONResponse(content={'message': 'Medical profile updated successfully'}, status_code=status.HTTP_200_OK)
1502
+ except Exception as e:
1503
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1504
+
1505
+ @app.post("/add_wound_details_v2")
1506
+ async def add_wound_details_v2(request_data: AddWoundDetailsV2Request = Depends(), image: UploadFile = File(...), db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1507
+ length = request_data.length
1508
+ breadth = request_data.breadth
1509
+ depth = request_data.depth
1510
+ area = request_data.area
1511
+ moisture = request_data.moisture
1512
+ wound_location = request_data.wound_location
1513
+ tissue = request_data.tissue
1514
+ exudate = request_data.exudate
1515
+ periwound = request_data.periwound
1516
+ periwound_type = request_data.periwound_type
1517
+ patient_id = request_data.patient_id
1518
+ type = request_data.type
1519
+ category = request_data.category
1520
+ edge = request_data.edge
1521
+ infection = request_data.infection
1522
+ last_dressing_date = request_data.last_dressing_date
1523
+
1524
+ updated_at = datetime.datetime.utcnow()
1525
+
1526
+ if not allowed_file(image.filename):
1527
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
1528
+
1529
+ filename = secure_filename(image.filename)
1530
+
1531
+ try:
1532
+ with db as session:
1533
+ query = text("SELECT area, id, created_at FROM wounds WHERE patient_id = :patient_id")
1534
+ result = session.execute(query, {'patient_id': patient_id}).fetchone()
1535
+
1536
+ if not result:
1537
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
1538
+
1539
+ existing_area = result[0]
1540
+ if not existing_area:
1541
+ existing_area = 0
1542
+ wound_id = result[1]
1543
+ created_at = result[2]
1544
+ area_difference = float(area) - float(existing_area)
1545
+ if area_difference > 0:
1546
+ size_variation = 'wound area increased'
1547
+ elif area_difference < 0:
1548
+ size_variation = 'wound area reduced'
1549
+ else:
1550
+ size_variation = 'wound area same'
1551
+
1552
+ patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
1553
+ os.makedirs(patient_folder, exist_ok=True)
1554
+ image_path = os.path.join(patient_folder, filename)
1555
+ with open(image_path, "wb") as buffer:
1556
+ buffer.write(await image.read())
1557
+
1558
+ image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{filename}"
1559
+
1560
+ query1 = text("""
1561
+ INSERT INTO wound_images (depth, width, height, uuid, updated_at, patient_id, size_variation, image, wound_id, created_at, area)
1562
+ VALUES (:depth, :breadth, :length, :uuid, :updated_at, :patient_id, :size_variation, :image_url, :wound_id, :created_at, :area)
1563
+ """)
1564
+ session.execute(query1, {
1565
+ 'depth': depth, 'breadth': breadth, 'length': length, 'uuid': generate_session_id(),
1566
+ 'updated_at': updated_at, 'patient_id': patient_id, 'size_variation': size_variation,
1567
+ 'image_url': image_url, 'wound_id': wound_id, 'created_at': created_at, 'area': area
1568
+ })
1569
+
1570
+ query2 = text("""
1571
+ UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area,
1572
+ moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate,
1573
+ periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category,
1574
+ edges = :edge, infection = :infection, updated_at = :updated_at, last_dressing = :last_dressing_date,
1575
+ image = :image_url
1576
+ WHERE patient_id = :patient_id
1577
+ """)
1578
+ session.execute(query2, {
1579
+ 'length': length, 'breadth': breadth, 'depth': depth, 'area': area,
1580
+ 'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue,
1581
+ 'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type,
1582
+ 'type': type, 'category': category, 'edge': edge, 'infection': infection,
1583
+ 'updated_at': updated_at, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id,
1584
+ 'image_url': image_url
1585
+ })
1586
+
1587
+ session.commit()
1588
+ return JSONResponse(content={
1589
+ 'message': 'Wound details and image stored successfully',
1590
+ 'image_url': image_url
1591
+ }, status_code=status.HTTP_200_OK)
1592
+
1593
+ except Exception as e:
1594
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1595
+
1596
+ @app.get("/wound_progress_timeline")
1597
+ async def get_wound_details_v2(patient_id: str, db: SessionLocal = Depends(get_db)):
1598
+ if not patient_id:
1599
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='patient_id is required')
1600
+
1601
+ try:
1602
+ with db as session:
1603
+ query = text("""
1604
+ SELECT * FROM wound_images WHERE patient_id = :patient_id ORDER BY updated_at DESC """)
1605
+ results = session.execute(query, {'patient_id': patient_id}).fetchall()
1606
+
1607
+ wound_details = []
1608
+ for row in results:
1609
+ wound_details.append({
1610
+ 'length': row.height,
1611
+ 'breadth': row.width,
1612
+ 'depth': row.depth,
1613
+ 'size_variation': row.size_variation,
1614
+ 'image': row.image,
1615
+ 'patient_id': row.patient_id,
1616
+ 'area': row.area,
1617
+ 'updated_at': row.updated_at.isoformat()
1618
+ })
1619
+
1620
+ return JSONResponse(content=wound_details, status_code=status.HTTP_200_OK)
1621
+
1622
+ except Exception as e:
1623
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1624
+
1625
+ @app.post("/med/forgot/pin/otp")
1626
+ async def med_forgot_pin_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1627
+ phone = request_data.phone
1628
+
1629
+ try:
1630
+ with db as session:
1631
+ query = text("SELECT * FROM users WHERE phone = :phone")
1632
+ user = session.execute(query, {'phone': phone}).fetchone()
1633
+
1634
+ if user:
1635
+ phone_with_code = user.c_code + user.phone
1636
+ otp = "1234" # generate_otp()
1637
+ # send_sms(phone_with_code, otp)
1638
+
1639
+ updated_at = datetime.datetime.utcnow()
1640
+ expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
1641
+ update_otp_in_med_database(session, phone, otp, expiry_time, updated_at)
1642
+
1643
+ return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'otp': otp}, status_code=status.HTTP_200_OK)
1644
+ else:
1645
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
1646
+ except Exception as e:
1647
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1648
+
1649
+ @app.post("/org/forgot/pin/otp")
1650
+ async def org_forgot_pin_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1651
+ phone = request_data.phone
1652
+
1653
+ try:
1654
+ with db as session:
1655
+ query = text("SELECT * FROM organisations WHERE phone = :phone")
1656
+ user = session.execute(query, {'phone': phone}).fetchone()
1657
+
1658
+ if user:
1659
+ phone_with_code = user.c_code + user.phone
1660
+ otp = "1234" # generate_otp()
1661
+ # send_sms(phone_with_code, otp)
1662
+
1663
+ updated_at = datetime.datetime.utcnow()
1664
+ expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
1665
+ update_otp_in_database(session, phone, otp, expiry_time, updated_at)
1666
+
1667
+ return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'otp': otp}, status_code=status.HTTP_200_OK)
1668
+ else:
1669
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
1670
+ except Exception as e:
1671
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1672
+
1673
+ @app.post("/update_scheduled_date_v2")
1674
+ async def update_scheduled_date_v2(request_data: UpdateScheduledDateRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1675
+ email = request_data.email
1676
+ patient_id = request_data.patient_id
1677
+ scheduled_date = request_data.scheduled_date
1678
+
1679
+ try:
1680
+ with db as session:
1681
+ query = text("UPDATE patients SET scheduled_date = :scheduled_date WHERE email = :email AND patient_id = :patient_id")
1682
+ result = session.execute(query, {'scheduled_date': scheduled_date, 'email': email, 'patient_id': patient_id})
1683
+ session.commit()
1684
+
1685
+ if result.rowcount == 0:
1686
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No matching record found')
1687
+
1688
+ return JSONResponse(content={'message': 'Scheduled date updated successfully'}, status_code=status.HTTP_200_OK)
1689
+ except Exception as e:
1690
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1691
+
1692
+ @app.get("/total_appointments_v2")
1693
+ async def total_appointments_v2(start_date: str, end_date: str, email: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1694
+ try:
1695
+ start_date_dt = datetime.datetime.strptime(start_date, '%Y-%m-%d')
1696
+ end_date_dt = datetime.datetime.strptime(end_date, '%Y-%m-%d')
1697
+
1698
+ with db as session:
1699
+ query = text("""
1700
+ SELECT DATE(scheduled_date) as date, COUNT(*) as total_appointments
1701
+ FROM patients
1702
+ WHERE scheduled_date BETWEEN :start_date AND :end_date
1703
+ AND email = :email
1704
+ GROUP BY DATE(scheduled_date)
1705
+ ORDER BY DATE(scheduled_date)
1706
+ """)
1707
+
1708
+ results = session.execute(query, {
1709
+ 'start_date': start_date_dt,
1710
+ 'end_date': end_date_dt,
1711
+ 'email': email
1712
+ }).fetchall()
1713
+
1714
+ appointments_by_day = {}
1715
+
1716
+ for row in results:
1717
+ date_str = row.date.strftime('%Y-%m-%d')
1718
+ appointments_by_day[date_str] = row.total_appointments
1719
+
1720
+ current_date = start_date_dt
1721
+ while current_date <= end_date_dt:
1722
+ date_str = current_date.strftime('%Y-%m-%d')
1723
+ if date_str not in appointments_by_day:
1724
+ appointments_by_day[date_str] = 0
1725
+ current_date += datetime.timedelta(days=1)
1726
+
1727
+ return JSONResponse(content={'appointments_by_day': appointments_by_day}, status_code=status.HTTP_200_OK)
1728
+
1729
+ except Exception as e:
1730
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1731
+
1732
+ @app.post("/add_patient_v2")
1733
+ async def add_patient_v2(request_data: AddPatientV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1734
+ name = request_data.name
1735
+ dob = request_data.dob
1736
+ gender = request_data.gender
1737
+ age = request_data.age
1738
+ height = request_data.height
1739
+ weight = request_data.weight
1740
+ email = request_data.email
1741
+ doctor = request_data.doctor
1742
+ role = request_data.role
1743
+ patient_id = generate_patient_id()
1744
+
1745
+ created_at = datetime.datetime.utcnow()
1746
+ updated_at = datetime.datetime.utcnow()
1747
+ scheduled_date = datetime.datetime.utcnow()
1748
+
1749
+ try:
1750
+ with db as session:
1751
+ uuid_val = generate_session_id()
1752
+ if role == "3":
1753
+ user_query = text("SELECT id FROM users WHERE email = :email AND name = :doctor")
1754
+ user_result = session.execute(user_query, {'email': email, 'doctor': doctor}).fetchone()
1755
+ if not user_result:
1756
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Doctor not found')
1757
+ doctor_id = user_result.id
1758
+
1759
+ pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, doctor, added_by, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :doctor_id, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
1760
+ session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'doctor_id': doctor_id, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
1761
+
1762
+ wound_query = text("INSERT INTO wounds (uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
1763
+ session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
1764
+
1765
+ elif role == "5":
1766
+ org_query = text("SELECT id FROM organisations WHERE email = :email AND name = :doctor")
1767
+ org_result = session.execute(org_query, {'email': email, 'doctor': doctor}).fetchone()
1768
+ if not org_result:
1769
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
1770
+ org_id = org_result.id
1771
+
1772
+ pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, org, added_by, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :org_id, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
1773
+ session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'org_id': org_id, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
1774
+
1775
+ wound_query = text("INSERT INTO wounds (uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
1776
+ session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
1777
+
1778
+ else:
1779
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid role')
1780
+
1781
+ session.commit()
1782
+
1783
+ return JSONResponse(content={'message': 'Patient added successfully', 'patient_id': patient_id}, status_code=status.HTTP_200_OK)
1784
+ except Exception as e:
1785
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1786
+
1787
+ @app.post("/save_notes_v2")
1788
+ async def save_notes_v2(request_data: SaveNotesV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1789
+ patient_id = request_data.patient_id
1790
+ notes = request_data.notes
1791
+ remarks = request_data.remarks
1792
+
1793
+ try:
1794
+ with db as session:
1795
+ query = text("UPDATE patients SET notes = :notes, remarks = :remarks WHERE patient_id = :patient_id")
1796
+ session.execute(query, {'notes': notes, 'remarks': remarks, 'patient_id': patient_id})
1797
+ session.commit()
1798
+
1799
+ return JSONResponse(content={'message': 'Notes saved successfully'}, status_code=status.HTTP_200_OK)
1800
+ except Exception as e:
1801
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1802
+
1803
+ @app.post("/admin_add_practitioner_v2")
1804
+ async def admin_add_practitioner_v2(request_data: AdminAddPractitionerV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1805
+ name = request_data.name
1806
+ email = request_data.email
1807
+ c_code = request_data.c_code
1808
+ phone = request_data.phone
1809
+ org_email = request_data.org_email
1810
+ created_at = datetime.datetime.utcnow()
1811
+ updated_at = datetime.datetime.utcnow()
1812
+
1813
+ try:
1814
+ with db as session:
1815
+ query = text("SELECT email FROM users WHERE email = :email")
1816
+ existing_email = session.execute(query, {'email': email}).fetchone()
1817
+
1818
+ query = text("SELECT phone FROM users WHERE phone = :phone")
1819
+ existing_phone = session.execute(query, {'phone': phone}).fetchone()
1820
+
1821
+ if existing_email:
1822
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email already exists. Please login.')
1823
+ elif existing_phone:
1824
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Phone number already exists. Please use another phone number.')
1825
+ else:
1826
+ org_query = text("SELECT id FROM organisations WHERE email = :org_email")
1827
+ org_result = session.execute(org_query, {'org_email': org_email}).fetchone()
1828
+ if not org_result:
1829
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
1830
+ org_id = org_result.id
1831
+ uuid_val = generate_session_id()
1832
+ license_key = generate_license_key()
1833
+
1834
+ query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, org, updated_at, created_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :org_id, :updated_at, :created_at)")
1835
+ session.execute(query, {
1836
+ 'name': name,
1837
+ 'email': email,
1838
+ 'c_code': c_code,
1839
+ 'phone': phone,
1840
+ 'uuid': uuid_val,
1841
+ 'license_key': license_key,
1842
+ 'org_id': org_id,
1843
+ 'updated_at': updated_at,
1844
+ 'created_at': created_at
1845
+ })
1846
+ session.commit()
1847
+ return JSONResponse(content={'message': 'Data added successfully'}, status_code=status.HTTP_200_OK)
1848
+ except Exception as e:
1849
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1850
+
1851
+ @app.post("/sort_patients")
1852
+ async def sort_patients(request_data: SortPatientsRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1853
+ email = request_data.email
1854
+ date_str = request_data.date
1855
+
1856
+ try:
1857
+ with db as session:
1858
+ if date_str:
1859
+ appointment_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
1860
+ query = text("""
1861
+ SELECT * FROM patients WHERE scheduled_date = :appointment_date AND email = :email
1862
+ """)
1863
+ results = session.execute(query, {'appointment_date': appointment_date, 'email': email}).fetchall()
1864
+ else:
1865
+ query = text("""
1866
+ SELECT * FROM patients WHERE email = :email
1867
+ """)
1868
+ results = session.execute(query, {'email': email}).fetchall()
1869
+
1870
+ patient_dicts = [dict(row._mapping) for row in results]
1871
+ return JSONResponse(content={'patients': patient_dicts}, status_code=status.HTTP_200_OK)
1872
+ except Exception as e:
1873
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1874
+
1875
+ @app.post("/get_appointment_count")
1876
+ async def get_appointment_count(request_data: GetAppointmentCountRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1877
+ email = request_data.email
1878
+ date_str = request_data.date
1879
+
1880
+ try:
1881
+ appointment_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
1882
+ with db as session:
1883
+ query = text("""
1884
+ SELECT COUNT(*) as count FROM patients WHERE scheduled_date = :appointment_date AND email = :email
1885
+ """)
1886
+ result = session.execute(query, {'appointment_date': appointment_date, 'email': email}).fetchone()
1887
+
1888
+ total_appointments = result.count if result else 0
1889
+
1890
+ return JSONResponse(content={
1891
+ 'title': f'The total number of appointments booked is {total_appointments}',
1892
+ 'description': f'The appointments are scheduled for {appointment_date}'
1893
+ }, status_code=status.HTTP_200_OK)
1894
+ except Exception as e:
1895
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1896
+
1897
+ @app.post("/add_wound_details_v3")
1898
+ async def add_wound_details_v3(request_data: AddWoundDetailsV3Request = Depends(), image: UploadFile = File(..., alias="image"), api_image: UploadFile = File(..., alias="api_image"), db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
1899
+ length = request_data.length
1900
+ breadth = request_data.breadth
1901
+ depth = request_data.depth
1902
+ area = request_data.area
1903
+ moisture = request_data.moisture
1904
+ wound_location = request_data.wound_location
1905
+ tissue = request_data.tissue
1906
+ exudate = request_data.exudate
1907
+ periwound = request_data.periwound
1908
+ periwound_type = request_data.periwound_type
1909
+ patient_id = request_data.patient_id
1910
+ type = request_data.type
1911
+ category = request_data.category
1912
+ edge = request_data.edge
1913
+ infection = request_data.infection
1914
+ last_dressing_date = request_data.last_dressing_date
1915
+
1916
+ updated_at = datetime.datetime.utcnow()
1917
+
1918
+ if not allowed_file(image.filename) or not allowed_file(api_image.filename):
1919
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type for one or both images')
1920
+
1921
+ normal_image_filename = secure_filename(image.filename)
1922
+ api_image_filename = secure_filename(api_image.filename)
1923
+
1924
+ try:
1925
+ with db as session:
1926
+ query = text("SELECT area, id, created_at FROM wounds WHERE patient_id = :patient_id")
1927
+ result = session.execute(query, {'patient_id': patient_id}).fetchone()
1928
+
1929
+ if not result:
1930
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
1931
+
1932
+ existing_area = result[0]
1933
+ if not existing_area:
1934
+ existing_area = 0
1935
+ wound_id = result[1]
1936
+ created_at = result[2]
1937
+ area_difference = float(area) - float(existing_area)
1938
+ if area_difference > 0:
1939
+ size_variation = 'wound area increased'
1940
+ elif area_difference < 0:
1941
+ size_variation = 'wound area reduced'
1942
+ else:
1943
+ size_variation = 'wound area same'
1944
+
1945
+ patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
1946
+ os.makedirs(patient_folder, exist_ok=True)
1947
+ normal_image_path = os.path.join(patient_folder, normal_image_filename)
1948
+ with open(normal_image_path, "wb") as buffer:
1949
+ buffer.write(await image.read())
1950
+
1951
+ api_patient_folder = os.path.join(UPLOAD_FOLDER, 'assessed_wounds', patient_id)
1952
+ os.makedirs(api_patient_folder, exist_ok=True)
1953
+ api_image_path = os.path.join(api_patient_folder, api_image_filename)
1954
+ with open(api_image_path, "wb") as buffer:
1955
+ buffer.write(await api_image.read())
1956
+
1957
+ image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{normal_image_filename}"
1958
+ api_image_url = f"{BASE_URL}/uploads/assessed_wounds/{patient_id}/{api_image_filename}"
1959
+
1960
+ query1 = text("""
1961
+ INSERT INTO wound_images (depth, width, height, uuid, updated_at, patient_id, size_variation, image, wound_id, created_at, area, photo_assessment)
1962
+ VALUES (:depth, :breadth, :length, :uuid, :updated_at, :patient_id, :size_variation, :image_url, :wound_id, :created_at, :area, :api_image_url)
1963
+ """)
1964
+ session.execute(query1, {
1965
+ 'depth': depth, 'breadth': breadth, 'length': length, 'uuid': generate_session_id(),
1966
+ 'updated_at': updated_at, 'patient_id': patient_id, 'size_variation': size_variation,
1967
+ 'image_url': image_url, 'wound_id': wound_id, 'created_at': created_at, 'area': area,
1968
+ 'api_image_url': api_image_url
1969
+ })
1970
+
1971
+ query2 = text("""
1972
+ UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area,
1973
+ moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate,
1974
+ periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category,
1975
+ edges = :edge, infection = :infection, updated_at = :updated_at, last_dressing = :last_dressing_date,
1976
+ image = :image_url, photo_assessment = :api_image_url
1977
+ WHERE patient_id = :patient_id
1978
+ """)
1979
+ session.execute(query2, {
1980
+ 'length': length, 'breadth': breadth, 'depth': depth, 'area': area,
1981
+ 'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue,
1982
+ 'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type,
1983
+ 'type': type, 'category': category, 'edge': edge, 'infection': infection,
1984
+ 'updated_at': updated_at, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id,
1985
+ 'image_url': image_url, 'api_image_url': api_image_url
1986
+ })
1987
+
1988
+ session.commit()
1989
+ return JSONResponse(content={
1990
+ 'message': 'Wound details and images stored successfully',
1991
+ 'image_url': image_url,
1992
+ 'photo_assessment': api_image_url
1993
+ }, status_code=status.HTTP_200_OK)
1994
+
1995
+ except Exception as e:
1996
+ raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
1997
+
1998
+
1999
+
2000
+
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ sqlalchemy==2.0.23
4
+ pymysql==1.1.0
5
+ python-jose[cryptography]==3.3.0
6
+ python-multipart==0.0.6
7
+ python-dotenv==1.0.0
8
+ twilio==8.10.3
9
+ requests==2.31.0
10
+ werkzeug==3.0.1
11
+ pydantic==2.5.0
12
+
test_app.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to validate the FastAPI application
4
+ """
5
+ import sys
6
+ import os
7
+
8
+ # Add the current directory to Python path
9
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
10
+
11
+ def test_import():
12
+ """Test if the main module can be imported without errors"""
13
+ try:
14
+ from main import app
15
+ print("βœ… Successfully imported FastAPI app")
16
+ return True
17
+ except Exception as e:
18
+ print(f"❌ Failed to import FastAPI app: {e}")
19
+ return False
20
+
21
+ def test_endpoints():
22
+ """Test if endpoints are properly defined"""
23
+ try:
24
+ from main import app
25
+ routes = [route.path for route in app.routes]
26
+ print(f"βœ… Found {len(routes)} routes defined")
27
+
28
+ # Check for some key endpoints
29
+ key_endpoints = [
30
+ "/send_email",
31
+ "/verify_license_key",
32
+ "/add_patient",
33
+ "/add_wound_details",
34
+ "/get_all_patient_details"
35
+ ]
36
+
37
+ missing_endpoints = []
38
+ for endpoint in key_endpoints:
39
+ if endpoint not in routes:
40
+ missing_endpoints.append(endpoint)
41
+
42
+ if missing_endpoints:
43
+ print(f"❌ Missing endpoints: {missing_endpoints}")
44
+ return False
45
+ else:
46
+ print("βœ… All key endpoints are defined")
47
+ return True
48
+
49
+ except Exception as e:
50
+ print(f"❌ Failed to check endpoints: {e}")
51
+ return False
52
+
53
+ def test_pydantic_models():
54
+ """Test if Pydantic models are properly defined"""
55
+ try:
56
+ from main import SendEmailRequest, AddPatientRequest, AddWoundDetailsRequest
57
+ print("βœ… Pydantic models imported successfully")
58
+
59
+ # Test model instantiation
60
+ test_email_request = SendEmailRequest(
61
+ name="Test",
62
+ email="test@example.com",
63
+ c_code="+1",
64
+ phone="1234567890"
65
+ )
66
+ print("βœ… Pydantic model validation works")
67
+ return True
68
+
69
+ except Exception as e:
70
+ print(f"❌ Failed to test Pydantic models: {e}")
71
+ return False
72
+
73
+ def main():
74
+ """Run all tests"""
75
+ print("πŸ§ͺ Testing FastAPI Application...")
76
+ print("=" * 50)
77
+
78
+ tests = [
79
+ ("Import Test", test_import),
80
+ ("Endpoints Test", test_endpoints),
81
+ ("Pydantic Models Test", test_pydantic_models)
82
+ ]
83
+
84
+ passed = 0
85
+ total = len(tests)
86
+
87
+ for test_name, test_func in tests:
88
+ print(f"\nπŸ” Running {test_name}...")
89
+ if test_func():
90
+ passed += 1
91
+
92
+ print("\n" + "=" * 50)
93
+ print(f"πŸ“Š Test Results: {passed}/{total} tests passed")
94
+
95
+ if passed == total:
96
+ print("πŸŽ‰ All tests passed! FastAPI application is ready.")
97
+ return True
98
+ else:
99
+ print("⚠️ Some tests failed. Please check the errors above.")
100
+ return False
101
+
102
+ if __name__ == "__main__":
103
+ success = main()
104
+ sys.exit(0 if success else 1)
105
+