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:
@@ -7,3 +7,4 @@ export { BatchValuationDto } from './batch-valuation.dto';
|
||||
export { ValuationHistoryDto } from './valuation-history.dto';
|
||||
export { ValuationComparisonDto } from './valuation-comparison.dto';
|
||||
export { AvmCompareQueryDto } from './avm-compare-query.dto';
|
||||
export { IndustrialValuationDto } from './industrial-valuation.dto';
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsString, IsNumber, Min, Max, IsOptional } from 'class-validator';
|
||||
|
||||
export class IndustrialValuationDto {
|
||||
@ApiProperty({ description: 'Province name (e.g. Bình Dương)', example: 'Bình Dương' })
|
||||
@IsString()
|
||||
province!: string;
|
||||
|
||||
@ApiProperty({ description: 'Region: south, north, central, mekong_delta', example: 'south' })
|
||||
@IsString()
|
||||
region!: string;
|
||||
|
||||
@ApiProperty({ description: 'Park occupancy rate (0-1)', example: 0.85 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
parkOccupancyRate!: number;
|
||||
|
||||
@ApiProperty({ description: 'Total park area in hectares', example: 500 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
parkAreaHa!: number;
|
||||
|
||||
@ApiProperty({ description: 'Park age in years', example: 10 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
parkAgeYears!: number;
|
||||
|
||||
@ApiProperty({ description: 'Distance to nearest seaport in km', example: 25 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
distanceToPortKm!: number;
|
||||
|
||||
@ApiProperty({ description: 'Distance to nearest airport in km', example: 40 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
distanceToAirportKm!: number;
|
||||
|
||||
@ApiProperty({ description: 'Distance to nearest highway in km', example: 5 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
distanceToHighwayKm!: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Industrial property type',
|
||||
example: 'factory',
|
||||
enum: ['warehouse', 'factory', 'ready_built_factory', 'ready_built_warehouse', 'open_yard', 'office_in_park'],
|
||||
})
|
||||
@IsString()
|
||||
propertyType!: string;
|
||||
|
||||
@ApiProperty({ description: 'Leasable area in m²', example: 5000 })
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(1)
|
||||
areaM2!: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Ceiling height in meters', example: 10 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
ceilingHeightM?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Floor load capacity in tons/m²', example: 3 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
floorLoadTonM2?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Power capacity in kVA', example: 2000 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
powerCapacityKva?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Building coverage ratio (0-1)', example: 0.6 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
buildingCoverage?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Number of loading docks', example: 4 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
loadingDocks?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Industrial zoning category',
|
||||
example: 'general_industrial',
|
||||
enum: ['general_industrial', 'heavy_industrial', 'light_industrial', 'logistics', 'free_trade_zone', 'high_tech'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
zoning?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Local industry demand index (0-1)', example: 0.7 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
industryDemandIndex?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Province FDI inflow in million USD', example: 3000 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
fdiProvinceMusd?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Average province labor cost in VND/month', example: 8000000 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
laborCostProvinceVnd?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Logistics connectivity score (0-1)', example: 0.7 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
logisticsConnectivityScore?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user