diff --git a/backend/app/api/v1/parcels.py b/backend/app/api/v1/parcels.py
index de982a43..075983ab 100644
--- a/backend/app/api/v1/parcels.py
+++ b/backend/app/api/v1/parcels.py
@@ -2161,6 +2161,8 @@ async def get_parcel_best_layouts_pdf(
)
except HTTPException:
raise
+ except ValueError as exc:
+ raise HTTPException(status_code=404, detail=str(exc)) from exc
except Exception as exc:
logger.error("best_layouts PDF endpoint failed for %s: %s", cad_num, exc)
raise HTTPException(status_code=500, detail="Internal server error") from exc
diff --git a/backend/app/services/exporters/layout_tz_pdf.py b/backend/app/services/exporters/layout_tz_pdf.py
index 2e169630..96178212 100644
--- a/backend/app/services/exporters/layout_tz_pdf.py
+++ b/backend/app/services/exporters/layout_tz_pdf.py
@@ -51,8 +51,8 @@ def render_layout_tz_pdf(
top_rows = "".join(
"
"
f"| {r.rank} | "
- f"{r.room_bucket} | "
- f"{r.area_bin} | "
+ f"{_html.escape(r.room_bucket)} | "
+ f"{_html.escape(r.area_bin)} | "
f"{r.velocity_per_month:.1f} | "
f"{r.avg_area_m2:.1f} | "
f"{_price_cell(r.avg_price_per_m2_rub)}"
@@ -64,7 +64,7 @@ def render_layout_tz_pdf(
# Recommendation mix table rows
mix_rows = "".join(
"
"
- f"| {m.room_bucket} | "
+ f"{_html.escape(m.room_bucket)} | "
f"{m.pct}% | "
f"{m.abs_units if m.abs_units is not None else '—'} | "
f"{f'{m.avg_target_area_m2:.1f}' if m.avg_target_area_m2 is not None else '—'} | "
diff --git a/frontend/src/components/site-finder/BestLayoutsBlock.tsx b/frontend/src/components/site-finder/BestLayoutsBlock.tsx
index 79f0ba4c..63224696 100644
--- a/frontend/src/components/site-finder/BestLayoutsBlock.tsx
+++ b/frontend/src/components/site-finder/BestLayoutsBlock.tsx
@@ -411,7 +411,8 @@ function RecommendationCard({
Основано на {rec.based_on_obj_count} ЖК ·{" "}
{rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "}
- {rec.data_window_start} — {rec.data_window_end}
+ {new Date(rec.data_window_start).toLocaleDateString("ru-RU")} —{" "}
+ {new Date(rec.data_window_end).toLocaleDateString("ru-RU")}
@@ -445,7 +446,10 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
? selectedCompetitorObjIds
: null,
min_velocity_per_month: minVelocity,
- target_total_flats: !Number.isNaN(parsed) && parsed > 0 ? parsed : null,
+ target_total_flats:
+ !Number.isNaN(parsed) && parsed > 0
+ ? Math.min(Math.max(parsed, 1), 10000)
+ : null,
};
}