diff --git a/.dockerignore b/.dockerignore
index a1cd5c7..ce18b20 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -20,3 +20,6 @@ uv.lock
# Container
container
+
+# Postgres
+postgres
diff --git a/.gitignore b/.gitignore
index a1cd5c7..ce18b20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@ uv.lock
# Container
container
+
+# Postgres
+postgres
diff --git a/docker-compose.yml b/docker-compose.yml
index 7c548d4..5471dec 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -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:
diff --git a/pyproject.toml b/pyproject.toml
index 4b9fc3b..0eb998a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"
diff --git a/src/apps/esia/scopes.py b/src/apps/esia/scopes.py
index 0df0afa..f8408fc 100644
--- a/src/apps/esia/scopes.py
+++ b/src/apps/esia/scopes.py
@@ -1,10 +1,10 @@
SCOPES = [
'openid',
'fullname',
- 'email',
- 'birthdate',
- 'gender',
- 'snils',
- 'id_doc',
- 'mobile',
+ # 'email',
+ # 'birthdate',
+ # 'gender',
+ # 'snils',
+ # 'id_doc',
+ # 'mobile',
]
diff --git a/src/apps/esia/v1/router.py b/src/apps/esia/v1/router.py
index eca32f1..9187455 100644
--- a/src/apps/esia/v1/router.py
+++ b/src/apps/esia/v1/router.py
@@ -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)
diff --git a/src/apps/users/auth.py b/src/apps/users/auth.py
index 436b07e..fa8d4bc 100644
--- a/src/apps/users/auth.py
+++ b/src/apps/users/auth.py
@@ -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
diff --git a/src/apps/users/models.py b/src/apps/users/models.py
new file mode 100644
index 0000000..6c2beac
--- /dev/null
+++ b/src/apps/users/models.py
@@ -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)
diff --git a/src/apps/users/v1/router.py b/src/apps/users/v1/router.py
index b28087a..e1b5c4e 100644
--- a/src/apps/users/v1/router.py
+++ b/src/apps/users/v1/router.py
@@ -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,16 @@ 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.DepartmentModel]
+ )
async def get_departments():
"""
Get list of departments.
@@ -38,7 +39,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 +49,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 +57,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 +81,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 +178,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 +195,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 +208,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': 'врач-терапевт',
+ }
+ ]
diff --git a/src/clients/__init__.py b/src/clients/__init__.py
index 653e46a..3e4bd0e 100644
--- a/src/clients/__init__.py
+++ b/src/clients/__init__.py
@@ -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()
diff --git a/src/clients/aemd/__init__.py b/src/clients/aemd/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/clients/aemd/api.py b/src/clients/aemd/api.py
new file mode 100644
index 0000000..1603cdb
--- /dev/null
+++ b/src/clients/aemd/api.py
@@ -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 (
+ ''
+ ''
+ ''
+ f'{endpoint}'
+ ''
+ ''
+ f'{settings.AEMD_TOKEN}'
+ ''
+ ''
+ 'http://gist-sdw.ezdrav.ru:8708/EMDAService'
+ ''
+ ''
+ f'{body}'
+ ''
+ ''
+ )
+
+ async def searchRegistryItem(self, patient_snils: str):
+ envelope = self.get_envelope(
+ 'searchRegistryItem',
+ f'{patient_snils}',
+ )
+ req = await self.post('/', content=envelope)
+
+ return req.text
diff --git a/src/clients/esia/schema.py b/src/clients/esia/schema.py
index 904b7cd..8f3a680 100644
--- a/src/clients/esia/schema.py
+++ b/src/clients/esia/schema.py
@@ -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
diff --git a/src/clients/vitacore/api.py b/src/clients/vitacore/api.py
index 820b38a..e527d7c 100644
--- a/src/clients/vitacore/api.py
+++ b/src/clients/vitacore/api.py
@@ -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:
diff --git a/src/clients/vitacore/schema.py b/src/clients/vitacore/schema.py
index cd96139..177d8ac 100644
--- a/src/clients/vitacore/schema.py
+++ b/src/clients/vitacore/schema.py
@@ -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=['Неорганизованное население'],
)
diff --git a/src/core/config.py b/src/core/config.py
index 1c5bb6b..02f4a7d 100644
--- a/src/core/config.py
+++ b/src/core/config.py
@@ -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')
diff --git a/src/core/main.py b/src/core/main.py
index 2b0dd49..38fb9c5 100644
--- a/src/core/main.py
+++ b/src/core/main.py
@@ -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,
)
diff --git a/src/migrations/env.py b/src/migrations/env.py
index 00d508f..b79b300 100644
--- a/src/migrations/env.py
+++ b/src/migrations/env.py
@@ -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
diff --git a/src/migrations/script.py.mako b/src/migrations/script.py.mako
index 480b130..045a768 100644
--- a/src/migrations/script.py.mako
+++ b/src/migrations/script.py.mako
@@ -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"}
diff --git a/src/migrations/versions/2025.10.09_17-42-25_a74bcd05c7b8.py b/src/migrations/versions/2025.10.09_17-42-25_a74bcd05c7b8.py
new file mode 100644
index 0000000..cc3f7b0
--- /dev/null
+++ b/src/migrations/versions/2025.10.09_17-42-25_a74bcd05c7b8.py
@@ -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 ###
diff --git a/src/migrations/versions/__init__.py b/src/migrations/versions/__init__.py
new file mode 100644
index 0000000..e69de29