Ctrl K
ComunidadGuías…

scripts/_dosm-product-demand.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Malaysia's refined-product demand from the Department of Statistics
// Malaysia (DOSM) open data API: annual final energy consumption by
// commodity (energy_consumption_commodity), in ktoe, from the Energy
// Commission's National Energy Balance. JODI carries Malaysia's product
// trade but no demand, so this is the energy shock's only demand for it.
//
// Final consumption excludes fuel burned for power generation and refinery
// own use. Values are converted to kb/d at IEA default net calorific values
// and standard barrels per tonne; the year's calendar days divide.

export const DOSM_ENERGY_CONSUMPTION_URL = 'https://api.data.gov.my/data-catalogue/?id=energy_consumption_commodity&limit=5000';

/** Energy shock product key → DOSM commodity. DOSM has no naphtha row. */
export const DOSM_PRODUCT_COMMODITIES = {
  gasoline: 'petrol',
  diesel: 'diesel',
  jet: 'jet_fuel',
  lpg: 'lpg',
};

const GJ_PER_TOE = 41.868;

// IEA default net calorific values (GJ/t) and barrels per tonne.
export const PRODUCT_CONVERSION = {
  gasoline: { ncvGjPerT: 44.3, bblPerT: 8.53 },
  diesel: { ncvGjPerT: 43.0, bblPerT: 7.46 },
  jet: { ncvGjPerT: 44.1, bblPerT: 7.88 },
  lpg: { ncvGjPerT: 46.0, bblPerT: 11.6 },
};

const daysInYear = (year) => ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 366 : 365);

/** Annual ktoe of one product to kb/d over that calendar year. */
export function ktoeToKbd(ktoe, product, year) {
  const { ncvGjPerT, bblPerT } = PRODUCT_CONVERSION[product];
  const kt = (ktoe * GJ_PER_TOE) / ncvGjPerT;
  return (kt * bblPerT) / daysInYear(year);
}

/**
 * The latest year in which DOSM has every modeled product, as kb/d (one
 * decimal) and the ktoe it came from; null when no year is complete.
 *
 * @param {Array<{date?: string, commodity?: string, consumption?: unknown}>} rows
 */
export function dosmProductDemand(rows) {
  const byYear = new Map();
  for (const r of rows ?? []) {
    const year = Number(String(r?.date ?? '').slice(0, 4));
    const value = r?.consumption;
    if (!Number.isInteger(year) || typeof value !== 'number' || !Number.isFinite(value) || value < 0) continue;
    if (!byYear.has(year)) byYear.set(year, new Map());
    byYear.get(year).set(String(r.commodity), value);
  }
  const entries = Object.entries(DOSM_PRODUCT_COMMODITIES);
  for (const year of [...byYear.keys()].sort((a, b) => b - a)) {
    const values = byYear.get(year);
    if (!entries.every(([, commodity]) => values.get(commodity) > 0)) continue;
    const ktoe = Object.fromEntries(entries.map(([key, commodity]) => [key, values.get(commodity)]));
    const products = Object.fromEntries(entries.map(([key]) => [key, Math.round(ktoeToKbd(ktoe[key], key, year) * 10) / 10]));
    return { year, ktoe, products };
  }
  return null;
}