feat(layouts): PDF ТЗ endpoint + BestLayoutsBlock UI (#113 PR D) #199
3 changed files with 11 additions and 5 deletions
|
|
@ -2161,6 +2161,8 @@ async def get_parcel_best_layouts_pdf(
|
||||||
)
|
)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("best_layouts PDF endpoint failed for %s: %s", cad_num, 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
|
raise HTTPException(status_code=500, detail="Internal server error") from exc
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ def render_layout_tz_pdf(
|
||||||
top_rows = "".join(
|
top_rows = "".join(
|
||||||
"<tr>"
|
"<tr>"
|
||||||
f"<td>{r.rank}</td>"
|
f"<td>{r.rank}</td>"
|
||||||
f"<td>{r.room_bucket}</td>"
|
f"<td>{_html.escape(r.room_bucket)}</td>"
|
||||||
f"<td>{r.area_bin}</td>"
|
f"<td>{_html.escape(r.area_bin)}</td>"
|
||||||
f"<td>{r.velocity_per_month:.1f}</td>"
|
f"<td>{r.velocity_per_month:.1f}</td>"
|
||||||
f"<td>{r.avg_area_m2:.1f}</td>"
|
f"<td>{r.avg_area_m2:.1f}</td>"
|
||||||
f"{_price_cell(r.avg_price_per_m2_rub)}"
|
f"{_price_cell(r.avg_price_per_m2_rub)}"
|
||||||
|
|
@ -64,7 +64,7 @@ def render_layout_tz_pdf(
|
||||||
# Recommendation mix table rows
|
# Recommendation mix table rows
|
||||||
mix_rows = "".join(
|
mix_rows = "".join(
|
||||||
"<tr>"
|
"<tr>"
|
||||||
f"<td>{m.room_bucket}</td>"
|
f"<td>{_html.escape(m.room_bucket)}</td>"
|
||||||
f"<td>{m.pct}%</td>"
|
f"<td>{m.pct}%</td>"
|
||||||
f"<td>{m.abs_units if m.abs_units is not None else '—'}</td>"
|
f"<td>{m.abs_units if m.abs_units is not None else '—'}</td>"
|
||||||
f"<td>{f'{m.avg_target_area_m2:.1f}' if m.avg_target_area_m2 is not None else '—'}</td>"
|
f"<td>{f'{m.avg_target_area_m2:.1f}' if m.avg_target_area_m2 is not None else '—'}</td>"
|
||||||
|
|
|
||||||
|
|
@ -411,7 +411,8 @@ function RecommendationCard({
|
||||||
<div style={{ fontSize: 11, color: "#9ca3af" }}>
|
<div style={{ fontSize: 11, color: "#9ca3af" }}>
|
||||||
Основано на {rec.based_on_obj_count} ЖК ·{" "}
|
Основано на {rec.based_on_obj_count} ЖК ·{" "}
|
||||||
{rec.based_on_total_deals.toLocaleString("ru-RU")} сделках · период{" "}
|
{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")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -445,7 +446,10 @@ export function BestLayoutsBlock({ cadNum, selectedCompetitorObjIds }: Props) {
|
||||||
? selectedCompetitorObjIds
|
? selectedCompetitorObjIds
|
||||||
: null,
|
: null,
|
||||||
min_velocity_per_month: minVelocity,
|
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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue