Files
ln-bi/src/shared/assets/assistant.ts
T
2026-09-23 18:05:10 +08:00

263 lines
7.0 KiB
TypeScript

export const DIMENSIONS = [
"region",
"city",
"type",
"model",
"department",
"status",
"vehicleClass",
] as const;
export const GROUPS = [...DIMENSIONS, "none"] as const;
export const CATEGORIES = [
"all",
"inventory",
"available",
"awaiting",
"operating",
"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";
analysis?: "distribution" | "compare" | "exceptions" | "briefing";
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[];
responseMode?: "vehicle-status";
excludeFilters?: Partial<Record<Dimension, string[]>>;
clarification?: string;
};
export type AssistantAction =
| { type: "explore"; interactionId: string; keys?: Partial<Record<Dimension, string>>; date?: string }
| { 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;
vehicleClass: string;
type: string;
model: string;
region: string;
city: string;
department: string;
status: string;
inventory?: boolean;
abnormal?: boolean;
pending?: boolean;
abnormalType?: string;
targetCompletionTime?: string;
actualCompletionTime?: string;
date?: string;
event?: string;
};
/** A model chooses a query and a renderer; values are always computed by the server. */
export type AnalysisDisplay = "metrics" | "chart" | "table" | "list";
export type PresentationBlock = {
id: string;
title?: string;
kind: "summary" | "narrative" | "metrics" | "chart" | "table";
text?: string;
metrics?: string[];
dataset?: "groups" | "rows" | "daily" | "comparison";
chart?: "bar" | "donut" | "line";
series?: { field: string; label: string }[];
columns?: { field: string; label: string }[];
interactionId?: string;
};
export type PresentationInteraction = {
id: string;
label: string;
kind: "group" | "point" | "followup";
plan: QueryPlan;
};
export type ResultPresentation = {
title?: string;
blocks: PresentationBlock[];
interactions: PresentationInteraction[];
};
export type AnalysisQuery = {
id: string;
title: string;
question: string;
plan: QueryPlan;
display: AnalysisDisplay;
};
export type AnalysisSection = Omit<AnalysisQuery, "plan"> & {
result?: AssistantResult;
error?: string;
};
export type AnalysisContext = {
id: string;
title: string;
plan: QueryPlan;
summary: string;
metrics: AssistantResult["metrics"];
};
export type AssistantGroup = {
name: string;
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;
presentation?: ResultPresentation;
presentationStatus?: "generated" | "fallback";
presentationWarning?: string;
layout?: "tabs" | "stack";
sections?: AnalysisSection[];
/** Aggregated context only: no vehicle rows are needed to refer across analyses. */
analyses?: AnalysisContext[];
answerKind?: "analysis" | "clarification" | "help";
engine?: "deepseek" | "local";
suggestions?: string[];
interpretation?: string;
contextNote?: string;
scope?: {
company: string;
permission: string;
filters: string[];
grouping: string;
period: string;
definitions: string[];
sourceUpdatedAt: string | null;
};
unanswered?: { question: string; reason: string }[];
correction?: { resultId?: string; message: string };
outcome?: "complete" | "partial" | "empty" | "unsupported" | "clarification" | "help";
inventoryBreakdown?: { title: string; summary: string; groups: AssistantGroup[] };
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;
returned: number;
replaced: number;
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> = {
region: "区域",
city: "城市",
type: "车型",
model: "型号",
department: "部门",
status: "运营状态",
vehicleClass: "车型细分",
none: "全部",
};
export const CATEGORY_LABELS: Record<QueryPlan["category"], string> = {
all: "全部资产",
inventory: "库存车辆",
available: "可运营库存",
awaiting: "待运营库存",
operating: "运营车辆",
pending: "待交车车辆",
abnormal: "异动车辆",
};