feat: 新增各渠道收入占比

This commit is contained in:
R524809
2026-09-07 17:42:45 +08:00
parent 9605228c3f
commit 44c6437b64
9 changed files with 234 additions and 3 deletions
+49 -2
View File
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const RAW_DIR = path.join(ROOT, 'data/ppmt/raw');
const RAWDATA_DIR = path.join(ROOT, 'data/ppmt/rawData');
const OUTPUT_DIR = path.join(ROOT, 'data/ppmt');
const FILES = {
@@ -565,8 +566,8 @@ writeViewFiles('ppmt_expense_structure', ['period', 'sales_expense', 'admin_expe
sales_expense: 2, admin_expense: 2, financial_expense: 2, four_expense_rate: 1,
});
function readRuntimeRows(filename) {
const [header, ...lines] = fs.readFileSync(path.join(OUTPUT_DIR, filename), 'utf8').trim().split(/\r?\n/);
function readCsvRows(dir, filename) {
const [header, ...lines] = fs.readFileSync(path.join(dir, filename), 'utf8').trim().split(/\r?\n/);
const headers = header.split(',');
return lines.map((line) => Object.fromEntries(line.split(',').map((cell, index) => {
const value = Number(cell);
@@ -574,6 +575,10 @@ function readRuntimeRows(filename) {
})));
}
function readRuntimeRows(filename) {
return readCsvRows(OUTPUT_DIR, filename);
}
function writeRuntimeViews(base, headers, views, digits = {}) {
writeViewFiles(base, headers, views, digits);
}
@@ -698,3 +703,45 @@ writeViewFiles('ppmt_inventory', ['period', 'inventory', 'yoy'], inventoryViews,
}, 2017);
console.log('已生成第二批连续图表的全部、年度、半年 CSV。');
// 渠道收入占比:底表 data/ppmt/rawData/地区渠道收入.csv 整理自
// 泡泡玛特-rawdata.xlsx「各地区渠道收入」工作表(中国含港澳台的四地区 × 四渠道明细)。
// 全球合计 = 四地区加总;披露口径只有 H1 与全年,H2 单半年 = 全年 − H1;
// 占比在金额视图之后计算,按当期四渠道合计归一化,不可用占比直接相减。
const CHANNEL_NAME_TO_KEY = {
零售店: 'retail',
机器人商店: 'robot_store',
线上渠道: 'online',
批发及其他: 'wholesale',
};
const channelRevenueByPeriod = new Map();
readCsvRows(RAWDATA_DIR, '地区渠道收入.csv').forEach(({ region, channel, period, revenue }) => {
const key = CHANNEL_NAME_TO_KEY[channel];
if (!key) throw new Error(`地区渠道收入底表存在未知渠道:${channel}`);
if (!period || !Number.isFinite(revenue)) throw new Error(`地区渠道收入底表行不完整:${region} ${channel} ${period} ${revenue}`);
if (!channelRevenueByPeriod.has(period)) {
channelRevenueByPeriod.set(period, { retail: 0, online: 0, robot_store: 0, wholesale: 0 });
}
channelRevenueByPeriod.get(period)[key] += revenue;
});
const channelAmountViews = buildViews((year, type) => {
const bucket = channelRevenueByPeriod.get(type === 'H1' ? `${year}H1` : `${year}FY`);
return bucket ?? null;
});
const channelShareViews = Object.fromEntries(Object.entries(channelAmountViews).map(([view, rows]) => [
view,
rows.map(({ period, ...amounts }) => {
const total = Object.values(amounts).reduce((sum, value) => sum + value, 0);
return {
period,
...Object.fromEntries(Object.entries(amounts).map(([key, value]) => [key, value / total * 100])),
total_revenue: total,
};
}),
]));
writeRuntimeViews('ppmt_channel_share', ['period', 'retail', 'online', 'robot_store', 'wholesale', 'total_revenue'], channelShareViews, {
retail: 1, online: 1, robot_store: 1, wholesale: 1, total_revenue: 2,
});
console.log('已生成渠道收入占比的全部、年度、半年 CSV。');