feat(asset-ai): add contextual analysis and unmet-demand feedback

This commit is contained in:
shishengliang
2026-09-23 16:01:10 +08:00
parent 0a32392215
commit 7e9760f5db
23 changed files with 5160 additions and 835 deletions
+111 -5
View File
@@ -1,12 +1,12 @@
export const GROUPS = [
export const DIMENSIONS = [
"region",
"city",
"type",
"model",
"department",
"status",
"none",
] as const;
export const GROUPS = [...DIMENSIONS, "none"] as const;
export const CATEGORIES = [
"all",
"inventory",
@@ -16,18 +16,55 @@ export const CATEGORIES = [
"pending",
"abnormal",
] as const;
export type Dimension = (typeof DIMENSIONS)[number];
export type QueryPlan = {
kind: "assets" | "flow" | "clarify";
category: (typeof CATEGORIES)[number];
groupBy: (typeof GROUPS)[number];
secondaryGroupBy?: Dimension;
view: "chart" | "table" | "list";
filters: Partial<
Record<"region" | "city" | "type" | "model" | "department", string>
>;
analysis?: "distribution" | "compare" | "exceptions";
filters: Partial<Record<Dimension, string[]>>;
start?: string;
end?: string;
comparePrevious?: boolean;
exception?: "overdue" | "dueSoon" | "missingDueDate";
topN?: number;
sortBy?: "count" | "rate" | "abnormal" | "pending";
sortOrder?: "asc" | "desc";
vehicleIds?: string[];
excludeFilters?: Partial<Record<Dimension, string[]>>;
clarification?: string;
};
export type AssistantAction =
| { type: "drill"; keys: Partial<Record<Dimension, string>> }
| {
type: "group";
groupBy: QueryPlan["groupBy"];
secondaryGroupBy?: Dimension;
}
| { type: "category"; category: QueryPlan["category"] }
| { type: "analysis"; analysis: NonNullable<QueryPlan["analysis"]> }
| { type: "view"; view: QueryPlan["view"] }
| { type: "day"; date: string }
| { type: "compare-period" }
| { type: "exception"; exception: "overdue" }
| { type: "restore" }
| { type: "clear-filter"; dimension: Dimension }
| { type: "reset-filters" }
| {
type: "sort";
sortBy: "count" | "rate" | "abnormal" | "pending";
sortOrder: "asc" | "desc";
topN?: number;
};
export type AssistantRequest = {
message?: string;
subject: string | null;
sessionId?: string;
baseResultId?: string;
action?: AssistantAction;
};
export type AssistantRow = {
id: string;
plate: string;
@@ -37,6 +74,12 @@ export type AssistantRow = {
city: string;
department: string;
status: string;
inventory?: boolean;
abnormal?: boolean;
pending?: boolean;
abnormalType?: string;
targetCompletionTime?: string;
actualCompletionTime?: string;
date?: string;
event?: string;
};
@@ -45,16 +88,77 @@ export type AssistantGroup = {
count: number;
operating: number;
rate: number;
keys?: Partial<Record<Dimension, string>>;
abnormal?: number;
pending?: number;
share?: number;
};
export const GAP_REASONS = {
unrecognized: "未识别需求",
unsupported: "暂不支持",
privacy: "输入未通过安全检查",
generation: "生成失败",
empty: "无匹配数据",
unsatisfied: "用户标记未解决",
} as const;
export type GapReason = keyof typeof GAP_REASONS;
export const GAP_STATUSES = {
open: "待评估",
planned: "已纳入计划",
resolved: "已解决",
dismissed: "暂不处理",
} as const;
export type GapStatus = keyof typeof GAP_STATUSES;
export type DemandGap = {
id: number;
demand: string;
reason: GapReason;
status: GapStatus;
occurrences: number;
firstSeen: string;
lastSeen: string;
};
export type AssistantResult = {
plan: QueryPlan;
answerKind?: "analysis" | "clarification" | "help";
engine?: "deepseek" | "local";
suggestions?: string[];
interpretation?: string;
contextNote?: string;
title: string;
summary: string;
source: string;
fetchedAt: string;
sessionId?: string;
resultId?: string;
breadcrumbs?: { resultId: string; label: string }[];
groups: AssistantGroup[];
rows: AssistantRow[];
metrics: { label: string; value: number; unit: string }[];
insights?: string[];
exceptions?: {
label: string;
count: number;
category?: QueryPlan["category"];
}[];
comparison?: {
label: string;
current: number;
previous: number;
delta: number;
rate: number | null;
}[];
groupComparison?: {
baseline: string;
rows: {
name: string;
count: number;
delta: number;
share: number;
rateDelta: number;
}[];
};
comparisonPeriod?: { start: string; end: string };
daily?: {
date: string;
delivered: number;
@@ -63,9 +167,11 @@ export type AssistantResult = {
total: number;
}[];
warning?: string;
gap?: { recorded: boolean; reason: GapReason; demand: string };
};
export type AssistantEvent =
| { type: "step"; label: string }
| { type: "answer"; text: string }
| { type: "result"; result: AssistantResult }
| { type: "error"; message: string };
export const GROUP_LABELS: Record<QueryPlan["groupBy"], string> = {