//
// FIXED FOLDER WHERE CSV FILES ARE STORED (relative to vault root)
//
const targetFolder = "100 CPD Record/Room Bookings";
//
// OPTIONAL DATE RANGE + HOURLY RATE FROM YAML FRONTMATTER
//
const fm = dv.current().file.frontmatter || {};
const startStr = fm.report_start || null; // e.g. 2025-11-01
const endStr = fm.report_end || null; // e.g. 2025-12-31
// hourly_rate in YAML, default to 8 if missing or invalid
let hourlyRate = Number(fm.hourly_rate);
if (!hourlyRate || isNaN(hourlyRate)) {
hourlyRate = 8;
}
function inRange(dateStr) {
if (!dateStr) return false;
if (startStr && dateStr < startStr) return false;
if (endStr && dateStr > endStr) return false;
return true;
}
//
// FIND ALL CSV FILES IN THE FIXED FOLDER
//
const allFiles = app.vault.getFiles();
const csvFiles = allFiles.filter(f =>
f.path.startsWith(targetFolder + "/") &&
f.name.startsWith("officernd_bookings-") &&
f.path.endsWith(".csv")
);
if (csvFiles.length === 0) {
dv.paragraph("No officernd_bookings-*.csv files found in '" + targetFolder + "'");
return;
}
//
// SIMPLE CSV PARSER
//
async function loadAndParseCsv(path) {
const text = await dv.io.load(path);
if (!text) return [];
const lines = text.trim().split(/\r?\n/);
if (lines.length <= 1) return [];
const headers = lines[0].split(",").map(h => h.trim());
const rows = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const cols = line.split(",");
const obj = {};
headers.forEach((h, idx) => {
obj[h] = (cols[idx] ?? "").trim();
});
rows.push(obj);
}
return rows;
}
//
// LOAD ALL CSVs AND DEDUPLICATE BY booking_id
//
const bookingsById = {};
for (let file of csvFiles) {
const rows = await loadAndParseCsv(file.path);
for (let row of rows) {
if (!row.booking_id) continue;
bookingsById[row.booking_id] = row; // latest wins if duplicates
}
}
const allRows = Object.values(bookingsById);
if (allRows.length === 0) {
dv.paragraph("No bookings found in parsed files.");
return;
}
//
// FILTER BY DATE RANGE + AGGREGATE BY MONTH (HOURS + COST)
//
const byMonth = {}; // month -> { hours, cost }
for (let row of allRows) {
const date = row.date_local;
if (!date || date.length < 10) continue;
if (!inRange(date)) continue;
const month = date.slice(0, 7); // YYYY-MM
const hours = Number(row.duration_hours) || 0;
const cost = hours * hourlyRate;
if (!byMonth[month]) {
byMonth[month] = { hours: 0, cost: 0 };
}
byMonth[month].hours += hours;
byMonth[month].cost += cost;
}
//
// DISPLAY THE RESULTS
//
const tableRows = Object.entries(byMonth)
.sort(([m1], [m2]) => m1.localeCompare(m2))
.map(([m, v]) => [
m,
v.hours.toFixed(2),
"£" + v.cost.toFixed(2)
]);
if (tableRows.length === 0) {
dv.paragraph("No bookings within the selected date range.");
} else {
dv.table(["Month", "Hours", `Cost (hourly £${hourlyRate})`], tableRows);
}
Downloading OfficeRND information about All Ears bookings for analysis.md