Skip to content

Commit d329809

Browse files
committed
fix: improve market review web flow
1 parent 7bb7dc9 commit d329809

15 files changed

Lines changed: 499 additions & 25 deletions

api/v1/endpoints/analysis.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020
import json
2121
import logging
2222
import re
23+
import threading
2324
from datetime import datetime
2425
from typing import Optional, Union, Dict, Any
2526

26-
from fastapi import APIRouter, HTTPException, Depends, Query
27+
from fastapi import APIRouter, HTTPException, Depends, Query, BackgroundTasks
2728
from fastapi.responses import JSONResponse, StreamingResponse
2829

2930
from api.deps import get_config_dep
@@ -38,6 +39,8 @@
3839
TaskInfo,
3940
TaskListResponse,
4041
DuplicateTaskErrorResponse,
42+
MarketReviewRequest,
43+
MarketReviewAccepted,
4144
)
4245
from api.v1.schemas.common import ErrorResponse
4346
from api.v1.schemas.history import (
@@ -69,6 +72,24 @@
6972
router = APIRouter()
7073

7174
_SUPPORTED_FREE_TEXT_RE = re.compile(r"^[A-Za-z0-9.*\-+\u3400-\u9fff\s]+$")
75+
_market_review_lock = threading.Lock()
76+
_market_review_running = False
77+
78+
79+
def _run_market_review_background(send_notification: bool) -> None:
80+
"""Run market review after the API response has been accepted."""
81+
global _market_review_running
82+
try:
83+
from src.core.market_review import run_market_review
84+
from src.notification import NotificationService
85+
86+
notifier = NotificationService()
87+
run_market_review(notifier, send_notification=send_notification)
88+
except Exception as exc:
89+
logger.error("大盘复盘后台任务失败: %s", exc, exc_info=True)
90+
finally:
91+
with _market_review_lock:
92+
_market_review_running = False
7293

7394

7495
def _invalid_analysis_input_error() -> HTTPException:
@@ -392,6 +413,47 @@ def _handle_sync_analysis(
392413
)
393414

394415

416+
# ============================================================
417+
# POST /market-review - 触发大盘复盘
418+
# ============================================================
419+
420+
@router.post(
421+
"/market-review",
422+
response_model=MarketReviewAccepted,
423+
status_code=202,
424+
responses={
425+
202: {"description": "大盘复盘任务已接受", "model": MarketReviewAccepted},
426+
409: {"description": "大盘复盘正在执行", "model": ErrorResponse},
427+
500: {"description": "提交失败", "model": ErrorResponse},
428+
},
429+
summary="触发大盘复盘",
430+
description="提交一个后台大盘复盘任务,复用 CLI 的大盘复盘链路并保存报告。",
431+
)
432+
def trigger_market_review(
433+
request: MarketReviewRequest,
434+
background_tasks: BackgroundTasks,
435+
) -> MarketReviewAccepted:
436+
"""Trigger market review from Web/API without blocking the request."""
437+
global _market_review_running
438+
with _market_review_lock:
439+
if _market_review_running:
440+
raise HTTPException(
441+
status_code=409,
442+
detail={
443+
"error": "duplicate_market_review",
444+
"message": "大盘复盘正在执行中,请稍后再试",
445+
},
446+
)
447+
_market_review_running = True
448+
449+
background_tasks.add_task(_run_market_review_background, request.send_notification)
450+
return MarketReviewAccepted(
451+
status="accepted",
452+
message="大盘复盘任务已提交,完成后会保存报告并按配置推送通知",
453+
send_notification=request.send_notification,
454+
)
455+
456+
395457
# ============================================================
396458
# GET /tasks - 获取任务列表
397459
# ============================================================

api/v1/schemas/analysis.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,23 @@ class Config:
8787
}
8888

8989

90+
class MarketReviewRequest(BaseModel):
91+
"""Market review trigger parameters."""
92+
93+
send_notification: bool = Field(
94+
True,
95+
description="是否在大盘复盘完成后发送推送通知",
96+
)
97+
98+
99+
class MarketReviewAccepted(BaseModel):
100+
"""Market review background task accepted response."""
101+
102+
status: str = Field("accepted", description="提交状态")
103+
message: str = Field(..., description="提示信息")
104+
send_notification: bool = Field(..., description="是否发送通知")
105+
106+
90107
class AnalysisResultResponse(BaseModel):
91108
"""分析结果响应模型"""
92109

