m.basis))} />
)}
diff --git a/src/app/(marketing)/products/page.tsx b/src/app/(marketing)/products/page.tsx
index 4ce6d38..9d8fb75 100644
--- a/src/app/(marketing)/products/page.tsx
+++ b/src/app/(marketing)/products/page.tsx
@@ -6,7 +6,7 @@ import ProductsContentV3 from './products-content-v3';
export const metadata: Metadata = {
title: `产品矩阵 - ${COMPANY_INFO.displayName}`,
- description: `覆盖企业数字化全场景的产品矩阵,从数据智能到业务协同,每一款产品都经过实战验证。`,
+ description: `覆盖企业数字化核心场景的自研产品矩阵,从业务系统到数据智能,套装与专业产品线互为补充。`,
};
export const revalidate = 3600;
diff --git a/src/app/(marketing)/products/product-detail-content-v3.tsx b/src/app/(marketing)/products/product-detail-content-v3.tsx
index a4f0d10..75ba655 100644
--- a/src/app/(marketing)/products/product-detail-content-v3.tsx
+++ b/src/app/(marketing)/products/product-detail-content-v3.tsx
@@ -325,7 +325,7 @@ export default function ProductDetailContentV3({ product }: ProductDetailContent
{product.methodology && product.methodology.length > 0 && (
)}
@@ -341,12 +341,13 @@ export default function ProductDetailContentV3({ product }: ProductDetailContent
{product.dataProofs && product.dataProofs.length > 0 && (
({
value: dp.value,
label: dp.metric,
description: dp.description,
+ basis: dp.basis,
}))}
/>
)}
diff --git a/src/app/(marketing)/products/products-content-v3.tsx b/src/app/(marketing)/products/products-content-v3.tsx
index 75a2b89..b421a3f 100644
--- a/src/app/(marketing)/products/products-content-v3.tsx
+++ b/src/app/(marketing)/products/products-content-v3.tsx
@@ -10,6 +10,7 @@ import { BentoItem } from '@/components/sections/bento-grid';
import { ArrowRight, ArrowUpRight, Package, Cpu, TrendingUp, Users, Settings, FileText } from 'lucide-react';
import type { Product } from '@/lib/constants/products';
import { CTA_LABELS } from '@/lib/constants/cta-labels';
+import { weakestBasis } from '@/lib/constants/metrics-basis';
import { cn } from '@/lib/utils';
const EASE_SPRING = { type: 'spring', stiffness: 300, damping: 30 } as const;
@@ -146,7 +147,7 @@ function ProductCard({ product, size = 'small' }: { product: Product; size?: 'la
))}
{product.metrics && product.metrics.length > 0 && product.status !== '已发布' && (
-
+ m.basis))} className="-mt-2 mb-4" />
)}
@@ -319,7 +320,7 @@ function SuiteCombosSection({ products }: { products: Product[] }) {
推荐产品组合
- 经过实战验证的产品组合,发挥 1+1 > 2 的协同效应
+ 套装与专业产品线按场景互补,覆盖从核心业务系统到数据分析的关键链路
diff --git a/src/app/(marketing)/products/standalone/[id]/client.tsx b/src/app/(marketing)/products/standalone/[id]/client.tsx
index 6432fa5..8bb2923 100644
--- a/src/app/(marketing)/products/standalone/[id]/client.tsx
+++ b/src/app/(marketing)/products/standalone/[id]/client.tsx
@@ -178,7 +178,7 @@ export function StandaloneProductClient({ item }: StandaloneProductClientProps)
技术参数
- 关键性能指标与技术指标,经实际场景验证。
+ 关键性能指标与技术规格,为产品设计目标口径,尚无第三方实测报告。
diff --git a/src/app/(marketing)/services/services-content-v3.tsx b/src/app/(marketing)/services/services-content-v3.tsx
index 5396e35..0ca1345 100644
--- a/src/app/(marketing)/services/services-content-v3.tsx
+++ b/src/app/(marketing)/services/services-content-v3.tsx
@@ -6,12 +6,13 @@ import { StaticLink } from '@/components/ui/static-link';
import { CTAButton } from '@/components/ui/cta-button';
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
-import type { Service } from '@/lib/constants/services';
+import type { Service, ServiceMetric } from '@/lib/constants/services';
+import { weakestBasis } from '@/lib/constants/metrics-basis';
const EASE_OUT = [0.22, 1, 0.36, 1] as const;
/** Fallback metrics keyed by service ID — used when CMS data does not provide metrics */
-const SERVICE_UI_META: Record = {
+const SERVICE_UI_META: Record = {
consulting: { metrics: [{ value: '10+', label: '人核心团队' }, { value: '6', label: '行业场景' }] },
software: { metrics: [{ value: '6', label: '款自研产品' }, { value: 'A+', label: '代码质量' }] },
data: { metrics: [{ value: '99.9%', label: '系统可用性' }, { value: '24/7', label: '运维支持' }] },
@@ -231,7 +232,9 @@ function ServicesGridSection({ services, pageCopy }: { services?: Service[]; pag
))}
- {service.metrics.length > 0 && }
+ {service.metrics.length > 0 && (
+ m.basis))} className="mb-6" />
+ )}
了解详情
diff --git a/src/app/(marketing)/solutions/solutions-content-v3.tsx b/src/app/(marketing)/solutions/solutions-content-v3.tsx
index e8c75c6..c85d289 100644
--- a/src/app/(marketing)/solutions/solutions-content-v3.tsx
+++ b/src/app/(marketing)/solutions/solutions-content-v3.tsx
@@ -6,6 +6,7 @@ import { StaticLink } from '@/components/ui/static-link';
import { CTAButton } from '@/components/ui/cta-button';
import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
import { CTA_LABELS, canonicalCtaLabel } from '@/lib/constants/cta-labels';
+import { weakestBasis } from '@/lib/constants/metrics-basis';
import { type Solution } from '@/lib/constants/solutions';
import type { Product } from '@/lib/constants/products';
@@ -135,7 +136,7 @@ function SolutionCard({ solution, index, products, featured = false }: { solutio
))}
{solution.outcomes && solution.outcomes.length > 0 && (
-
+ o.basis))} className="-mt-2 mb-4" />
)}
diff --git a/src/components/content/sections.tsx b/src/components/content/sections.tsx
index d7cde5c..8f07ab5 100644
--- a/src/components/content/sections.tsx
+++ b/src/components/content/sections.tsx
@@ -5,6 +5,8 @@ import { ScrollReveal, StaggerReveal } from '@/components/ui/scroll-reveal';
import { EASE_OUT } from '@/components/ui/page-decoration';
import { CheckCircle2, ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
+import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
+import { weakestBasis, type MetricBasis } from '@/lib/constants/metrics-basis';
import { useState } from 'react';
export interface MethodologyLayer {
@@ -318,6 +320,7 @@ export interface DataMetric {
description?: string;
trend?: string;
trendUp?: boolean;
+ basis?: MetricBasis;
}
export interface DataProofSectionProps {
@@ -328,6 +331,7 @@ export interface DataProofSectionProps {
export function DataProofSection({ title, subtitle, metrics }: DataProofSectionProps) {
if (!metrics || metrics.length === 0) return null;
+ const basis = weakestBasis(metrics.map((m) => m.basis));
return (
@@ -375,6 +379,7 @@ export function DataProofSection({ title, subtitle, metrics }: DataProofSectionP
))}
+
);
diff --git a/src/components/detail/detail-product-value.tsx b/src/components/detail/detail-product-value.tsx
index 9bdfa10..f061215 100644
--- a/src/components/detail/detail-product-value.tsx
+++ b/src/components/detail/detail-product-value.tsx
@@ -15,6 +15,8 @@ import {
type LucideIcon,
} from 'lucide-react';
import type { Product } from '@/lib/constants/products';
+import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
+import { weakestBasis } from '@/lib/constants/metrics-basis';
interface ProductValueSectionProps {
product: Product;
@@ -250,11 +252,11 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
- 用真实效果证明价值
+ 以可量化的目标约束交付
@@ -275,6 +277,10 @@ export function ProductValueSection({ product }: ProductValueSectionProps) {
);
})}
+ proof.basis))}
+ className="mt-8"
+ />
)}
diff --git a/src/components/detail/detail-trust-section.tsx b/src/components/detail/detail-trust-section.tsx
index 9c48cdc..55e3706 100644
--- a/src/components/detail/detail-trust-section.tsx
+++ b/src/components/detail/detail-trust-section.tsx
@@ -5,6 +5,8 @@ import { useRef, useEffect, useState } from 'react';
import { CaseStudyCard } from './detail-case-study';
import { CertificationList } from './detail-certification';
import type { CaseStudy, DataProof, Certification } from '@/lib/constants/products';
+import { MetricsBasisNote } from '@/components/ui/metrics-basis-note';
+import { weakestBasis } from '@/lib/constants/metrics-basis';
interface DetailTrustSectionProps {
caseStudies?: CaseStudy[];
@@ -84,13 +86,13 @@ export function DetailTrustSection({
- 来自真实客户的使用数据,
+ 以设计目标约束交付,
- 让效果可衡量、可验证
+ 在首批共创中转为可核验数据
@@ -114,6 +116,10 @@ export function DetailTrustSection({
))}
+ proof.basis))}
+ className="mt-8"
+ />
)}
diff --git a/src/components/detail/detail.test.tsx b/src/components/detail/detail.test.tsx
index 4e771fa..fc0ffb7 100644
--- a/src/components/detail/detail.test.tsx
+++ b/src/components/detail/detail.test.tsx
@@ -435,14 +435,14 @@ describe('ProductValueSection', () => {
it('should render data proofs when available', async () => {
const { ProductValueSection } = await import('./detail-product-value');
render();
- expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
+ expect(screen.getByText('以可量化的目标约束交付')).toBeInTheDocument();
});
it('should not render data proofs section when empty', async () => {
const { ProductValueSection } = await import('./detail-product-value');
const productWithoutData = { ...mockProduct, dataProofs: [] };
render();
- expect(screen.queryByText('用真实效果证明价值')).not.toBeInTheDocument();
+ expect(screen.queryByText('以可量化的目标约束交付')).not.toBeInTheDocument();
});
});
@@ -462,8 +462,10 @@ describe('DetailTrustSection', () => {
dataProofs={mockProduct.dataProofs}
/>
);
- expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
+ expect(screen.getByText('目标口径')).toBeInTheDocument();
expect(screen.getByText(mockProduct.dataProofs[0]!.metric)).toBeInTheDocument();
+ // 未声明 basis 的 dataProofs 必须自动落最弱口径角注,不得裸奔
+ expect(screen.getByTestId('metrics-basis-note')).toHaveTextContent('目标口径,非既成结果');
});
it('should render case studies', async () => {
@@ -500,7 +502,7 @@ describe('DetailTrustSection', () => {
accentColor="#C41E3A"
/>
);
- expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
+ expect(screen.getByText('目标口径')).toBeInTheDocument();
expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument();
expect(screen.getByText('资质认证')).toBeInTheDocument();
});
diff --git a/src/components/detail/early-access-notice.tsx b/src/components/detail/early-access-notice.tsx
index 218c8fc..948ed3d 100644
--- a/src/components/detail/early-access-notice.tsx
+++ b/src/components/detail/early-access-notice.tsx
@@ -9,7 +9,7 @@
* - 内容源于 `EARLY_ACCESS` 常量(company.ts L119),单一真源。
*
* 与 L3SignalsSlot 关系:EarlyAccessNotice 是 L3EmptyFallback 的次要兜底块,
- * 在 signals(4 类信号卡)下方呈现——回答「为什么没有真实客户案例」这个问题。
+ * 在 signals(4 类信号卡)下方呈现——回答「为什么还没有可公开案例」这个问题。
*/
import Link from 'next/link';
diff --git a/src/components/ui/metrics-basis-note.tsx b/src/components/ui/metrics-basis-note.tsx
index 14ef77c..6a5c79c 100644
--- a/src/components/ui/metrics-basis-note.tsx
+++ b/src/components/ui/metrics-basis-note.tsx
@@ -1,14 +1,21 @@
import { cn } from '@/lib/utils';
-import { METRICS_BASIS_NOTE } from '@/lib/constants/metrics-basis';
+import { METRIC_BASIS_NOTES, resolveMetricBasis, type MetricBasis } from '@/lib/constants/metrics-basis';
/**
* 结果型数字(提升百分比 / 可用性 / 达标率等)下方的口径角注。
- * 全站统一文案,见 METRICS_BASIS_NOTE。供 solutions / services / home / about / products 复用。
+ * 文案按数字声明的 `basis` 口径解析,未声明一律按最弱的 target 处理,见 METRIC_BASIS_NOTES。
+ * 供 solutions / services / home / about / products 与详情页 DataProofSection 复用。
*/
-export function MetricsBasisNote({ className }: { className?: string }) {
+export function MetricsBasisNote({
+ className,
+ basis = 'target',
+}: {
+ className?: string;
+ basis?: MetricBasis | string | null;
+}) {
return (
- {METRICS_BASIS_NOTE}
+ {METRIC_BASIS_NOTES[resolveMetricBasis(basis)]}
);
}
diff --git a/src/lib/cms/content-types.ts b/src/lib/cms/content-types.ts
index fb7d225..0b13e9d 100644
--- a/src/lib/cms/content-types.ts
+++ b/src/lib/cms/content-types.ts
@@ -1,5 +1,22 @@
import type { FieldDefinition } from './types';
+/**
+ * 数字口径声明字段——所有以大字数字样式呈现的指标(metrics / outcomes / dataProofs)必须携带。
+ * 渲染端按此自动附口径角注;留空一律按最弱的 target 处理,见 lib/constants/metrics-basis.ts。
+ * 只有具备可核验实测出处(报告、监控、已交付客户数据)时才应选 verified。
+ */
+const metricBasisField: FieldDefinition = {
+ name: 'basis',
+ type: 'select',
+ label: '数据口径',
+ description: '留空按「目标口径」处理并自动标注;仅在有可核验实测出处时选「已验证」',
+ options: [
+ { label: '目标口径(设计/承诺值,非既成结果)', value: 'target' },
+ { label: '团队历史项目成果(非本公司交付数据)', value: 'team-history' },
+ { label: '已验证(有可核验实测出处)', value: 'verified' },
+ ],
+};
+
export interface ContentTypeConfig {
model: {
id: string;
@@ -164,6 +181,7 @@ const serviceFields: FieldDefinition[] = [
fields: [
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'label', type: 'text', label: '标签', required: true },
+ { ...metricBasisField },
],
},
{
@@ -186,6 +204,7 @@ const serviceFields: FieldDefinition[] = [
{ name: 'metric', type: 'text', label: '指标名称', required: true },
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'description', type: 'text', label: '描述', required: true },
+ { ...metricBasisField },
],
},
{
@@ -363,6 +382,7 @@ const productFields: FieldDefinition[] = [
fields: [
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'label', type: 'text', label: '标签', required: true },
+ { ...metricBasisField },
],
},
{
@@ -385,6 +405,7 @@ const productFields: FieldDefinition[] = [
{ name: 'metric', type: 'text', label: '指标名称', required: true },
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'description', type: 'text', label: '描述', required: true },
+ { ...metricBasisField },
],
},
{
@@ -548,6 +569,7 @@ const standaloneProductFields: FieldDefinition[] = [
fields: [
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'label', type: 'text', label: '标签', required: true },
+ { ...metricBasisField },
],
},
{
@@ -570,6 +592,7 @@ const standaloneProductFields: FieldDefinition[] = [
{ name: 'metric', type: 'text', label: '指标名称', required: true },
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'description', type: 'text', label: '描述', required: true },
+ { ...metricBasisField },
],
},
{
@@ -755,6 +778,7 @@ const solutionFields: FieldDefinition[] = [
fields: [
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'label', type: 'text', label: '标签', required: true },
+ { ...metricBasisField },
],
},
{
@@ -1180,6 +1204,7 @@ const aboutPageFields: FieldDefinition[] = [
{ name: 'value', type: 'text', label: '数值', required: true },
{ name: 'label', type: 'text', label: '标签', required: true },
{ name: 'sub', type: 'text', label: '副标题' },
+ { ...metricBasisField },
],
},
{
diff --git a/src/lib/constants/metrics-basis.test.ts b/src/lib/constants/metrics-basis.test.ts
index a6f8d88..107ff62 100644
--- a/src/lib/constants/metrics-basis.test.ts
+++ b/src/lib/constants/metrics-basis.test.ts
@@ -1,12 +1,25 @@
/**
- * 结果型数字口径机械防线(2026-09-19 critique P0):
- * 「目标口径,非既成结果」必须来自 METRICS_BASIS_NOTE 单一真源;
- * 且每个呈现结果型数字的营销页必须接入 MetricsBasisNote。
+ * 数字口径机械防线(2026-09-19 critique P0 → 结构强制收尾):
+ * 1) 「目标口径,非既成结果」必须来自 METRICS_BASIS_NOTE 单一真源,不得散写字面量;
+ * 2) 每个呈现结果型数字的渲染源(含详情页共享数字组件)必须接入 MetricsBasisNote;
+ * 3) 走共享数字样式渲染的指标必须声明 basis(target|team-history|verified),
+ * 且口径解析对缺失/非法值保守回落到最弱的 target;
+ * 4) 禁止再引入「数字源自真实客户案例 / 经过实战验证」式虚构佐证(本站零公开客户案例)。
* 扫描范围:src/app + src/components 的 .ts/.tsx(排除测试文件与 _archive 归档)。
*/
import { readdirSync, readFileSync, statSync } from 'fs';
import { join } from 'path';
-import { METRICS_BASIS_NOTE } from './metrics-basis';
+import {
+ METRICS_BASIS_NOTE,
+ METRIC_BASES,
+ METRIC_BASIS_NOTES,
+ FORBIDDEN_PROOF_PHRASES,
+ resolveMetricBasis,
+ weakestBasis,
+} from './metrics-basis';
+import { PRODUCTS } from './products';
+import { SOLUTIONS } from './solutions';
+import { STANDALONE_PRODUCTS } from '../../../prisma/seeds/standalone-products';
const SOURCE_ROOTS = ['src/app', 'src/components'];
@@ -16,6 +29,10 @@ const REQUIRED_ANNOTATED_SOURCES = [
'src/app/(marketing)/solutions/solutions-content-v3.tsx',
'src/app/(marketing)/services/services-content-v3.tsx',
'src/app/(marketing)/home-content-v15.tsx',
+ // 详情页同样以大数字样式渲染 dataProofs,不得裸奔
+ 'src/components/content/sections.tsx',
+ 'src/components/detail/detail-trust-section.tsx',
+ 'src/components/detail/detail-product-value.tsx',
];
function collectSourceFiles(dir: string, acc: string[] = []): string[] {
@@ -32,11 +49,29 @@ function collectSourceFiles(dir: string, acc: string[] = []): string[] {
return acc;
}
+/** 把三处 seed 里的全部数字条目摊平,用于口径声明完整性断言。 */
+function allSeedMetricBases(): Array {
+ const bases: Array = [];
+ for (const p of [...PRODUCTS, ...STANDALONE_PRODUCTS]) {
+ (p.metrics ?? []).forEach((m) => bases.push(m.basis));
+ (p.dataProofs ?? []).forEach((d) => bases.push(d.basis));
+ }
+ for (const s of SOLUTIONS) {
+ (s.outcomes ?? []).forEach((o) => bases.push(o.basis));
+ (s.dataProofs ?? []).forEach((d) => bases.push(d.basis));
+ }
+ return bases;
+}
+
describe('结果型数字口径角注防线', () => {
it('METRICS_BASIS_NOTE 固定为「目标口径,非既成结果」', () => {
expect(METRICS_BASIS_NOTE).toBe('目标口径,非既成结果');
});
+ it('METRICS_BASIS_NOTE 等于最弱口径 target 的文案', () => {
+ expect(METRICS_BASIS_NOTE).toBe(METRIC_BASIS_NOTES.target);
+ });
+
it('生产源码不得硬编码口径角注字面量(必须引用单一真源)', () => {
const violations: string[] = [];
for (const root of SOURCE_ROOTS) {
@@ -63,3 +98,86 @@ describe('结果型数字口径角注防线', () => {
}
});
});
+
+describe('basis 口径解析', () => {
+ it('枚举固定为 target / team-history / verified', () => {
+ expect(METRIC_BASES).toEqual(['target', 'team-history', 'verified']);
+ });
+
+ it('三个口径均有文案,无空串', () => {
+ for (const basis of METRIC_BASES) {
+ expect(METRIC_BASIS_NOTES[basis].length).toBeGreaterThan(0);
+ }
+ });
+
+ it('合法口径原样解析(正控制,防匹配零的假绿)', () => {
+ expect(resolveMetricBasis('verified')).toBe('verified');
+ expect(resolveMetricBasis('team-history')).toBe('team-history');
+ expect(resolveMetricBasis('target')).toBe('target');
+ });
+
+ it('缺失/非法/大小写错误一律保守回落到 target', () => {
+ expect(resolveMetricBasis(undefined)).toBe('target');
+ expect(resolveMetricBasis(null)).toBe('target');
+ expect(resolveMetricBasis('VERIFIED')).toBe('target');
+ expect(resolveMetricBasis('已验证')).toBe('target');
+ expect(resolveMetricBasis('verified ')).toBe('target');
+ });
+
+ it('weakestBasis 取最弱口径:任一 target 存在即整体为 target', () => {
+ expect(weakestBasis(['verified', 'team-history'])).toBe('team-history');
+ expect(weakestBasis(['verified', 'target'])).toBe('target');
+ expect(weakestBasis(['verified', 'verified'])).toBe('verified');
+ });
+
+ it('weakestBasis 空数组与含未声明项均落到 target', () => {
+ expect(weakestBasis([])).toBe('target');
+ expect(weakestBasis(['verified', undefined])).toBe('target');
+ });
+});
+
+describe('seed 数字必须显式声明口径', () => {
+ it('存在可检出的数字条目(覆盖证明,防空跑)', () => {
+ expect(allSeedMetricBases().length).toBeGreaterThan(40);
+ });
+
+ it('每条 metrics / outcomes / dataProofs 均声明 basis', () => {
+ const missing = allSeedMetricBases().filter((b) => !b);
+ expect(missing).toEqual([]);
+ });
+
+ it('声明的 basis 全部合法', () => {
+ const invalid = allSeedMetricBases().filter(
+ (b) => !(METRIC_BASES as readonly string[]).includes(String(b)),
+ );
+ expect(invalid).toEqual([]);
+ });
+
+ it('当前无任何条目自称 verified(零编造:尚无客户实测数据)', () => {
+ const claimed = allSeedMetricBases().filter((b) => b === 'verified');
+ expect(claimed).toEqual([]);
+ });
+});
+
+describe('虚构佐证禁词防线', () => {
+ it('正控制:禁词表非空且每条均可被检出', () => {
+ expect(FORBIDDEN_PROOF_PHRASES.length).toBeGreaterThan(0);
+ for (const phrase of FORBIDDEN_PROOF_PHRASES) {
+ expect(`示例文案${phrase}结尾`).toContain(phrase);
+ }
+ });
+
+ it('生产源码不含「真实客户案例 / 实战验证」式虚构佐证', () => {
+ const violations: string[] = [];
+ for (const root of SOURCE_ROOTS) {
+ for (const file of collectSourceFiles(root)) {
+ const lines = readFileSync(file, 'utf-8').split('\n');
+ lines.forEach((line, i) => {
+ const hit = FORBIDDEN_PROOF_PHRASES.find((p) => line.includes(p));
+ if (hit) violations.push(`${file}:${i + 1} ${hit}`);
+ });
+ }
+ }
+ expect(violations).toEqual([]);
+ });
+});
diff --git a/src/lib/constants/metrics-basis.ts b/src/lib/constants/metrics-basis.ts
index 53727c4..bd212d3 100644
--- a/src/lib/constants/metrics-basis.ts
+++ b/src/lib/constants/metrics-basis.ts
@@ -1,9 +1,59 @@
/**
- * METRICS_BASIS_NOTE — 全站「结果型数字」口径角注单一真源。
+ * 数字口径(metric basis)单一真源。
*
- * 2026-09-19 critique P0:solutions / services / home / about 把「30% 库存周转提升」
- * 「99.9% 可用性」这类目标值/能力值以既成结果口吻呈现,缺出处即构成误导。
- * 与 CTA_LABELS 同属渲染入口的诚实性防线:数字保留,但强制附口径说明。
- * 根治方式是补充真实案例数据或 admin 修正内容,此处为最后一道防线。
+ * 2026-09-19 critique P0:solutions / services / home / about / products 把「30% 库存周转提升」
+ * 「99.9% 系统可用率」这类目标值/能力值以既成结果口吻呈现,缺出处即构成误导。
+ *
+ * 本文件把「每个数字必须声明口径」从个别页的自觉升级为结构约束:数字携带 `basis`,
+ * 渲染端据此自动附角注;未声明或声明非法一律按最弱口径 `target` 处理(保守默认,
+ * 宁可多标注也不裸奔)。真实出处由业务在 admin 录入,代码零虚构。
*/
-export const METRICS_BASIS_NOTE = '目标口径,非既成结果';
+
+/** 由弱到强:聚合一组数字时取最弱者,保证角注不夸大。 */
+export const METRIC_BASES = ['target', 'team-history', 'verified'] as const;
+
+export type MetricBasis = (typeof METRIC_BASES)[number];
+
+const BASIS_STRENGTH: Record = {
+ target: 0,
+ 'team-history': 1,
+ verified: 2,
+};
+
+/** 各口径对应的角注文案。 */
+export const METRIC_BASIS_NOTES: Record = {
+ target: '目标口径,非既成结果',
+ 'team-history': '团队历史项目成果,非本公司交付数据',
+ verified: '已有可核验的实测出处',
+};
+
+/** 全站默认口径角注(= 最弱口径 target 的文案)。 */
+export const METRICS_BASIS_NOTE = METRIC_BASIS_NOTES.target;
+
+/** 把数据里的任意值归一为合法口径;缺失/非法一律回落到最弱的 `target`。 */
+export function resolveMetricBasis(basis?: string | null): MetricBasis {
+ return (METRIC_BASES as readonly string[]).includes(String(basis))
+ ? (basis as MetricBasis)
+ : 'target';
+}
+
+/** 一组数字的聚合口径:取最弱的一个。 */
+export function weakestBasis(bases: Array): MetricBasis {
+ if (bases.length === 0) return 'target';
+ return bases.map(resolveMetricBasis).reduce(
+ (weakest, cur) => (BASIS_STRENGTH[cur] < BASIS_STRENGTH[weakest] ? cur : weakest),
+ resolveMetricBasis(bases[0]),
+ );
+}
+
+/**
+ * 虚构佐证禁词表:本站 2026-01 成立、零公开客户案例(见 early-access-notice),
+ * 因此任何「数字源自真实客户案例 / 经过实战验证」的措辞都是编造佐证。
+ * 由 metrics-basis.test.ts 在 src/app + src/components 全源码扫描强制。
+ */
+export const FORBIDDEN_PROOF_PHRASES = [
+ '源自真实客户案例',
+ '真实客户案例',
+ '经过实战验证',
+ '实战验证',
+] as const;
diff --git a/src/lib/constants/products.ts b/src/lib/constants/products.ts
index 13fd764..1659125 100644
--- a/src/lib/constants/products.ts
+++ b/src/lib/constants/products.ts
@@ -1,3 +1,5 @@
+import type { MetricBasis } from './metrics-basis';
+
export interface CaseStudy {
client: string;
industry: string;
@@ -10,6 +12,7 @@ export interface DataProof {
metric: string;
value: string;
description: string;
+ basis?: MetricBasis;
}
export interface Certification {
@@ -78,7 +81,7 @@ export interface Product {
techStack?: TechStackCategory[];
faqs?: FAQItem[];
scenario?: string;
- metrics?: { value: string; label: string }[];
+ metrics?: { value: string; label: string; basis?: MetricBasis }[];
bundle: 'enterprise' | 'standalone';
capabilities?: string[];
}
diff --git a/src/lib/constants/services.ts b/src/lib/constants/services.ts
index 61d2363..2facc2c 100644
--- a/src/lib/constants/services.ts
+++ b/src/lib/constants/services.ts
@@ -37,6 +37,7 @@ export interface FAQItem {
export interface ServiceMetric {
value: string;
label: string;
+ basis?: import('./metrics-basis').MetricBasis;
}
export interface Service {
diff --git a/src/lib/constants/solutions.ts b/src/lib/constants/solutions.ts
index 78cbd10..2b94288 100644
--- a/src/lib/constants/solutions.ts
+++ b/src/lib/constants/solutions.ts
@@ -24,7 +24,7 @@ export interface Solution {
dataProofs?: import('./products').DataProof[];
certifications?: import('./products').Certification[];
painPoints?: string[];
- outcomes?: { value: string; label: string }[];
+ outcomes?: { value: string; label: string; basis?: import('./metrics-basis').MetricBasis }[];
recommendedProducts?: string[];
}