perf(api): свод 2 — новый режим страниц с total и эндпоинт вариантов фильтров
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e3a9a08e84
commit
db28138b5b
@ -8,7 +8,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from src.api.v1.deps import get_current_user
|
from src.api.v1.deps import get_current_user
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
from src.domain.schemas import BaseListResponse, SheetResponse
|
from src.domain.schemas import (
|
||||||
|
BaseListResponse,
|
||||||
|
BaseSingleResponse,
|
||||||
|
SheetResponse,
|
||||||
|
Svod2Facets,
|
||||||
|
Svod2PageResponse,
|
||||||
|
)
|
||||||
from src.services.summary_service import SummaryService
|
from src.services.summary_service import SummaryService
|
||||||
|
|
||||||
|
|
||||||
@ -90,7 +96,33 @@ async def get_summary(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/2/{sheet}/{year}")
|
def is_svod2_page_mode(
|
||||||
|
view: Optional[str],
|
||||||
|
search: Optional[str],
|
||||||
|
smeta_type: Optional[str],
|
||||||
|
smeta_direction: Optional[str],
|
||||||
|
org_name: Optional[str],
|
||||||
|
) -> bool:
|
||||||
|
return any(
|
||||||
|
value is not None
|
||||||
|
for value in (view, search, smeta_type, smeta_direction, org_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _check_summary2_sheet(sheet: str) -> str:
|
||||||
|
sheet = sheet.upper()
|
||||||
|
if sheet not in VALID_SUMMARY2_SHEETS:
|
||||||
|
raise HTTPException(
|
||||||
|
404,
|
||||||
|
f"Лист свода {sheet} должен быть одним из: {', '.join(sorted(VALID_SUMMARY2_SHEETS))}",
|
||||||
|
)
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/2/{sheet}/{year}",
|
||||||
|
response_model=Svod2PageResponse | BaseListResponse[SheetResponse],
|
||||||
|
)
|
||||||
async def get_summary2(
|
async def get_summary2(
|
||||||
sheet: str,
|
sheet: str,
|
||||||
year: int,
|
year: int,
|
||||||
@ -100,28 +132,47 @@ async def get_summary2(
|
|||||||
limit: Optional[int] = Query(default=None, ge=1),
|
limit: Optional[int] = Query(default=None, ge=1),
|
||||||
sort_by: Literal["org_name", "section_code"] = "org_name",
|
sort_by: Literal["org_name", "section_code"] = "org_name",
|
||||||
sort_direction: Literal["asc", "desc"] = "asc",
|
sort_direction: Literal["asc", "desc"] = "asc",
|
||||||
|
view: Optional[Literal["full", "compact"]] = None,
|
||||||
|
search: Optional[str] = None,
|
||||||
|
smeta_type: Optional[str] = None,
|
||||||
|
smeta_direction: Optional[str] = None,
|
||||||
|
org_name: Optional[str] = None,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(get_current_user),
|
current_user: AppUser = Depends(get_current_user),
|
||||||
) -> BaseListResponse[SheetResponse]:
|
) -> Svod2PageResponse | BaseListResponse[SheetResponse]:
|
||||||
sheet = sheet.upper()
|
sheet = _check_summary2_sheet(sheet)
|
||||||
if sheet not in VALID_SUMMARY2_SHEETS:
|
page_mode = is_svod2_page_mode(view, search, smeta_type, smeta_direction, org_name)
|
||||||
raise HTTPException(
|
|
||||||
404,
|
|
||||||
f"Лист свода {sheet} должен быть одним из: {', '.join(sorted(VALID_SUMMARY2_SHEETS))}",
|
|
||||||
)
|
|
||||||
|
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
summary_service = SummaryService(db)
|
summary_service = SummaryService(db)
|
||||||
rows = await summary_service.get_svod2_rows(
|
total = None
|
||||||
year=year,
|
if page_mode:
|
||||||
sheet=sheet,
|
rows, total = await summary_service.get_svod2_page(
|
||||||
user=current_user,
|
year=year,
|
||||||
org_ids=_parse_org_ids(org_ids),
|
sheet=sheet,
|
||||||
offset=offset,
|
user=current_user,
|
||||||
limit=limit,
|
org_ids=_parse_org_ids(org_ids),
|
||||||
sort_by=sort_by,
|
offset=offset,
|
||||||
sort_direction=sort_direction,
|
limit=limit,
|
||||||
)
|
sort_by=sort_by,
|
||||||
|
sort_direction=sort_direction,
|
||||||
|
search=search,
|
||||||
|
smeta_type=smeta_type,
|
||||||
|
smeta_direction=smeta_direction,
|
||||||
|
org_name=org_name,
|
||||||
|
view=view or "full",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
rows = await summary_service.get_svod2_rows(
|
||||||
|
year=year,
|
||||||
|
sheet=sheet,
|
||||||
|
user=current_user,
|
||||||
|
org_ids=_parse_org_ids(org_ids),
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_direction=sort_direction,
|
||||||
|
)
|
||||||
db_ms = (time.perf_counter() - t0) * 1000
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
||||||
|
|
||||||
@ -136,11 +187,69 @@ async def get_summary2(
|
|||||||
"limit": limit,
|
"limit": limit,
|
||||||
"sort_by": sort_by,
|
"sort_by": sort_by,
|
||||||
"sort_direction": sort_direction,
|
"sort_direction": sort_direction,
|
||||||
|
"view": view,
|
||||||
|
"search": search,
|
||||||
|
"smeta_type": smeta_type,
|
||||||
|
"smeta_direction": smeta_direction,
|
||||||
|
"org_name": org_name,
|
||||||
"user": current_user.id,
|
"user": current_user.id,
|
||||||
}
|
}
|
||||||
logger.info(f"db_time: {extra}", extra=extra)
|
logger.info(f"db_time: {extra}", extra=extra)
|
||||||
|
|
||||||
|
if page_mode:
|
||||||
|
return Svod2PageResponse(
|
||||||
|
count=len(rows),
|
||||||
|
result=_rows_to_sheet_response(rows),
|
||||||
|
total=total,
|
||||||
|
)
|
||||||
return BaseListResponse(
|
return BaseListResponse(
|
||||||
count=len(rows),
|
count=len(rows),
|
||||||
result=_rows_to_sheet_response(rows),
|
result=_rows_to_sheet_response(rows),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/2/{sheet}/{year}/facets")
|
||||||
|
async def get_summary2_facets(
|
||||||
|
sheet: str,
|
||||||
|
year: int,
|
||||||
|
response: Response,
|
||||||
|
org_ids: Optional[str] = None,
|
||||||
|
search: Optional[str] = None,
|
||||||
|
smeta_type: Optional[str] = None,
|
||||||
|
smeta_direction: Optional[str] = None,
|
||||||
|
org_name: Optional[str] = None,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
current_user: AppUser = Depends(get_current_user),
|
||||||
|
) -> BaseSingleResponse[Svod2Facets]:
|
||||||
|
sheet = _check_summary2_sheet(sheet)
|
||||||
|
|
||||||
|
t0 = time.perf_counter()
|
||||||
|
facets = await SummaryService(db).get_svod2_facets(
|
||||||
|
year=year,
|
||||||
|
sheet=sheet,
|
||||||
|
user=current_user,
|
||||||
|
org_ids=_parse_org_ids(org_ids),
|
||||||
|
search=search,
|
||||||
|
smeta_type=smeta_type,
|
||||||
|
smeta_direction=smeta_direction,
|
||||||
|
org_name=org_name,
|
||||||
|
)
|
||||||
|
db_ms = (time.perf_counter() - t0) * 1000
|
||||||
|
response.headers["X-DB-Time-Ms"] = f"{db_ms:.2f}"
|
||||||
|
|
||||||
|
extra = {
|
||||||
|
"type": "db_time",
|
||||||
|
"time_ms": f"{db_ms:.2f}",
|
||||||
|
"handler": "get_summary2_facets",
|
||||||
|
"sheet": sheet,
|
||||||
|
"year": year,
|
||||||
|
"org_ids": org_ids,
|
||||||
|
"search": search,
|
||||||
|
"smeta_type": smeta_type,
|
||||||
|
"smeta_direction": smeta_direction,
|
||||||
|
"org_name": org_name,
|
||||||
|
"user": current_user.id,
|
||||||
|
}
|
||||||
|
logger.info(f"db_time: {extra}", extra=extra)
|
||||||
|
|
||||||
|
return BaseSingleResponse(result=facets)
|
||||||
|
|||||||
10
api/tests/unit/test_summary2_mode.py
Normal file
10
api/tests/unit/test_summary2_mode.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from src.api.v1.summary import is_svod2_page_mode
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("args,expected", [
|
||||||
|
((None, None, None, None, None), False), (("full", None, None, None, None), True),
|
||||||
|
((None, "", None, None, None), True), ((None, None, None, None, "x"), True)])
|
||||||
|
def test_is_svod2_page_mode(args, expected):
|
||||||
|
assert is_svod2_page_mode(*args) is expected
|
||||||
Loading…
x
Reference in New Issue
Block a user