Reads a person's own carbon-factor file, cost book or climate answer into the tables the carbon, cost and solar panes calculate from.
How the precision is kept
The numbers are the source's; this code converts units and cleans them. Factor and money cells have currency signs, spaces and every comma removed before they become 64-bit numbers. Monthly climate values are rounded to two decimals when stored. A request to a climate provider carries the location to three decimals of a degree, and answers are cached on a 0.1 degree grid.
Open item. Read a decimal comma in imported files, or refuse a cell whose only comma is followed by one or two digits, so that 1,5 is never read as 15.
Calculations under this policy
5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
Currency signs, white space and every comma are removed, then Number() converts the rest. A thousands comma is handled; a decimal comma is not: '1,5' gives 15 and '0,25' gives 25.
data-packs.js
line 28
BimCraft 0.8.1
const num = (v) => { const s = String(v ?? '').replace(/[$€£,\s]/g, ''); if (s === '') return null; const n = Number(s); return Number.isFinite(n) ? n : null; };
Carbon factor from a file row
64-bit
A material's embodied-carbon factor in the unit the carbon pane works in.
A1-A3 factor = value × unit factor (1 for kg, m, m², m³ and items; 0.001 for tonnes)
One 64-bit multiplication; 0.001 is not exact in binary, so a per-tonne value can change in its last digit. A per-kilogram factor with no density takes the density of the matching material in the starter table, and the row's note says so.
data-packs.js
lines 83 to 106
BimCraft 0.8.1
export function carbonFromFile(text, opts = {}) {
const all = parseCsv(text);
if (all.length < 2) return { rows: [], errors: [{ line: 0, code: 'empty' }], columns: {} };
const head = all[0].cells.map((x) => x.trim());
const c = pickCols(head, opts.map, ['name', 'unit', 'gwp', 'density'], SYN);
if (c.name < 0 || c.gwp < 0) return { rows: [], errors: [{ line: all[0].line, code: 'header' }], columns: c };
// no unit column: read it from a header like "kgCO2e/kg" or "kgCO2e per m3"
const hu = /(?:\/|per)\s*(kg|t|tonne|m3|m³|m2|m²|m)\b/i.exec(head[c.gwp] || '');
const rows = [], errors = [], seen = new Set();
for (const r of all.slice(1, 5001)) {
const g = (i) => (i >= 0 ? unguard(String(r.cells[i] ?? '')) : '');
const u = unitOf(g(c.unit)) || (hu ? unitOf(hu[1]) : null) || (opts.unit ? unitOf(opts.unit) : null);
const gwp = num(g(c.gwp));
if (!g(c.name).trim() || gwp === null || !u) { errors.push({ line: r.line, code: !u ? 'unit' : 'value' }); continue; }
let id = slug(g(c.name)); for (let n = 2; seen.has(id); n++) id = slug(g(c.name)).slice(0, 44) + '-' + n; seen.add(id);
// a per-kg figure needs a density to meet the model's volumes: the file's, the caller's, else the starter table's for that name (said in the note)
let dens = num(g(c.density)) ?? opts.density ?? null, dnote = '';
if (u[0] === 'kg' && dens === null) { const m = matchRow(STARTER, { material: g(c.name), name: g(c.name) }); if (m) { dens = m.density; dnote = ' Density ' + m.density + ' kg/m³ from the starter table.'; } }
const cl = cleanRow({ id, match: [g(c.name)], classes: [], basis: u[0], density: dens, a1a3: gwp * u[1], source: opts.source || 'your file', checked: false, note: IMPORT_LABEL + dnote });
if (!cl.row) { errors.push({ line: r.line, code: cl.errors[0] }); continue; }
rows.push({ ...cl.row, origin: 'yours' });
}
return { rows, errors, columns: c };
}
Monthly climate from NASA POWER
64-bit
Twelve monthly values of daily sunlight on a horizontal surface and of air temperature.
ghi = value rounded to 0.01 kWh/m²/day; T = value rounded to 0.01 °C
A value outside 0 to 12 kWh/m²/day or −60 to 60 °C rejects the whole answer. What is kept is rounded to two decimals.
data-packs.js
lines 140 to 146
BimCraft 0.8.1
export function parseNasaPower(j) {
const p = j?.properties?.parameter, a = p?.ALLSKY_SFC_SW_DWN, t = p?.T2M;
if (!a || !t) return null;
const ghi = MONTHS.map((m) => Number(a[m])), tAmb = MONTHS.map((m) => Number(t[m]));
if (ghi.some((x) => !Number.isFinite(x) || x < 0 || x > 12) || tAmb.some((x) => !Number.isFinite(x) || x < -60 || x > 60)) return null; // NASA marks missing as -999
return { ghi: ghi.map(r2), tAmb: tAmb.map(r2), source: 'NASA POWER climatology (ALLSKY_SFC_SW_DWN kWh/m²/day, T2M daily-mean °C)' };
}
Monthly climate from PVGIS
64-bit
Twelve monthly values of daily sunlight and air temperature, averaged over the years in the answer.
ghi = Σ H(h)_m ÷ years ÷ days in month; T = Σ T2m ÷ years; both rounded to 0.01
Monthly sums are averaged over the years in the answer and divided by the days in the month, with February as 28.25 days. A missing temperature makes that month NaN and the answer is rejected. The results are rounded to two decimals.
data-packs.js
lines 147 to 160
BimCraft 0.8.1
export function parsePvgis(j) {
const list = j?.outputs?.monthly;
if (!Array.isArray(list) || !list.length) return null;
const sum = new Array(12).fill(0), ts = new Array(12).fill(0), n = new Array(12).fill(0);
for (const e of list) {
const m = Number(e.month) - 1, h = Number(e['H(h)_m']), t = Number(e.T2m ?? e['T2m']);
if (!(m >= 0 && m < 12) || !Number.isFinite(h)) return null;
sum[m] += h; ts[m] += Number.isFinite(t) ? t : NaN; n[m]++;
}
if (n.some((x) => !x)) return null;
const ghi = sum.map((s, m) => r2(s / n[m] / DIM[m])), tAmb = ts.map((s, m) => r2(s / n[m]));
if (ghi.some((x) => x < 0 || x > 12) || tAmb.some((x) => !Number.isFinite(x))) return null;
return { ghi, tAmb, source: 'PVGIS MRcalc monthly (H(h)_m kWh/m²/month ÷ days, T2m °C)' };
}
Location sent to the climate provider
64-bit
The latitude and longitude put into the request address.
latitude.toFixed(3), longitude.toFixed(3)
The request carries the position rounded to three decimals of a degree. A position outside ±90 and ±180 degrees gives no address.
Carbon factor per kilogram = the file's value × 0.001 when its unit is the tonne. PVGIS monthly irradiation ÷ years ÷ days in the month gives kWh/m² per day; NASA POWER values are taken per day as given.
Number types
64-bit numbers.
Limits
A decimal comma is not understood: '1,5' is read as 15. The units t, tonne, tonnes and ton all use the factor 0.001. February counts 28.25 days. Stored climate values differ from the source by up to 0.005.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.