feat: 更新泡泡玛特图表,加入单店收入
This commit is contained in:
@@ -184,13 +184,21 @@ const workingCapitalDaysRows = indicators.dates.map((date, index) => ({
|
||||
})).filter((item) => (
|
||||
item.date >= '2020-01-01'
|
||||
&& item.inventory !== null && item.receivable !== null && item.payable !== null
|
||||
)).map((item) => [
|
||||
periodFor(item.date),
|
||||
item.inventory.toFixed(1),
|
||||
item.receivable.toFixed(1),
|
||||
item.payable.toFixed(1),
|
||||
(item.inventory + item.receivable - item.payable).toFixed(1),
|
||||
]).reverse();
|
||||
)).map((item) => {
|
||||
const period = periodFor(item.date);
|
||||
// 原始指标表按半年 180 天、全年 360 天计算;此处统一换算为自然日 181/365 天。
|
||||
const factor = period.endsWith('H1') ? 181 / 180 : 365 / 360;
|
||||
const inventory = item.inventory * factor;
|
||||
const receivable = item.receivable * factor;
|
||||
const payable = item.payable * factor;
|
||||
return [
|
||||
period,
|
||||
inventory.toFixed(1),
|
||||
receivable.toFixed(1),
|
||||
payable.toFixed(1),
|
||||
(inventory + receivable - payable).toFixed(1),
|
||||
];
|
||||
}).reverse();
|
||||
|
||||
writeCSV('ppmt_working_capital_days.csv', [
|
||||
'period', 'inventory_days', 'receivable_days', 'payable_days', 'cash_conversion_cycle',
|
||||
@@ -341,9 +349,9 @@ function yoyFor(rows, key, outputKey = key) {
|
||||
});
|
||||
}
|
||||
|
||||
function toOutputRows(rows, headers, digits = {}) {
|
||||
function toOutputRows(rows, headers, digits = {}, startYear = OUTPUT_START_YEAR) {
|
||||
return rows
|
||||
.filter((row) => Number(row.period.slice(0, 4)) >= OUTPUT_START_YEAR)
|
||||
.filter((row) => Number(row.period.slice(0, 4)) >= startYear)
|
||||
.map((row) => headers.map((header) => {
|
||||
if (header === 'period') return row.period;
|
||||
const value = row[header];
|
||||
@@ -351,14 +359,14 @@ function toOutputRows(rows, headers, digits = {}) {
|
||||
}));
|
||||
}
|
||||
|
||||
function writeViewFiles(base, headers, views, digits) {
|
||||
function writeViewFiles(base, headers, views, digits, startYear = OUTPUT_START_YEAR) {
|
||||
const filenames = {
|
||||
all: `${base}.csv`,
|
||||
annual: `${base}_annual.csv`,
|
||||
halfyear: `${base}_halfyear.csv`,
|
||||
};
|
||||
Object.entries(filenames).forEach(([view, filename]) => {
|
||||
writeCSV(filename, headers, toOutputRows(views[view], headers, digits));
|
||||
writeCSV(filename, headers, toOutputRows(views[view], headers, digits, startYear));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -614,6 +622,31 @@ writeRuntimeViews('ppmt_category', ['period', 'plush', 'figure', 'mega', 'other'
|
||||
plush: 2, figure: 2, mega: 2, other: 2,
|
||||
});
|
||||
|
||||
// 2024H1 起门店披露可将中国内地与港澳台合并,并按全球总数归一化为地区占比。
|
||||
const retailStoreRows = readRuntimeRows('ppmt_stores_retail.csv');
|
||||
const retailStoreShareViews = { all: [], annual: [], halfyear: [] };
|
||||
retailStoreRows.filter((row) => (
|
||||
Number(row.period.slice(0, 4)) >= 2024
|
||||
&& ['mainland', 'hkmtw', 'apac', 'americas', 'europe', 'global'].every((key) => Number.isFinite(row[key]))
|
||||
)).forEach((row) => {
|
||||
const china = row.mainland + row.hkmtw;
|
||||
const other = row.global - china - row.apac - row.americas - row.europe;
|
||||
const shareRow = {
|
||||
period: row.period,
|
||||
global: row.global,
|
||||
china: china / row.global * 100,
|
||||
apac: row.apac / row.global * 100,
|
||||
americas: row.americas / row.global * 100,
|
||||
europe: row.europe / row.global * 100,
|
||||
other: other / row.global * 100,
|
||||
};
|
||||
retailStoreShareViews.all.push(shareRow);
|
||||
retailStoreShareViews[row.period.endsWith('FY') ? 'annual' : 'halfyear'].push(shareRow);
|
||||
});
|
||||
writeRuntimeViews('ppmt_stores_retail_share', ['period', 'global', 'china', 'apac', 'americas', 'europe', 'other'], retailStoreShareViews, {
|
||||
global: 0, china: 1, apac: 1, americas: 1, europe: 1, other: 1,
|
||||
});
|
||||
|
||||
// 资产负债表为时点指标:H2 是年末时点,而非“全年累计”。
|
||||
function buildSnapshotViews(base, headers, digits) {
|
||||
const source = readRuntimeRows(`${base}.csv`);
|
||||
@@ -643,4 +676,25 @@ buildSnapshotViews('ppmt_liquidity_working_capital', ['period', 'cash', 'total_l
|
||||
cash: 2, total_liabilities: 2, net_working_capital: 2,
|
||||
});
|
||||
|
||||
// 存货为资产负债表期末时点数:半年仅取中报期末,年度仅取年报期末。
|
||||
// 同比严格按相同报告期(H1 对 H1、FY 对 FY)计算,不把中报与年报交叉比较。
|
||||
const inventoryBalances = row(balance, '存货');
|
||||
const inventoryViews = { all: [], annual: [], halfyear: [] };
|
||||
balance.dates.map((date, index) => ({
|
||||
date,
|
||||
inventory: inventoryBalances[index],
|
||||
})).filter(({ date, inventory }) => (
|
||||
date >= '2017-01-01' && inventory > 0 && (date.endsWith('-06-30') || date.endsWith('-12-31'))
|
||||
)).sort((a, b) => a.date.localeCompare(b.date)).forEach(({ date, inventory }) => {
|
||||
const row = { period: periodFor(date), inventory: inventory / HUNDRED_MILLION };
|
||||
inventoryViews.all.push(row);
|
||||
inventoryViews[row.period.endsWith('FY') ? 'annual' : 'halfyear'].push(row);
|
||||
});
|
||||
Object.keys(inventoryViews).forEach((view) => {
|
||||
inventoryViews[view] = yoyFor(inventoryViews[view], 'inventory', 'yoy');
|
||||
});
|
||||
writeViewFiles('ppmt_inventory', ['period', 'inventory', 'yoy'], inventoryViews, {
|
||||
inventory: 2, yoy: 1,
|
||||
}, 2017);
|
||||
|
||||
console.log('已生成第二批连续图表的全部、年度、半年 CSV。');
|
||||
|
||||
Reference in New Issue
Block a user