feat(transfer): add Claude Vision condition assessment for transfer pricing

Add POST /transfer/estimate-from-photos endpoint that uses Claude Vision API
to assess furniture/appliance condition from photos, integrating with the
existing rule-based pricing engine. Includes rate limiting (5/min), image hash
caching, graceful fallback, and 17 unit tests covering all paths.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 14:41:32 +07:00
parent b22543d59e
commit ca41f7e604
9 changed files with 833 additions and 1 deletions

View File

@@ -2,14 +2,16 @@ import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@ne
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard, CurrentUser } from '@modules/auth';
import { NotFoundException } from '@modules/shared';
import { EndpointRateLimit, EndpointRateLimitGuard, NotFoundException } from '@modules/shared';
import { CreateTransferListingCommand } from '../../application/commands/create-transfer-listing/create-transfer-listing.command';
import { EstimateFromPhotosCommand } from '../../application/commands/estimate-from-photos/estimate-from-photos.command';
import { EstimateTransferPricesCommand } from '../../application/commands/estimate-transfer-prices/estimate-transfer-prices.command';
import { UpdateTransferListingCommand } from '../../application/commands/update-transfer-listing/update-transfer-listing.command';
import { GetTransferListingQuery } from '../../application/queries/get-transfer-listing/get-transfer-listing.query';
import { ListTransferListingsQuery } from '../../application/queries/list-transfer-listings/list-transfer-listings.query';
import { TransferStatsQuery } from '../../application/queries/transfer-stats/transfer-stats.query';
import { type CreateTransferListingDto } from '../dto/create-transfer-listing.dto';
import { type EstimateFromPhotosDto } from '../dto/estimate-from-photos.dto';
import { type EstimateTransferPricesDto } from '../dto/estimate-transfer-prices.dto';
import { type SearchTransferListingsDto } from '../dto/search-transfer-listings.dto';
import { type UpdateTransferListingDto } from '../dto/update-transfer-listing.dto';
@@ -72,6 +74,21 @@ export class TransferController {
);
}
@ApiOperation({
summary: 'Ước tính giá từ ảnh (AI Vision)',
description: 'Sử dụng Claude Vision để đánh giá tình trạng nội thất từ ảnh, kết hợp với công cụ định giá rule-based',
})
@ApiResponse({ status: 200, description: 'Kết quả đánh giá tình trạng và ước tính giá' })
@ApiResponse({ status: 429, description: 'Rate limit exceeded' })
@EndpointRateLimit({ limit: 5, windowSeconds: 60, keyStrategy: 'ip' })
@UseGuards(EndpointRateLimitGuard)
@Post('estimate-from-photos')
async estimateFromPhotos(@Body() dto: EstimateFromPhotosDto) {
return this.commandBus.execute(
new EstimateFromPhotosCommand(dto.items),
);
}
// ── Authenticated endpoints ───────────────────────────────────────
@ApiOperation({ summary: 'Tạo tin sang nhượng', description: 'Đăng tin sang nhượng mới' })