From bf39b8e574fa1434860373d342bd446dbeb9f0ad Mon Sep 17 00:00:00 2001 From: Miwory Date: Tue, 2 Dec 2025 03:45:25 +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/remd/v1/router.py | 19 ++++- src/apps/users/v1/router.py | 37 +++++++-- src/apps/users/v1/schema.py | 24 ++++++ src/clients/aemd/_schema.py | 146 ------------------------------------ src/clients/vitacore/api.py | 39 +++++++++- 5 files changed, 106 insertions(+), 159 deletions(-) delete mode 100644 src/clients/aemd/_schema.py diff --git a/src/apps/remd/v1/router.py b/src/apps/remd/v1/router.py index 1fab60b..efb3fad 100644 --- a/src/apps/remd/v1/router.py +++ b/src/apps/remd/v1/router.py @@ -1,6 +1,6 @@ from logging import getLogger -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException, Request logger = getLogger(__name__) router = APIRouter( @@ -12,5 +12,18 @@ router = APIRouter( @router.post('/callback') -async def callback(): - return +async def callback(request: Request): + if not request.headers.get('content-type', '').startswith( + 'application/xml' + ): + logger.warning('Content-Type must be application/xml') + raise HTTPException( + status_code=400, detail='Content-Type must be application/xml' + ) + + body_bytes = await request.body() + if not body_bytes: + logger.warning('Empty body') + raise HTTPException(status_code=400, detail='Empty body') + + print(body_bytes) diff --git a/src/apps/users/v1/router.py b/src/apps/users/v1/router.py index 6470020..431d728 100644 --- a/src/apps/users/v1/router.py +++ b/src/apps/users/v1/router.py @@ -1,6 +1,7 @@ from datetime import UTC, datetime from json import dumps from logging import getLogger +from secrets import token_urlsafe from typing import Annotated from fastapi import APIRouter, Body, Depends, UploadFile, status @@ -180,20 +181,42 @@ async def queue(_: Annotated[User, Depends(login)]): @router.get('/aemd') -async def aemd(user: Annotated[User, Depends(login)]): +async def get_aemd(user: Annotated[User, Depends(login)]): profile = await c.vitacore_api.getProfile(user.vita_id) snils = profile.SNILS.replace('-', '').replace(' ', '') docs = await c.aemd_api.searchRegistryItem(patient_snils=snils) - doc = docs['items'][0] + items: list[s.AEMDFile] = docs['items'] + return_items: list[s.AEMDReturnFile] = [] - from datetime import datetime + for item in items: + is_cached = await cache.get(f'aemd:{item["localUid"]}') - msg = f'AEMD Current datetime: {datetime.now(UTC).isoformat()}' - logger.info(msg) + return_items.append( + s.AEMDReturnFile( + emdrId=item['emdrId'], + registrationDate=item['registrationDate'], + DocKind=item['DocKind'], + IsSemd=item['IsSemd'], + isCached=bool(is_cached), + ) + ) - return await c.aemd_api.demandContent( - messageId='test123', emdrId=doc['emdrId'] + return return_items + + +@router.post('/aemd', status_code=status.HTTP_202_ACCEPTED) +async def post_aemd(user: Annotated[User, Depends(login)], emdrId: str): + messageId = token_urlsafe(32) + data = s.AEMDDemandContent( + messageId=messageId, emdrId=emdrId, vitaId=user.vita_id ) + await cache.set(f'aemd_messages:{messageId}', dumps(data)) + await c.aemd_api.demandContent(messageId=messageId, emdrId=emdrId) + + +@router.get('/aemd/{emdrId}') +async def get_aemd_file(user: Annotated[User, Depends(login)], emdrId: str): + return @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED) diff --git a/src/apps/users/v1/schema.py b/src/apps/users/v1/schema.py index 1e518b2..46bd37c 100644 --- a/src/apps/users/v1/schema.py +++ b/src/apps/users/v1/schema.py @@ -3,6 +3,30 @@ from typing import TypedDict from pydantic import BaseModel +class AEMDFile(TypedDict): + emdrId: str + localUid: str + registrationDate: str + registrationDateTime: str + storeTillDate: str + DocKind: str + IsSemd: bool + + +class AEMDReturnFile(TypedDict): + emdrId: str + registrationDate: str + DocKind: str + IsSemd: bool + isCached: bool + + +class AEMDDemandContent(TypedDict): + messageId: str + emdrId: str + vitaId: str + + class Notifications(TypedDict): notifications: list[dict[str, str | bool]] diff --git a/src/clients/aemd/_schema.py b/src/clients/aemd/_schema.py deleted file mode 100644 index 5c6409f..0000000 --- a/src/clients/aemd/_schema.py +++ /dev/null @@ -1,146 +0,0 @@ -from datetime import datetime -from typing import Literal - -from pydantic_xml import BaseXmlModel, attr, element, wrapped - -# SOAP Envelope namespaces -NS_SOAP = 'http://www.w3.org/2003/05/soap-envelope' -NS_ADDR = 'http://www.w3.org/2005/08/addressing' -NS_WSSE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' -NS_WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' -NS_DS = 'http://www.w3.org/2000/09/xmldsig#' -NS_APP = 'http://egisz.rosminzdrav.ru/iehr/emdr/service/' - -NSMAP = { - 's': NS_SOAP, - 'a': NS_ADDR, - 'wsse': NS_WSSE, - 'wsu': NS_WSU, - 'ds': NS_DS, - 'app': NS_APP, -} - - -class Reference(BaseXmlModel, tag='Reference', ns='ds', nsmap=NSMAP): - uri: str = attr(name='URI') - digest_method: Literal['http://www.w3.org/2001/04/xmlenc#sha256'] = ( - element(tag='DigestMethod', ns='ds', attr_name='Algorithm') - ) - digest_value: str = element(tag='DigestValue', ns='ds') - - -class SignedInfo(BaseXmlModel, tag='SignedInfo', ns=NS_DS, nsmap=NSMAP): - canon_method: Literal['http://www.w3.org/2001/10/xml-exc-c14n#'] = element( - tag='CanonicalizationMethod', ns=NS_DS, attr_name='Algorithm' - ) - sig_method: Literal[ - 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256' - ] = element(tag='SignatureMethod', ns=NS_DS, attr_name='Algorithm') - references: list[Reference] = element(tag='Reference', ns=NS_DS) - - -class SecurityTokenReference( - BaseXmlModel, - tag='SecurityTokenReference', - ns=NS_WSSE, - nsmap={'wsse': NS_WSSE}, -): - ref_uri: str = wrapped('Reference', attr(name='URI')) - ref_type: Literal[ - 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3' - ] = wrapped('Reference', attr(name='ValueType')) - - -class KeyInfo(BaseXmlModel, tag='KeyInfo', ns=NS_DS, nsmap=NSMAP): - token_ref: SecurityTokenReference = element( - tag='SecurityTokenReference', ns=NS_WSSE - ) - - -class Signature(BaseXmlModel, tag='Signature', ns=NS_DS, nsmap=NSMAP): - signed_info: SignedInfo = element() - signature_value: str = element(tag='SignatureValue') - key_info: KeyInfo = element() - - -class BinarySecurityToken( - BaseXmlModel, - tag='BinarySecurityToken', - ns=NS_WSSE, - nsmap=NSMAP, -): - id: str = attr(name='Id', ns=NS_WSU) - value_type: Literal[ - 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3' - ] = attr(name='ValueType') - encoding_type: Literal[ - 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary' - ] = attr(name='EncodingType') - value: str - - -class Security(BaseXmlModel, tag='Security', ns=NS_WSSE, nsmap=NSMAP): - token: BinarySecurityToken = element() - signature: Signature = element() - - -class Action(BaseXmlModel, tag='Action', ns=NS_ADDR, nsmap=NSMAP): - id: str = attr(name='Id', ns=NS_WSU) - - -class MessageID(BaseXmlModel, tag='MessageID', ns=NS_ADDR, nsmap=NSMAP): - id: str = attr(name='Id', ns=NS_WSU) - value: str - - -class Header(BaseXmlModel, tag='Header', ns=NS_SOAP, nsmap=NSMAP): - action: Action | None = element(tag='Action', ns=NS_ADDR) - message_id: MessageID | None = element(tag='MessageID', ns=NS_ADDR) - security: Security = element(tag='Security', ns=NS_WSSE) - - -class Page(BaseXmlModel, tag='page', ns=NS_APP): - items_per_page: int = element(tag='itemsPerPage') - has_next: bool = element(tag='hasNext') - - -class Item(BaseXmlModel, tag='item', ns=NS_APP): - emdr_id: str = element(tag='emdrId') - local_uid: str = element(tag='localUid') - registration_date: datetime = element(tag='registrationDate') - registration_date_time: datetime = element(tag='registrationDateTime') - store_till_date: str = element(tag='storeTillDate') - doc_kind: str = element(tag='DocKind') - is_semd: bool = element(tag='IsSemd') - - -class Matches(BaseXmlModel, tag='matches', ns=NS_APP): - items: list[Item] = element(tag='item') - page: Page = element() - - -class SearchRegistryItemResponse( - BaseXmlModel, - tag='searchRegistryItemResponse', - ns=NS_APP, - nsmap={'app': NS_APP}, -): - status: Literal['success'] = element() - matches: Matches = element() - - -class Body(BaseXmlModel, tag='Body', ns=NS_SOAP, nsmap=NSMAP): - id: str = attr(name='Id', ns=NS_WSU) - response: SearchRegistryItemResponse = element( - tag='searchRegistryItemResponse', ns=NS_APP - ) - - -class Envelope( - BaseXmlModel, - tag='Envelope', - ns=NS_SOAP, - nsmap=NSMAP, -): - header: Header = element(tag='Header', ns=NS_SOAP) - body: Body = element(tag='Body', ns=NS_SOAP) diff --git a/src/clients/vitacore/api.py b/src/clients/vitacore/api.py index bc4c43d..2d3eff9 100644 --- a/src/clients/vitacore/api.py +++ b/src/clients/vitacore/api.py @@ -135,6 +135,11 @@ class VITACORE_API(AsyncClient): raise e.UnknownException async def getWorkers(self, departmentId: str): + data = await self.get_cache(f'vitacore_getWorkers:{departmentId}') + + if data: + return s.WorkersModel.model_validate(data) + token = await self.get_token() req = await self.get( '/getWorkers', @@ -144,7 +149,13 @@ class VITACORE_API(AsyncClient): match req.status_code: case st.HTTP_200_OK: - return s.WorkersModel.model_validate(req.json()) + data = s.WorkersModel.model_validate(req.json()) + await self.set_cache( + f'vitacore_getWorkers:{departmentId}', + data.model_dump_json(), + 14400, + ) + return data case st.HTTP_206_PARTIAL_CONTENT: return s.WorkersModel(Workers=[]) @@ -154,6 +165,11 @@ class VITACORE_API(AsyncClient): raise e.UnknownException async def getSpecsV021(self): + data = await self.get_cache('vitacore_getSpecsV021') + + if data: + return s.SpecsV021Model.model_validate(data) + token = await self.get_token() req = await self.get( '/getSpecsV021', headers={'Authorization': f'Bearer {token}'} @@ -161,7 +177,13 @@ class VITACORE_API(AsyncClient): match req.status_code: case st.HTTP_200_OK: - return s.SpecsV021Model.model_validate(req.json()) + data = s.SpecsV021Model.model_validate(req.json()) + await self.set_cache( + 'vitacore_getSpecsV021', + data.model_dump_json(), + 14400, + ) + return data case _: self.logger.error(req.json()) raise e.UnknownException @@ -218,6 +240,11 @@ class VITACORE_API(AsyncClient): raise e.UnknownException async def getMedExamDict(self): + data = await self.get_cache('vitacore_getMedExamDict') + + if data: + return s.MedExamDictModel.model_validate(data) + token = await self.get_token() req = await self.get( '/getMedExamDict', headers={'Authorization': f'Bearer {token}'} @@ -225,7 +252,13 @@ class VITACORE_API(AsyncClient): match req.status_code: case st.HTTP_200_OK: - return s.MedExamDictModel.model_validate(req.json()) + data = s.MedExamDictModel.model_validate(req.json()) + await self.set_cache( + 'vitacore_getMedExamDict', + data.model_dump_json(), + 14400, + ) + return data case _: self.logger.error(req.json()) raise e.UnknownException