feat(analytics): AVM v2 batch valuation, comparison, history + frontend upgrade

Add batch valuation (POST /analytics/valuation/batch, max 50 properties),
valuation comparison (POST /analytics/valuation/compare, 2-5 properties),
and history endpoint (GET /analytics/valuation/history/:propertyId) with
confidence explanation helper. Frontend: enhanced valuation form with project
autocomplete and deep analysis toggle, results with confidence badges and
price range visualization, comparables table, history chart, market context
card, and PDF export.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 05:08:05 +07:00
parent 93a390efb9
commit 8da488711b
27 changed files with 1715 additions and 162 deletions

View File

@@ -1,28 +1,28 @@
import { z } from 'zod';
export const VALUATION_PROPERTY_TYPES = [
{ value: 'APARTMENT', label: 'Can ho' },
{ value: 'HOUSE', label: 'Nha rieng' },
{ value: 'VILLA', label: 'Biet thu' },
{ value: 'LAND', label: 'Dat nen' },
{ value: 'OFFICE', label: 'Van phong' },
{ value: 'APARTMENT', label: 'Căn h' },
{ value: 'HOUSE', label: 'Nhà riêng' },
{ value: 'VILLA', label: 'Bit th' },
{ value: 'LAND', label: 'Đất nn' },
{ value: 'OFFICE', label: 'Văn phòng' },
{ value: 'SHOPHOUSE', label: 'Shophouse' },
] as const;
export const CITIES = [
{ value: 'Ho Chi Minh', label: 'TP. Ho Chi Minh' },
{ value: 'Ha Noi', label: 'Ha Noi' },
{ value: 'Da Nang', label: 'Da Nang' },
{ value: 'Ho Chi Minh', label: 'TP. H Chí Minh' },
{ value: 'Ha Noi', label: 'Hà Ni' },
{ value: 'Da Nang', label: 'Đà Nng' },
] as const;
export const valuationFormSchema = z.object({
propertyType: z.string().min(1, 'Vui long chon loai bat dong san'),
area: z.string().min(1, 'Vui long nhap dien tich').refine(
propertyType: z.string().min(1, 'Vui lòng chn loi bt động sn'),
area: z.string().min(1, 'Vui lòng nhp din tích').refine(
(val) => !isNaN(Number(val)) && Number(val) > 0,
'Dien tich phai lon hon 0',
'Din tích phi ln hơn 0',
),
district: z.string().min(1, 'Vui long nhap quan/huyen'),
city: z.string().min(1, 'Vui long chon tinh/thanh pho'),
district: z.string().min(1, 'Vui lòng nhp qun/huyn'),
city: z.string().min(1, 'Vui lòng chn tnh/thành ph'),
bedrooms: z.string().optional(),
bathrooms: z.string().optional(),
floors: z.string().optional(),
@@ -30,6 +30,10 @@ export const valuationFormSchema = z.object({
roadWidth: z.string().optional(),
yearBuilt: z.string().optional(),
hasLegalPaper: z.boolean().optional(),
/** New fields for enhanced form */
projectId: z.string().optional(),
description: z.string().optional(),
deepAnalysis: z.boolean().optional(),
});
export type ValuationFormData = z.infer<typeof valuationFormSchema>;