Files
ln-bi/src/shared/analytics/catalog.ts
T

427 lines
15 KiB
TypeScript

export type MetricDomain = 'mileage' | 'hydrogen' | 'electric' | 'etc';
export type MetricUnit = 'km' | 'kg' | 'kwh' | 'cny' | 'cny-per-kwh' | 'percent' | 'vehicle' | 'station' | 'order' | 'passage' | 'timestamp';
export type MetricAggregation = 'sum' | 'count' | 'derived' | 'weighted-ratio' | 'distinct-count' | 'latest';
export type MetricTimeSemantics = 'flow' | 'snapshot' | 'freshness';
export interface MetricDefinition {
id: string;
domain: MetricDomain;
label: string;
description: string;
unit: MetricUnit;
aggregation: MetricAggregation;
timeSemantics: MetricTimeSemantics;
formula: string;
sources: readonly string[];
dimensions: readonly string[];
drillEntity: 'vehicle' | 'assessment-target' | 'hydrogen-station' | 'hydrogen-customer' | 'hydrogen-order' | 'electric-charge-order' | 'etc-toll-record' | 'etc-bill';
}
export const METRIC_CATALOG_VERSION = 5;
const MILEAGE_METRICS: readonly MetricDefinition[] = [
{
id: 'mileage.daily_total',
domain: 'mileage',
label: '当日总里程',
description: '所选自然日内有效车辆日里程之和。',
unit: 'km',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(vehicle_daily_mileage_km)',
sources: ['OneOS mileage API'],
dimensions: ['date', 'department', 'region', 'customer', 'vehicle-model', 'plate'],
drillEntity: 'vehicle',
},
{
id: 'mileage.period_total',
domain: 'mileage',
label: '区间总里程',
description: '所选自然日区间内有效车辆日里程之和。',
unit: 'km',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(vehicle_daily_mileage_km) OVER selected_date_range',
sources: ['OneOS mileage range API'],
dimensions: ['date', 'department', 'region', 'customer', 'vehicle-model', 'plate'],
drillEntity: 'vehicle',
},
{
id: 'mileage.vehicle_count',
domain: 'mileage',
label: '监控车辆数',
description: '当前权限和筛选范围内的去重车辆数量。',
unit: 'vehicle',
aggregation: 'distinct-count',
timeSemantics: 'snapshot',
formula: 'COUNT(DISTINCT vehicle_id)',
sources: ['Asset database'],
dimensions: ['department', 'region', 'customer', 'vehicle-model'],
drillEntity: 'vehicle',
},
{
id: 'mileage.assessment_completion_rate',
domain: 'mileage',
label: '考核完成率',
description: '按目标里程加权汇总,不平均车辆或考核组的完成率。',
unit: 'percent',
aggregation: 'weighted-ratio',
timeSemantics: 'snapshot',
formula: 'SUM(completed_mileage_km) / NULLIF(SUM(target_mileage_km), 0) * 100',
sources: ['Mileage assessment database'],
dimensions: ['assessment-year', 'assessment-target', 'department', 'customer', 'plate'],
drillEntity: 'assessment-target',
},
{
id: 'mileage.data_freshness',
domain: 'mileage',
label: '数据统计时间',
description: '当前选源协议最后一条有效里程数据的业务时间。',
unit: 'timestamp',
aggregation: 'latest',
timeSemantics: 'freshness',
formula: 'COALESCE(data_time, updated_at, calculated_at)',
sources: ['OneOS mileage API'],
dimensions: ['source-protocol', 'plate'],
drillEntity: 'vehicle',
},
];
const HYDROGEN_METRICS: readonly MetricDefinition[] = [
{
id: 'hydrogen.refuel_mass',
domain: 'hydrogen',
label: '加氢量',
description: '有效加氢账本订单的实际加注质量之和。',
unit: 'kg',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(amount_kg) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'payer', 'plate'],
drillEntity: 'hydrogen-order',
},
{
id: 'hydrogen.refuel_cost',
domain: 'hydrogen',
label: '加氢成本',
description: '有效加氢订单记录的站点结算成本之和,不代表客户应收。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(cost_total) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'payer', 'plate'],
drillEntity: 'hydrogen-order',
},
{
id: 'hydrogen.customer_revenue',
domain: 'hydrogen',
label: '客户收入',
description: '有效加氢订单账本中客户应收金额 fee_total 之和。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(fee_total) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'plate'],
drillEntity: 'hydrogen-customer',
},
{
id: 'hydrogen.customer_order_cost',
domain: 'hydrogen',
label: '客户单成本',
description: '存在客户单价或客户应收金额的订单,其站点结算成本之和。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(cost_total) WHERE del_flag = '0' AND (customer_price > 0 OR fee_total > 0)",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'plate'],
drillEntity: 'hydrogen-customer',
},
{
id: 'hydrogen.customer_order_gross_profit',
domain: 'hydrogen',
label: '客户单毛利',
description: '客户收入减去客户单对应的站点结算成本,不包含公司承担订单及其他经营费用。',
unit: 'cny',
aggregation: 'derived',
timeSemantics: 'flow',
formula: 'hydrogen.customer_revenue - hydrogen.customer_order_cost',
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer'],
drillEntity: 'hydrogen-customer',
},
{
id: 'hydrogen.refuel_order_count',
domain: 'hydrogen',
label: '加氢订单数',
description: '有效加氢账本订单数量。',
unit: 'order',
aggregation: 'count',
timeSemantics: 'flow',
formula: "COUNT(*) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'payer', 'plate'],
drillEntity: 'hydrogen-order',
},
{
id: 'hydrogen.station_count',
domain: 'hydrogen',
label: '有加注站点数',
description: '所选时间和筛选范围内产生有效加氢订单的去重站点数量。',
unit: 'station',
aggregation: 'distinct-count',
timeSemantics: 'flow',
formula: "COUNT(DISTINCT station_id) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger', 'hydrogen_station'],
dimensions: ['date', 'region', 'payer'],
drillEntity: 'hydrogen-station',
},
{
id: 'hydrogen.vehicle_count',
domain: 'hydrogen',
label: '加氢车辆数',
description: '所选时间和筛选范围内产生有效加氢订单的去重车牌数量。',
unit: 'vehicle',
aggregation: 'distinct-count',
timeSemantics: 'flow',
formula: "COUNT(DISTINCT plate_number) WHERE del_flag = '0'",
sources: ['hydrogen_fuel_ledger'],
dimensions: ['date', 'station', 'region', 'customer', 'payer'],
drillEntity: 'vehicle',
},
];
const ELECTRIC_METRICS: readonly MetricDefinition[] = [
{
id: 'electric.charge_energy',
domain: 'electric',
label: '充电量',
description: '所选时间和车辆归属范围内,去重导入充电订单的电量之和。',
unit: 'kwh',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(kwh)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer', 'plate', 'order-status'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.charge_total_fee',
domain: 'electric',
label: '充电费用',
description: '导入账单的充电费用之和;当前数据中等于充电电费与充电服务费之和,零费用订单仍参与统计。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(fee)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer', 'plate', 'order-status'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.electricity_fee',
domain: 'electric',
label: '充电电费',
description: '导入账单中充电电费字段 e_fee 之和,不含服务费。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(e_fee)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer', 'plate'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.service_fee',
domain: 'electric',
label: '充电服务费',
description: '导入账单中充电服务费字段 service_fee 之和。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: 'SUM(service_fee)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer', 'plate'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.blended_cost_intensity',
domain: 'electric',
label: '综合费用强度',
description: '充电费用除以充电量;包含零费用订单,用于观察综合支出强度,不等同于合同电价或单笔订单单价。',
unit: 'cny-per-kwh',
aggregation: 'weighted-ratio',
timeSemantics: 'flow',
formula: 'SUM(fee) / NULLIF(SUM(kwh), 0)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.charge_order_count',
domain: 'electric',
label: '充电订单数',
description: '导入充电账单中按订单编号去重后的订单数量。',
unit: 'order',
aggregation: 'distinct-count',
timeSemantics: 'flow',
formula: 'COUNT(DISTINCT order_no)',
sources: ['bi_ele_charge_record'],
dimensions: ['date', 'station', 'region', 'operating-company', 'vehicle-kind', 'customer', 'plate', 'order-status'],
drillEntity: 'electric-charge-order',
},
{
id: 'electric.data_freshness',
domain: 'electric',
label: '电能数据截至时间',
description: '已导入充电订单中最后一条订单的业务开始时间,用于区分当期零业务与数据尚未更新。',
unit: 'timestamp',
aggregation: 'latest',
timeSemantics: 'freshness',
formula: 'MAX(start_time)',
sources: ['bi_ele_charge_record'],
dimensions: ['station', 'vehicle-kind'],
drillEntity: 'electric-charge-order',
},
];
const ETC_METRICS: readonly MetricDefinition[] = [
{
id: 'etc.toll_amount',
domain: 'etc',
label: '通行费',
description: '有效 ETC 通行记录中的道路通行费之和,不含服务费。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(toll_amount) WHERE del_flag = '0'",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'cost-type', 'payment-mode', 'contract-match'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.service_fee',
domain: 'etc',
label: 'ETC 服务费',
description: '有效 ETC 通行记录中的服务费之和。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(service_fee) WHERE del_flag = '0'",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'cost-type', 'payment-mode'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.total_amount',
domain: 'etc',
label: 'ETC 总费用',
description: '有效 ETC 通行记录的总金额之和,业务定义为通行费加服务费。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(total_amount) WHERE del_flag = '0'",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'cost-type', 'payment-mode', 'deduction-status'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.passage_count',
domain: 'etc',
label: '通行次数',
description: '有效 ETC 通行记录按记录编码去重后的通行次数。',
unit: 'passage',
aggregation: 'distinct-count',
timeSemantics: 'flow',
formula: "COUNT(DISTINCT record_code) WHERE del_flag = '0'",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'cost-type', 'payment-mode'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.vehicle_count',
domain: 'etc',
label: '通行车辆数',
description: '所选范围内产生有效 ETC 通行记录的去重车牌数量。',
unit: 'vehicle',
aggregation: 'distinct-count',
timeSemantics: 'flow',
formula: "COUNT(DISTINCT plate_number) WHERE del_flag = '0'",
sources: ['etc_toll_record'],
dimensions: ['date', 'customer', 'route', 'cost-type'],
drillEntity: 'vehicle',
},
{
id: 'etc.company_borne_amount',
domain: 'etc',
label: '我方承担 ETC 费用',
description: '费用承担方 cost_type=2 的有效 ETC 总费用。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(total_amount) WHERE del_flag = '0' AND cost_type = 2",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'payment-mode'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.customer_borne_amount',
domain: 'etc',
label: '客户承担 ETC 费用',
description: '费用承担方 cost_type=1 的有效 ETC 总费用。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(total_amount) WHERE del_flag = '0' AND cost_type = 1",
sources: ['etc_toll_record'],
dimensions: ['date', 'plate', 'customer', 'route', 'payment-mode'],
drillEntity: 'etc-toll-record',
},
{
id: 'etc.bill_receivable',
domain: 'etc',
label: 'ETC 账单应收',
description: '有效 ETC 账单的应收金额之和,与通行记录费用属于不同结算粒度。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(receivable_amount) WHERE del_flag = '0'",
sources: ['energy_etc_bill'],
dimensions: ['bill-period', 'customer', 'payment-status', 'review-status', 'finance-status'],
drillEntity: 'etc-bill',
},
{
id: 'etc.bill_paid_amount',
domain: 'etc',
label: 'ETC 账单已收',
description: '有效 ETC 账单的已支付金额之和。',
unit: 'cny',
aggregation: 'sum',
timeSemantics: 'flow',
formula: "SUM(paid_amount) WHERE del_flag = '0'",
sources: ['energy_etc_bill'],
dimensions: ['bill-period', 'customer', 'payment-status', 'review-status', 'finance-status'],
drillEntity: 'etc-bill',
},
];
export const METRIC_CATALOG: readonly MetricDefinition[] = [
...MILEAGE_METRICS,
...HYDROGEN_METRICS,
...ELECTRIC_METRICS,
...ETC_METRICS,
];
export function isMetricDomain(value: string): value is MetricDomain {
return METRIC_CATALOG.some(metric => metric.domain === value);
}
export function listMetricDefinitions(domain?: MetricDomain): MetricDefinition[] {
return METRIC_CATALOG
.filter(metric => !domain || metric.domain === domain)
.map(metric => ({ ...metric }));
}