From ebd4d145088b8c2b3f668a326cb0d76f9fe1314f Mon Sep 17 00:00:00 2001 From: Miwory Date: Sun, 5 Oct 2025 10:51:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B0=D1=82=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apps/tdn/__init__.py | 0 src/apps/tdn/auth.py | 15 +++++ src/apps/users/auth.py | 2 +- src/apps/users/v1/router.py | 20 ++++++- src/clients/__init__.py | 9 +++ src/clients/tdn/__init__.py | 0 src/clients/tdn/api.py | 106 +++++++++++++++++++++++++++++++++ src/clients/tdn/schema.py | 80 +++++++++++++++++++++++++ src/clients/vitacore/schema.py | 7 +-- src/core/config.py | 5 ++ 10 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 src/apps/tdn/__init__.py create mode 100644 src/apps/tdn/auth.py create mode 100644 src/clients/tdn/__init__.py create mode 100644 src/clients/tdn/api.py create mode 100644 src/clients/tdn/schema.py diff --git a/src/apps/tdn/__init__.py b/src/apps/tdn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/tdn/auth.py b/src/apps/tdn/auth.py new file mode 100644 index 0000000..f0d80f0 --- /dev/null +++ b/src/apps/tdn/auth.py @@ -0,0 +1,15 @@ +from clients import clients as c +from shared.redis import client as cache + + +async def token(): + access_token = cache.get('tdn_token') + + if access_token is None: + tokens = await c.tdn_api.signin() + cache.set('tdn_token', tokens.accessToken, 60) + + return tokens.accessToken + + else: + return access_token.decode() diff --git a/src/apps/users/auth.py b/src/apps/users/auth.py index 69b3d8d..436b07e 100644 --- a/src/apps/users/auth.py +++ b/src/apps/users/auth.py @@ -17,4 +17,4 @@ async def login( if is_exist is None: raise e.UnauthorizedException - return True + return is_exist.decode() diff --git a/src/apps/users/v1/router.py b/src/apps/users/v1/router.py index 161ad5c..0cee689 100644 --- a/src/apps/users/v1/router.py +++ b/src/apps/users/v1/router.py @@ -59,7 +59,7 @@ async def get_entries(): Get list of entries for user by id. """ return await c.vitacore_api.getEntries( - 'b172ddc1-bd94-407f-885f-725193dcc502' + '6c7978f0-c573-4ccf-8c6e-f0cd9aceb1e1' ) @@ -170,6 +170,24 @@ async def get_pat_flg(): ) +# @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED) +# async def measurement(tdn_access_token: Annotated[str, Depends(token)]): +# patientId = '6debe050-b57e-442b-9b0e-8d304ca382b0' +# observations = await c.tdn_api.observations_search( +# tdn_access_token, patientId +# ) + +# if observations.total == 0: +# raise e.NotFoundException(detail='No observations found') + +# observation = observations.items[-1] +# observation_measurements = await c.tdn_api.observations_measurement_search( +# tdn_access_token, observation.uid +# ) + +# return observation_measurements + + @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED) async def measurement( user: Annotated[str, Depends(login)], diff --git a/src/clients/__init__.py b/src/clients/__init__.py index d00c30a..653e46a 100644 --- a/src/clients/__init__.py +++ b/src/clients/__init__.py @@ -1,10 +1,12 @@ from .esia.api import ESIA_API +from .tdn.api import TDN_API from .vitacore.api import VITACORE_API class ClientsObject: _esia_api = None _vitacore_api = None + _tdn_api = None @property def esia_api(self): @@ -20,5 +22,12 @@ class ClientsObject: return self._vitacore_api + @property + def tdn_api(self): + if not self._tdn_api: + self._tdn_api = TDN_API() + + return self._tdn_api + clients = ClientsObject() diff --git a/src/clients/tdn/__init__.py b/src/clients/tdn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/clients/tdn/api.py b/src/clients/tdn/api.py new file mode 100644 index 0000000..7f0f6f1 --- /dev/null +++ b/src/clients/tdn/api.py @@ -0,0 +1,106 @@ +from json import dumps +from logging import getLogger +from urllib.parse import quote, urlencode + +from fastapi import status as st +from httpx import AsyncClient + +from core.config import settings +from shared import exceptions as e + +from . import schema as s + + +class TDN_API(AsyncClient): + def __init__(self): + self.logger = getLogger(__name__) + super().__init__( + base_url=settings.TDN_BASE_URL, + headers={ + 'Content-Type': 'application/json', + }, + ) + + async def signin(self): + data = { + 'username': settings.TDN_LOGIN, + 'password': settings.TDN_PASSWORD, + } + + res = await self.post('/core/auth/signin', json=data) + + match res.status_code: + case st.HTTP_200_OK: + return s.SignInModel.model_validate(res.json()) + case _: + self.logger.error(res.json()) + raise e.UnknownException + + async def patient_search(self, access_token: str, vitaId: str): + data = quote(dumps({'vitaId': vitaId})) + + _ = await self.get( + '/ddn/patient/search', + params={'query': data}, + headers={'Authorization': f'Bearer {access_token}'}, + ) + + async def observations_search(self, access_token: str, patientUid: str): + data = quote(dumps({'where': {'patientUid': patientUid}})) + + res = await self.get( + '/ddn/observations/search', + params={'query': data}, + headers={'Authorization': f'Bearer {access_token}'}, + ) + + match res.status_code: + case st.HTTP_200_OK: + return s.ObservationsModel.model_validate(res.json()) + case _: + self.logger.error(res.json()) + raise e.UnknownException + + async def observations_measurement_search( + self, access_token: str, observationUid: str + ): + # data = urlencode( + # dumps( + # { + # 'where': {'observationUid': observationUid}, + # 'relations': [ + # 'measurement', + # 'obsrvMtMetrics', + # 'obsrvMtMetrics.metric', + # ], + # } + # ) + # ) + encoded_query = urlencode( + { + 'query': dumps( + { + 'where': {'observationUid': observationUid}, + 'relations': [ + 'measurement', + 'obsrvMtMetrics', + 'obsrvMtMetrics.metric', + ], + } + ) + } + ) + + res = await self.get( + f'/ddn/observation/obsrv-measurements/search?{encoded_query}', + headers={'Authorization': f'Bearer {access_token}'}, + ) + + match res.status_code: + case st.HTTP_200_OK: + return s.ObservationMeasurementsModel.model_validate( + res.json() + ) + case _: + self.logger.error(res.json()) + raise e.UnknownException diff --git a/src/clients/tdn/schema.py b/src/clients/tdn/schema.py new file mode 100644 index 0000000..f4ad010 --- /dev/null +++ b/src/clients/tdn/schema.py @@ -0,0 +1,80 @@ +from datetime import datetime + +from pydantic import BaseModel + + +class SignInModel(BaseModel): + accessToken: str + refreshToken: str + + +class ObservationModel(BaseModel): + uid: str + createdAt: datetime + updatedAt: datetime + realmUid: str + patientUid: str + nosologyUid: str + exclusionReasonUid: str | None + exclusionComment: str | None + exclusionDate: datetime | None + employeeUid: str + mobileId: str | None + + +class ObservationsModel(BaseModel): + items: list[ObservationModel] + total: int + + +class MeasurementModel(BaseModel): + uid: str + createdAt: datetime + updatedAt: datetime + code: str + title: str + order: int + isSelfControl: bool + + +class MetricModel(BaseModel): + uid: str + createdAt: datetime + updatedAt: datetime + code: str + title: str + order: int + shortName: str + measureUid: str | None + format: str + + +class ObservationMtMetricModel(BaseModel): + uid: str + createdAt: datetime + updatedAt: datetime + obsrvMeasurementUid: str + metricUid: str + mobileId: str | None + metric: MetricModel + + +class ObservationMeasurementModel(BaseModel): + uid: str + createdAt: datetime + updatedAt: datetime + observationUid: str + measurementUid: str + timeFrequency: int + timePeriod: int + timePeriodMeasureUid: str + timeOfDay: list[str] + comment: str | None + mobileId: str | None + measurement: MeasurementModel + obsrvMtMetrics: list[ObservationMtMetricModel] + + +class ObservationMeasurementsModel(BaseModel): + items: list[ObservationMeasurementModel] + total: int diff --git a/src/clients/vitacore/schema.py b/src/clients/vitacore/schema.py index 4b29c9b..cd96139 100644 --- a/src/clients/vitacore/schema.py +++ b/src/clients/vitacore/schema.py @@ -266,9 +266,10 @@ class MedExamItemModel(BaseModel): title='Наименрование услуги', examples=['Осмотр фельдшером (акушеркой) или врачом акушером'], ) - SpecialityName: str = Field( + SpecialityName: str | None = Field( title='Специальность', examples=['Акушер-гинеколог'], + default=None, ) @@ -360,9 +361,7 @@ class ExaminationModel(BaseModel): DateTime: str = Field( title='Дата и время создания', examples=['01.08.2025 15:47:15'] ) - Resource: str = Field( - title='Врач', examples=['Абдуллина Ирина Владимировна'] - ) + Post: str = Field(title='Врач', examples=['Абдуллина Ирина Владимировна']) Speciality: str = Field( title='Специальность врача', examples=['Акушер-гинеколог'] ) diff --git a/src/core/config.py b/src/core/config.py index c7e9d44..1c5bb6b 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -57,6 +57,11 @@ class Settings(BaseSettings): default='https://gist-cws.ezdrav.ru:8899/MP_API' ) + # TDN + TDN_BASE_URL: str = Field(default='https://tdn.tatar.ru/api') + TDN_LOGIN: str = Field(default='') + TDN_PASSWORD: str = Field(default='') + @model_validator(mode='after') def celery_env(self): environ['CELERY_BROKER_URL'] = self.REDIS_URL