Compare commits

...

2 Commits

Author SHA1 Message Date
db48b14ac5 Патч
Some checks failed
Build And Push / publish (push) Failing after 6m24s
2025-10-16 16:43:50 +03:00
a4239a0c52 Патч 2025-10-16 16:43:18 +03:00
21 changed files with 525 additions and 140 deletions

View File

@ -20,3 +20,6 @@ uv.lock
# Container
container
# Postgres
postgres

3
.gitignore vendored
View File

@ -20,3 +20,6 @@ uv.lock
# Container
container
# Postgres
postgres

View File

@ -8,13 +8,14 @@ x-app-common: &app-common
restart: unless-stopped
stop_signal: SIGINT
env_file:
- .env
- .test.env
environment:
DATABASE_URL: "postgresql://postgres:example@db:5432/postgres"
REDIS_URL: "redis://valkey:6379/0"
volumes:
- "./container/certt.cer:/var/opt/cprocsp/keys/cert.cer"
- "./container/cont:/app/cont"
# - "./container/certt.cer:/var/opt/cprocsp/keys/cert.cer"
- "./container/test.cer:/var/opt/cprocsp/keys/cert.cer"
- "./container/cont2:/app/cont"
services:
valkey:
@ -28,6 +29,21 @@ services:
timeout: 10s
retries: 5
db:
image: postgres:17.2-alpine
restart: unless-stopped
ports:
- ${POSTGRES_PORT:-5432}:5432
environment:
POSTGRES_PASSWORD: example
volumes:
- "${POSTGRES_DATA:-./postgres}:/var/lib/postgresql/data/"
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 5s
timeout: 10s
retries: 5
web:
<<: *app-common
ports:

View File

@ -50,9 +50,9 @@ _lint = "pre-commit run --all-files"
lint = ["_git", "_lint"]
check = "uv pip ls --outdated"
run = "uv run --directory ./src/ server.py"
manage = "uv run --directory ./src/ manage.py"
migrate = "uv run --directory ./src/ alembic revision --autogenerate"
run = "uv run --env-file ../.env --directory ./src/ server.py"
manage = "uv run --env-file ../.env --directory ./src/ manage.py"
migrate = "uv run --env-file ../.env --directory ./src/ alembic revision --autogenerate"
[tool.uv]
required-version = ">=0.7.0"

View File

@ -1,10 +1,10 @@
SCOPES = [
'openid',
'fullname',
'email',
'birthdate',
'gender',
'snils',
'id_doc',
'mobile',
# 'email',
# 'birthdate',
# 'gender',
# 'snils',
# 'id_doc',
# 'mobile',
]

View File

@ -2,9 +2,12 @@ import secrets
from logging import getLogger
from fastapi import APIRouter
from sqlmodel import select
from apps.esia.sign import get_url
from apps.users.models import User
from clients import clients as c
from database import AsyncSessionDep
from shared import exceptions as e
from shared.redis import client as cache
@ -26,7 +29,7 @@ async def login():
@router.post('/callback')
async def callback(code: str):
async def callback(session: AsyncSessionDep, code: str):
token = None
for i in range(3):
try:
@ -42,9 +45,34 @@ async def callback(code: str):
if token is None:
raise e.BadRequestException
await c.esia_api.get_user_info(token.access_token, token.id_token)
esia_user = await c.esia_api.get_user_info(
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]
existing_user_stmt = (
select(User).where(User.vita_id == vita_user.id).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)
session.add(user)
await session.commit()
await session.refresh(user)
else:
user = existing_user
access_token = secrets.token_urlsafe(32)
cache.set(access_token, access_token)
cache.set(access_token, f'user:{user.id}')
return s.Token(access_token=access_token)

View File

