import base64 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 from orjson import loads from apps.remd.dependencies import convert_aemd_to_pdf, get_parsable_ids from apps.tdn.auth import token from apps.users.auth import login from apps.users.models import User from clients import clients as c from clients.tmk import schema as ts from clients.vitacore import schema as vs from shared import exceptions as e from shared.redis import client as cache from . import schema as s logger = getLogger(__name__) router = APIRouter( prefix='/user', tags=[ 'User', ], ) @router.get( '/getProfile', responses={ status.HTTP_200_OK: {'model': vs.ProfileModel}, }, ) async def get_profile(user: Annotated[User, Depends(login)]): """ Get profile of user. """ return await c.vitacore_api.getProfile(user.vita_id) @router.get( '/getDepartments', responses={ status.HTTP_200_OK: {'model': vs.OrganizationsModel}, }, ) async def get_departments(): """ Get list of departments. """ return await c.vitacore_api.getDepartments() @router.get( '/getWorkers', responses={status.HTTP_200_OK: {'model': vs.WorkersModel}} ) async def get_workers( user: Annotated[User, Depends(login)], departmentId: str ): """ Get list of workers by department id. """ return await c.vitacore_api.getWorkers(departmentId) @router.get( '/getSpecs', responses={status.HTTP_200_OK: {'model': vs.SpecsV021Model}}, ) async def get_specs(user: Annotated[User, Depends(login)]): """ Get list of specialties. """ return await c.vitacore_api.getSpecsV021() @router.get( '/getEntries', responses={status.HTTP_200_OK: {'model': vs.EntriesModel}} ) async def get_entries(user: Annotated[User, Depends(login)]): """ Get list of entries for user by id. """ return await c.vitacore_api.getEntries(user.vita_id) @router.get('/getVaccsReport') async def get_vaccs_report( user: Annotated[User, Depends(login)], resultId: str | None = None ): """ Get report of vaccinations for user by id. """ if resultId is not None: return await c.vitacore_api.getDiagResultFile(resultId) return await c.vitacore_api.getVaccsReport(user.vita_id) @router.get('/getMedExamDict') async def get_med_exam_dict(user: Annotated[User, Depends(login)]): """ Get medical examination dictionary. """ return await c.vitacore_api.getMedExamDict() @router.get('/getRoutesList') async def get_routes_list(user: Annotated[User, Depends(login)]): """ Get list of routes. """ return await c.vitacore_api.getRoutesList(user.vita_id) @router.get('/getHospExaminations') async def get_hosp_examinations( user: Annotated[User, Depends(login)], examId: str | None = None ): """ Get list of hospital examinations. """ return await c.vitacore_api.getHospExaminations( user.vita_id, examId, ) @router.get('/getCurrHosp') async def get_curr_hosp(user: Annotated[User, Depends(login)]): """ Get current hospitalization. """ return await c.vitacore_api.getCurrHosp(user.vita_id) @router.get('/getHosps') async def get_hosps(user: Annotated[User, Depends(login)]): """ Get list of hospitals. """ return await c.vitacore_api.getHosps(user.vita_id) @router.get('/getHospRecommendations') async def get_hosp_recommendations(user: Annotated[User, Depends(login)]): """ Get list of recommended hospitals. """ return await c.vitacore_api.getHospRecommendations(user.vita_id) @router.get('/getHospRoutes') async def get_hosp_routes(user: Annotated[User, Depends(login)]): """ Get list of recommended hospitals. """ return await c.vitacore_api.getHospRoutes(user.vita_id) @router.get('/getDiagnosticResults') async def get_diagnostic_results(user: Annotated[User, Depends(login)]): """ Get list of diagnostic results. """ return await c.vitacore_api.getDiagnosticResults(user.vita_id) @router.get('/getELNs') async def get_eln(user: Annotated[User, Depends(login)]): """ Get list of ELNs. """ return await c.vitacore_api.getELNs(user.vita_id) @router.get('/getPatFLG') async def get_pat_flg(user: Annotated[User, Depends(login)]): """ Get list of ELNs. """ return await c.vitacore_api.getPatFLG(user.vita_id) @router.get('/queue', response_model=list[ts.QueueModel]) async def queue(user: Annotated[User, Depends(login)]): """ Get list of VKS queues. """ profile = await c.vitacore_api.getProfile(user.vita_id) return await c.tmk_api.getQueue(patient_snils=profile.SNILS) @router.get('/aemd') async def get_aemd( user: Annotated[User, Depends(login)], parsable_ids: Annotated[list[str], Depends(get_parsable_ids)], ): profile = await c.vitacore_api.getProfile(user.vita_id) snils = profile.SNILS.replace('-', '').replace(' ', '') docs = await c.aemd_api.searchRegistryItem(patient_snils=snils) items: list[s.AEMDFile] = docs['items'] return_items: list[s.AEMDReturnFile] = [] for item in items: if item['DocKind'] not in parsable_ids: continue is_cached = await cache.get(f'aemd:{user.vita_id}:{item["emdrId"]}') return_items.append( s.AEMDReturnFile( emdrId=item['emdrId'], registrationDate=item['registrationDate'], 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, docKind: str ): data = await cache.get(f'aemd:{user.vita_id}:{emdrId}') if not data: raise e.NotFoundException(status_code=404, detail='File not found') b64 = loads(data)['data'] decoded = base64.b64decode(b64) pdf = await convert_aemd_to_pdf(decoded, docKind) b64_pdf = base64.b64encode(pdf).decode('utf-8') return { 'filename': f'{emdrId}.pdf', 'content_type': 'application/pdf', 'data': b64_pdf, } @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED) async def measurement( tdn_access_token: Annotated[str, Depends(token)], user: Annotated[User, Depends(login)], ad: Annotated[int, Body()], sd: Annotated[int, Body()], pulse: Annotated[int, Body()], comment: Annotated[str, Body()], status: Annotated[int, Body(ge=1, le=3)], serial_number: Annotated[str, Body()], ekg: UploadFile, ): vitaId = '109df850-2268-49fb-bb78-2e0f3c57314d' patient = await c.tdn_api.patient_search(tdn_access_token, vitaId) patientUid = patient['items'][0]['uid'] observations = await c.tdn_api.observations_search( tdn_access_token, patientUid ) if observations.total == 0: raise e.NotFoundException(detail='No observations found') ad_obsrvMeasurementUid = None ad_observationUid = None health_obsrvMeasurementUid = None health_observationUid = None sad_measurement = None dad_measurement = None pulse_measurement = None health_measurement = None health_measurement_created_at = None observations = observations.items[::-1] for observation in observations: observation_measurements = ( await c.tdn_api.observations_measurement_search( tdn_access_token, observation.uid ) ) for measurement in observation_measurements.items: if measurement.measurement.code == 'ADPULSE': ad_obsrvMeasurementUid = measurement.uid ad_observationUid = measurement.observationUid for metric in measurement.obsrvMtMetrics: if metric.metric.code == 'SAD': sad_measurement = metric.uid if metric.metric.code == 'DAD': dad_measurement = metric.uid if metric.metric.code == 'PULSE': pulse_measurement = metric.uid if measurement.measurement.code == 'HEALTH': health_obsrvMeasurementUid = measurement.uid health_observationUid = measurement.observationUid for metric in measurement.obsrvMtMetrics: if metric.metric.code == 'HEALTH' and ( health_measurement_created_at is None or metric.createdAt > health_measurement_created_at ): health_measurement = metric.uid health_measurement_created_at = metric.createdAt if ( not ad_obsrvMeasurementUid or not sad_measurement or not dad_measurement or not pulse_measurement or not ad_observationUid or not health_obsrvMeasurementUid or not health_observationUid or not health_measurement or not health_measurement_created_at ): ad_obsrvMeasurementUid = None sad_measurement = None dad_measurement = None pulse_measurement = None ad_observationUid = None health_obsrvMeasurementUid = None health_observationUid = None health_measurement = None health_measurement_created_at = None else: break if not ad_obsrvMeasurementUid or not ad_observationUid: raise e.NotFoundException(detail='No ADPULSE measurement found') if not sad_measurement: raise e.NotFoundException(detail='No SAD measurement found') if not dad_measurement: raise e.NotFoundException(detail='No DAD measurement found') if not pulse_measurement: raise e.NotFoundException(detail='No PULSE measurement found') if not health_obsrvMeasurementUid or not health_observationUid: raise e.NotFoundException(detail='No HEALTH measurement found') if not health_measurement: raise e.NotFoundException(detail='No HEALTH measurement found') if not health_obsrvMeasurementUid or not health_observationUid: raise e.NotFoundException(detail='No HEALTH measurement found') ad_series = await c.tdn_api.create_series( tdn_access_token, ad_observationUid, ad_obsrvMeasurementUid, ) ad_series_uid = ad_series.uid # SAD await c.tdn_api.create_series_values( tdn_access_token, ad_series_uid, sad_measurement, nvalue=sd ) # DAD await c.tdn_api.create_series_values( tdn_access_token, ad_series_uid, dad_measurement, nvalue=ad ) # PULSE await c.tdn_api.create_series_values( tdn_access_token, ad_series_uid, pulse_measurement, nvalue=pulse ) health_series = await c.tdn_api.create_series( tdn_access_token, health_observationUid, health_obsrvMeasurementUid, ) health_series_uid = health_series.uid # HEALTH await c.tdn_api.create_series_values( tdn_access_token, health_series_uid, health_measurement, nvalue=status, svalue=comment, ) # EKG await c.tdn_api.ekg( tdn_access_token, patientUid, serial_number, ekg, ) created = datetime.now(UTC).strftime('%Y-%m-%d %H:%M:%S') data = { 'ad': ad, 'sd': sd, 'pulse': pulse, 'created_at': created, 'comment': comment, 'status': status, } cache_key = f'tdn:measurement:{user.id}:{created}' await cache.set(cache_key, dumps(data)) @router.get('/measurements') async def measurements( user: Annotated[User, Depends(login)], ): data = [ await cache.get(key) for key in await cache.keys(f'tdn:measurement:{user.id}:*') ] return data @router.delete('/account', status_code=status.HTTP_204_NO_CONTENT) async def delete_account(user: Annotated[User, Depends(login)]): return @router.get('/notifications') async def notifications(user: Annotated[User, Depends(login)]): keys = await cache.keys(f'tmk:{user.id}:*') notif: list[dict[str, str | bool]] = [] for key in keys: val = await cache.get(key) if val is None: continue value = loads(val) notif_val = value.copy() notif.append(notif_val) if value['is_read'] is False: value['is_read'] = True await cache.set(key, dumps(value)) return s.Notifications( notifications=notif, ) @router.post('/complaint', status_code=status.HTTP_204_NO_CONTENT) async def complaint( user: Annotated[User, Depends(login)], complaints: s.Complaints ): cache_key = f'complaint:{user.vita_id}' await cache.set(cache_key, dumps(complaints.complaints))