From cd0aa26c981f1ebfbea9d2dd705012d5f5992737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=E5=85=88=E7=94=9F?= <1211120248@qq.com> Date: Tue, 14 Jul 2026 02:04:35 +0800 Subject: [PATCH] fix: refine smart management DeepSeek reports --- src/services/smartArtifactApi.js | 770 +-------------------------- src/services/smartContextBuilder.js | 157 +++++- src/views/wisdomManagement/index.vue | 50 +- 3 files changed, 143 insertions(+), 834 deletions(-) diff --git a/src/services/smartArtifactApi.js b/src/services/smartArtifactApi.js index 87cb466..7d26c97 100644 --- a/src/services/smartArtifactApi.js +++ b/src/services/smartArtifactApi.js @@ -60,7 +60,7 @@ export async function generateSmartArtifact({ throw new Error("代码模型没有返回完整HTML") } if (isDataDumpArtifactHtml(html)) { - throw new Error("专题页面仍为原始数据表,已切换报告模板") + throw new Error("专题页面仍为原始数据表,已拒绝展示,请重新生成") } return { @@ -69,670 +69,6 @@ export async function generateSmartArtifact({ } } -export function buildLocalSmartArtifact({ - question, - answer = "", - charts = [], - datasets = [], - context = null, - reason = "", -} = {}) { - const data = JSON.parse(buildArtifactDataJson({ question, answer, charts, datasets, context })) - const report = buildLocalReport(data, reason) - - return ` - - - - - ${escapeHtml(report.title)} - - - -
-
-
-

智慧管理专题报告

-

${escapeHtml(report.title)}

-

${escapeHtml(report.subtitle)}

-
-
${escapeHtml(report.sourceText)}
-
- - ${renderReportMetrics(report.metrics)} - -
-
-

总体态势

-
${renderReportParagraphs(report.summary)}
-
- -
-

${escapeHtml(report.primaryChart.title)}

- ${renderReportBars(report.primaryChart)} -
- -
-

${escapeHtml(report.table.title)}

- ${renderReportTable(report.table)} -
- - ${report.secondaryChart ? `

${escapeHtml(report.secondaryChart.title)}

${renderReportBars(report.secondaryChart)}
` : ""} - -
-

需关注事项

- ${renderReportList(report.attention)} -
- -
-

工作建议

- ${renderReportList(report.recommendations)} -
-
- - ${report.footnotes.length ? `
${report.footnotes.map((item) => `
${escapeHtml(item)}
`).join("")}
` : ""} -
- -` -} - -function buildLocalReport(data, reason) { - const kind = getReportKind(data) - if (kind === "balance") return buildBalanceReport(data, reason) - if (kind === "yak") return buildYakReport(data, reason) - return buildGenericReport(data, reason) -} - -function getReportKind(data) { - const question = String(data?.question || "") - const intentText = normalizeList(data?.context?.intents) - .map((item) => `${item?.key || ""}${item?.label || ""}`) - .join(" ") - const text = `${question} ${intentText}` - if (/草畜|平衡|承载|载畜|超载|放牧压力/.test(text)) return "balance" - if (/牦牛|存栏|畜群|牛群|出栏|交易|预测/.test(text)) return "yak" - return "generic" -} - -function buildBalanceReport(data, reason) { - const scope = data.context?.scope || "红原县" - const county = firstRow(data, "balance.county") || firstRow(data, "balance.target") || findFirstRow(data, (row) => row?.type === "county") || {} - const townRows = rowsOf(data, "balance.townRows").length - ? rowsOf(data, "balance.townRows") - : findRows(data, (row) => row?.type === "town" && row?.overloadRate !== undefined) - const chartRows = rowsOfFirstDataset(data, (dataset) => /草畜平衡|超载率/.test(dataset?.title || "") && normalizeList(dataset?.rows).some((row) => row?.value !== undefined)) - const pressureRows = (townRows.length ? townRows : chartRows) - .map((row) => ({ - name: row.name || row.areaName || row.key, - overloadRate: toLocalNumber(row.overloadRate ?? row.value), - level: row.level || row.extra || "", - livestockCapacity: toLocalNumber(row.livestockCapacity ?? row.yakNumber), - bearingCapacity: toLocalNumber(row.bearingCapacity), - })) - .filter((row) => row.name && Number.isFinite(row.overloadRate)) - .sort((a, b) => b.overloadRate - a.overloadRate) - const topPressures = pressureRows.slice(0, 5) - const countyRate = toLocalNumber(county.overloadRate) - const livestock = toLocalNumber(county.livestockCapacity ?? county.yakNumber) - const bearing = toLocalNumber(county.bearingCapacity) - const area = toLocalNumber(county.areaRange) - const level = county.level || (Number.isFinite(countyRate) ? "基本实现草畜平衡" : "待核算") - const pressureNames = topPressures.map((row) => `${row.name}${formatRateSuffix(row.overloadRate)}`).join("、") - const advice = splitAdvice(county.advice) - - return { - title: "草畜平衡情况", - subtitle: `${scope} · 草畜平衡专题`, - sourceText: "数据来源:平台草畜平衡核算数据", - metrics: [ - metric("全县超载率", formatRate(countyRate), level), - metric("实际载畜量", formatHead(livestock), "当前核算口径"), - metric("理论承载力", formatHead(bearing), "按草地面积折算"), - metric("草地面积", formatWanMu(area), "纳入平衡核算"), - ], - summary: [ - `${scope}当前草畜平衡总体处于“${level}”状态,超载率为${formatRate(countyRate)},实际载畜量${formatHead(livestock)},理论承载力${formatHead(bearing)}。`, - pressureNames - ? `乡镇层面,放牧压力相对较高的区域集中在${pressureNames},这些区域应作为后续动态监测和调控的重点。` - : "乡镇层面的草畜平衡压力整体可控,后续仍需结合季节牧草产量和牲畜流动持续校核。", - ], - primaryChart: { - title: "乡镇超载率对比", - note: "按超载率由高到低展示前几项", - rows: pressureRows.slice(0, 8).map((row) => ({ - label: row.name, - value: row.overloadRate, - unit: "%", - })), - }, - secondaryChart: buildCapacityGapChart(pressureRows), - table: { - title: "重点区域与平衡状态", - columns: ["区域", "超载率", "实际载畜量", "理论承载力", "状态"], - rows: pressureRows.slice(0, 6).map((row) => [ - row.name, - formatRate(row.overloadRate), - formatHead(row.livestockCapacity), - formatHead(row.bearingCapacity), - row.level || "待跟踪", - ]), - }, - attention: [ - topPressures.length ? `${topPressures[0].name}超载率最高,为${formatRate(topPressures[0].overloadRate)},建议优先复核载畜量和可利用草地面积。` : "暂无明显高压乡镇,需保持常态化监测。", - "草畜平衡结果受季节牧草产量、牲畜出入栏、补饲能力影响,应定期更新核算口径。", - "接近平衡上限的区域不宜继续扩大放牧规模,避免从基本平衡转为超载。", - ], - recommendations: advice.length ? advice : [ - "对高压力乡镇开展轮牧、休牧和补饲组合管理。", - "将超载率、载畜量和草地面积纳入月度跟踪,及时发现压力变化。", - "结合出栏计划和饲草供应能力,动态优化畜群规模。", - ], - footnotes: buildReportFootnotes(data, reason), - } -} - -function buildYakReport(data, reason) { - const scope = data.context?.scope || "红原县" - const townRows = rowsOf(data, "yak.townDistribution") - .map((row) => ({ - name: row.name || row.areaName || row.key, - count: toLocalNumber(row.count ?? row.value ?? row.total), - })) - .filter((row) => row.name && Number.isFinite(row.count)) - .sort((a, b) => b.count - a.count) - const total = townRows.reduce((sum, row) => sum + row.count, 0) - const topRows = townRows.slice(0, 5) - const topShare = total ? topRows.reduce((sum, row) => sum + row.count, 0) / total * 100 : NaN - const structureRows = rowsOf(data, "yak.structure") - .map((row) => ({ - name: row.name || row.label || row.key, - ratio: toLocalNumber(row.ratio ?? row.value), - count: toLocalNumber(row.count), - })) - .filter((row) => row.name && (Number.isFinite(row.ratio) || Number.isFinite(row.count))) - const forecastRows = rowsOf(data, "yak.forecast") - const latestForecast = forecastRows[forecastRows.length - 1] || {} - const latestStock = toLocalNumber(latestForecast.stock) - const topTown = townRows[0] - - return { - title: "牦牛分布情况", - subtitle: `${scope} · 牦牛存栏专题`, - sourceText: "数据来源:平台牦牛存栏与畜群结构数据", - metrics: [ - metric("牦牛存栏", formatHead(total), "乡镇汇总"), - metric("最高乡镇", topTown ? topTown.name : "--", topTown ? formatHead(topTown.count) : ""), - metric("覆盖乡镇", `${townRows.length || "--"}个`, "当前展示范围"), - metric("预测存栏", formatHead(latestStock), latestForecast.label || "趋势预测"), - ], - summary: [ - `${scope}当前牦牛存栏约${formatHead(total)},主要分布在${topRows.map((row) => row.name).join("、") || "重点牧区"}等区域。`, - Number.isFinite(topShare) - ? `前五个乡镇合计占全县存栏的${formatRate(topShare)},存栏分布具有较明显的重点区域特征。` - : "当前已完成乡镇存栏数据整理,可用于后续联动草畜平衡、出栏计划和疫病防控管理。", - ], - primaryChart: { - title: "乡镇牦牛存栏分布", - note: Number.isFinite(topShare) ? `前五项占比${formatRate(topShare)}` : "", - rows: townRows.slice(0, 9).map((row) => ({ - label: row.name, - value: row.count, - unit: "头", - })), - }, - secondaryChart: structureRows.length ? { - title: "畜群结构占比", - rows: structureRows.slice(0, 8).map((row) => ({ - label: row.name, - value: Number.isFinite(row.ratio) ? row.ratio : row.count, - unit: Number.isFinite(row.ratio) ? "%" : "头", - })), - } : null, - table: { - title: "重点乡镇存栏情况", - columns: ["乡镇", "存栏量", "全县占比"], - rows: townRows.slice(0, 6).map((row) => [ - row.name, - formatHead(row.count), - total ? formatRate(row.count / total * 100) : "--", - ]), - }, - attention: [ - topTown ? `${topTown.name}存栏量最高,约${formatHead(topTown.count)},建议同步关注草地承载和疫病防控压力。` : "暂无可排序的乡镇存栏数据。", - "存栏密集区域需要与草畜平衡结果联动研判,避免单看数量忽略草场承载能力。", - "预测存栏继续上行时,应提前校核出栏节奏、补饲能力和畜群结构。", - ], - recommendations: [ - "对高存栏乡镇建立动态监测清单,联动草地面积、出栏和补饲指标。", - "围绕能繁母牛、犊牛和种公牛结构优化畜群质量,避免单纯追求数量增长。", - "将存栏分布结果用于牧户服务、疫病防控和草畜平衡调控的优先级排序。", - ], - footnotes: buildReportFootnotes(data, reason), - } -} - -function buildGenericReport(data, reason) { - const scope = data.context?.scope || "红原县" - const metrics = normalizeLocalMetrics(data.context?.insights) - const metricRows = metrics.length ? metrics : deriveLocalMetrics(data.datasets) - const firstChart = normalizeList(data.datasets) - .map((dataset) => ({ - title: dataset?.title || "指标对比", - rows: normalizeList(dataset?.rows) - .map((row, index) => ({ - label: pickRowLabel(row, index), - value: pickNumericValue(row), - unit: row?.unit || dataset?.unit || "", - })) - .filter((row) => row.label && Number.isFinite(row.value)) - .slice(0, 8), - })) - .find((chart) => chart.rows.length) - return { - title: buildLocalTitle(data.question), - subtitle: `${scope} · 智慧管理专题`, - sourceText: "数据来源:平台业务汇总数据", - metrics: metricRows.slice(0, 4).map((item) => metric(item.label, item.value, item.note)), - summary: [ - `${scope}已完成本次问题相关业务数据整理,并形成专题分析结果。`, - "报告优先呈现关键指标、主要分布和可执行建议,原始字段明细不作为页面主体展示。", - ], - primaryChart: firstChart || { title: "核心指标对比", rows: [] }, - secondaryChart: null, - table: buildGenericReportTable(data), - attention: ["需结合业务口径持续复核关键指标变化。", "若涉及乡镇、牧户或生态区域,应按重点对象继续下钻。"], - recommendations: ["将本报告作为专题研判入口,后续结合业务部门口径补充结论。", "对指标波动较大的对象建立跟踪清单。"], - footnotes: buildReportFootnotes(data, reason), - } -} - -function buildCapacityGapChart(rows) { - const gapRows = rows - .map((row) => ({ - label: row.name, - value: Number.isFinite(row.livestockCapacity) && Number.isFinite(row.bearingCapacity) - ? Math.max(0, row.livestockCapacity - row.bearingCapacity) - : NaN, - unit: "头", - })) - .filter((row) => row.label && Number.isFinite(row.value) && row.value > 0) - .slice(0, 8) - if (!gapRows.length) return null - return { - title: "超承载量估算", - note: "仅展示实际载畜量高于理论承载力的区域", - rows: gapRows, - } -} - -function buildGenericReportTable(data) { - const rows = normalizeList(data.datasets) - .flatMap((dataset) => normalizeList(dataset?.rows).map((row, index) => ({ - target: pickRowLabel(row, index), - value: pickNumericValue(row), - unit: row?.unit || dataset?.unit || "", - note: dataset?.title || "", - }))) - .filter((row) => row.target && Number.isFinite(row.value)) - .slice(0, 6) - return { - title: "重点指标明细", - columns: ["对象", "数值", "说明"], - rows: rows.map((row) => [ - row.target, - `${formatLocalNumber(row.value)}${row.unit || ""}`, - row.note, - ]), - } -} - -function renderReportMetrics(metrics) { - const rows = normalizeList(metrics).filter(Boolean).slice(0, 4) - if (!rows.length) return "" - return `
${rows.map((item) => ` -
- ${escapeHtml(item.label)} - ${escapeHtml(item.value)} - ${escapeHtml(item.note || "")} -
`).join("")}
` -} - -function renderReportParagraphs(paragraphs) { - return normalizeList(paragraphs) - .filter(Boolean) - .map((item) => `

${escapeHtml(item)}

`) - .join("") -} - -function renderReportBars(chart) { - const rows = normalizeList(chart?.rows).filter((row) => row?.label && Number.isFinite(Number(row.value))).slice(0, 9) - if (!rows.length) return '

当前问题暂无可绘制的核心指标。

' - const maxValue = Math.max(...rows.map((row) => Math.abs(Number(row.value))), 1) - return `
- ${rows.map((row) => { - const value = Number(row.value) - const width = Math.max(3, Math.round(Math.abs(value) / maxValue * 100)) - return `
- ${escapeHtml(row.label)} -
- ${escapeHtml(formatLocalNumber(value))}${escapeHtml(row.unit || "")} -
` - }).join("")} - ${chart?.note ? `

${escapeHtml(chart.note)}

` : ""} -
` -} - -function renderReportTable(table) { - const rows = normalizeList(table?.rows).slice(0, 8) - const columns = normalizeList(table?.columns) - if (!rows.length || !columns.length) return '

暂无可展示的重点明细。

' - return ` - ${columns.map((column) => ``).join("")} - - ${rows.map((row) => `${normalizeList(row).map((cell) => ``).join("")}`).join("")} - -
${escapeHtml(column)}
${escapeHtml(cell)}
` -} - -function renderReportList(items) { - const rows = normalizeList(items).filter(Boolean).slice(0, 6) - if (!rows.length) return '

暂无补充事项。

' - return `` -} - -function metric(label, value, note = "") { - return { - label: label || "指标", - value: value === undefined || value === null || value === "" ? "--" : String(value), - note: note || "", - } -} - -function rowsOf(data, id) { - return normalizeList(normalizeList(data?.datasets).find((dataset) => dataset?.id === id)?.rows) -} - -function firstRow(data, id) { - return rowsOf(data, id).find(Boolean) -} - -function findRows(data, predicate) { - return normalizeList(data?.datasets) - .flatMap((dataset) => normalizeList(dataset?.rows)) - .filter((row) => row && typeof row === "object" && predicate(row)) -} - -function findFirstRow(data, predicate) { - return findRows(data, predicate).find(Boolean) -} - -function rowsOfFirstDataset(data, predicate) { - const dataset = normalizeList(data?.datasets).find(predicate) - return normalizeList(dataset?.rows) -} - -function splitAdvice(value) { - return uniqueText(String(value || "").split(/[;;。.\n]+/)).slice(0, 5) -} - -function buildReportFootnotes(data) { - const notes = uniqueText([...(data.context?.notes || [])]) - return notes.slice(0, 3) -} - -function formatWanMu(value) { - const number = Number(value) - if (!Number.isFinite(number)) return "--" - return `${formatLocalNumber(number / 10000)}万亩` -} - -function formatHead(value) { - const number = Number(value) - if (!Number.isFinite(number)) return "--" - return `${formatLocalNumber(number)}头` -} - -function formatRate(value) { - const number = Number(value) - if (!Number.isFinite(number)) return "--" - return `${formatLocalNumber(number)}%` -} - -function formatRateSuffix(value) { - const text = formatRate(value) - return text === "--" ? "" : `(${text})` -} - function buildArtifactDataJson({ question, answer, charts, datasets, context }) { const payload = { question: String(question || "").trim(), @@ -761,110 +97,6 @@ function buildArtifactDataJson({ question, answer, charts, datasets, context }) return serialized } -function buildLocalTitle(question) { - const text = String(question || "").replace(/[??。!!]+$/g, "").trim() - return text ? `${text}专题分析` : "智慧管理专题分析" -} - -function normalizeList(value) { - return Array.isArray(value) ? value : [] -} - -function uniqueText(values) { - const seen = new Set() - return normalizeList(values) - .map((item) => String(item || "").trim()) - .filter((item) => { - if (!item || seen.has(item)) return false - seen.add(item) - return true - }) -} - -function normalizeLocalMetrics(insights) { - return normalizeList(insights) - .map((item) => ({ - label: item?.label || item?.name || "指标", - value: item?.value ?? "--", - note: item?.note || item?.title || "", - })) - .filter((item) => item.label && item.value !== undefined && item.value !== null) - .slice(0, 6) -} - -function deriveLocalMetrics(datasets = []) { - return normalizeList(datasets) - .map((dataset) => { - const row = normalizeList(dataset?.rows).find(Boolean) - if (!row || typeof row !== "object") return null - const value = pickNumericValue(row) - if (!Number.isFinite(value)) return null - return { - label: pickRowLabel(row, 0), - value: `${formatLocalNumber(value)}${row?.unit || dataset?.unit || ""}`, - note: dataset?.title || "", - } - }) - .filter(Boolean) - .slice(0, 6) -} - -function pickRowLabel(row, index) { - if (!row || typeof row !== "object") return `指标${index + 1}` - const direct = row.name || row.label || row.areaName || row.town || row.title || row.key - if (direct) return String(direct) - const firstText = Object.values(row).find((value) => typeof value === "string" && value.trim()) - return firstText || `指标${index + 1}` -} - -function pickNumericValue(row) { - if (!row || typeof row !== "object") return NaN - const preferredKeys = [ - "value", - "count", - "total", - "rate", - "ratio", - "yakNumber", - "livestockCapacity", - "overloadRate", - "onlineRate", - "areaRange", - "amount", - "number", - ] - for (const key of preferredKeys) { - const number = toLocalNumber(row[key]) - if (Number.isFinite(number)) return number - } - for (const value of Object.values(row)) { - const number = toLocalNumber(value) - if (Number.isFinite(number)) return number - } - return NaN -} - -function toLocalNumber(value) { - if (value === undefined || value === null || value === "") return NaN - const number = Number(String(value).replace(/,/g, "").replace(/%/g, "")) - return Number.isFinite(number) ? number : NaN -} - -function formatLocalNumber(value) { - const number = Number(value) - if (!Number.isFinite(number)) return value || "--" - return number.toLocaleString("zh-CN", { maximumFractionDigits: 2 }) -} - -function escapeHtml(value) { - return String(value || "") - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'") -} - function sanitizeContext(context) { if (!context || typeof context !== "object") return null const { diff --git a/src/services/smartContextBuilder.js b/src/services/smartContextBuilder.js index 9732993..6846e14 100644 --- a/src/services/smartContextBuilder.js +++ b/src/services/smartContextBuilder.js @@ -1,35 +1,44 @@ import { getSmartChartsForQuestion } from "./smartChartApi" +import { + getYakForecastRows as fetchYakForecastRows, + getYakStructureRows as fetchYakStructureRows, + getYakTownRows as fetchYakTownRows, + getYakTradeRows as fetchYakTradeRows, +} from "./yakApi" import { businessMatrixRows, + centerOrbitNodes, dataFlowRows, homeHeroStats, homeTopStats, resourceCompositionRows, + supportRows, townSynergyRows, + tradeRows, yakTownRows as homeYakTownRows, } from "@/views/home/data" -import { - forecastFactorRows, - forecastPeriods, - genderAgeRows, - structureRows, - townRows as yakTownRows, - tradeImpactRows as yakTradeRows, -} from "@/views/yakManagement/data" import { pastureAgeRows, pastureCoreRows, pastureGenderRows, + pastureGrasslandRows, pastureListRows, + pastureMapRows, pastureOnlineRows, + pastureSeasonRows, + pastureStructureRows, + pastureTopStats, pastureTownRows, } from "@/views/pastureManagement/data" import { BALANCE_CALCULATION_PARAMS, + balanceFallbackRows, balanceTownRows, countyBalanceRow, } from "@/views/grassLivestockBalance/data" import { + engineeringAnalysisPanels, + engineeringAnalysisProjectFeatures, engineeringAnalysisTopStats, parkCoreRows, parkPanels, @@ -39,14 +48,20 @@ import { restorationTopStats, } from "@/views/ecologicalProtection/data" import { + breedingLevelRows, forageSecurityRows, + industryChainListRows, + industryFinancialRows, industryOverviewRows, + industryPointRows, industryStageRows, industryTopStats, industryValueRows, + processingPointRows, processingAbilityRows, serviceCapabilityRows, stageCompositionRows, + supportPointRows, } from "@/views/yakIndustryChain/data" const PAGE_NAME = "智慧管理" @@ -99,7 +114,8 @@ export async function buildSmartQuestionContext(question, options = {}) { const datasetResults = await Promise.all(selectedIntents.map((intent) => intent.build(normalizedQuestion, warnings))) const datasets = uniqueDatasets(datasetResults.flat()) const charts = await chartsPromise - const mergedDatasets = datasets.length ? datasets : buildOverviewDatasets() + const isOverviewIntent = selectedIntents.length === 1 && selectedIntents[0]?.key === "overview" + const mergedDatasets = datasets.length ? datasets : (isOverviewIntent ? buildOverviewDatasets() : []) const scope = inferScope(normalizedQuestion, mergedDatasets) const context = { question: normalizedQuestion, @@ -158,28 +174,112 @@ function buildOverviewDatasets() { dataset("home.heroStats", "资源与草畜概览", homeHeroStats, "平台业务汇总数据"), dataset("home.resourceComposition", "资源构成", resourceCompositionRows, "平台业务汇总数据"), dataset("home.businessMatrix", "业务矩阵", businessMatrixRows, "平台业务汇总数据"), + dataset("home.townSynergy", "乡镇林草湿与牦牛协同", townSynergyRows, "平台业务汇总数据"), + dataset("home.yakTown", "首页牦牛乡镇排行", homeYakTownRows, "平台业务汇总数据"), + dataset("home.trade", "首页交易结构", tradeRows, "平台业务汇总数据"), + dataset("home.support", "首页支撑产业", supportRows, "平台业务汇总数据"), dataset("home.dataFlow", "数据流汇总", dataFlowRows, "平台业务汇总数据"), + dataset("home.centerOrbit", "首页中心节点", centerOrbitNodes, "平台业务汇总数据"), ] } -function buildYakDatasets() { +async function buildYakDatasets(question, warnings = []) { + const [townResult, structureResult, forecastResult, tradeResult] = await Promise.allSettled([ + fetchYakTownRows(), + fetchYakStructureRows(), + fetchYakForecastRows(), + fetchYakTradeRows(), + ]) + appendDatasetWarning(townResult, warnings, "牦牛乡镇存栏数据暂未取得") + appendDatasetWarning(structureResult, warnings, "牦牛畜群结构数据暂未取得") + appendDatasetWarning(forecastResult, warnings, "牦牛预测数据暂未取得") + appendDatasetWarning(tradeResult, warnings, "牦牛交易数据暂未取得") + + const townRows = normalizeActualYakTownRows(getSettledRows(townResult)) + const structureRows = normalizeActualYakMetricRows(getSettledRows(structureResult)) + const forecastRows = normalizeActualYakForecastRows(getSettledRows(forecastResult)) + const tradeRows = normalizeActualYakMetricRows(getSettledRows(tradeResult)) + return [ - dataset("yak.townDistribution", "乡镇牦牛存栏分布", yakTownRows.length ? yakTownRows : homeYakTownRows, "平台业务汇总数据"), - dataset("yak.structure", "畜群结构", structureRows, "平台业务汇总数据"), - dataset("yak.genderAge", "年龄性别结构", genderAgeRows, "平台业务汇总数据"), - dataset("yak.forecast", "牦牛存栏预测", forecastPeriods, "平台业务汇总数据"), - dataset("yak.forecastFactors", "预测因素", forecastFactorRows, "平台业务汇总数据"), - dataset("yak.tradeImpact", "交易影响", yakTradeRows, "平台业务汇总数据"), + dataset("yak.town", "乡镇牦牛存栏分布", townRows, "牦牛管理接口数据"), + dataset("yak.structureRows", "畜群结构数量", structureRows, "牦牛管理接口数据"), + dataset("yak.forecastRows", "牦牛月度预测", forecastRows, "牦牛管理接口数据"), + dataset("yak.tradeImpact", "交易影响", tradeRows, "牦牛交易接口数据"), ] } +function getSettledRows(result) { + return result.status === "fulfilled" && Array.isArray(result.value) ? result.value : [] +} + +function appendDatasetWarning(result, warnings, fallback) { + if (result.status === "rejected") { + warnings.push(getFriendlyContextError(result.reason, fallback)) + } +} + +function normalizeActualYakTownRows(rows = []) { + return rows + .map((row, index) => { + const name = String(row.name || row.areaName || row.area_name || row.townName || row.zoneName || "").trim() + const count = toDatasetNumber(row.count ?? row.value ?? row.number ?? row.yakCount ?? row.yak_count) + return { + key: row.key || row.id || row.areaCode || row.area_code || `${name || "yak-town"}-${index}`, + name, + count, + value: count, + unit: row.unit || "头", + } + }) + .filter((row) => row.name && Number.isFinite(row.count)) + .sort((a, b) => b.count - a.count) +} + +function normalizeActualYakMetricRows(rows = []) { + return rows + .map((row, index) => { + const name = String(row.name || row.label || row.type || row.key || "").trim() + const count = toDatasetNumber(row.count ?? row.value ?? row.number ?? row.yakCount ?? row.headCount ?? row.amount) + return { + key: row.key || row.id || `${name || "yak-metric"}-${index}`, + name, + count, + value: count, + unit: row.unit || "头", + extra: row.extra || "", + } + }) + .filter((row) => row.name && Number.isFinite(row.count)) + .sort((a, b) => b.count - a.count) +} + +function normalizeActualYakForecastRows(rows = []) { + return rows + .map((row, index) => { + const name = String(row.name || row.label || row.month || row.date || row.time || "").trim() + const stock = toDatasetNumber(row.stock ?? row.count ?? row.value ?? row.number ?? row.yakCount) + return { + key: row.key || row.id || `${name || "yak-forecast"}-${index}`, + name, + label: name, + stock, + count: stock, + value: stock, + unit: row.unit || "头", + extra: row.extra || "", + } + }) + .filter((row) => row.name && Number.isFinite(row.stock)) +} + function buildBalanceDatasets(question) { const focusRow = findBalanceContextRow(question) const focusName = focusRow?.name || "红原县" return [ dataset("balance.target", `${focusName}草畜平衡`, [focusRow], "平台业务汇总数据"), dataset("balance.county", "全县草畜平衡", [countyBalanceRow], "平台业务汇总数据"), - dataset("balance.townRows", "乡镇草畜平衡", balanceTownRows, "平台业务汇总数据"), + dataset("balance.town", "乡镇草畜平衡", balanceTownRows, "平台业务汇总数据"), + dataset("balance.fallbackRows", "草畜平衡兜底数据", balanceFallbackRows, "平台业务汇总数据"), dataset("balance.params", "草畜平衡计算参数", [BALANCE_CALCULATION_PARAMS], "平台业务汇总数据"), ] } @@ -187,11 +287,16 @@ function buildBalanceDatasets(question) { function buildPastureDatasets() { return [ dataset("pasture.core", "牧户核心指标", pastureCoreRows, "平台业务汇总数据"), - dataset("pasture.townDistribution", "牧户乡镇分布", pastureTownRows, "平台业务汇总数据"), + dataset("pasture.topStats", "牧户顶部指标", pastureTopStats, "平台业务汇总数据"), + dataset("pasture.town", "牧户乡镇分布", pastureTownRows, "平台业务汇总数据"), dataset("pasture.online", "牧户在线率", pastureOnlineRows, "平台业务汇总数据"), + dataset("pasture.structure", "牧户年龄性别结构", pastureStructureRows, "平台业务汇总数据"), dataset("pasture.age", "牧户年龄结构", pastureAgeRows, "平台业务汇总数据"), dataset("pasture.gender", "牧户性别结构", pastureGenderRows, "平台业务汇总数据"), - dataset("pasture.examples", "牧户示例列表", pastureListRows, "平台业务汇总数据"), + dataset("pasture.list", "牧户示例列表", pastureListRows, "平台业务汇总数据"), + dataset("pasture.grassland", "牧户草场资源", pastureGrasslandRows, "平台业务汇总数据"), + dataset("pasture.season", "牧户季节草场", pastureSeasonRows, "平台业务汇总数据"), + dataset("pasture.map", "牧户地图统计", pastureMapRows, "平台业务汇总数据"), ] } @@ -202,8 +307,10 @@ function buildEcologyDatasets() { dataset("ecology.parkTownRows", "国家公园乡镇数据", parkTownRows, "平台业务汇总数据"), dataset("ecology.restorationTopStats", "生态修复项目汇总", restorationTopStats, "平台业务汇总数据"), dataset("ecology.engineeringTopStats", "生态工程分析指标", engineeringAnalysisTopStats, "平台业务汇总数据"), + dataset("ecology.engineeringProjectFeatures", "工程分析项目点", engineeringAnalysisProjectFeatures, "平台业务汇总数据"), dataset("ecology.parkPanels", "生态保护图表数据", flattenPanelRows(parkPanels), "平台业务汇总数据"), dataset("ecology.restorationPanels", "生态修复图表数据", flattenPanelRows(restorationPanels), "平台业务汇总数据"), + dataset("ecology.engineeringAnalysisPanels", "工程分析图表数据", flattenPanelRows(engineeringAnalysisPanels), "平台业务汇总数据"), dataset("home.resourceComposition", "林草湿资源构成", resourceCompositionRows, "平台业务汇总数据"), dataset("home.townSynergy", "乡镇林草湿与牦牛协同", townSynergyRows, "平台业务汇总数据"), ] @@ -215,10 +322,16 @@ function buildIndustryDatasets() { dataset("industry.overview", "产业链总览", industryOverviewRows, "平台业务汇总数据"), dataset("industry.stageComposition", "产业阶段构成", stageCompositionRows, "平台业务汇总数据"), dataset("industry.stageRows", "产业阶段说明", industryStageRows, "平台业务汇总数据"), + dataset("industry.breedingLevel", "养殖水平", breedingLevelRows, "平台业务汇总数据"), dataset("industry.forageSecurity", "饲草保障能力", forageSecurityRows, "平台业务汇总数据"), dataset("industry.value", "产业价值指标", industryValueRows, "平台业务汇总数据"), dataset("industry.processingAbility", "加工能力指标", processingAbilityRows, "平台业务汇总数据"), dataset("industry.serviceCapability", "服务能力指标", serviceCapabilityRows, "平台业务汇总数据"), + dataset("industry.financial", "金融服务", industryFinancialRows, "平台业务汇总数据"), + dataset("industry.supportPoints", "饲草储备点位", supportPointRows, "平台业务汇总数据"), + dataset("industry.processingPoints", "加工屠宰点位", processingPointRows, "平台业务汇总数据"), + dataset("industry.points", "产业链点位", industryPointRows, "平台业务汇总数据"), + dataset("industry.list", "产业链主体列表", industryChainListRows, "平台业务汇总数据"), ] } @@ -369,6 +482,12 @@ function getFriendlyContextError(error, fallback) { return fallback } +function toDatasetNumber(value) { + if (value === undefined || value === null || value === "") return NaN + const number = Number(String(value).replace(/,/g, "").replace(/%/g, "")) + return Number.isFinite(number) ? number : NaN +} + function formatNumber(value) { const number = Number(String(value).replace(/,/g, "")) if (!Number.isFinite(number)) return value || "--" diff --git a/src/views/wisdomManagement/index.vue b/src/views/wisdomManagement/index.vue index eee522e..6641ab5 100644 --- a/src/views/wisdomManagement/index.vue +++ b/src/views/wisdomManagement/index.vue @@ -217,7 +217,7 @@