This commit is contained in:
10
Dockerfile
10
Dockerfile
@ -67,6 +67,16 @@ FROM builder-base AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ────────────────────── WEASYPRINT SYSTEM DEPENDENCIES ──────────────────────
|
||||
# These are the exact packages required for WeasyPrint to work on Debian Bookworm
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gcc libpq-dev \
|
||||
libcairo2 libcairo2-dev libpangocairo-1.0-0 weasyprint && \
|
||||
apt clean && \
|
||||
rm -rf /var/cache/apt/*
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
RUN chown -R appuser:appuser /app
|
||||
|
||||
COPY --from=python-base /app/.python /app/.python
|
||||
|
||||
@ -31,6 +31,8 @@ dependencies = [
|
||||
"pyjwt==2.10.1",
|
||||
"xmltodict==1.0.2",
|
||||
"python-multipart==0.0.20",
|
||||
"weasyprint==66.0",
|
||||
"lxml==6.0.2; sys_platform != 'win32'",
|
||||
# CLI
|
||||
"typer-slim==0.16.1",
|
||||
]
|
||||
|
||||
33
src/apps/remd/dependencies.py
Normal file
33
src/apps/remd/dependencies.py
Normal file
@ -0,0 +1,33 @@
|
||||
from anyio import Path
|
||||
from fastapi import HTTPException
|
||||
from lxml import etree # type: ignore
|
||||
from weasyprint import HTML # type: ignore
|
||||
|
||||
|
||||
async def get_parsable_ids():
|
||||
parsable_files_dir = await Path('/app/apps/remd/xls').resolve()
|
||||
parsable_ids: list[str] = [
|
||||
file.name.split('.')[0]
|
||||
async for file in parsable_files_dir.iterdir()
|
||||
if await file.is_file()
|
||||
]
|
||||
|
||||
return parsable_ids
|
||||
|
||||
|
||||
async def convert_aemd_to_pdf(xml_str: bytes, docKind: str):
|
||||
xml = etree.fromstring(xml_str) # type: ignore
|
||||
xsl = etree.parse(f'/app/apps/remd/xls/{docKind}.xsl') # type: ignore
|
||||
transform = etree.XSLT(xsl) # type: ignore
|
||||
|
||||
html = transform(xml) # type: ignore
|
||||
html_str = etree.tostring( # type: ignore
|
||||
html, pretty_print=True, encoding='unicode', method='html'
|
||||
)
|
||||
|
||||
pdf = HTML(string=html_str).write_pdf() # type: ignore
|
||||
|
||||
if not pdf:
|
||||
raise HTTPException(status_code=500, detail='PDF not generated')
|
||||
|
||||
return pdf
|
||||
1221
src/apps/remd/xls/110.xsl
Normal file
1221
src/apps/remd/xls/110.xsl
Normal file
File diff suppressed because it is too large
Load Diff
1928
src/apps/remd/xls/111.xsl
Normal file
1928
src/apps/remd/xls/111.xsl
Normal file
File diff suppressed because it is too large
Load Diff
1374
src/apps/remd/xls/119.xsl
Normal file
1374
src/apps/remd/xls/119.xsl
Normal file
File diff suppressed because it is too large
Load Diff
1161
src/apps/remd/xls/122.xsl
Normal file
1161
src/apps/remd/xls/122.xsl
Normal file
File diff suppressed because it is too large
Load Diff
2019
src/apps/remd/xls/148.xsl
Normal file
2019
src/apps/remd/xls/148.xsl
Normal file
File diff suppressed because it is too large
Load Diff
1329
src/apps/remd/xls/75.xsl
Normal file
1329
src/apps/remd/xls/75.xsl
Normal file
File diff suppressed because it is too large
Load Diff
3702
src/apps/remd/xls/92.xsl
Normal file
3702
src/apps/remd/xls/92.xsl
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,5 @@
|
||||
import base64
|
||||
import io
|
||||
from datetime import UTC, datetime
|
||||
from json import dumps
|
||||
from logging import getLogger
|
||||
@ -5,8 +7,10 @@ from secrets import token_urlsafe
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, UploadFile, status
|
||||
from fastapi.responses import StreamingResponse
|
||||
from orjson import loads
|
||||
|
||||
from apps.remd.dependencies import convert_aemd_to_pdf, get_parsable_ids
|
||||
from apps.tdn.auth import token
|
||||
from apps.users.auth import login
|
||||
from apps.users.models import User
|
||||
@ -28,13 +32,13 @@ router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@cache_response(ttl=600, namespace='main')
|
||||
@router.get(
|
||||
'/getProfile',
|
||||
responses={
|
||||
status.HTTP_200_OK: {'model': vs.ProfileModel},
|
||||
},
|
||||
)
|
||||
@cache_response(ttl=600, namespace='main')
|
||||
async def get_profile(user: Annotated[User, Depends(login)]):
|
||||
"""
|
||||
Get profile of user.
|
||||
@ -42,13 +46,13 @@ async def get_profile(user: Annotated[User, Depends(login)]):
|
||||
return await c.vitacore_api.getProfile(user.vita_id)
|
||||
|
||||
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
@router.get(
|
||||
'/getDepartments',
|
||||
responses={
|
||||
status.HTTP_200_OK: {'model': vs.OrganizationsModel},
|
||||
},
|
||||
)
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
async def get_departments():
|
||||
"""
|
||||
Get list of departments.
|
||||
@ -56,10 +60,10 @@ async def get_departments():
|
||||
return await c.vitacore_api.getDepartments()
|
||||
|
||||
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
@router.get(
|
||||
'/getWorkers', responses={status.HTTP_200_OK: {'model': vs.WorkersModel}}
|
||||
)
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
async def get_workers(
|
||||
user: Annotated[User, Depends(login)], departmentId: str
|
||||
):
|
||||
@ -69,11 +73,11 @@ async def get_workers(
|
||||
return await c.vitacore_api.getWorkers(departmentId)
|
||||
|
||||
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
@router.get(
|
||||
'/getSpecs',
|
||||
responses={status.HTTP_200_OK: {'model': vs.SpecsV021Model}},
|
||||
)
|
||||
@cache_response(ttl=3600, namespace='main')
|
||||
async def get_specs(user: Annotated[User, Depends(login)]):
|
||||
"""
|
||||
Get list of specialties.
|
||||
@ -198,7 +202,10 @@ async def queue(_: Annotated[User, Depends(login)]):
|
||||
|
||||
|
||||
@router.get('/aemd')
|
||||
async def get_aemd(user: Annotated[User, Depends(login)]):
|
||||
async def get_aemd(
|
||||
user: Annotated[User, Depends(login)],
|
||||
parsable_ids: Annotated[list[str], Depends(get_parsable_ids)],
|
||||
):
|
||||
profile = await c.vitacore_api.getProfile(user.vita_id)
|
||||
snils = profile.SNILS.replace('-', '').replace(' ', '')
|
||||
docs = await c.aemd_api.searchRegistryItem(patient_snils=snils)
|
||||
@ -206,6 +213,9 @@ async def get_aemd(user: Annotated[User, Depends(login)]):
|
||||
return_items: list[s.AEMDReturnFile] = []
|
||||
|
||||
for item in items:
|
||||
if item['DocKind'] not in parsable_ids:
|
||||
continue
|
||||
|
||||
is_cached = await cache.get(f'aemd:{user.vita_id}:{item["emdrId"]}')
|
||||
|
||||
return_items.append(
|
||||
@ -232,13 +242,25 @@ async def post_aemd(user: Annotated[User, Depends(login)], emdrId: str):
|
||||
|
||||
|
||||
@router.get('/aemd/{emdrId}')
|
||||
async def get_aemd_file(user: Annotated[User, Depends(login)], emdrId: str):
|
||||
async def get_aemd_file(
|
||||
user: Annotated[User, Depends(login)], emdrId: str, docKind: str
|
||||
):
|
||||
data = await cache.get(f'aemd:{user.vita_id}:{emdrId}')
|
||||
|
||||
if not data:
|
||||
raise e.NotFoundException(status_code=404, detail='File not found')
|
||||
|
||||
return loads(data)
|
||||
b64 = loads(data)['data']
|
||||
decoded = base64.b64decode(b64)
|
||||
pdf = await convert_aemd_to_pdf(decoded, docKind)
|
||||
|
||||
return StreamingResponse(
|
||||
io.BytesIO(pdf),
|
||||
media_type='application/pdf',
|
||||
headers={
|
||||
'Content-Disposition': f'attachment; filename="{emdrId}.pdf"'
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post('/measurement', status_code=status.HTTP_202_ACCEPTED)
|
||||
|
||||
@ -26,9 +26,21 @@ class Config:
|
||||
def __init__(self):
|
||||
self.version = 1
|
||||
self.disable_existing_loggers = False
|
||||
self.formatters = self._get_formatters()
|
||||
self.handlers = self._get_handlers()
|
||||
self.loggers = self._get_loggers()
|
||||
|
||||
@staticmethod
|
||||
def _get_formatters() -> dict[str, Any]:
|
||||
# Common formatter that includes logger name
|
||||
fmt = '%(asctime)s | %(levelname)-8s | %(message)s'
|
||||
return {
|
||||
'default': {
|
||||
'format': fmt,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_handlers():
|
||||
handlers: dict[str, Any] = {
|
||||
@ -36,6 +48,7 @@ class Config:
|
||||
'class': 'logging.StreamHandler',
|
||||
'level': logging.INFO,
|
||||
'stream': 'ext://sys.stderr',
|
||||
'formatter': 'default',
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,12 +66,16 @@ class Config:
|
||||
},
|
||||
}
|
||||
|
||||
loggers['fontTools'] = {'level': logging.CRITICAL, 'propagate': False}
|
||||
loggers['weasyprint'] = {'level': logging.CRITICAL, 'propagate': False}
|
||||
|
||||
return loggers
|
||||
|
||||
def render(self):
|
||||
return {
|
||||
'version': self.version,
|
||||
'disable_existing_loggers': self.disable_existing_loggers,
|
||||
'formatters': self.formatters,
|
||||
'handlers': self.handlers,
|
||||
'loggers': self.loggers,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user