diff --git a/tradein-mvp/backend/app/schemas/trade_in.py b/tradein-mvp/backend/app/schemas/trade_in.py
index 475f80e3..614383cd 100644
--- a/tradein-mvp/backend/app/schemas/trade_in.py
+++ b/tradein-mvp/backend/app/schemas/trade_in.py
@@ -16,8 +16,8 @@ class TradeInEstimateInput(BaseModel):
address: str = Field(min_length=3, max_length=500)
area_m2: float = Field(gt=10, lt=500)
rooms: int = Field(ge=0, le=10) # 0 = студия
- floor: int = Field(ge=1, le=100)
- total_floors: int = Field(ge=1, le=100)
+ floor: int | None = Field(default=None, ge=1, le=100)
+ total_floors: int | None = Field(default=None, ge=1, le=100)
year_built: int | None = Field(default=None, ge=1800, le=2100)
house_type: Literal["panel", "brick", "monolith", "monolith_brick", "other"] | None = None
repair_state: Literal["needs_repair", "standard", "good", "excellent"] | None = None
diff --git a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx
index 2c37c266..b266a138 100644
--- a/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx
+++ b/tradein-mvp/frontend/src/components/trade-in/EstimateForm.tsx
@@ -54,11 +54,18 @@ function validate(f: FormState): string | null {
if (!f.area_m2 || isNaN(a) || a <= 10 || a >= 500) return "Площадь: от 10 до 500 м²";
const r = Number(f.rooms);
if (f.rooms === "" || isNaN(r) || r < 0 || r > 10) return "Комнат: 0 (студия) — 10";
- const fl = Number(f.floor);
- if (!f.floor || isNaN(fl) || fl < 1 || fl > 100) return "Этаж: 1—100";
- const tf = Number(f.total_floors);
- if (!f.total_floors || isNaN(tf) || tf < 1 || tf > 100) return "Этажей в доме: 1—100";
- if (fl > tf) return "Этаж не может быть больше этажей в доме";
+ if (f.floor !== "") {
+ const fl = Number(f.floor);
+ if (isNaN(fl) || fl < 1 || fl > 100) return "Этаж: 1—100";
+ if (f.total_floors !== "") {
+ const tf = Number(f.total_floors);
+ if (!isNaN(tf) && fl > tf) return "Этаж не может быть больше этажей в доме";
+ }
+ }
+ if (f.total_floors !== "") {
+ const tf = Number(f.total_floors);
+ if (isNaN(tf) || tf < 1 || tf > 100) return "Этажей в доме: 1—100";
+ }
if (f.year_built !== "") {
const y = Number(f.year_built);
if (isNaN(y) || y < 1800 || y > 2100) return "Год постройки: 1800—2100";
@@ -71,8 +78,8 @@ function toPayload(f: FormState): TradeInEstimateInput {
address: f.address.trim(),
area_m2: Number(f.area_m2),
rooms: Number(f.rooms),
- floor: Number(f.floor),
- total_floors: Number(f.total_floors),
+ floor: f.floor !== "" ? Number(f.floor) : null,
+ total_floors: f.total_floors !== "" ? Number(f.total_floors) : null,
has_balcony: f.has_balcony,
};
if (f.year_built !== "") out.year_built = Number(f.year_built);
@@ -212,7 +219,7 @@ export function EstimateForm({ onSubmit, isPending, error }: Props) {
+
+ 1-й и последний этаж снижают цену на 5–10% — заполни если знаешь
+
{/* Year + House type */}
diff --git a/tradein-mvp/frontend/src/components/trade-in/TestPresets.tsx b/tradein-mvp/frontend/src/components/trade-in/TestPresets.tsx
index e629d77d..385047e8 100644
--- a/tradein-mvp/frontend/src/components/trade-in/TestPresets.tsx
+++ b/tradein-mvp/frontend/src/components/trade-in/TestPresets.tsx
@@ -13,6 +13,19 @@ interface Preset {
}
const PRESETS: Preset[] = [
+ {
+ label: "Академика Ландау · 1к/38м²",
+ hint: "Академический новостройки · 686 сделок/год",
+ data: {
+ address: "Екатеринбург, ул. Академика Ландау, 27",
+ area_m2: 38,
+ rooms: 1,
+ floor: 12,
+ total_floors: 25,
+ year_built: 2015,
+ house_type: "monolith",
+ },
+ },
{
label: "Малышева 84 · 2к/55м²",
hint: "Центр · 36 аналогов",
@@ -27,37 +40,37 @@ const PRESETS: Preset[] = [
},
},
{
- label: "Куйбышева 48 · 3к/75м²",
- hint: "Парковый · 22 аналога",
+ label: "Краснолесья · 3к/75м²",
+ hint: "Академический эконом · 487 сделок/год",
data: {
- address: "Екатеринбург, ул. Куйбышева, 48",
+ address: "Екатеринбург, ул. Краснолесья, 30",
area_m2: 75,
rooms: 3,
floor: 7,
- total_floors: 12,
- year_built: 2005,
- house_type: "monolith",
+ total_floors: 16,
+ year_built: 2010,
+ house_type: "panel",
},
},
{
- label: "Цвиллинга 58 · 1к/46м²",
- hint: "Гольфстрим · 17 аналогов",
+ label: "Космонавтов 50 · 2к/55м²",
+ hint: "Уралмаш бюджет · 415 сделок/год",
data: {
- address: "Екатеринбург, улица Цвиллинга, 58",
- area_m2: 46,
- rooms: 1,
- floor: 4,
- total_floors: 25,
- year_built: 2020,
- house_type: "monolith",
+ address: "Екатеринбург, ул. Космонавтов, 50",
+ area_m2: 55,
+ rooms: 2,
+ floor: 5,
+ total_floors: 9,
+ year_built: 1975,
+ house_type: "panel",
},
},
{
- label: "8 Марта 50 · 1к/45м²",
- hint: "Ленинский · 19 аналогов",
+ label: "8 Марта 80 · 1к/35м²",
+ hint: "Центр Ленинский · 305 сделок/год",
data: {
- address: "Екатеринбург, ул. 8 Марта, 50",
- area_m2: 45,
+ address: "Екатеринбург, ул. 8 Марта, 80",
+ area_m2: 35,
rooms: 1,
floor: 3,
total_floors: 9,
@@ -66,31 +79,18 @@ const PRESETS: Preset[] = [
},
},
{
- label: "Малышева 84 · 3к/68м²",
- hint: "Центр · 33 аналога",
+ label: "Московская · 2к/55м²",
+ hint: "Центр премиум · 426 сделок/год",
data: {
- address: "Екатеринбург, ул. Малышева, 84",
- area_m2: 68,
- rooms: 3,
- floor: 5,
+ address: "Екатеринбург, ул. Московская, 195",
+ area_m2: 55,
+ rooms: 2,
+ floor: 6,
total_floors: 9,
year_built: 1980,
house_type: "brick",
},
},
- {
- label: "Белинского 200 · 2к/50м²",
- hint: "ЮЗ · 7 аналогов · премиум",
- data: {
- address: "Екатеринбург, ул. Белинского, 200",
- area_m2: 50,
- rooms: 2,
- floor: 6,
- total_floors: 10,
- year_built: 1990,
- house_type: "panel",
- },
- },
];
interface Props {
diff --git a/tradein-mvp/frontend/src/types/trade-in.ts b/tradein-mvp/frontend/src/types/trade-in.ts
index 2766bcc9..97345d55 100644
--- a/tradein-mvp/frontend/src/types/trade-in.ts
+++ b/tradein-mvp/frontend/src/types/trade-in.ts
@@ -16,8 +16,8 @@ export interface TradeInEstimateInput {
address: string; // min 3, max 500
area_m2: number; // 10 < x < 500
rooms: number; // 0-10 (0 = студия)
- floor: number; // 1-100
- total_floors: number; // 1-100
+ floor: number | null; // 1-100, optional
+ total_floors: number | null; // 1-100, optional
year_built?: number; // 1800-2100
house_type?: HouseType;
repair_state?: RepairState;