734 lines
24 KiB
Python
734 lines
24 KiB
Python
"""市场行情管理路由
|
|
|
|
实现竞品机构、竞品价格、标杆价格和市场分析
|
|
"""
|
|
|
|
from typing import Optional
|
|
from datetime import date
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy import select, func, or_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.database import get_db
|
|
from app.models import (
|
|
Project,
|
|
Competitor,
|
|
CompetitorPrice,
|
|
BenchmarkPrice,
|
|
MarketAnalysisResult,
|
|
Category,
|
|
)
|
|
from app.schemas.common import ResponseModel, PaginatedData, ErrorCode
|
|
from app.schemas.competitor import (
|
|
CompetitorCreate,
|
|
CompetitorUpdate,
|
|
CompetitorResponse,
|
|
CompetitorPriceCreate,
|
|
CompetitorPriceUpdate,
|
|
CompetitorPriceResponse,
|
|
Positioning,
|
|
)
|
|
from app.schemas.market import (
|
|
BenchmarkPriceCreate,
|
|
BenchmarkPriceUpdate,
|
|
BenchmarkPriceResponse,
|
|
MarketAnalysisRequest,
|
|
MarketAnalysisResult as MarketAnalysisResultSchema,
|
|
MarketAnalysisResponse,
|
|
)
|
|
from app.services.market_service import MarketService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
# ============ 竞品机构 CRUD ============
|
|
|
|
@router.get("/competitors", response_model=ResponseModel[PaginatedData[CompetitorResponse]])
|
|
async def get_competitors(
|
|
page: int = Query(1, ge=1, description="页码"),
|
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
|
positioning: Optional[Positioning] = Query(None, description="定位筛选"),
|
|
is_key_competitor: Optional[bool] = Query(None, description="是否重点关注"),
|
|
keyword: Optional[str] = Query(None, description="关键词搜索"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取竞品机构列表"""
|
|
query = select(Competitor).options(selectinload(Competitor.prices))
|
|
|
|
if positioning:
|
|
query = query.where(Competitor.positioning == positioning.value)
|
|
if is_key_competitor is not None:
|
|
query = query.where(Competitor.is_key_competitor == is_key_competitor)
|
|
if keyword:
|
|
query = query.where(
|
|
or_(
|
|
Competitor.competitor_name.contains(keyword),
|
|
Competitor.address.contains(keyword),
|
|
)
|
|
)
|
|
|
|
query = query.order_by(Competitor.is_key_competitor.desc(), Competitor.id.desc())
|
|
|
|
# 分页
|
|
offset = (page - 1) * page_size
|
|
query = query.offset(offset).limit(page_size)
|
|
|
|
result = await db.execute(query)
|
|
competitors = result.scalars().all()
|
|
|
|
# 统计总数
|
|
count_query = select(func.count(Competitor.id))
|
|
if positioning:
|
|
count_query = count_query.where(Competitor.positioning == positioning.value)
|
|
if is_key_competitor is not None:
|
|
count_query = count_query.where(Competitor.is_key_competitor == is_key_competitor)
|
|
if keyword:
|
|
count_query = count_query.where(
|
|
or_(
|
|
Competitor.competitor_name.contains(keyword),
|
|
Competitor.address.contains(keyword),
|
|
)
|
|
)
|
|
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar() or 0
|
|
|
|
# 构建响应
|
|
items = []
|
|
for c in competitors:
|
|
# 获取最新价格更新日期
|
|
last_price_update = None
|
|
if c.prices:
|
|
last_price_update = max(p.collected_at for p in c.prices)
|
|
|
|
items.append(CompetitorResponse(
|
|
id=c.id,
|
|
competitor_name=c.competitor_name,
|
|
address=c.address,
|
|
distance_km=float(c.distance_km) if c.distance_km else None,
|
|
positioning=Positioning(c.positioning),
|
|
contact=c.contact,
|
|
is_key_competitor=c.is_key_competitor,
|
|
is_active=c.is_active,
|
|
price_count=len(c.prices),
|
|
last_price_update=last_price_update,
|
|
created_at=c.created_at,
|
|
updated_at=c.updated_at,
|
|
))
|
|
|
|
return ResponseModel(
|
|
data=PaginatedData(
|
|
items=items,
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
total_pages=(total + page_size - 1) // page_size,
|
|
)
|
|
)
|
|
|
|
|
|
@router.get("/competitors/{competitor_id}", response_model=ResponseModel[CompetitorResponse])
|
|
async def get_competitor(
|
|
competitor_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取竞品机构详情"""
|
|
result = await db.execute(
|
|
select(Competitor).options(
|
|
selectinload(Competitor.prices)
|
|
).where(Competitor.id == competitor_id)
|
|
)
|
|
competitor = result.scalar_one_or_none()
|
|
|
|
if not competitor:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品机构不存在"}
|
|
)
|
|
|
|
last_price_update = None
|
|
if competitor.prices:
|
|
last_price_update = max(p.collected_at for p in competitor.prices)
|
|
|
|
return ResponseModel(
|
|
data=CompetitorResponse(
|
|
id=competitor.id,
|
|
competitor_name=competitor.competitor_name,
|
|
address=competitor.address,
|
|
distance_km=float(competitor.distance_km) if competitor.distance_km else None,
|
|
positioning=Positioning(competitor.positioning),
|
|
contact=competitor.contact,
|
|
is_key_competitor=competitor.is_key_competitor,
|
|
is_active=competitor.is_active,
|
|
price_count=len(competitor.prices),
|
|
last_price_update=last_price_update,
|
|
created_at=competitor.created_at,
|
|
updated_at=competitor.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.post("/competitors", response_model=ResponseModel[CompetitorResponse])
|
|
async def create_competitor(
|
|
data: CompetitorCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""创建竞品机构"""
|
|
competitor = Competitor(
|
|
competitor_name=data.competitor_name,
|
|
address=data.address,
|
|
distance_km=data.distance_km,
|
|
positioning=data.positioning.value,
|
|
contact=data.contact,
|
|
is_key_competitor=data.is_key_competitor,
|
|
is_active=data.is_active,
|
|
)
|
|
db.add(competitor)
|
|
await db.flush()
|
|
await db.refresh(competitor)
|
|
|
|
return ResponseModel(
|
|
message="创建成功",
|
|
data=CompetitorResponse(
|
|
id=competitor.id,
|
|
competitor_name=competitor.competitor_name,
|
|
address=competitor.address,
|
|
distance_km=float(competitor.distance_km) if competitor.distance_km else None,
|
|
positioning=Positioning(competitor.positioning),
|
|
contact=competitor.contact,
|
|
is_key_competitor=competitor.is_key_competitor,
|
|
is_active=competitor.is_active,
|
|
price_count=0,
|
|
last_price_update=None,
|
|
created_at=competitor.created_at,
|
|
updated_at=competitor.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.put("/competitors/{competitor_id}", response_model=ResponseModel[CompetitorResponse])
|
|
async def update_competitor(
|
|
competitor_id: int,
|
|
data: CompetitorUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""更新竞品机构"""
|
|
result = await db.execute(
|
|
select(Competitor).options(
|
|
selectinload(Competitor.prices)
|
|
).where(Competitor.id == competitor_id)
|
|
)
|
|
competitor = result.scalar_one_or_none()
|
|
|
|
if not competitor:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品机构不存在"}
|
|
)
|
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if field == "positioning" and value:
|
|
value = value.value
|
|
setattr(competitor, field, value)
|
|
|
|
await db.flush()
|
|
await db.refresh(competitor)
|
|
|
|
last_price_update = None
|
|
if competitor.prices:
|
|
last_price_update = max(p.collected_at for p in competitor.prices)
|
|
|
|
return ResponseModel(
|
|
message="更新成功",
|
|
data=CompetitorResponse(
|
|
id=competitor.id,
|
|
competitor_name=competitor.competitor_name,
|
|
address=competitor.address,
|
|
distance_km=float(competitor.distance_km) if competitor.distance_km else None,
|
|
positioning=Positioning(competitor.positioning),
|
|
contact=competitor.contact,
|
|
is_key_competitor=competitor.is_key_competitor,
|
|
is_active=competitor.is_active,
|
|
price_count=len(competitor.prices),
|
|
last_price_update=last_price_update,
|
|
created_at=competitor.created_at,
|
|
updated_at=competitor.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.delete("/competitors/{competitor_id}", response_model=ResponseModel)
|
|
async def delete_competitor(
|
|
competitor_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""删除竞品机构"""
|
|
result = await db.execute(
|
|
select(Competitor).where(Competitor.id == competitor_id)
|
|
)
|
|
competitor = result.scalar_one_or_none()
|
|
|
|
if not competitor:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品机构不存在"}
|
|
)
|
|
|
|
await db.delete(competitor)
|
|
|
|
return ResponseModel(message="删除成功")
|
|
|
|
|
|
# ============ 竞品价格管理 ============
|
|
|
|
@router.get("/competitors/{competitor_id}/prices", response_model=ResponseModel[list[CompetitorPriceResponse]])
|
|
async def get_competitor_prices(
|
|
competitor_id: int,
|
|
project_id: Optional[int] = Query(None, description="项目筛选"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取竞品价格列表"""
|
|
# 检查竞品机构是否存在
|
|
competitor_result = await db.execute(
|
|
select(Competitor).where(Competitor.id == competitor_id)
|
|
)
|
|
competitor = competitor_result.scalar_one_or_none()
|
|
|
|
if not competitor:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品机构不存在"}
|
|
)
|
|
|
|
query = select(CompetitorPrice).where(
|
|
CompetitorPrice.competitor_id == competitor_id
|
|
)
|
|
|
|
if project_id:
|
|
query = query.where(CompetitorPrice.project_id == project_id)
|
|
|
|
query = query.order_by(CompetitorPrice.collected_at.desc())
|
|
|
|
result = await db.execute(query)
|
|
prices = result.scalars().all()
|
|
|
|
response_items = []
|
|
for p in prices:
|
|
response_items.append(CompetitorPriceResponse(
|
|
id=p.id,
|
|
competitor_id=p.competitor_id,
|
|
competitor_name=competitor.competitor_name,
|
|
project_id=p.project_id,
|
|
project_name=p.project_name,
|
|
original_price=float(p.original_price),
|
|
promo_price=float(p.promo_price) if p.promo_price else None,
|
|
member_price=float(p.member_price) if p.member_price else None,
|
|
price_source=p.price_source,
|
|
collected_at=p.collected_at,
|
|
remark=p.remark,
|
|
created_at=p.created_at,
|
|
updated_at=p.updated_at,
|
|
))
|
|
|
|
return ResponseModel(data=response_items)
|
|
|
|
|
|
@router.post("/competitors/{competitor_id}/prices", response_model=ResponseModel[CompetitorPriceResponse])
|
|
async def create_competitor_price(
|
|
competitor_id: int,
|
|
data: CompetitorPriceCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""添加竞品价格"""
|
|
# 检查竞品机构是否存在
|
|
competitor_result = await db.execute(
|
|
select(Competitor).where(Competitor.id == competitor_id)
|
|
)
|
|
competitor = competitor_result.scalar_one_or_none()
|
|
|
|
if not competitor:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品机构不存在"}
|
|
)
|
|
|
|
# 检查关联项目是否存在
|
|
if data.project_id:
|
|
project_result = await db.execute(
|
|
select(Project).where(Project.id == data.project_id)
|
|
)
|
|
if not project_result.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "关联项目不存在"}
|
|
)
|
|
|
|
price = CompetitorPrice(
|
|
competitor_id=competitor_id,
|
|
project_id=data.project_id,
|
|
project_name=data.project_name,
|
|
original_price=data.original_price,
|
|
promo_price=data.promo_price,
|
|
member_price=data.member_price,
|
|
price_source=data.price_source.value,
|
|
collected_at=data.collected_at,
|
|
remark=data.remark,
|
|
)
|
|
db.add(price)
|
|
await db.flush()
|
|
await db.refresh(price)
|
|
|
|
return ResponseModel(
|
|
message="添加成功",
|
|
data=CompetitorPriceResponse(
|
|
id=price.id,
|
|
competitor_id=price.competitor_id,
|
|
competitor_name=competitor.competitor_name,
|
|
project_id=price.project_id,
|
|
project_name=price.project_name,
|
|
original_price=float(price.original_price),
|
|
promo_price=float(price.promo_price) if price.promo_price else None,
|
|
member_price=float(price.member_price) if price.member_price else None,
|
|
price_source=price.price_source,
|
|
collected_at=price.collected_at,
|
|
remark=price.remark,
|
|
created_at=price.created_at,
|
|
updated_at=price.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.put("/competitor-prices/{price_id}", response_model=ResponseModel[CompetitorPriceResponse])
|
|
async def update_competitor_price(
|
|
price_id: int,
|
|
data: CompetitorPriceUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""更新竞品价格"""
|
|
result = await db.execute(
|
|
select(CompetitorPrice).options(
|
|
selectinload(CompetitorPrice.competitor)
|
|
).where(CompetitorPrice.id == price_id)
|
|
)
|
|
price = result.scalar_one_or_none()
|
|
|
|
if not price:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品价格不存在"}
|
|
)
|
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if field == "price_source" and value:
|
|
value = value.value
|
|
setattr(price, field, value)
|
|
|
|
await db.flush()
|
|
await db.refresh(price)
|
|
|
|
return ResponseModel(
|
|
message="更新成功",
|
|
data=CompetitorPriceResponse(
|
|
id=price.id,
|
|
competitor_id=price.competitor_id,
|
|
competitor_name=price.competitor.competitor_name if price.competitor else None,
|
|
project_id=price.project_id,
|
|
project_name=price.project_name,
|
|
original_price=float(price.original_price),
|
|
promo_price=float(price.promo_price) if price.promo_price else None,
|
|
member_price=float(price.member_price) if price.member_price else None,
|
|
price_source=price.price_source,
|
|
collected_at=price.collected_at,
|
|
remark=price.remark,
|
|
created_at=price.created_at,
|
|
updated_at=price.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.delete("/competitor-prices/{price_id}", response_model=ResponseModel)
|
|
async def delete_competitor_price(
|
|
price_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""删除竞品价格"""
|
|
result = await db.execute(
|
|
select(CompetitorPrice).where(CompetitorPrice.id == price_id)
|
|
)
|
|
price = result.scalar_one_or_none()
|
|
|
|
if not price:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "竞品价格不存在"}
|
|
)
|
|
|
|
await db.delete(price)
|
|
|
|
return ResponseModel(message="删除成功")
|
|
|
|
|
|
# ============ 标杆价格管理 ============
|
|
|
|
@router.get("/benchmark-prices", response_model=ResponseModel[PaginatedData[BenchmarkPriceResponse]])
|
|
async def get_benchmark_prices(
|
|
page: int = Query(1, ge=1, description="页码"),
|
|
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
|
category_id: Optional[int] = Query(None, description="分类筛选"),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取标杆价格列表"""
|
|
query = select(BenchmarkPrice).options(selectinload(BenchmarkPrice.category))
|
|
|
|
if category_id:
|
|
query = query.where(BenchmarkPrice.category_id == category_id)
|
|
|
|
query = query.order_by(BenchmarkPrice.effective_date.desc())
|
|
|
|
# 分页
|
|
offset = (page - 1) * page_size
|
|
query = query.offset(offset).limit(page_size)
|
|
|
|
result = await db.execute(query)
|
|
benchmarks = result.scalars().all()
|
|
|
|
# 统计总数
|
|
count_query = select(func.count(BenchmarkPrice.id))
|
|
if category_id:
|
|
count_query = count_query.where(BenchmarkPrice.category_id == category_id)
|
|
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar() or 0
|
|
|
|
items = []
|
|
for b in benchmarks:
|
|
items.append(BenchmarkPriceResponse(
|
|
id=b.id,
|
|
benchmark_name=b.benchmark_name,
|
|
category_id=b.category_id,
|
|
category_name=b.category.category_name if b.category else None,
|
|
min_price=float(b.min_price),
|
|
max_price=float(b.max_price),
|
|
avg_price=float(b.avg_price),
|
|
price_tier=b.price_tier,
|
|
effective_date=b.effective_date,
|
|
remark=b.remark,
|
|
created_at=b.created_at,
|
|
updated_at=b.updated_at,
|
|
))
|
|
|
|
return ResponseModel(
|
|
data=PaginatedData(
|
|
items=items,
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
total_pages=(total + page_size - 1) // page_size,
|
|
)
|
|
)
|
|
|
|
|
|
@router.post("/benchmark-prices", response_model=ResponseModel[BenchmarkPriceResponse])
|
|
async def create_benchmark_price(
|
|
data: BenchmarkPriceCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""创建标杆价格"""
|
|
# 检查分类是否存在
|
|
category_name = None
|
|
if data.category_id:
|
|
category_result = await db.execute(
|
|
select(Category).where(Category.id == data.category_id)
|
|
)
|
|
category = category_result.scalar_one_or_none()
|
|
if not category:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "分类不存在"}
|
|
)
|
|
category_name = category.category_name
|
|
|
|
benchmark = BenchmarkPrice(
|
|
benchmark_name=data.benchmark_name,
|
|
category_id=data.category_id,
|
|
min_price=data.min_price,
|
|
max_price=data.max_price,
|
|
avg_price=data.avg_price,
|
|
price_tier=data.price_tier.value,
|
|
effective_date=data.effective_date,
|
|
remark=data.remark,
|
|
)
|
|
db.add(benchmark)
|
|
await db.flush()
|
|
await db.refresh(benchmark)
|
|
|
|
return ResponseModel(
|
|
message="创建成功",
|
|
data=BenchmarkPriceResponse(
|
|
id=benchmark.id,
|
|
benchmark_name=benchmark.benchmark_name,
|
|
category_id=benchmark.category_id,
|
|
category_name=category_name,
|
|
min_price=float(benchmark.min_price),
|
|
max_price=float(benchmark.max_price),
|
|
avg_price=float(benchmark.avg_price),
|
|
price_tier=benchmark.price_tier,
|
|
effective_date=benchmark.effective_date,
|
|
remark=benchmark.remark,
|
|
created_at=benchmark.created_at,
|
|
updated_at=benchmark.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.put("/benchmark-prices/{benchmark_id}", response_model=ResponseModel[BenchmarkPriceResponse])
|
|
async def update_benchmark_price(
|
|
benchmark_id: int,
|
|
data: BenchmarkPriceUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""更新标杆价格"""
|
|
result = await db.execute(
|
|
select(BenchmarkPrice).options(
|
|
selectinload(BenchmarkPrice.category)
|
|
).where(BenchmarkPrice.id == benchmark_id)
|
|
)
|
|
benchmark = result.scalar_one_or_none()
|
|
|
|
if not benchmark:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "标杆价格不存在"}
|
|
)
|
|
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if field == "price_tier" and value:
|
|
value = value.value
|
|
setattr(benchmark, field, value)
|
|
|
|
await db.flush()
|
|
await db.refresh(benchmark)
|
|
|
|
# 获取分类名称
|
|
category_name = None
|
|
if benchmark.category_id:
|
|
cat_result = await db.execute(
|
|
select(Category).where(Category.id == benchmark.category_id)
|
|
)
|
|
category = cat_result.scalar_one_or_none()
|
|
if category:
|
|
category_name = category.category_name
|
|
|
|
return ResponseModel(
|
|
message="更新成功",
|
|
data=BenchmarkPriceResponse(
|
|
id=benchmark.id,
|
|
benchmark_name=benchmark.benchmark_name,
|
|
category_id=benchmark.category_id,
|
|
category_name=category_name,
|
|
min_price=float(benchmark.min_price),
|
|
max_price=float(benchmark.max_price),
|
|
avg_price=float(benchmark.avg_price),
|
|
price_tier=benchmark.price_tier,
|
|
effective_date=benchmark.effective_date,
|
|
remark=benchmark.remark,
|
|
created_at=benchmark.created_at,
|
|
updated_at=benchmark.updated_at,
|
|
)
|
|
)
|
|
|
|
|
|
@router.delete("/benchmark-prices/{benchmark_id}", response_model=ResponseModel)
|
|
async def delete_benchmark_price(
|
|
benchmark_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""删除标杆价格"""
|
|
result = await db.execute(
|
|
select(BenchmarkPrice).where(BenchmarkPrice.id == benchmark_id)
|
|
)
|
|
benchmark = result.scalar_one_or_none()
|
|
|
|
if not benchmark:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "标杆价格不存在"}
|
|
)
|
|
|
|
await db.delete(benchmark)
|
|
|
|
return ResponseModel(message="删除成功")
|
|
|
|
|
|
# ============ 市场分析 ============
|
|
|
|
@router.post("/projects/{project_id}/market-analysis", response_model=ResponseModel[MarketAnalysisResultSchema])
|
|
async def analyze_market(
|
|
project_id: int,
|
|
data: MarketAnalysisRequest = MarketAnalysisRequest(),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""执行市场价格分析"""
|
|
market_service = MarketService(db)
|
|
|
|
try:
|
|
result = await market_service.analyze_market(
|
|
project_id=project_id,
|
|
competitor_ids=data.competitor_ids,
|
|
include_benchmark=data.include_benchmark,
|
|
)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": str(e)}
|
|
)
|
|
|
|
return ResponseModel(message="分析完成", data=result)
|
|
|
|
|
|
@router.get("/projects/{project_id}/market-analysis", response_model=ResponseModel[MarketAnalysisResponse])
|
|
async def get_market_analysis(
|
|
project_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""获取最新市场分析结果"""
|
|
# 检查项目是否存在
|
|
project_result = await db.execute(
|
|
select(Project).where(Project.id == project_id)
|
|
)
|
|
if not project_result.scalar_one_or_none():
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "项目不存在"}
|
|
)
|
|
|
|
market_service = MarketService(db)
|
|
result = await market_service.get_latest_analysis(project_id)
|
|
|
|
if not result:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail={"code": ErrorCode.NOT_FOUND, "message": "暂无分析结果,请先执行市场分析"}
|
|
)
|
|
|
|
return ResponseModel(
|
|
data=MarketAnalysisResponse(
|
|
id=result.id,
|
|
project_id=result.project_id,
|
|
analysis_date=result.analysis_date,
|
|
competitor_count=result.competitor_count,
|
|
market_min_price=float(result.market_min_price),
|
|
market_max_price=float(result.market_max_price),
|
|
market_avg_price=float(result.market_avg_price),
|
|
market_median_price=float(result.market_median_price),
|
|
suggested_range_min=float(result.suggested_range_min),
|
|
suggested_range_max=float(result.suggested_range_max),
|
|
created_at=result.created_at,
|
|
)
|
|
)
|