@ -2,7 +2,10 @@ from typing import Annotated
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlmodel import select
from apps.users.models import User
from database import AsyncSessionDep
from shared import exceptions as e
from shared.redis import client as cache
@ -10,11 +13,20 @@ BEARER = HTTPBearer()
async def login(
session: AsyncSessionDep,
credentials: Annotated[HTTPAuthorizationCredentials, Depends(BEARER)],
):
is_exist = cache.get(credentials.credentials)
user = cache.get(credentials.credentials)
if is_exist is None:
if user is None:
raise e.UnauthorizedException
return is_exist.decode()
_, user_id = user.decode().split(':')
user_model_stmt = select(User).where(User.id == int(user_id))
user_model = (await session.execute(user_model_stmt)).scalar_one_or_none()
if user_model is None:
raise e.UnauthorizedException
return user_model

8
src/apps/users/models.py Normal file
View File

@ -0,0 +1,8 @@
from sqlmodel import Field, SQLModel
class User(SQLModel, table=True):
__tablename__: str = 'users' # type: ignore
id: int = Field(default=None, primary_key=True)
vita_id: str = Field(unique=True)

View File

@ -6,6 +6,7 @@ from typing import Annotated
from fastapi import APIRouter, Body, Depends, status
from apps.users.auth import login
from apps.users.models import User
from clients import clients as c
from clients.vitacore import schema as s
from shared.redis import client as cache
@ -20,16 +21,17 @@ router = APIRouter(
@router.get('/getProfile', response_model=s.ProfileModel)
async def get_profile():
async def get_profile(user: Annotated[User, Depends(login)]):
"""
Get profile of user by id.
Get profile of user.
"""
return await c.vitacore_api.getProfile(
'b62e9f22-a871-4c52-96d6-559c707a716d'
)
return await c.vitacore_api.getProfile(user.vita_id)
@router.get('/getDepartments', response_model=list[s.DepartmentModel])
@router.get(
'/getDepartments',
response_model=list[s.OrganizationsModel]
)
async def get_departments():
"""
Get list of departments.
@ -38,7 +40,9 @@ async def get_departments():
@router.get('/getWorkers', response_model=s.WorkersModel)
async def get_workers(departmentId: str):
async def get_workers(
user: Annotated[User, Depends(login)], departmentId: str
):
"""
Get list of workers by department id.
"""
@ -46,7 +50,7 @@ async def get_workers(departmentId: str):
@router.get('/getSpecs', response_model=s.SpecsV021Model)
async def get_specs():
async def get_specs(user: Annotated[User, Depends(login)]):
"""
Get list of specialties.
"""
@ -54,27 +58,23 @@ async def get_specs():
@router.get('/getEntries', response_model=s.EntriesModel)
async def get_entries():
async def get_entries(user: Annotated[User, Depends(login)]):
"""
Get list of entries for user by id.
"""
return await c.vitacore_api.getEntries(
'6c7978f0-c573-4ccf-8c6e-f0cd9aceb1e1'
)
return await c.vitacore_api.getEntries(user.vita_id)
@router.get('/getVaccsReport')
async def get_vaccs_report():
async def get_vaccs_report(user: Annotated[User, Depends(login)]):
"""
Get report of vaccinations for user by id.
"""
return await c.vitacore_api.getVaccsReport(
'6fe66cae-409a-4f56-8ae9-d55d3c38569b'
)
return await c.vitacore_api.getVaccsReport(user.vita_id)
@router.get('/getMedExamDict')
async def get_med_exam_dict():
async def get_med_exam_dict(user: Annotated[User, Depends(login)]):
"""
Get medical examination dictionary.
"""
@ -82,92 +82,80 @@ async def get_med_exam_dict():
@router.get('/getRoutesList')
async def get_routes_list():
async def get_routes_list(user: Annotated[User, Depends(login)]):
"""
Get list of routes.
"""
return await c.vitacore_api.getRoutesList(
'4e6de5f7-4dc9-451b-bf0d-7a64a9b8c279'
)
return await c.vitacore_api.getRoutesList(user.vita_id)
@router.get('/getHospExaminations')
async def get_hosp_examinations():
async def get_hosp_examinations(
user: Annotated[User, Depends(login)], examId: str
):
"""
Get list of hospital examinations.
"""
return await c.vitacore_api.getHospExaminations(
'7bbdac30-9a33-4f13-9458-2c229c0c20f5',
'f22be2c9-8e68-42d6-851e-fbf4a5e8f657',
user.vita_id,
examId,
)
@router.get('/getCurrHosp')
async def get_curr_hosp():
async def get_curr_hosp(user: Annotated[User, Depends(login)]):
"""
Get current hospitalization.
"""
return await c.vitacore_api.getCurrHosp(
'b708e782-4f83-4f3b-8639-512c0c9637bf'
)
return await c.vitacore_api.getCurrHosp(user.vita_id)
@router.get('/getHosps')
async def get_hosps():
async def get_hosps(user: Annotated[User, Depends(login)]):
"""
Get list of hospitals.
"""
return await c.vitacore_api.getHosps(
'b708e782-4f83-4f3b-8639-512c0c9637bf'
)
return await c.vitacore_api.getHosps(user.vita_id)
@router.get('/getHospRecommendations')
async def get_hosp_recommendations():
async def get_hosp_recommendations(user: Annotated[User, Depends(login)]):
"""
Get list of recommended hospitals.
"""
return await c.vitacore_api.getHospRecommendations(
'b708e782-4f83-4f3b-8639-512c0c9637bf'
)
return await c.vitacore_api.getHospRecommendations(user.vita_id)
@router.get('/getHospRoutes')
async def get_hosp_routes():
async def get_hosp_routes(user: Annotated[User, Depends(login)]):
"""
Get list of recommended hospitals.
"""
return await c.vitacore_api.getHospRoutes(
'3092e1c5-e08b-4654-a027-82be90fe8a49'
)
return await c.vitacore_api.getHospRoutes(user.vita_id)
@router.get('/getDiagnosticResults')
async def get_diagnostic_results():
async def get_diagnostic_results(user: Annotated[User, Depends(login)]):
"""
Get list of diagnostic results.
"""
return await c.vitacore_api.getDiagnosticResults(
'4867cc79-9805-4ae2-98d3-2f822848635e'
)
return await c.vitacore_api.getDiagnosticResults(user.vita_id)
@router.get('/getELNs')
async def get_eln():
async def get_eln(user: Annotated[User, Depends(login)]):
"""
Get list of ELNs.
"""
return await c.vitacore_api.getELNs('d4493f1c-fcbb-4242-99e6-32328bed53b9')
return await c.vitacore_api.getELNs(user.vita_id)
@router.get('/getPatFLG')
async def get_pat_flg():
async def get_pat_flg(user: Annotated[User, Depends(login)]):
"""
Get list of ELNs.
"""
return await c.vitacore_api.getPatFLG(
'0bf2e271-e565-42a8-924e-0017bcdedecd'
)
return await c.vitacore_api.getPatFLG(user.vita_id)
# @router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)
@ -191,7 +179,7 @@ async def get_pat_flg():
@router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)
async def measurement(
user: Annotated[str, Depends(login)],
user: Annotated[User, Depends(login)],
ad: Annotated[int, Body()],
sd: Annotated[int, Body()],
pulse: Annotated[int, Body()],
@ -208,7 +196,7 @@ async def measurement(
'comment': comment,
'status': status,
}
cache_key = f'tdn:measurement:{user}:{created}'
cache_key = f'tdn:measurement:{user.id}:{created}'
cache.set(cache_key, dumps(data))
return
@ -221,28 +209,35 @@ async def measurements(
return data
@router.get('/test')
async def test_route():
return await c.aemd_api.searchRegistryItem('16247900267')
@router.get('/queue')
async def queue(user: Annotated[bool, Depends(login)]):
return {
'id': 60,
'guid': '92b3343d-1cb2-47b2-8497-a37e38b6ba24',
'tmk_date': None,
'created_at': '2025-04-02 15:21:19.890343',
'code_mo': '166502',
'mo_name': 'ГАУЗ "ГКБ№7 ИМ. М.Н.САДЫКОВА"',
'doctor_spec': '109',
'doctor_snils': None,
'patient_name': 'Иванов Петр Федорович',
'patient_birthday': '1997-03-01',
'patient_snils': '099-678-666 12',
'patient_policy': None,
'patient_phone': '+79123456789',
'patient_email': None,
'tmk_status': 1,
'tmk_status_name': 'Создана',
'tmk_cancel_reason': None,
'tmk_cancel_reason_name': None,
'vks_doctor_link': None,
'vks_patient_link': None,
'doctor_spec_name': 'врач-терапевт',
}
return [
{
'id': 60,
'guid': '92b3343d-1cb2-47b2-8497-a37e38b6ba24',
'tmk_date': None,
'created_at': '2025-04-02 15:21:19.890343',
'code_mo': '166502',
'mo_name': 'ГАУЗ "ГКБ№7 ИМ. М.Н.САДЫКОВА"',
'doctor_spec': '109',
'doctor_snils': None,
'patient_name': 'Иванов Петр Федорович',
'patient_birthday': '1997-03-01',
'patient_snils': '099-678-666 12',
'patient_policy': None,
'patient_phone': '+79123456789',
'patient_email': None,
'tmk_status': 1,
'tmk_status_name': 'Создана',
'tmk_cancel_reason': None,
'tmk_cancel_reason_name': None,
'vks_doctor_link': None,
'vks_patient_link': None,
'doctor_spec_name': 'врач-терапевт',
}
]

View File

@ -1,3 +1,4 @@
from .aemd.api import AEMD_API
from .esia.api import ESIA_API
from .tdn.api import TDN_API
from .vitacore.api import VITACORE_API
@ -7,6 +8,7 @@ class ClientsObject:
_esia_api = None
_vitacore_api = None
_tdn_api = None
_aemd_api = None
@property
def esia_api(self):
@ -29,5 +31,12 @@ class ClientsObject:
return self._tdn_api
@property
def aemd_api(self):
if not self._aemd_api:
self._aemd_api = AEMD_API()
return self._aemd_api
clients = ClientsObject()

View File

46
src/clients/aemd/api.py Normal file
View File

@ -0,0 +1,46 @@
from logging import getLogger
from httpx import AsyncClient
from core.config import settings
class AEMD_API(AsyncClient):
def __init__(self):
self.logger = getLogger(__name__)
super().__init__(
base_url=settings.AEMD_BASE_URL,
headers={'Content-Type': 'application/soap+xml'},
)
def get_envelope(self, endpoint: str, body: str):
return (
'<?xml version="1.0" encoding="utf-8"?>'
'<s:Envelope xmlns:s= "http://www.w3.org/2003/05/soap-envelope" '
'xmlns:a= "http://www.w3.org/2005/08/addressing">'
'<s:Header>'
f'<a:Action s:mustUnderstand="1">{endpoint}</a:Action>'
'<h:transportHeader xmlns:h="http://egisz.rosminzdrav.ru" '
'xmlns="http://egisz.rosminzdrav.ru" '
'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
'xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance">'
'<authInfo>'
f'<clientEntityId>{settings.AEMD_TOKEN}</clientEntityId>'
'</authInfo>'
'</h:transportHeader>'
'<a:To s:mustUnderstand= "1">http://gist-sdw.ezdrav.ru:8708/EMDAService</a:To>'
'</s:Header>'
'<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">'
f'{body}'
'</s:Body>'
'</s:Envelope>'
)
async def searchRegistryItem(self, patient_snils: str):
envelope = self.get_envelope(
'searchRegistryItem',
f'<searchRegistryItemRequest xmlns="http://egisz.rosminzdrav.ru/iehr/emdr/service/"><patientSnils>{patient_snils}</patientSnils></searchRegistryItemRequest>',
)
req = await self.post('/', content=envelope)
return req.text

View File

@ -33,7 +33,7 @@ class IDTokenModel(BaseModel):
exp: int
iat: int
iss: str
acr: IDTokenACRModel
# acr: IDTokenACRModel
urn_esia_amd: str = Field(alias='urn:esia:amd')
urn_esia_sid: str = Field(alias='urn:esia:sid')
urn_esia_sbj: IDTokenSBJModel = Field(alias='urn:esia:sbj')
@ -44,10 +44,10 @@ class UserInfoModel(BaseModel):
firstName: str
lastName: str
middleName: str
birthDate: str
gender: str
# birthDate: str
# gender: str
trusted: bool
citizenship: str
# citizenship: str
snils: str
inn: int
updatedOn: int

View File

@ -1,10 +1,12 @@
from datetime import UTC, datetime
from logging import getLogger
from fastapi import status as st
from httpx import AsyncClient
from httpx import AsyncClient, BasicAuth
from core.config import settings
from shared import exceptions as e
from shared.redis import client as cache
from . import schema as s
@ -14,11 +16,56 @@ class VITACORE_API(AsyncClient):
self.logger = getLogger(__name__)
super().__init__(base_url=settings.VITACORE_BASE_URL)
async def get_token(self):
token = cache.get('vitacore_token')
if token is None:
token = await self.login()
cache.set('vitacore_token', token, 10800)
else:
token = token.decode()
return token
async def login(self):
req = await self.post(
'/auth',
auth=BasicAuth(
settings.VITACORE_USERNAME, settings.VITACORE_PASSWORD
),
)
match req.status_code:
case st.HTTP_200_OK:
return req.text
case _:
self.logger.error(req.text)
raise e.UnknownException
async def findBySnils(self, snils: str):
return
token = await self.get_token()
req = await self.get(
'/findBySnils',
params={'snils': snils},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.PatientsModel.model_validate(req.json())
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getProfile(self, patId: str):
req = await self.get('/getProfile', params={'patId': patId})
token = await self.get_token()
req = await self.get(
'/getProfile',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
@ -28,18 +75,24 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getDepartments(self):
req = await self.get('/getDepartments')
token = await self.get_token()
req = await self.get(
'/getDepartments', headers={'Authorization': f'Bearer {token}'}
)
match req.status_code:
case st.HTTP_200_OK:
return s.OrganizationsModel.model_validate(req.json())
case _:
self.logger.error(req.json())
self.logger.error(req.text)
raise e.UnknownException
async def getWorkers(self, departmentId: str):
token = await self.get_token()
req = await self.get(
'/getWorkers', params={'departmentId': departmentId}
'/getWorkers',
params={'departmentId': departmentId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
@ -50,7 +103,10 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getSpecsV021(self):
req = await self.get('/getSpecsV021')
token = await self.get_token()
req = await self.get(
'/getSpecsV021', headers={'Authorization': f'Bearer {token}'}
)
match req.status_code:
case st.HTTP_200_OK:
@ -60,7 +116,13 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getEntries(self, patId: str):
req = await self.get('/getEntries', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getEntries',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
@ -76,17 +138,31 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getVaccsReport(self, patId: str):
req = await self.get('/getVaccsReport', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getVaccsReport',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.VaccsReportModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if error.error == 'Не найдены записи по указанному patId':
return s.VaccsReportModel(content='')
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getMedExamDict(self):
req = await self.get('/getMedExamDict')
token = await self.get_token()
req = await self.get(
'/getMedExamDict', headers={'Authorization': f'Bearer {token}'}
)
match req.status_code:
case st.HTTP_200_OK:
@ -96,19 +172,39 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getRoutesList(self, patId: str):
req = await self.get('/getRoutesList', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getRoutesList',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.RoutesListModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if error.error == 'Не найдены случаи по указанному patId':
return s.RoutesListModel(
EventID='none',
EventDate=datetime.now(UTC),
LpuName='fakeName',
Routes=[],
)
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getHospExaminations(self, patId: str, examId: str):
token = await self.get_token()
req = await self.get(
'/getHospExaminations',
params={'patId': patId, 'examId': examId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
@ -119,49 +215,123 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getCurrHosp(self, patId: str):
req = await self.get('/getCurrHosp', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getCurrHosp',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.CurHospitalizationsModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if error.error == 'Пациент не госпитализирован!':
return s.CurHospitalizationsModel(
Hospitalizations=[],
)
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getHosps(self, patId: str):
req = await self.get('/getHosps', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getHosps',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.HospitalizationsModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if (
error.error
== 'Не найдены госпитализации по указанному patId'
):
return s.HospitalizationsModel(
Hospitalizations=[],
)
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getHospRecommendations(self, patId: str):
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getHospRecommendations', params={'patId': patId}
'/getHospRecommendations',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.HospRecommendationsModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if (
error.error
== 'Не найдены госпитализации по указанному patId'
):
return s.HospRecommendationsModel(
EventID='none',
EventDate=datetime.now(UTC),
Recommendations=[],
)
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getHospRoutes(self, patId: str):
req = await self.get('/getHospRoutes', params={'patId': patId})
token = await self.get_token()
patId = 'b66a85f1-4aaa-4db8-942a-2de44341824e'
req = await self.get(
'/getHospRoutes',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
return s.HospRoutesModel.model_validate(req.json())
case st.HTTP_206_PARTIAL_CONTENT:
error = s.ErrorModel.model_validate(req.json())
if error.error == 'Пациент не госпитализирован!':
return s.HospRoutesModel(
EventID='none',
EventDate=datetime.now(UTC),
RoutesToDoctor=[],
RoutesToDiagnostic=[],
)
case _:
self.logger.error(req.json())
raise e.UnknownException
async def getDiagnosticResults(self, patId: str):
req = await self.get('/getDiagnosticResults', params={'patId': patId})
token = await self.get_token()
req = await self.get(
'/getDiagnosticResults',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
@ -171,7 +341,12 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getELNs(self, patId: str):
req = await self.get('/getELNs', params={'patId': patId})
token = await self.get_token()
req = await self.get(
'/getELNs',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:
@ -181,7 +356,12 @@ class VITACORE_API(AsyncClient):
raise e.UnknownException
async def getPatFLG(self, patId: str):
req = await self.get('/getPatFLG', params={'patId': patId})
token = await self.get_token()
req = await self.get(
'/getPatFLG',
params={'patId': patId},
headers={'Authorization': f'Bearer {token}'},
)
match req.status_code:
case st.HTTP_200_OK:

View File

@ -7,6 +7,31 @@ class ErrorModel(BaseModel):
error: str = Field(title='Текст ошибки')
class PatientModel(BaseModel):
id: str = Field(
title='Идентификатор пациента',
examples=['b62e9f22-a871-4c52-96d6-559c707a716d'],
)
SNILS: str = Field(title='СНИЛС', examples=['000-000-600 18'])
lastName: str = Field(title='Фамилия', examples=['Тестовый'])
firstName: str = Field(title='Имя', examples=['Пациент'])
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):
parentSnils: str = Field(
title='СНИЛС представителя', examples=['156-125-394 57']
@ -66,7 +91,7 @@ class ProfileModel(BaseModel):
# examples=['99'],
# )
trustedPersons: list[TrustedPersonModel] = Field(
title='Информация о представителе',
title='Информация о представителе', default=[]
)
@ -79,10 +104,10 @@ class DepartmentAddressModel(BaseModel):
title='Адрес строкой',
examples=['420097, г.Казань, ул.Заслонова, д.5'],
)
latitude: float | None = Field(
latitude: str | None = Field(
title='Широта, при наличии', examples=[55.789], default=None
)
longitude: float | None = Field(
longitude: str | None = Field(
title='Долгота, при наличии', examples=[37.789], default=None
)
@ -92,16 +117,19 @@ class DepartmentModel(BaseModel):
title='Идентификатор МО/Филиала',
examples=['a3677271-3385-4f27-a65d-c3430b7c61c2'],
)
OID: str = Field(
title='OID МО / Филиала', examples=['1.2.643.5.1.13.13.12.2.16.1084']
OID: str | None = Field(
title='OID МО / Филиала',
examples=['1.2.643.5.1.13.13.12.2.16.1084'],
default=None,
)
parentId: str | None = Field(
title='Идентификатор вышестоящего подразделения',
examples=['a3677271-3385-4f27-a65d-c3430b7c61c2'],
)
fullname: str = Field(
fullname: str | None = Field(
title='Полное наименование',
examples=['ГБУЗС "Тестовая медицинская организация"'],
default=None,
)
shortname: str = Field(
title='Краткое наименование',
@ -112,14 +140,12 @@ class DepartmentModel(BaseModel):
', для филиалов: Стационар / Поликлиника / ФАП / Амбулатория)',
examples=['Юридическое лицо'],
)
inn: str = Field(title='ИНН', examples=['0000000000'])
ogrn: str = Field(title='ОГРН', examples=['1149204047816'])
inn: str | None = Field(title='ИНН', examples=['0000000000'], default=None)
ogrn: str | None = Field(
title='ОГРН', examples=['1149204047816'], default=None
)
kpp: str | None = Field(title='КПП', examples=['0000000000'], default=None)
address: list[DepartmentAddressModel]
# code: str = Field(
# title='Региональный код или код ТФОМС',
# examples=['0000000000'],
# )
address: list[DepartmentAddressModel] | None = None
class OrganizationsModel(BaseModel):
@ -642,7 +668,7 @@ class PatientFLGModel(BaseModel):
title='Дата следующего флюорографического осмотра',
examples=['2021-09-24'],
)
PrgContingent: str = Field(
PrgContingent: str | None = Field(
title='Контингент (флюорография)',
examples=['Неорганизованное население'],
)

View File

@ -56,6 +56,14 @@ class Settings(BaseSettings):
VITACORE_BASE_URL: str = Field(
default='https://gist-cws.ezdrav.ru:8899/MP_API'
)
VITACORE_USERNAME: str = Field(default='')
VITACORE_PASSWORD: str = Field(default='')
# AEMD
AEMD_BASE_URL: str = Field(
default='http://gist-sdw.ezdrav.ru:8708/EMDAService'
)
AEMD_TOKEN: str = Field(default='')
# TDN
TDN_BASE_URL: str = Field(default='https://tdn.tatar.ru/api')

View File

@ -3,7 +3,7 @@ from logging import getLogger
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
# from database import lifespan
from database import lifespan
from middlewares import register_middlewares
from .config import settings
@ -19,7 +19,7 @@ app = FastAPI(
version=str(settings.VERSION),
openapi_url=None,
default_response_class=ORJSONResponse,
# lifespan=lifespan,
lifespan=lifespan,
docs_url=None,
redoc_url=None,
)

View File

@ -4,6 +4,7 @@ from alembic import context
from sqlalchemy import engine_from_config, pool
from sqlmodel import SQLModel
from apps.users.models import * # noqa: F403
from core.log import config as log_config
from database import db_manager

View File

@ -1,28 +1,32 @@
"""${message}
"""
${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
import sqlmodel.sql.sqltypes
from alembic import op
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
down_revision: str | None = ${repr(down_revision)}
branch_labels: str | Sequence[str] | None = ${repr(branch_labels)}
depends_on: str | Sequence[str] | None = ${repr(depends_on)}
if not sqlmodel.sql:
msg = 'Something went wrong'
raise Exception(msg)
def upgrade() -> None:
"""Upgrade schema."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Downgrade schema."""
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,46 @@
"""
empty message
Revision ID: a74bcd05c7b8
Revises:
Create Date: 2025-10-09 17:42:25.347412
"""
from collections.abc import Sequence
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from alembic import op
# revision identifiers, used by Alembic.
revision: str = 'a74bcd05c7b8'
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
if not sqlmodel.sql:
msg = 'Something went wrong'
raise Exception(msg)
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column(
'vita_id', sqlmodel.sql.sqltypes.AutoString(), nullable=False
),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('id'),
sa.UniqueConstraint('vita_id'),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('users')
# ### end Alembic commands ###

View File