Compare commits
2 Commits
cb3b138241
...
db48b14ac5
| Author | SHA1 | Date | |
|---|---|---|---|
| db48b14ac5 | |||
| a4239a0c52 |
@ -20,3 +20,6 @@ uv.lock
|
||||
|
||||
# Container
|
||||
container
|
||||
|
||||
# Postgres
|
||||
postgres
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,3 +20,6 @@ uv.lock
|
||||
|
||||
# Container
|
||||
container
|
||||
|
||||
# Postgres
|
||||
postgres
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
SCOPES = [
|
||||
'openid',
|
||||
'fullname',
|
||||
'email',
|
||||
'birthdate',
|
||||
'gender',
|
||||
'snils',
|
||||
'id_doc',
|
||||
'mobile',
|
||||
# 'email',
|
||||
# 'birthdate',
|
||||
# 'gender',
|
||||
# 'snils',
|
||||
# 'id_doc',
|
||||
# 'mobile',
|
||||
]
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
8
src/apps/users/models.py
Normal 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)
|
||||
@ -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.OrganizationsModel]
|
||||
)
|
||||
|
||||
|
||||
@router.get('/getDepartments', response_model=list[s.DepartmentModel])
|
||||
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,9 +209,15 @@ 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 {
|
||||
return [
|
||||
{
|
||||
'id': 60,
|
||||
'guid': '92b3343d-1cb2-47b2-8497-a37e38b6ba24',
|
||||
'tmk_date': None,
|
||||
@ -246,3 +240,4 @@ async def queue(user: Annotated[bool, Depends(login)]):
|
||||
'vks_patient_link': None,
|
||||
'doctor_spec_name': 'врач-терапевт',
|
||||
}
|
||||
]
|
||||
|
||||
@ -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()
|
||||
|
||||
0
src/clients/aemd/__init__.py
Normal file
0
src/clients/aemd/__init__.py
Normal file
46
src/clients/aemd/api.py
Normal file
46
src/clients/aemd/api.py
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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=['Неорганизованное население'],
|
||||
)
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"}
|
||||
|
||||
46
src/migrations/versions/2025.10.09_17-42-25_a74bcd05c7b8.py
Normal file
46
src/migrations/versions/2025.10.09_17-42-25_a74bcd05c7b8.py
Normal 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 ###
|
||||
0
src/migrations/versions/__init__.py
Normal file
0
src/migrations/versions/__init__.py
Normal file
Reference in New Issue
Block a user