Патч
All checks were successful
Build And Push / publish (push) Successful in 53s

This commit is contained in:
2025-12-02 03:45:25 +03:00
parent 9612a8be67
commit bf39b8e574
5 changed files with 106 additions and 159 deletions

View File

@ -1,6 +1,6 @@
from logging import getLogger from logging import getLogger
from fastapi import APIRouter from fastapi import APIRouter, HTTPException, Request
logger = getLogger(__name__) logger = getLogger(__name__)
router = APIRouter( router = APIRouter(
@ -12,5 +12,18 @@ router = APIRouter(
@router.post('/callback') @router.post('/callback')
async def callback(): async def callback(request: Request):
return 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)

View File

@ -1,6 +1,7 @@
from datetime import UTC, datetime from datetime import UTC, datetime
from json import dumps from json import dumps
from logging import getLogger from logging import getLogger
from secrets import token_urlsafe
from typing import Annotated from typing import Annotated
from fastapi import APIRouter, Body, Depends, UploadFile, status from fastapi import APIRouter, Body, Depends, UploadFile, status
@ -180,20 +181,42 @@ async def queue(_: Annotated[User, Depends(login)]):
@router.get('/aemd') @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) profile = await c.vitacore_api.getProfile(user.vita_id)
snils = profile.SNILS.replace('-', '').replace(' ', '') snils = profile.SNILS.replace('-', '').replace(' ', '')
docs = await c.aemd_api.searchRegistryItem(patient_snils=snils) 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()}' return_items.append(
logger.info(msg) s.AEMDReturnFile(
emdrId=item['emdrId'],
return await c.aemd_api.demandContent( registrationDate=item['registrationDate'],
messageId='test123', emdrId=doc['emdrId'] DocKind=item['DocKind'],
IsSemd=item['IsSemd'],
isCached=bool(is_cached),
) )
)
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) @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)

View File

@ -3,6 +3,30 @@ from typing import TypedDict
from pydantic import BaseModel 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): class Notifications(TypedDict):
notifications: list[dict[str, str | bool]] notifications: list[dict[str, str | bool]]

View File

@ -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)

View File

@ -135,6 +135,11 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException raise e.UnknownException
async def getWorkers(self, departmentId: str): 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() token = await self.get_token()
req = await self.get( req = await self.get(
'/getWorkers', '/getWorkers',
@ -144,7 +149,13 @@ class VITACORE_API(AsyncClient):
match req.status_code: match req.status_code:
case st.HTTP_200_OK: 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: case st.HTTP_206_PARTIAL_CONTENT:
return s.WorkersModel(Workers=[]) return s.WorkersModel(Workers=[])
@ -154,6 +165,11 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException raise e.UnknownException
async def getSpecsV021(self): async def getSpecsV021(self):
data = await self.get_cache('vitacore_getSpecsV021')
if data:
return s.SpecsV021Model.model_validate(data)
token = await self.get_token() token = await self.get_token()
req = await self.get( req = await self.get(
'/getSpecsV021', headers={'Authorization': f'Bearer {token}'} '/getSpecsV021', headers={'Authorization': f'Bearer {token}'}
@ -161,7 +177,13 @@ class VITACORE_API(AsyncClient):
match req.status_code: match req.status_code:
case st.HTTP_200_OK: 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 _: case _:
self.logger.error(req.json()) self.logger.error(req.json())
raise e.UnknownException raise e.UnknownException
@ -218,6 +240,11 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException raise e.UnknownException
async def getMedExamDict(self): async def getMedExamDict(self):
data = await self.get_cache('vitacore_getMedExamDict')
if data:
return s.MedExamDictModel.model_validate(data)
token = await self.get_token() token = await self.get_token()
req = await self.get( req = await self.get(
'/getMedExamDict', headers={'Authorization': f'Bearer {token}'} '/getMedExamDict', headers={'Authorization': f'Bearer {token}'}
@ -225,7 +252,13 @@ class VITACORE_API(AsyncClient):
match req.status_code: match req.status_code:
case st.HTTP_200_OK: 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 _: case _:
self.logger.error(req.json()) self.logger.error(req.json())
raise e.UnknownException raise e.UnknownException