Some checks failed
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m31s
Deploy / Build API Image (push) Failing after 25s
E2E Tests / Playwright E2E (push) Failing after 23s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Deploy / Build Web Image (push) Failing after 17s
Deploy / Build AI Services Image (push) Failing after 13s
Security Scanning / Trivy Scan — Web Image (push) Failing after 58s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 51s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m55s
Security Scanning / Trivy Filesystem Scan (push) Failing after 45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 3s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 8s
CI / E2E Tests (push) Has been skipped
Closes the last gap from the tec-2725 branch: the valuation form's v2
extended-features section and POST endpoint can now submit real
predictions through to the Python ensemble model.
Backend
- New DTO apps/api/src/modules/analytics/presentation/dto/predict-valuation.dto.ts
with all v1 fields + 8 v2 fields (useV2 toggle, distanceToHospital/Park/
Mall in km, floodZoneRisk enum NONE|LOW|MEDIUM|HIGH, hasElevator/
Parking/Pool booleans).
- New CQRS handler apps/api/src/modules/analytics/application/queries/
predict-valuation/ that routes to AVM_SERVICE.estimateValue() with the
full request body.
- Extend AVMParams (domain) with the same v2 fields + inline v1 fields
(district, city, bedrooms, bathrooms, floors, frontage, roadWidth,
hasLegalPaper, projectId, imageUrl, description, deepAnalysis).
- HttpAVMService.estimateViaAi now branches on `useV2`: v2 calls the new
aiClient.predictV2() → POST /avm/v2/predict on the Python service,
mapping floodZoneRisk enum → 0..1 float and computing
building_age_years from yearBuilt. v1 path gets all the inline
descriptors wired through so non-propertyId calls no longer lose
context.
- AiServiceClient gets AiPredictV2Request / AiPredictV2Response types
mirroring libs/ai-services/app/models/avm_v2.py::AVMv2PredictRequest
(which already accepts all 7 numeric/boolean v2 fields — no Python
change needed).
- Register PredictValuationHandler in AnalyticsModule.
- New route POST /analytics/valuation on AnalyticsController:
JwtAuthGuard + QuotaGuard + EndpointRateLimitGuard (10/min),
@RequireQuota('analytics_queries'), full Swagger doc. Total endpoint
count 179 → 180.
Frontend
- Extend ValuationRequest with useV2, 3 distance-km fields,
floodZoneRisk, hasElevator/Parking/Pool + export FloodZoneRisk type
and FLOOD_RISK_OPTIONS.
- valuationApi.predict() body mapping now includes v2 fields and renames
'areaM2' → 'area' to match the backend DTO contract.
- valuationFormSchema gains matching optional Zod fields + exports
FLOOD_RISK_OPTIONS for the form.
- valuation-form.tsx gets:
* Image upload hardening: MIME+size validation (JPG/PNG ≤5MB) before
preview, role="progressbar" + aria-labels on the progress bar,
role="alert" + data-testid="image-upload-error" on errors. Matches
the upload-progress part of the task/tec-2725 commit 4ee0129 that
was previously parked as blocked.
* New Sparkles-branded "Mô hình v2 (Ensemble)" toggle alongside the
existing Bot-branded "Phân tích chuyên sâu" toggle.
* Collapsible "Đặc trưng mở rộng (AVM v2)" section with distance
inputs, flood-risk select, and three amenity checkboxes.
* handleFormSubmit passes all v2 fields through to onSubmit.
Python service unchanged — AVMv2PredictRequest already has every field
we send (distance_to_hospital_km, flood_zone_risk as float,
has_elevator/parking/pool, etc.).
Typecheck clean for the valuation surface. Pre-existing errors in
metadata.spec.ts and transfer-wizard-client.tsx are unrelated and left
for a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
4.4 KiB
TypeScript
91 lines
4.4 KiB
TypeScript
import { Module } from '@nestjs/common';
|
|
import { CqrsModule } from '@nestjs/cqrs';
|
|
import { GenerateReportHandler } from './application/commands/generate-report/generate-report.handler';
|
|
import { TrackEventHandler } from './application/commands/track-event/track-event.handler';
|
|
import { UpdateMarketIndexHandler } from './application/commands/update-market-index/update-market-index.handler';
|
|
import { ListingCreatedModerationHandler } from './application/event-handlers/listing-created-moderation.handler';
|
|
import { BatchValuationHandler } from './application/queries/batch-valuation/batch-valuation.handler';
|
|
import { IndustrialValuationHandler } from './application/queries/industrial-valuation/industrial-valuation.handler';
|
|
import { GetDistrictStatsHandler } from './application/queries/get-district-stats/get-district-stats.handler';
|
|
import { GetHeatmapHandler } from './application/queries/get-heatmap/get-heatmap.handler';
|
|
import { GetMarketReportHandler } from './application/queries/get-market-report/get-market-report.handler';
|
|
import { GetNeighborhoodScoreHandler } from './application/queries/get-neighborhood-score/get-neighborhood-score.handler';
|
|
import { GetPriceTrendHandler } from './application/queries/get-price-trend/get-price-trend.handler';
|
|
import { GetValuationHandler } from './application/queries/get-valuation/get-valuation.handler';
|
|
import { PredictValuationHandler } from './application/queries/predict-valuation/predict-valuation.handler';
|
|
import { ValuationComparisonHandler } from './application/queries/valuation-comparison/valuation-comparison.handler';
|
|
import { ValuationExplanationHandler } from './application/queries/valuation-explanation/valuation-explanation.handler';
|
|
import { ValuationHistoryHandler } from './application/queries/valuation-history/valuation-history.handler';
|
|
import { MARKET_INDEX_REPOSITORY } from './domain/repositories/market-index.repository';
|
|
import { VALUATION_REPOSITORY } from './domain/repositories/valuation.repository';
|
|
import { AVM_SERVICE } from './domain/services/avm-service';
|
|
import { NEIGHBORHOOD_SCORE_SERVICE } from './domain/services/neighborhood-score.service';
|
|
import { PrismaMarketIndexRepository } from './infrastructure/repositories/prisma-market-index.repository';
|
|
import { PrismaValuationRepository } from './infrastructure/repositories/prisma-valuation.repository';
|
|
import { AI_SERVICE_CLIENT, AiServiceClient } from './infrastructure/services/ai-service.client';
|
|
import { HttpAVMService } from './infrastructure/services/http-avm.service';
|
|
import { MarketIndexCronService } from './infrastructure/services/market-index-cron.service';
|
|
import {
|
|
HttpNeighborhoodScoreService,
|
|
PrismaNeighborhoodScoreService,
|
|
} from './infrastructure/services/neighborhood-score.service';
|
|
import { PrismaAVMService } from './infrastructure/services/prisma-avm.service';
|
|
import { AnalyticsController } from './presentation/controllers/analytics.controller';
|
|
import { AvmController } from './presentation/controllers/avm.controller';
|
|
|
|
const CommandHandlers = [
|
|
TrackEventHandler,
|
|
GenerateReportHandler,
|
|
UpdateMarketIndexHandler,
|
|
];
|
|
|
|
const QueryHandlers = [
|
|
GetMarketReportHandler,
|
|
GetHeatmapHandler,
|
|
GetPriceTrendHandler,
|
|
GetDistrictStatsHandler,
|
|
GetValuationHandler,
|
|
PredictValuationHandler,
|
|
BatchValuationHandler,
|
|
ValuationHistoryHandler,
|
|
ValuationComparisonHandler,
|
|
ValuationExplanationHandler,
|
|
GetNeighborhoodScoreHandler,
|
|
IndustrialValuationHandler,
|
|
];
|
|
|
|
const EventHandlers = [
|
|
ListingCreatedModerationHandler,
|
|
];
|
|
|
|
@Module({
|
|
imports: [CqrsModule],
|
|
controllers: [AnalyticsController, AvmController],
|
|
providers: [
|
|
// AI service client
|
|
{ provide: AI_SERVICE_CLIENT, useClass: AiServiceClient },
|
|
|
|
// Repositories
|
|
{ provide: MARKET_INDEX_REPOSITORY, useClass: PrismaMarketIndexRepository },
|
|
{ provide: VALUATION_REPOSITORY, useClass: PrismaValuationRepository },
|
|
|
|
// AVM: HttpAVMService calls Python AI first, falls back to PrismaAVMService
|
|
PrismaAVMService,
|
|
{ provide: AVM_SERVICE, useClass: HttpAVMService },
|
|
|
|
// Neighborhood scoring: HTTP proxy → Python AI service, falls back to Prisma scoring
|
|
PrismaNeighborhoodScoreService,
|
|
{ provide: NEIGHBORHOOD_SCORE_SERVICE, useClass: HttpNeighborhoodScoreService },
|
|
|
|
// Cron
|
|
MarketIndexCronService,
|
|
|
|
// CQRS
|
|
...CommandHandlers,
|
|
...QueryHandlers,
|
|
...EventHandlers,
|
|
],
|
|
exports: [MARKET_INDEX_REPOSITORY, VALUATION_REPOSITORY, AVM_SERVICE, AI_SERVICE_CLIENT],
|
|
})
|
|
export class AnalyticsModule {}
|