This commit is contained in:
@ -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)
|
||||||
|
|||||||
@ -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'],
|
||||||
|
registrationDate=item['registrationDate'],
|
||||||
|
DocKind=item['DocKind'],
|
||||||
|
IsSemd=item['IsSemd'],
|
||||||
|
isCached=bool(is_cached),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return await c.aemd_api.demandContent(
|
return return_items
|
||||||
messageId='test123', emdrId=doc['emdrId']
|
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|||||||
@ -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]]
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user