From f3c9cb42d6186391e0e2e04d631729a66aebdd59 Mon Sep 17 00:00:00 2001 From: Miwory Date: Thu, 27 Nov 2025 13:28:58 +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/esia/v1/router.py | 14 ++++---- src/apps/tmk/__init__.py | 0 src/apps/tmk/v1/__init__.py | 0 src/apps/tmk/v1/router.py | 41 ++++++++++++++++++++++ src/apps/users/v1/router.py | 44 ++++++++++++++--------- src/apps/users/v1/schema.py | 12 +++---- src/apps/vitacore/v1/router.py | 17 +++++++-- src/apps/vitacore/v1/schema.py | 16 +++++++++ src/clients/tmk/api.py | 16 +++++++++ src/clients/vitacore/api.py | 64 ++++++++++++++-------------------- src/clients/vitacore/schema.py | 34 +++++++++--------- src/core/routers/v1.py | 2 ++ 12 files changed, 172 insertions(+), 88 deletions(-) create mode 100644 src/apps/tmk/__init__.py create mode 100644 src/apps/tmk/v1/__init__.py create mode 100644 src/apps/tmk/v1/router.py create mode 100644 src/apps/vitacore/v1/schema.py diff --git a/src/apps/esia/v1/router.py b/src/apps/esia/v1/router.py index 934f3cb..f66dff0 100644 --- a/src/apps/esia/v1/router.py +++ b/src/apps/esia/v1/router.py @@ -49,22 +49,20 @@ async def callback(session: AsyncSessionDep, code: str): token.access_token, token.id_token ) - vita_user = await c.vitacore_api.findBySnils(esia_user.snils) - - if len(vita_user.patients) == 0: - raise e.BadRequestException(detail='Patient not found') - - vita_user = vita_user.patients[0] + try: + vita_user = await c.vitacore_api.findBySnils(esia_user.snils) + except e.UnknownException: + raise e.BadRequestException(detail='Patient not found') from None existing_user_stmt = ( - select(User).where(User.vita_id == vita_user.id).limit(1) + select(User).where(User.vita_id == vita_user.patId).limit(1) ) existing_user = ( await session.execute(existing_user_stmt) ).scalar_one_or_none() if existing_user is None: - user = User(vita_id=vita_user.id) + user = User(vita_id=vita_user.patId) session.add(user) await session.commit() await session.refresh(user) diff --git a/src/apps/tmk/__init__.py b/src/apps/tmk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/tmk/v1/__init__.py b/src/apps/tmk/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/tmk/v1/router.py b/src/apps/tmk/v1/router.py new file mode 100644 index 0000000..e13661e --- /dev/null +++ b/src/apps/tmk/v1/router.py @@ -0,0 +1,41 @@ +from json import dumps +from logging import getLogger +from typing import Annotated + +from fastapi import APIRouter, Body +from sqlmodel import select + +from apps.users.models import User +from clients import clients as c +from database import AsyncSessionDep +from shared.exceptions import UnknownException +from shared.redis import client as cache + +logger = getLogger(__name__) +router = APIRouter( + prefix='/tmk', + tags=[ + 'TMK', + ], +) + + +@router.post('/update') +async def update(session: AsyncSessionDep, tmk_id: Annotated[str, Body()]): + info = await c.tmk_api.getInfo(tmk_id) + snils = info.patient_snils + + try: + patient = await c.vitacore_api.findBySnils(snils) + except UnknownException: + return + + user_stmt = select(User).where(User.vita_id == patient.patId).limit(1) + user = await session.scalar(user_stmt) + + if user is None: + return + + key = f'tmk:{user.id}:{tmk_id}' + value = {'id': info.id, 'status': info.tmk_status_name, 'is_read': False} + await cache.set(key, dumps(value)) diff --git a/src/apps/users/v1/router.py b/src/apps/users/v1/router.py index 6c3a54b..6470020 100644 --- a/src/apps/users/v1/router.py +++ b/src/apps/users/v1/router.py @@ -4,6 +4,7 @@ from logging import getLogger from typing import Annotated from fastapi import APIRouter, Body, Depends, UploadFile, status +from orjson import loads from apps.tdn.auth import token from apps.users.auth import login @@ -387,22 +388,31 @@ async def delete_account(user: Annotated[User, Depends(login)]): @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=[ - { - 'id': 1, - 'title': 'Заголовок уведомления 1', - 'description': 'Описание уведомления', - }, - { - 'id': 2, - 'title': 'Заголовок уведомления 2', - 'description': 'Описание уведомления', - }, - { - 'id': 3, - 'title': 'Заголовок уведомления 3', - 'description': 'Описание уведомления', - }, - ] + 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)) diff --git a/src/apps/users/v1/schema.py b/src/apps/users/v1/schema.py index 103bae7..1e518b2 100644 --- a/src/apps/users/v1/schema.py +++ b/src/apps/users/v1/schema.py @@ -1,11 +1,11 @@ from typing import TypedDict - -class Notification(TypedDict): - id: int - title: str - description: str +from pydantic import BaseModel class Notifications(TypedDict): - notifications: list[Notification] + notifications: list[dict[str, str | bool]] + + +class Complaints(BaseModel): + complaints: str diff --git a/src/apps/vitacore/v1/router.py b/src/apps/vitacore/v1/router.py index 06d9996..e416411 100644 --- a/src/apps/vitacore/v1/router.py +++ b/src/apps/vitacore/v1/router.py @@ -2,6 +2,10 @@ from logging import getLogger from fastapi import APIRouter +from shared.redis import client as cache + +from . import schema as s + logger = getLogger(__name__) router = APIRouter( prefix='/vitacore', @@ -12,5 +16,14 @@ router = APIRouter( @router.post('/hospComplaint') -async def callback(): - pass +async def callback(complaint: s.HospComplaint): + value = await cache.get(f'complaint:{complaint.patID}') + value = value.decode() if value else '' + + return s.ComplaintData( + patId=complaint.patID, + complaints=( + f'{value} | {complaint.eventId} | {complaint.datetime} | ' + f'{complaint.MO_id}' + ), + ) diff --git a/src/apps/vitacore/v1/schema.py b/src/apps/vitacore/v1/schema.py new file mode 100644 index 0000000..8366a4a --- /dev/null +++ b/src/apps/vitacore/v1/schema.py @@ -0,0 +1,16 @@ +from datetime import datetime +from typing import TypedDict + +from pydantic import BaseModel + + +class HospComplaint(BaseModel): + patID: str + eventId: str + datetime: datetime + MO_id: str + + +class ComplaintData(TypedDict): + patId: str + complaints: str diff --git a/src/clients/tmk/api.py b/src/clients/tmk/api.py index 05d1557..3d36806 100644 --- a/src/clients/tmk/api.py +++ b/src/clients/tmk/api.py @@ -93,3 +93,19 @@ class TMK_API(AsyncClient): case _: self.logger.error(req.json()) raise e.UnknownException + + async def getInfo(self, guid: str): + token = await self.get_token() + req = await self.get( + '/getTMKInfo', + headers={'Authorization': f'Bearer {token}'}, + params={'guid': guid}, + ) + + match req.status_code: + case st.HTTP_200_OK: + return s.QueueModel.model_validate(req.json()) + + case _: + self.logger.error(req.json()) + raise e.UnknownException diff --git a/src/clients/vitacore/api.py b/src/clients/vitacore/api.py index 3736a3b..9e756b1 100644 --- a/src/clients/vitacore/api.py +++ b/src/clients/vitacore/api.py @@ -58,27 +58,28 @@ class VITACORE_API(AsyncClient): raise e.UnknownException async def findBySnils(self, snils: str): - data = await self.get_cache(f'vitacore_findBySnils:{snils}') + data = await self.get_cache(f'vitacore_findBySnils2:{snils}') if data: - return s.PatientsModel.model_validate(data) + return s.PatientModel.model_validate(data) token = await self.get_token() req = await self.get( - '/findBySnils', + '/findBySnils2', params={'snils': snils}, headers={'Authorization': f'Bearer {token}'}, ) match req.status_code: case st.HTTP_200_OK: - model = s.PatientsModel.model_validate(req.json()) + model = s.PatientModel.model_validate(req.json()) await self.set_cache( - f'vitacore_findBySnils:{snils}', + f'vitacore_findBySnils2:{snils}', model.model_dump_json(), 14400, ) return model + case _: self.logger.error(req.json()) raise e.UnknownException @@ -177,10 +178,7 @@ class VITACORE_API(AsyncClient): async def getEntries(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' req = await self.get( @@ -204,11 +202,9 @@ class VITACORE_API(AsyncClient): async def getVaccsReport(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getVaccsReport', params={'patId': patId}, @@ -221,7 +217,11 @@ class VITACORE_API(AsyncClient): case st.HTTP_206_PARTIAL_CONTENT: error = s.ErrorModel.model_validate(req.json()) - if error.error == 'Не найдены записи по указанному patId': + if ( + error.error == 'Не найдены записи по указанному patId' + or error.error + == 'Не найдены вакцинации по данному пациенту' + ): return s.VaccsReportModel(content='') case _: self.logger.error(req.json()) @@ -242,11 +242,9 @@ class VITACORE_API(AsyncClient): async def getRoutesList(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getRoutesList', params={'patId': patId}, @@ -273,11 +271,9 @@ class VITACORE_API(AsyncClient): raise e.UnknownException async def getHospExaminations(self, patId: str, examId: str | None = None): - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + token = await self.get_token() req = await self.get( '/getHospExaminations', @@ -306,11 +302,9 @@ class VITACORE_API(AsyncClient): async def getCurrHosp(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getCurrHosp', params={'patId': patId}, @@ -335,11 +329,9 @@ class VITACORE_API(AsyncClient): async def getHosps(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getHosps', params={'patId': patId}, @@ -367,11 +359,9 @@ class VITACORE_API(AsyncClient): async def getHospRecommendations(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getHospRecommendations', params={'patId': patId}, @@ -401,11 +391,9 @@ class VITACORE_API(AsyncClient): async def getHospRoutes(self, patId: str): token = await self.get_token() - if ( - patId == 'a72d18cf-c152-4b9e-b8be-313234b87400' - or patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd' - ): + if patId == '9a4d4b06-5928-4101-b95e-e5ba03a1abfd': patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e' + req = await self.get( '/getHospRoutes', params={'patId': patId}, diff --git a/src/clients/vitacore/schema.py b/src/clients/vitacore/schema.py index 35dc413..2fff5bf 100644 --- a/src/clients/vitacore/schema.py +++ b/src/clients/vitacore/schema.py @@ -1,14 +1,19 @@ from datetime import datetime -from pydantic import BaseModel, Field, field_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator class ErrorModel(BaseModel): - error: str = Field(title='Текст ошибки') + model_config = ConfigDict( + validate_by_alias=True, + validate_by_name=True, + ) + + error: str = Field(title='Текст ошибки', alias='message') class PatientModel(BaseModel): - id: str = Field( + patId: str = Field( title='Идентификатор пациента', examples=['b62e9f22-a871-4c52-96d6-559c707a716d'], ) @@ -18,18 +23,6 @@ class PatientModel(BaseModel): middleName: str = Field(title='Отчество', examples=['Пациентович']) birthDate: datetime = Field(title='Дата рождения', examples=['2024-10-16']) gender: str = Field(title='Пол', examples=['М']) - docType: str = Field(title='Тип документа', examples=['Паспорт РФ']) - docSer: str = Field(title='Серия документа', examples=['12 34']) - docNum: str = Field(title='Номер документа', examples=['999999']) - polNum: str = Field(title='Номер полиса', examples=['999999']) - address1: str = Field( - title='Адрес проживания', - examples=['г. Москва, ул. Пушкина, д. 1'], - ) - - -class PatientsModel(BaseModel): - patients: list[PatientModel] class TrustedPersonModel(BaseModel): @@ -215,7 +208,9 @@ class WorkerModel(BaseModel): firstName: str = Field(title='Имя', examples=['Владимир']) lastName: str = Field(title='Фамилия', examples=['Камашев']) middleName: str = Field(title='Отчество', examples=['Михайлович']) - birthDate: datetime = Field(title='Дата рождения', examples=['30.05.1961']) + birthDate: datetime | None = Field( + title='Дата рождения', examples=['30.05.1961'], default=None + ) positions: list[WorkersPositionModel] @@ -411,9 +406,10 @@ class ExaminationModel(BaseModel): 'style="BORDER-TOP: #ffffff 1px..... ' ], ) - Recommendation: str = Field( + Recommendation: str | None = Field( title='Идентификатор результата исследования', examples=['рекомендации 1 тест'], + default=None, ) SEMDs: list[SEMDModel] | None = Field(title='Список СЭМД', default=None) @@ -688,6 +684,10 @@ class PatientFLGModel(BaseModel): title='Контингент (флюорография)', examples=['Неорганизованное население'], ) + PrgDecision: str | None = Field( + title='Решение (флюорография)', + examples=['Требует дообследования'], + ) class DiagResultFileModel(BaseModel): diff --git a/src/core/routers/v1.py b/src/core/routers/v1.py index 9526b8d..9c69326 100644 --- a/src/core/routers/v1.py +++ b/src/core/routers/v1.py @@ -2,6 +2,7 @@ from fastapi import APIRouter, HTTPException from apps.esia.v1.router import router as esia_router from apps.remd.v1.router import router as remd_router +from apps.tmk.v1.router import router as tmk_router from apps.users.v1.router import router as users_router from apps.vitacore.v1.router import router as vitacore_router @@ -13,6 +14,7 @@ router.include_router(esia_router) router.include_router(users_router) router.include_router(remd_router) router.include_router(vitacore_router) +router.include_router(tmk_router) openapi_schema = get_openapi_schema(router) swagger_ui_html = get_swagger_html(router)