feat(asset-ai): generate contextual presentations and drilldown plans
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
shishengliang
2026-09-23 17:52:33 +08:00
parent b5a48b6417
commit dc0923b566
33 changed files with 6193 additions and 1165 deletions
@@ -0,0 +1,194 @@
import {
CATEGORY_LABELS,
GROUP_LABELS,
type AssistantResult,
type Dimension,
type QueryPlan,
} from "../../../shared/assets/assistant";
export function compactAnalysisPath(
breadcrumbs: AssistantResult["breadcrumbs"] = [],
) {
const path: NonNullable<AssistantResult["breadcrumbs"]> = [];
for (const crumb of breadcrumbs) {
if (path.at(-1)?.label === crumb.label) path[path.length - 1] = crumb;
else path.push(crumb);
}
return path;
}
export function queryPath(plan: QueryPlan): string[] {
return [
plan.kind === "flow" ? "交退换车" : CATEGORY_LABELS[plan.category],
...Object.entries(plan.filters).map(
([dimension, values]) =>
`${GROUP_LABELS[dimension as Dimension]}:${values.join("、")}`,
),
...Object.entries(plan.excludeFilters || {}).map(
([dimension, values]) =>
`排除${GROUP_LABELS[dimension as Dimension]}:${values.join("、")}`,
),
...(plan.vehicleIds?.length ? [`已选${plan.vehicleIds.length}台车辆`] : []),
...(plan.exception
? [
plan.exception === "overdue"
? "已超期"
: plan.exception === "dueSoon"
? "即将到期"
: "未填写预计完成时间",
]
: []),
plan.groupBy === "none" && !plan.secondaryGroupBy
? "总量"
: `按${[GROUP_LABELS[plan.groupBy], plan.secondaryGroupBy && GROUP_LABELS[plan.secondaryGroupBy]].filter(Boolean).join(" × ")}统计`,
];
}
export function resultDisplay(
plan: QueryPlan,
): "metrics" | "chart" | "table" | "list" {
if (
plan.kind === "assets" &&
plan.groupBy === "none" &&
!plan.secondaryGroupBy &&
plan.view === "chart"
)
return "metrics";
return plan.view;
}
export function displayTime(value: string | null | undefined) {
if (!value) return "未提供";
const date = new Date(value);
return Number.isNaN(date.getTime())
? value
: date.toLocaleString("zh-CN", { hour12: false });
}
export function resultScope(result: AssistantResult, subject: string | null) {
if (result.scope) return result.scope;
const { plan } = result;
return {
company: subject || "全部所属公司",
permission: "当前账号可见范围",
filters: [
CATEGORY_LABELS[plan.category],
...Object.entries(plan.filters).map(
([dimension, values]) =>
`${GROUP_LABELS[dimension as Dimension]}:${values.join("、")}`,
),
...Object.entries(plan.excludeFilters || {}).map(
([dimension, values]) =>
`排除${GROUP_LABELS[dimension as Dimension]}:${values.join("、")}`,
),
...(plan.vehicleIds?.length
? [`指定车辆:${plan.vehicleIds.length}台`]
: []),
...(plan.exception
? [
plan.exception === "overdue"
? "已超过预计完成时间"
: plan.exception === "dueSoon"
? "即将到期"
: "未填写预计完成时间",
]
: []),
],
grouping: [
GROUP_LABELS[plan.groupBy],
plan.secondaryGroupBy && GROUP_LABELS[plan.secondaryGroupBy],
]
.filter(Boolean)
.join(" × "),
period: plan.start
? `${plan.start} — ${plan.end || plan.start}`
: "当前资产台账",
definitions: [],
sourceUpdatedAt: null,
};
}
export function copyResultText(
result: AssistantResult,
subject: string | null,
correctionMessage?: string,
): string {
if (result.sections?.length) return [
result.title, result.summary,
...(correctionMessage ? [`【此回答已撤回】${correctionMessage}`] : []),
...result.sections.map((section, index) => `\n${index + 1}. ${section.title}\n${section.result ? copyResultText(section.result, subject) : section.error || "暂未完成"}`),
...(result.unanswered?.length ? ["尚未回答:", ...result.unanswered.map(item => `${item.question}:${item.reason}`)] : []),
].join("\n");
const scope = resultScope(result, subject);
return [
result.presentation?.title || result.title,
...(correctionMessage ? [`【此回答已撤回】${correctionMessage}`] : []),
...(result.correction ? [`【纠正说明】${result.correction.message}`] : []),
result.summary,
...(result.presentation?.blocks.filter(block => block.kind === "narrative" && block.text).map(block => `${block.title ? `${block.title}:` : ""}${block.text}`) || []),
...result.metrics.map(
(metric) =>
`${metric.label}:${metric.value.toLocaleString()}${metric.unit}`,
),
...(result.plan.kind === "assets" &&
result.plan.analysis !== "briefing" &&
result.groups.length
? [
"分组统计:",
...result.groups.map(
(group) =>
`${group.name}:${group.count}台${group.share === undefined ? "" : `,占比${group.share}%`},运营率${group.rate}%${group.abnormal === undefined ? "" : `,其中异动${group.abnormal}台`}${group.pending === undefined ? "" : `,其中待交车${group.pending}台`}`,
),
]
: []),
...(result.plan.kind === "flow" && result.daily?.length
? [
"每日流转(车次):替换交车包含在交车中,不重复相加。",
...result.daily.map(
(day) =>
`${day.date}:交车${day.delivered}车次,还车${day.returned}车次,其中替换交车${day.replaced}车次,合计${day.total}车次`,
),
]
: []),
...(result.insights?.length ? ["分析发现:", ...result.insights] : []),
...(result.inventoryBreakdown
? [
result.inventoryBreakdown.title,
result.inventoryBreakdown.summary,
...result.inventoryBreakdown.groups.map(
(group) =>
`${group.name}:${group.count}台${group.share === undefined ? "" : `,占库存${group.share}%`}`,
),
]
: []),
...(result.comparison?.length
? [
`对比期间:${result.comparisonPeriod?.start || "未提供"} — ${result.comparisonPeriod?.end || "未提供"}`,
...result.comparison.map(
(metric) =>
`${metric.label}:本期${metric.current},上期${metric.previous},差值${metric.delta},变化率${metric.rate === null ? "基期为0,不计算" : `${metric.rate}%`}`,
),
]
: []),
...(result.unanswered?.length
? [
"尚未回答:",
...result.unanswered.map(
(item) => `${item.question}:${item.reason}`,
),
]
: []),
`公司:${scope.company}`,
`权限范围:${scope.permission}`,
`筛选条件:${scope.filters.join(";") || "无额外筛选"}`,
`分组:${scope.grouping}`,
`统计期间:${scope.period}`,
...(result.plan.topN
? [`仅展示前${result.plan.topN}组;总计与明细包含全部匹配车辆`]
: []),
...scope.definitions.map((definition) => `统计口径:${definition}`),
`来源:${result.source}`,
`源数据更新时间:${displayTime(scope.sourceUpdatedAt)}`,
`查询时间:${displayTime(result.fetchedAt)}`,
].join("\n");
}