263 lines
7.0 KiB
TypeScript
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: "异动车辆",
|
|
};
|