外包风险评估系统:领域引擎+前端+服务端持久化与生产部署

- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解
- 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护)
- 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理
- 专业图标体系替换全部emoji、看板美化
- 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC
- 444单测+204前端测试+51 e2e
This commit is contained in:
freedakgmail
2026-06-13 01:06:39 +08:00
commit c670b9e454
404 changed files with 61820 additions and 0 deletions
+882
View File
@@ -0,0 +1,882 @@
/**
* HTTP API 客户端:封装 fetch 调用后端 REST APIReq 16.2)。
*
* 所有 API 返回 Promise<T>,错误时抛出 ApiError(含 status 与 message)。
*/
/**
* 后端 API 基址。
* - 开发模式:默认 http://localhost:3005Vite 代理或直连本地后端)。
* - 生产模式:默认空串 → 走相对路径 /api/...,由 nginx 同源反代到后端,避免硬编码域名/端口。
* - 可用环境变量 VITE_API_BASE 覆盖。
*/
export const API_BASE =
(import.meta.env.VITE_API_BASE as string | undefined) ??
(import.meta.env.DEV ? 'http://localhost:3005' : '');
/** 读取本地保存的鉴权令牌(生产 RBAC);演示模式下为空,请求不带令牌。 */
function authHeader(): Record<string, string> {
try {
const t = localStorage.getItem('risk-agent-token');
return t !== null && t !== '' ? { Authorization: `Bearer ${t}` } : {};
} catch {
return {};
}
}
/** API 调用错误。 */
export class ApiError extends Error {
constructor(
readonly status: number,
message: string,
) {
super(message);
this.name = 'ApiError';
}
}
async function request<T>(
method: 'GET' | 'POST',
path: string,
body?: unknown,
): Promise<T> {
const url = `${API_BASE}${path}`;
const init: RequestInit = {
method,
headers: { 'Content-Type': 'application/json', ...authHeader() },
};
if (body !== undefined) {
init.body = JSON.stringify(body);
}
const res = await fetch(url, init);
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw new ApiError(
res.status,
typeof data.error === 'string' ? data.error : `HTTP ${res.status}`,
);
}
return data as T;
}
/** 分类结果(镜像后端 ClassificationResult)。 */
export interface ClassificationResult {
readonly businessType: string;
readonly businessTypeConfidence: number;
readonly businessTypeCandidates: ReadonlyArray<{ value: string; confidence: number }>;
readonly needsBusinessTypeConfirm: boolean;
readonly industry: string;
readonly industryConfidence: number;
readonly industryCandidates: ReadonlyArray<{ value: string; confidence: number }>;
readonly needsIndustryConfirm: boolean;
}
/** 评估运行结果(镜像后端 RunAssessment 响应)。 */
export interface RunAssessmentResult {
readonly assessmentId: string;
readonly assessment: unknown;
readonly report: unknown;
readonly classification: ClassificationResult;
readonly confirmed: { businessType: string; industry: string };
readonly riskScore: number;
readonly riskGrade: string;
readonly acceptability: string;
readonly residualGaps: readonly string[];
}
/** 工作流状态。 */
export type WorkflowStatus =
| 'draft'
| 'pending_risk_review'
| 'risk_reviewed'
| 'pending_management_approval'
| 'approved'
| 'rejected'
| 'abandoned';
/** 操作记录条目。 */
export interface AuditLogEntry {
readonly role: string;
readonly username: string;
readonly action: string;
readonly comment?: string;
readonly timestamp: string;
}
/** 评估列表项。 */
export interface AssessmentListItem {
readonly id: string;
readonly projectDescription: string;
readonly businessType: string;
readonly industry: string;
readonly region: string;
readonly riskScore?: number;
readonly riskGrade?: string;
readonly acceptability?: string;
readonly createdAt: string;
readonly assessorId: string;
readonly status: WorkflowStatus;
readonly auditLog: AuditLogEntry[];
readonly archived?: boolean;
readonly recommendation?: { level: string; title: string };
}
/** 综合承接建议。 */
export interface Recommendation {
readonly level: 'accept' | 'conditional' | 'caution' | 'reject';
readonly title: string;
readonly note: string;
readonly targetMargin: number;
readonly netMargin: number | null;
}
/** 运营控制指标——单条。 */
export interface ControlMetric {
readonly name: string;
readonly target: string;
readonly current: string | null;
readonly rationale: string;
readonly action: string;
}
/** 运营控制指标——分组。 */
export interface ControlGroup {
readonly category: '盈利管控' | '运营管控' | '质量管控';
readonly metrics: readonly ControlMetric[];
}
/** 运营控制指标集合(建议承接项目的盈利/运营/质量管控看板)。 */
export interface OperatingControls {
readonly groups: readonly ControlGroup[];
readonly note: string;
}
/** 模板列表项。 */
export interface TemplateListItem {
readonly id: string;
readonly name: string;
readonly businessType: string;
readonly industry: string;
readonly isDefault: boolean;
readonly dimensions: ReadonlyArray<{
readonly id: string;
readonly name: string;
readonly weight: number;
readonly enabled: boolean;
readonly indicators: ReadonlyArray<{
readonly id: string;
readonly name: string;
readonly weight: number;
readonly enabled: boolean;
}>;
}>;
readonly redlines: ReadonlyArray<{
readonly id: string;
readonly triggerCondition: string;
readonly consequence: string;
readonly enabled: boolean;
}>;
}
/** 输入项目描述,返回分类识别结果。 */
export async function classifyProject(
projectDescription: string,
): Promise<ClassificationResult> {
return request<ClassificationResult>('POST', '/api/assessments/classify', {
projectDescription,
});
}
/** 运行完整评估流程。 */
export async function runAssessment(
params: {
readonly projectDescription: string;
readonly confirmation?: { readonly businessType?: string; readonly industry?: string };
readonly region?: string;
readonly assessorId?: string;
readonly assessmentId?: string;
readonly expectedSavedAt?: string;
readonly clientTotalHeadcount?: number;
readonly knownData?: ReadonlyArray<readonly [string, number]>;
readonly costInputs?: Record<string, number>;
readonly useLlm?: boolean;
readonly profitabilityInputs?: ProfitabilityInputs;
},
): Promise<RunAssessmentResult> {
return request<RunAssessmentResult>('POST', '/api/assessments/run', params);
}
/** 单个指标的分级选项。 */
export interface IndicatorLevelOption {
readonly level: number;
readonly label: string;
readonly description: string;
}
/** LLM 对某指标的预填建议。 */
export interface IndicatorSuggestion {
readonly indicatorId: string;
readonly level: number;
readonly confidence: number;
readonly rationale: string;
}
/** 追问指标项(含话术、分级含义与 LLM 预填)。 */
export interface IndicatorQuestion {
readonly dimensionId: string;
readonly dimensionName: string;
readonly indicatorId: string;
readonly name: string;
readonly askPrompt: string;
readonly evidenceRequired: string;
readonly levels: readonly IndicatorLevelOption[];
readonly suggestion: IndicatorSuggestion | null;
}
/** questions 接口响应。 */
export interface QuestionsResponse {
readonly businessType: string;
readonly industry: string;
readonly llmEnabled: boolean;
readonly indicators: readonly IndicatorQuestion[];
}
/** 获取指定业务类型/行业下的指标清单(含 LLM 预填)。 */
export async function fetchQuestions(params: {
readonly projectDescription: string;
readonly businessType: string;
readonly industry?: string;
readonly skipPrefill?: boolean;
}): Promise<QuestionsResponse> {
return request<QuestionsResponse>('POST', '/api/assessments/questions', params);
}
/** 用 LLM 从项目描述抽取岗位明细(名称+人数)。失败时后端返回空数组。 */
export async function extractPositions(
projectDescription: string,
): Promise<Array<{ name: string; headcount: number }>> {
const resp = await request<{ positions?: Array<{ name: string; headcount: number }> }>(
'POST',
'/api/assessments/extract-positions',
{ projectDescription },
);
return resp.positions ?? [];
}
/** LLM 综合研判结果。 */
export interface SynthesisResult {
readonly suggestedGrade: string;
readonly confidence: number;
readonly overall: string;
readonly crossRisks: readonly string[];
readonly suggestedConditions: readonly string[];
readonly divergent: boolean;
}
/** 盈利分析——岗位明细输入。 */
export interface PositionInput {
name: string;
headcount: number;
monthlyGrossSalary: number;
socialInsuranceBase?: number;
housingFundBase?: number;
monthlyBenefits?: number;
unitPrice?: number;
}
/** 盈利分析输入。 */
export interface ProfitabilityInputs {
businessType: string;
region?: string;
pricingModel: 'per_head' | 'cost_plus' | 'fixed_total' | 'volume';
contractMonths?: number;
positions: PositionInput[];
managementFeePerHeadMonth?: number;
markupRate?: number;
contractTotal?: number;
attritionMonthlyRate?: number;
recruitingCostPerHire?: number;
trainingCostPerHead?: number;
employerLiabilityInsuranceRate?: number;
managementSpan?: number;
managementMonthlyCostPerManager?: number;
accountPeriodMonths?: number;
annualInterestRate?: number;
periodExpenseRate?: number;
utilizationRate?: number;
vatMode?: 'general' | 'simplified_diff';
}
/** 盈利分析结果(镜像后端 ProfitabilityResult 关键字段)。 */
export interface ProfitabilityResult {
businessType: string;
pricingModel: string;
region: string;
contractMonths: number;
vatMode: string;
positions: ReadonlyArray<{
name: string;
headcount: number;
perHead: {
grossSalary: number;
socialInsurance: number;
housingFund: number;
benefits: number;
employerInsurance: number;
recruitingAmortized: number;
trainingAmortized: number;
managementAllocated: number;
fullyLoaded: number;
};
loadingFactor: number;
monthlyCost: number;
monthlyRevenue: number;
}>;
totalHeadcount: number;
loadingFactor: number;
monthly: {
revenueGross: number;
revenueNet: number;
laborCost: number;
managementCost: number;
totalCost: number;
grossProfit: number;
grossMargin: number;
periodExpense: number;
financeCost: number;
vat: number;
surcharge: number;
riskReserve: number;
badDebtReserve: number;
netProfit: number;
netMargin: number;
};
contract: {
revenueGross: number;
revenueNet: number;
totalCost: number;
grossProfit: number;
netProfit: number;
};
breakeven: { unitPrice?: number; markupRate?: number; utilization?: number };
sensitivity: ReadonlyArray<{ variable: string; baseNetMargin: number; shockedNetMargin: number; deltaPct: number }>;
cashflow: {
maxAdvance: number;
peakMonth: number;
points: ReadonlyArray<{ month: number; cumulative: number }>;
};
marginCurve: ReadonlyArray<{ priceFactor: number; unitPrice: number | null; netMargin: number }>;
assumptions: readonly string[];
}
/** 盈利分析(无状态计算)。 */
export async function analyzeProfitability(inputs: ProfitabilityInputs): Promise<ProfitabilityResult> {
return request<ProfitabilityResult>('POST', '/api/assessments/profitability', inputs);
}
/** 对已完成评估生成 LLM 综合研判。 */
export async function fetchSynthesis(id: string): Promise<SynthesisResult> {
return request<SynthesisResult>('POST', `/api/assessments/${id}/synthesis`, {});
}
/** 重新生成综合承接建议(可指定目标净利率)。 */
export async function regenerateRecommendation(
id: string,
targetMargin?: number,
): Promise<Recommendation> {
return request<Recommendation>(
'POST',
`/api/assessments/${id}/recommendation`,
targetMargin !== undefined ? { targetMargin } : {},
);
}
/** 获取评估列表(全量,向后兼容)。 */
export async function fetchAssessments(): Promise<AssessmentListItem[]> {
return request<AssessmentListItem[]>('GET', '/api/assessments');
}
/** 分页列表响应。 */
export interface AssessmentPage {
readonly items: AssessmentListItem[];
readonly total: number;
readonly page: number;
readonly pageSize: number;
}
/** 服务端分页 + 状态过滤 + 关键词搜索(直接走 SQL)。 */
export async function fetchAssessmentsPage(params: {
readonly page: number;
readonly pageSize: number;
readonly status?: string;
readonly q?: string;
readonly archived?: 'active' | 'archived' | 'all';
}): Promise<AssessmentPage> {
const sp = new URLSearchParams();
sp.set('page', String(params.page));
sp.set('pageSize', String(params.pageSize));
if (params.status !== undefined && params.status !== '' && params.status !== 'all') {
sp.set('status', params.status);
}
if (params.q !== undefined && params.q.trim() !== '') {
sp.set('q', params.q.trim());
}
if (params.archived !== undefined && params.archived !== 'active') {
sp.set('archived', params.archived);
}
return request<AssessmentPage>('GET', `/api/assessments?${sp.toString()}`);
}
/** 归档 / 取消归档评估。 */
export async function archiveAssessment(
id: string,
archived: boolean,
user?: string,
): Promise<{ id: string; archived: boolean; status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/archive`, {
archived,
...(user !== undefined ? { user } : {}),
});
}
/** 工作台统计。 */
export interface AssessmentSummary {
readonly total: number;
readonly byStatus: Record<string, number>;
readonly archived?: number;
}
/** 获取各状态评估数统计。 */
export async function fetchSummary(): Promise<AssessmentSummary> {
return request<AssessmentSummary>('GET', '/api/assessments/summary');
}
/** 评估详情响应。 */
export interface AssessmentDetailResponse {
readonly assessment: unknown;
readonly report: unknown;
readonly savedAt: string;
readonly status: WorkflowStatus;
readonly auditLog: AuditLogEntry[];
readonly profitability?: ProfitabilityResult | null;
readonly recommendation?: Recommendation | null;
readonly operatingControls?: OperatingControls | null;
readonly archived?: boolean;
/** 红线 id → 中文标题映射(用于将结果中的红线 id 显示为中文)。 */
readonly redlineTitles?: Record<string, string>;
/** 盈利测算原始输入(供编辑时完整带入)。 */
readonly profitabilityInputs?: ProfitabilityInputs | null;
/** 评估有效期(到期需重新评估)。 */
readonly expiresAt?: string | null;
}
/** 获取单条评估详情。 */
export async function fetchAssessmentDetail(id: string): Promise<AssessmentDetailResponse> {
return request<AssessmentDetailResponse>('GET', `/api/assessments/${id}`);
}
/** 下载评估报告(json/html 自包含文件)。 */
export async function downloadReport(id: string, format: 'json' | 'html'): Promise<void> {
const res = await fetch(`${API_BASE}/api/assessments/${id}/report/export?format=${format}`, {
headers: authHeader(),
});
if (!res.ok) {
const err = await res.json().catch(() => ({})) as { error?: string };
throw new ApiError(res.status, err.error ?? `HTTP ${res.status}`);
}
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `risk-report-${id}.${format}`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
/** 风控审核。 */
export async function reviewAssessment(
id: string,
action: 'approve' | 'reject',
user: string,
comment?: string,
): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/review`, {
action,
user,
...(comment !== undefined && comment !== '' ? { comment } : {}),
});
}
/** 管理层审批。通过=最终通过,放弃=终态,驳回可退回风控复审或退回销售。 */
export async function approveAssessment(
id: string,
action: 'approve' | 'reject' | 'abandon',
user: string,
comment?: string,
rejectTo?: 'risk' | 'origin',
): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/approve`, {
action,
user,
...(comment !== undefined && comment !== '' ? { comment } : {}),
...(rejectTo !== undefined ? { rejectTo } : {}),
});
}
/** 重新提交评估。 */
export async function resubmitAssessment(id: string, user: string): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/resubmit`, { user });
}
/** 申报:将草稿评估报送风控审核。 */
export async function submitAssessment(id: string, user: string): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/submit`, { user });
}
/** 应用预测准确度校准(调整目标净利率基准,管理层)。 */
export async function applyCalibration(): Promise<{ appliedBase: number; previousBase: number; deviationPct: number }> {
return request('POST', '/api/calibration/apply', {});
}
/** 风控/管理层对「待核实」红线进行人工裁定(命中/未命中),闭环判定。 */
export async function submitRedlineVerdict(
id: string,
params: { redlineId: string; status: '命中' | '未命中'; note?: string; user: string; role?: string; title?: string },
): Promise<{ redlineId: string; status: string; acceptability: string }> {
return request('POST', `/api/assessments/${id}/redline-verdict`, params);
}
/** 管理层对「已通过」或「已放弃」项目直接调整工作流状态(留痕)。 */
export async function overrideAssessmentStatus(
id: string,
status: WorkflowStatus,
user: string,
comment?: string,
): Promise<{ status: WorkflowStatus }> {
return request('POST', `/api/assessments/${id}/override`, {
status,
user,
...(comment !== undefined && comment !== '' ? { comment } : {}),
});
}
/** 获取默认模板列表。 */
export async function fetchTemplates(): Promise<TemplateListItem[]> {
return request<TemplateListItem[]>('GET', '/api/templates');
}
/* ------------------------------------------------------------------ *
* P1-P3 新增功能 API
* ------------------------------------------------------------------ */
/** 费率条目。 */
export interface RateEntry {
id: number;
region: string;
category: string;
key: string;
value: number;
effectiveDate: string;
version: number;
reviewed: boolean;
}
export async function fetchAllRates(): Promise<RateEntry[]> {
return request<RateEntry[]>('GET', '/api/rates');
}
export async function createRate(entry: Omit<RateEntry, 'id'>): Promise<RateEntry> {
return request('POST', '/api/rates', entry);
}
export async function reviewRate(id: number): Promise<void> {
await request('POST', `/api/rates/${id}/review`, {});
}
export async function deleteRateEntry(id: number): Promise<void> {
await fetch(`${API_BASE}/api/rates/${id}`, { method: 'DELETE', headers: authHeader() });
}
/** 社保单位部分各险种费率。 */
export interface SocialInsuranceRates {
pension: number;
medical: number;
unemployment: number;
injury: number;
maternity: number;
}
/** 地域完整费率套(与引擎 RegionRates 对齐)。 */
export interface RegionRates {
regionName: string;
socialInsurance: SocialInsuranceRates;
housingFund: number;
vatGeneralRate: number;
vatSimplifiedRate: number;
surchargeRate: number;
}
/** 地域费率套记录(含复核状态)。 */
export interface RegionRateRecord {
region: string;
rates: RegionRates;
reviewed: boolean;
updatedBy: string | null;
updatedAt: string;
}
export async function fetchEngineDefaults(): Promise<{ national: RegionRates; regions: Record<string, RegionRates> }> {
return request('GET', '/api/region-rates/engine-defaults');
}
export async function fetchRegionRates(): Promise<RegionRateRecord[]> {
return request<RegionRateRecord[]>('GET', '/api/region-rates');
}
export async function saveRegionRate(region: string, rates: RegionRates, updatedBy?: string): Promise<void> {
await request('POST', '/api/region-rates', { region, rates, ...(updatedBy ? { updatedBy } : {}) });
}
export async function reviewRegionRate(region: string): Promise<void> {
await request('POST', `/api/region-rates/${encodeURIComponent(region)}/review`, {});
}
export async function deleteRegionRate(region: string): Promise<void> {
await fetch(`${API_BASE}/api/region-rates/${encodeURIComponent(region)}`, { method: 'DELETE', headers: authHeader() });
}
/** 红线规则。 */
export interface RedlineRuleItem {
id: string;
title: string;
triggerCondition: string;
consequence: string;
region: string | null;
businessType: string | null;
enabled: boolean;
version: number;
regulationRef: string | null;
/** 关联度量(可计算红线):4 个数值度量,或 `ind:<指标id>`(绑定指标风险等级);null 表示人工核实。 */
linkedMetric?: string | null;
/** 比较运算符。 */
compareOp?: '>=' | '<=' | '>' | '<' | null;
/** 阈值(百分比类按百分数;逾期按天;指标等级按 1-5)。 */
threshold?: number | null;
/** 可选第二条件(与主条件 AND 组合)。 */
linkedMetric2?: string | null;
compareOp2?: '>=' | '<=' | '>' | '<' | null;
threshold2?: number | null;
}
export async function fetchRedlineRules(): Promise<RedlineRuleItem[]> {
return request<RedlineRuleItem[]>('GET', '/api/redline-rules');
}
export async function createRedlineRule(rule: RedlineRuleItem): Promise<RedlineRuleItem> {
return request('POST', '/api/redline-rules', rule);
}
export async function deleteRedlineRuleApi(id: string): Promise<void> {
await fetch(`${API_BASE}/api/redline-rules/${id}`, { method: 'DELETE', headers: authHeader() });
}
/** 客户档案。 */
export interface CustomerItem {
id: string;
name: string;
creditRating: string;
avgOverdueDays: number;
totalContractAmount: number;
assessmentCount: number;
notes: string | null;
}
export async function fetchCustomers(): Promise<CustomerItem[]> {
return request<CustomerItem[]>('GET', '/api/customers');
}
export async function createCustomer(c: CustomerItem): Promise<CustomerItem> {
return request('POST', '/api/customers', c);
}
export async function deleteCustomerApi(id: string): Promise<void> {
await fetch(`${API_BASE}/api/customers/${id}`, { method: 'DELETE', headers: authHeader() });
}
export async function fetchConcentration(id: string): Promise<{ concentration: number; warning: string | null }> {
return request('GET', `/api/customers/${id}/concentration`);
}
/** 客户回款记录。 */
export interface CustomerPayment {
id: number;
customerId: string;
invoiceAmount: number;
dueDate: string;
paidDate: string | null;
note: string | null;
}
export async function fetchPayments(customerId: string): Promise<CustomerPayment[]> {
return request<CustomerPayment[]>('GET', `/api/customers/${customerId}/payments`);
}
export async function addPayment(
customerId: string,
p: { invoiceAmount: number; dueDate: string; paidDate?: string | null; note?: string | null },
): Promise<{ saved: boolean; avgOverdueDays: number }> {
return request('POST', `/api/customers/${customerId}/payments`, p);
}
export async function deletePaymentApi(customerId: string, paymentId: number): Promise<{ avgOverdueDays: number }> {
const res = await fetch(`${API_BASE}/api/customers/${customerId}/payments/${paymentId}`, { method: 'DELETE', headers: authHeader() });
return res.json();
}
/** 各地域最低工资标准。 */
export interface MinWageItem {
region: string;
monthlyWage: number;
updatedBy: string | null;
updatedAt: string;
}
export async function fetchMinWages(): Promise<MinWageItem[]> {
return request<MinWageItem[]>('GET', '/api/min-wages');
}
export async function saveMinWage(region: string, monthlyWage: number, updatedBy?: string): Promise<void> {
await request('POST', '/api/min-wages', { region, monthlyWage, ...(updatedBy ? { updatedBy } : {}) });
}
export async function deleteMinWageApi(region: string): Promise<void> {
await fetch(`${API_BASE}/api/min-wages/${encodeURIComponent(region)}`, { method: 'DELETE', headers: authHeader() });
}
/** 向导草稿(服务端持久化,跨设备)。列表项不含 form。 */
export interface DraftItem {
id: string;
assessorId: string | null;
sourceAssessmentId: string | null;
projectName: string | null;
updatedAt: string;
}
/** 草稿详情(含完整向导快照 form)。 */
export interface DraftRecord extends DraftItem {
form: unknown;
}
/** 列出草稿(可按评估人过滤)。 */
export async function listDrafts(assessorId?: string): Promise<DraftItem[]> {
const q = assessorId ? `?assessorId=${encodeURIComponent(assessorId)}` : '';
return request<DraftItem[]>('GET', `/api/drafts${q}`);
}
/** 取草稿详情(含 form)。 */
export async function getDraft(id: string): Promise<DraftRecord> {
return request<DraftRecord>('GET', `/api/drafts/${encodeURIComponent(id)}`);
}
/** 保存(新增/更新)草稿。 */
export async function saveDraft(input: {
id: string;
assessorId?: string | null;
sourceAssessmentId?: string | null;
projectName?: string | null;
form: unknown;
}): Promise<DraftRecord> {
return request<DraftRecord>('POST', '/api/drafts', input);
}
/** 删除草稿。 */
export async function deleteDraftApi(id: string): Promise<void> {
await fetch(`${API_BASE}/api/drafts/${encodeURIComponent(id)}`, { method: 'DELETE', headers: authHeader() });
}
/** 方案对比。 */
export interface ScenarioItem {
id: string;
label: string;
inputs: ProfitabilityInputs;
result: ProfitabilityResult;
}
export async function fetchScenarios(assessmentId: string): Promise<ScenarioItem[]> {
return request<ScenarioItem[]>('GET', `/api/assessments/${assessmentId}/scenarios`);
}
export async function createScenario(assessmentId: string, label: string, inputs: ProfitabilityInputs): Promise<ScenarioItem> {
return request('POST', `/api/assessments/${assessmentId}/scenarios`, { label, inputs });
}
/** 相似项目。 */
export interface SimilarProjectItem {
id: string;
projectDescription: string;
businessType: string;
industry: string;
riskScore: number | null;
riskGrade: string | null;
netMargin: number | null;
rank: number;
}
export async function fetchSimilarProjects(description: string): Promise<SimilarProjectItem[]> {
const q = encodeURIComponent(description);
return request<SimilarProjectItem[]>('GET', `/api/similar?description=${q}`);
}
/** 看板统计。 */
export interface DashboardStats {
byBusinessType: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>;
byIndustry: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>;
byRegion: Array<{ dimension: string; count: number; avgRiskScore: number | null; passRate: number | null }>;
riskDist: Array<{ grade: string; count: number }>;
trend: Array<{ month: string; count: number }>;
}
export async function fetchDashboardStats(): Promise<DashboardStats> {
return request<DashboardStats>('GET', '/api/dashboard/stats');
}
/** 经验库。 */
export interface ExperienceItem {
id: number;
assessmentId: string;
businessType: string;
industry: string;
projectSummary: string;
lesson: string;
tags: string[];
}
export async function fetchExperience(businessType?: string, industry?: string): Promise<ExperienceItem[]> {
const sp = new URLSearchParams();
if (businessType) sp.set('businessType', businessType);
if (industry) sp.set('industry', industry);
const qs = sp.toString();
return request<ExperienceItem[]>('GET', `/api/experience${qs ? '?' + qs : ''}`);
}
/** 销售修改项目资料(仅驳回状态)。 */
export async function updateAssessment(
id: string,
data: { projectDescription?: string; region?: string; profitabilityInputs?: ProfitabilityInputs; user?: string },
): Promise<{ updated: boolean; profitability: ProfitabilityResult | null; recommendation: Recommendation | null }> {
const res = await fetch(`${API_BASE}/api/assessments/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...authHeader() },
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json().catch(() => ({})) as { error?: string };
throw new ApiError(res.status, err.error ?? `HTTP ${res.status}`);
}
return res.json();
}