Skip to content

Commit 7e38287

Browse files
committed
Init: Added base integrations and models with get_appointments method
1 parent 898526a commit 7e38287

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

ecw_integrations.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from datetime import datetime, timezone
2+
import json
3+
import time
4+
from typing import Literal
5+
from urllib.parse import urlencode
6+
import aiohttp
7+
from fake_useragent import UserAgent
8+
from fastapi import HTTPException
9+
from fastapi.logger import logger
10+
from fastapi.responses import JSONResponse
11+
from integrations.ecw.ecw_config import ECW_URLS, AuthTokens
12+
from integrations.ecw.ecw_utils import parse_appointments_xml
13+
from submodule_integrations.ecw.ecw_models import (
14+
GetAppointmentsRequest,
15+
get_default_date,
16+
)
17+
from submodule_integrations.models.integration import Integration
18+
from submodule_integrations.utils.errors import (
19+
IntegrationAPIError,
20+
)
21+
22+
23+
class ECWIntegration(Integration):
24+
def __init__(self, auth_tokens: AuthTokens, user_agent: str = UserAgent().chrome):
25+
super().__init__("ecw")
26+
self.user_agent = user_agent
27+
self.network_requester = None
28+
self.url = "https://nybukaapp.eclinicalweb.com/mobiledoc/jsp/catalog/xml"
29+
self.auth_tokens: AuthTokens = auth_tokens
30+
31+
@classmethod
32+
async def create(cls, auth_tokens: AuthTokens, network_requester=None):
33+
instance = cls(auth_tokens)
34+
instance.network_requester = network_requester
35+
36+
return instance
37+
38+
async def _make_request(self, method: str, url: str, **kwargs):
39+
if self.network_requester:
40+
response = await self.network_requester.request(method, url, **kwargs)
41+
return response
42+
else:
43+
async with aiohttp.ClientSession() as session:
44+
async with session.request(method, url, **kwargs) as response:
45+
return await self._handle_response(response)
46+
47+
async def _handle_response(self, response: aiohttp.ClientResponse):
48+
response_text = await response.text()
49+
status = response.status
50+
51+
parsed_data = None
52+
53+
try:
54+
if response_text.strip().startswith("<?xml"):
55+
parsed_data = parse_appointments_xml(response_text)
56+
else:
57+
parsed_data = json.loads(response_text)
58+
except Exception as e:
59+
logger.warning(f"Response parsing failed: {str(e)}")
60+
parsed_data = {"error": {"message": "Parsing error", "raw": response_text}}
61+
62+
if 200 <= status < 300:
63+
return parsed_data
64+
65+
error_message = parsed_data.get("error", {}).get("message", "Unknown error")
66+
error_code = parsed_data.get("error", {}).get("code", str(status))
67+
68+
logger.debug(f"{status} - {parsed_data}")
69+
70+
if 400 <= status < 500:
71+
raise HTTPException(status_code=status, detail=parsed_data)
72+
elif status >= 500:
73+
raise IntegrationAPIError(
74+
self.integration_name,
75+
f"Downstream server error (translated to HTTP 501): {error_message}",
76+
501,
77+
error_code,
78+
)
79+
else:
80+
raise IntegrationAPIError(
81+
self.integration_name,
82+
f"{error_message} (HTTP {status})",
83+
status,
84+
error_code,
85+
)
86+
87+
async def _setup_headers(self, content_type: str = None):
88+
_headers = {
89+
"User-Agent": self.user_agent,
90+
"Cookie": self.auth_tokens.Cookie,
91+
"Sec-Ch-Ua": "'Chromium';v='134', 'Not:A-Brand';v='24', 'Google Chrome';v='134'",
92+
"x-csrf-token": self.auth_tokens.x_csrf_token,
93+
"Cip": self.auth_tokens.Cip,
94+
}
95+
if content_type:
96+
_headers["Content-type"] = content_type
97+
98+
return _headers
99+
100+
async def get_appointments(self, get_appointments_request: GetAppointmentsRequest):
101+
eDate = get_appointments_request.eDate or get_default_date()
102+
maxCount = get_appointments_request.maxCount or 100
103+
104+
logger.debug(
105+
f"Fetching {maxCount} doctor's appointments for user: {self.auth_tokens.TrUserId}"
106+
)
107+
108+
headers = await self._setup_headers(
109+
content_type="application/x-www-form-urlencoded; charset=UTF-8"
110+
)
111+
112+
# Format dynamic URL
113+
url = ECW_URLS["get appointments"].format(
114+
sessionDID=self.auth_tokens.sessionDID,
115+
TrUserId=self.auth_tokens.TrUserId,
116+
timestamp=int(time.time() * 1000),
117+
clientTimezone="UTC",
118+
)
119+
120+
payload = {
121+
"eDate": eDate,
122+
"doctorId": 0,
123+
"sortBy": "time",
124+
"facilityId": 0,
125+
"apptTime": 0,
126+
"checkinstatus": 0,
127+
"FacilityGrpId": 0,
128+
"maxCount": maxCount,
129+
"nCounter": 0,
130+
"DeptId": 0,
131+
"fromWeb": "yes",
132+
"fromAfterCare": "officeVisit",
133+
"tabId": 3,
134+
"toDate": "",
135+
"selectedChkShowASCvisits": "false",
136+
"includeResourceAppt": "true",
137+
}
138+
139+
encoded_payload = urlencode(payload)
140+
141+
return await self._make_request(
142+
method="POST",
143+
url=url,
144+
headers=headers,
145+
data=encoded_payload,
146+
)

ecw_models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pydantic import BaseModel
2+
from typing import Optional
3+
from datetime import date
4+
from datetime import datetime, timezone
5+
6+
7+
def get_default_date():
8+
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
9+
10+
11+
class GetAppointmentsRequest(BaseModel):
12+
eDate: Optional[str] = None
13+
maxCount: Optional[int] = 100

0 commit comments

Comments
 (0)