feat(api): add POST /avm/industrial endpoint for industrial rent estimation

Wire NestJS controller to Python AI service's industrial AVM. Adds CQRS
query/handler, Swagger-annotated DTOs, AI client method, and 7 unit tests
covering parameter mapping, response camelCase conversion, and error handling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 18:01:23 +07:00
parent 9eaec46a37
commit 0dda2bffdb
9 changed files with 599 additions and 0 deletions

View File

@@ -23,6 +23,55 @@ export interface AiPredictResponse {
price_range_high: number;
}
export interface AiIndustrialPredictRequest {
province: string;
region: string;
park_occupancy_rate: number;
park_area_ha: number;
park_age_years: number;
distance_to_port_km: number;
distance_to_airport_km: number;
distance_to_highway_km: number;
property_type: string;
area_m2: number;
ceiling_height_m?: number;
floor_load_ton_m2?: number;
power_capacity_kva?: number;
building_coverage?: number;
loading_docks?: number;
zoning?: string;
industry_demand_index?: number;
fdi_province_musd?: number;
labor_cost_province_vnd?: number;
logistics_connectivity_score?: number;
}
export interface AiIndustrialComparable {
park_name: string;
province: string;
property_type: string;
area_m2: number;
rent_usd_m2: number;
similarity_score: number;
}
export interface AiIndustrialFeatureImportance {
feature: string;
importance: number;
}
export interface AiIndustrialPredictResponse {
estimated_rent_usd_m2: number;
confidence: number;
rent_range_low_usd_m2: number;
rent_range_high_usd_m2: number;
annual_rent_usd_m2: number;
total_monthly_rent_usd: number;
comparables: AiIndustrialComparable[];
drivers: AiIndustrialFeatureImportance[];
model_version: string;
}
export interface AiModerationRequest {
text: string;
context?: string;
@@ -46,6 +95,7 @@ export const AI_SERVICE_CLIENT = Symbol('AI_SERVICE_CLIENT');
export interface IAiServiceClient {
predict(req: AiPredictRequest): Promise<AiPredictResponse>;
predictIndustrial(req: AiIndustrialPredictRequest): Promise<AiIndustrialPredictResponse>;
moderate(req: AiModerationRequest): Promise<AiModerationResponse>;
isAvailable(): Promise<boolean>;
}
@@ -66,6 +116,10 @@ export class AiServiceClient implements IAiServiceClient {
return this.post<AiPredictResponse>('/avm/predict', req);
}
async predictIndustrial(req: AiIndustrialPredictRequest): Promise<AiIndustrialPredictResponse> {
return this.post<AiIndustrialPredictResponse>('/avm/industrial/predict', req);
}
async moderate(req: AiModerationRequest): Promise<AiModerationResponse> {
return this.post<AiModerationResponse>('/moderation/check', req);
}