apps/dsa-web/src/api/__tests__/systemConfig.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest';
22
import { systemConfigApi } from '../systemConfig';
33

4+
const get = vi.hoisted(() => vi.fn());
45
const post = vi.hoisted(() => vi.fn());
56

67
vi.mock('../index', () => ({
78
default: {
8-
get: vi.fn(),
9+
get,
910
post,
1011
put: vi.fn(),
1112
},
1213
}));
1314

1415
describe('systemConfigApi', () => {
1516
beforeEach(() => {
17+
get.mockReset();
1618
post.mockReset();
1719
post.mockResolvedValue({
1820
data: {
@@ -111,4 +113,33 @@ describe('systemConfigApi', () => {
111113
expect(result.attempts[0].errorCode).toBeNull();
112114
expect(result.attempts[0].httpStatus).toBe(200);
113115
});
116+
117+
it('loads first-run setup status with camelCase fields', async () => {
118+
get.mockResolvedValueOnce({
119+
data: {
120+
is_complete: false,
121+
ready_for_smoke: false,
122+
required_missing_keys: ['llm_primary'],
123+
next_step_key: 'llm_primary',
124+
checks: [
125+
{
126+
key: 'llm_primary',
127+
title: 'LLM 主渠道',
128+
category: 'ai_model',
129+
required: true,
130+
status: 'needs_action',
131+
message: '缺少主模型配置',
132+
next_step: '打开系统设置',
133+
},
134+
],
135+
},
136+
});
137+
138+
const result = await systemConfigApi.getSetupStatus();
139+
140+
expect(get).toHaveBeenCalledWith('/api/v1/system/config/setup/status');
141+
expect(result.isComplete).toBe(false);
142+
expect(result.nextStepKey).toBe('llm_primary');
143+
expect(result.checks[0].nextStep).toBe('打开系统设置');
144+
});
114145
});

apps/dsa-web/src/api/analysis.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type {
66
AnalyzeResponse,
77
AnalyzeAsyncResponse,
88
AnalysisReport,
9+
MarketReviewAccepted,
10+
MarketReviewRequest,
911
TaskStatus,
1012
TaskListResponse,
1113
} from '../types/analysis';
@@ -87,6 +89,31 @@ export const analysisApi = {
8789
return toCamelCase<AnalyzeAsyncResponse>(response.data);
8890
},
8991

92+
/**
93+
* Trigger market review in background mode.
94+
*/
95+
triggerMarketReview: async (data: MarketReviewRequest = {}): Promise<MarketReviewAccepted> => {
96+
const response = await apiClient.post<Record<string, unknown>>(
97+
'/api/v1/analysis/market-review',
98+
{
99+
send_notification: data.sendNotification ?? true,
100+
},
101+
{
102+
validateStatus: (status) => status === 202 || status === 409,
103+
}
104+
);
105+
106+
if (response.status === 409) {
107+
const detail = response.data?.detail;
108+
const message = detail && typeof detail === 'object' && 'message' in detail
109+
? String((detail as { message?: unknown }).message || '')
110+
: String(response.data?.message || '');
111+
throw new Error(message || '大盘复盘正在执行中,请稍后再试');
112+
}
113+
114+
return toCamelCase<MarketReviewAccepted>(response.data);
115+
},
116+
90117
/**
91118
* Get async task status.
92119
* @param taskId Task ID

apps/dsa-web/src/api/systemConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
DiscoverLLMChannelModelsResponse,
77
ExportSystemConfigResponse,
88
ImportSystemConfigRequest,
9+
SetupStatusResponse,
910
SystemConfigConflictResponse,
1011
SystemConfigResponse,
1112
SystemConfigSchemaResponse,
@@ -144,6 +145,11 @@ export const systemConfigApi = {
144145
return toCamelCase<SystemConfigSchemaResponse>(response.data);
145146
},
146147

148+
async getSetupStatus(): Promise<SetupStatusResponse> {
149+
const response = await apiClient.get<Record<string, unknown>>('/api/v1/system/config/setup/status');
150+
return toCamelCase<SetupStatusResponse>(response.data);
151+
},
152+
147153
async validate(payload: ValidateSystemConfigRequest): Promise<ValidateSystemConfigResponse> {
148154
const response = await apiClient.post<Record<string, unknown>>(
149155
'/api/v1/system/config/validate',

0 commit comments

Comments
 (0)