Driverscore Card
pandancake034
plain_text
08/27/2026 10:27 AM
62.9 KB
1
Indexable
// ==========================================
// DEELBARE DEMOVERSIE SCORE ENGINE
//
// Privacy: dit bestand bevat geen live spreadsheet-ID's, persoonsgegevens,
// interne tabnamen, hubcodes of opslaglocaties. De scorelogica is ongewijzigd.
// De placeholders hieronder zijn bewust niet gekoppeld aan productiegegevens.
// ==========================================
const IL20_EXTERNAL_SPREADSHEET_ID = 'VUL_HIER_EEN_EIGEN_SPREADSHEET_ID_IN';
const IL20_SOURCE_SHEET_NAME = 'DRIVER_KPI_DATA';
const IL20_ROUTES_DATA_SHEET_NAME = 'ROUTES_DATA';
const IL20_INACTIVE_SHEET_NAME = 'INACTIVE_DRIVERS';
const IL20_SAMSARA_SHEET_NAME = 'SAFETY_SCORES';
const IL20_TARGET_HUB = 'HUB_X';
const IL20_HUBS = {
'HUB_X': 'Voorbeeldhub',
'NL_HUB_1': 'Nederlandse voorbeeldhub',
'BE_HUB_1': 'Belgische voorbeeldhub'
};
const IL20_DAMAGE_POINTS = {
one_sided: 15,
multi_sided: 30,
immobilization: 40
};
const IL20_WEIGHTS = {
damage: 0.30,
fines: 0.10,
driving_behavior: 0.60
};
// Minimaal 2 actieve weken en per actieve week minimaal 2 routes
const IL20_MIN_ACTIVE_WEEKS = 2;
const IL20_MIN_ROUTES_PER_ACTIVE_WEEK = 2;
const IL20_WEEKS_TO_ANALYZE = 4;
const IL20_FINES_WEEKS_TO_ANALYZE = 4;
const IL20_REQUIRE_SAMSARA_SCORE = true;
const IL20_KPI_BADGE_INDEX = 1;
const IL20_SAMSARA_BADGE_INDEX = 0;
const IL20_SAMSARA_SCORE_INDEX = 3;
// Voorbeeld: de kolom met het aantal 5-sterrenreacties = index 20
const IL20_FIVE_STAR_COUNT_INDEX = 20;
// 5-star bonus:
// five_star_ratio = five_star_comments / routes
// routes < 4 = 0 bonus
// five_star_comments <= 0 = 0 bonus
// five_star_ratio > 1 = +8
// five_star_ratio >= 0.5 en <= 1 = +6
// five_star_ratio >= 0.1 = +3
// five_star_ratio > 0 = +1
const IL20_FIVE_STAR_MIN_ROUTES = 4;
const IL20_FIVE_STAR_MAX_BONUS = 8;
// ==========================================
// HOOFD FUNCTIE
// ==========================================
function il20_generateHubBattleRankings() {
try {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(IL20_SOURCE_SHEET_NAME);
if (!sheet) {
throw new Error('Tabblad "' + IL20_SOURCE_SHEET_NAME + '" niet gevonden.');
}
const data = sheet.getDataRange().getValues();
if (data.length < 2) {
throw new Error('Geen data gevonden in tabblad "' + IL20_SOURCE_SHEET_NAME + '".');
}
const headers = data[0];
const rows = data.slice(1);
const allWeeks = il20_getAllWeeks(rows, headers);
const lastPeriodWeeks = allWeeks.slice(-IL20_WEEKS_TO_ANALYZE);
const finesWeeks = allWeeks.slice(-IL20_FINES_WEEKS_TO_ANALYZE);
const lifetimeWeeks = allWeeks.filter(week => week >= '2026-W01');
if (lastPeriodWeeks.length === 0) {
throw new Error('Geen weken gevonden in brondata.');
}
console.log(`IL2.0 - Scoreweken: ${lastPeriodWeeks.join(', ')}`);
console.log(`IL2.0 - Fines weken: ${finesWeeks.join(', ')}`);
const inactiveDriverIds = il20_getInactiveDriverIds();
const samsaraScoreMap = il20_getSamsaraSafetyScoreMap();
const fiveStarCountMap = il20_getFiveStarCountMap(lastPeriodWeeks);
const aggregatedDataLastPeriod = il20_aggregateDriverData(
rows,
headers,
lastPeriodWeeks,
finesWeeks,
samsaraScoreMap,
fiveStarCountMap
);
let activeDriversOnly = aggregatedDataLastPeriod.filter(driver => {
return (
!inactiveDriverIds.includes(il20_normalizeBadgeNumber(driver.badge_number)) &&
il20_isDriverEligible(driver)
);
});
if (IL20_REQUIRE_SAMSARA_SCORE) {
activeDriversOnly = activeDriversOnly.filter(driver => driver.samsara_safety_score !== null);
}
const skippedDrivers = aggregatedDataLastPeriod.length - activeDriversOnly.length;
console.log(`IL2.0 - Chauffeurs genegeerd: ${skippedDrivers}`);
const lifetimeData = il20_calculateLifetimeStats(rows, headers, lifetimeWeeks);
const driverScores = il20_calculateDriverScores(
activeDriversOnly,
aggregatedDataLastPeriod,
lifetimeData
);
const rankChanges = il20_calculateRankChanges(
rows,
headers,
allWeeks,
lastPeriodWeeks,
finesWeeks,
lifetimeData,
inactiveDriverIds,
samsaraScoreMap
);
const hubRankings = il20_createHubRankings(driverScores, rankChanges);
const stats = il20_createHubRankings._lastHubStats || [];
const weeklyPenaltyPerformance = il20_computeWeeklyPenaltyPerformance(
rows,
headers,
lastPeriodWeeks,
aggregatedDataLastPeriod,
samsaraScoreMap
);
const jsonData = {
period: `${lastPeriodWeeks[0]} to ${lastPeriodWeeks[lastPeriodWeeks.length - 1]}`,
weeks_analyzed: lastPeriodWeeks,
lifetime_period_start: lifetimeWeeks[0] || null,
generated_at: new Date().toISOString(),
target_hub: IL20_TARGET_HUB,
hubs: hubRankings,
stats: stats,
weekly_penalty_performance: weeklyPenaltyPerformance,
total_drivers: driverScores.length,
scoring_config: {
target_hub: IL20_TARGET_HUB,
minimum_routes: IL20_MIN_ACTIVE_WEEKS * IL20_MIN_ROUTES_PER_ACTIVE_WEEK,
minimum_active_weeks: IL20_MIN_ACTIVE_WEEKS,
minimum_routes_per_active_week: IL20_MIN_ROUTES_PER_ACTIVE_WEEK,
eligibility_calculation: 'weeks_active >= 2 AND every active week has at least 2 routes',
weeks_analyzed: IL20_WEEKS_TO_ANALYZE,
weights: IL20_WEIGHTS,
require_samsara_score: IL20_REQUIRE_SAMSARA_SCORE,
source_sheet: IL20_SOURCE_SHEET_NAME,
routes_data_sheet: IL20_ROUTES_DATA_SHEET_NAME,
samsara_sheet: IL20_SAMSARA_SHEET_NAME,
badge_match: 'Unieke chauffeurreferentie uit SAFETY_SCORES wordt gekoppeld aan DRIVER_KPI_DATA',
calculation_method: 'three_penalty_tables_with_five_star_bonus',
removed_from_score: ['speeding', 'g-force'],
note: 'JSON output toont raw_points, weighted_raw_points, weighted_raw_penalty_per_route en five_star_bonus_points.',
five_star_bonus: {
source_sheet: IL20_ROUTES_DATA_SHEET_NAME,
source_column: 'U',
source_column_index: IL20_FIVE_STAR_COUNT_INDEX,
calculation: 'five_star_ratio = five_star_comments / routes',
min_routes: IL20_FIVE_STAR_MIN_ROUTES,
max_bonus_points: IL20_FIVE_STAR_MAX_BONUS,
score_cap: 100,
ranges: {
no_five_star_comments: 0,
five_star_ratio_above_0_to_below_0_1: 1,
five_star_ratio_0_1_to_below_0_5: 3,
five_star_ratio_0_5_to_1: 6,
five_star_ratio_above_1: 8
}
}
}
};
const lastWeek = lastPeriodWeeks[lastPeriodWeeks.length - 1];
const weekNumber = String(lastWeek).split('-W')[1] || 'unknown';
const filename = `HUB-driverscore-demo-W${weekNumber}.json`;
const file = il20_saveJsonToDrive(jsonData, filename);
return {
jsonData,
fileId: file.getId(),
fileUrl: file.getUrl()
};
} catch (error) {
console.error('IL2.0 - Fout bij genereren van de ranking:', error);
throw error;
}
}
// ==========================================
// SAMSARA DATA
// ==========================================
function il20_getSamsaraSafetyScoreMap() {
try {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(IL20_SAMSARA_SHEET_NAME);
if (!sheet) {
console.warn(`IL2.0 - Samsara-tabblad "${IL20_SAMSARA_SHEET_NAME}" niet gevonden.`);
return {};
}
const data = sheet.getDataRange().getValues();
if (data.length < 2) {
console.warn('IL2.0 - Samsara-tabblad bevat geen data.');
return {};
}
const scoreMap = {};
for (let i = 1; i < data.length; i++) {
const badge = il20_normalizeBadgeNumber(data[i][IL20_SAMSARA_BADGE_INDEX]);
const score = il20_normalizeScore(data[i][IL20_SAMSARA_SCORE_INDEX]);
if (!badge || score === null) continue;
scoreMap[badge] = score;
}
console.log(`IL2.0 - Samsara Safety Scores gevonden: ${Object.keys(scoreMap).length}`);
return scoreMap;
} catch (error) {
console.error('IL2.0 - Fout bij ophalen Samsara Safety Scores:', error);
return {};
}
}
// ==========================================
// DATA OPHALEN
// ==========================================
function il20_getInactiveDriverIds() {
try {
const externalSs = SpreadsheetApp.openById(IL20_EXTERNAL_SPREADSHEET_ID);
let sheet = externalSs.getSheetByName(IL20_INACTIVE_SHEET_NAME);
if (!sheet) {
const sheets = externalSs.getSheets();
if (sheets.length >= 7) {
sheet = sheets[6];
} else {
console.warn("IL2.0 - Tabblad 'NOT ACTIVE' of 7e tabblad niet gevonden.");
return [];
}
}
const data = sheet.getRange('C:C').getValues();
const inactiveIds = [];
for (let i = 1; i < data.length; i++) {
const id = il20_normalizeBadgeNumber(data[i][0]);
if (id !== '') {
inactiveIds.push(id);
}
}
console.log(`IL2.0 - Inactieve drivers gevonden: ${inactiveIds.length}`);
return inactiveIds;
} catch (error) {
console.error('IL2.0 - Fout bij openen externe sheet: ' + error.message);
return [];
}
}
function il20_getFiveStarCountMap(scoreWeeks) {
try {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(IL20_ROUTES_DATA_SHEET_NAME);
if (!sheet) {
console.warn(`IL2.0 - Tabblad "${IL20_ROUTES_DATA_SHEET_NAME}" niet gevonden.`);
return {};
}
const data = sheet.getDataRange().getValues();
if (data.length < 2) {
console.warn(`IL2.0 - Tabblad "${IL20_ROUTES_DATA_SHEET_NAME}" bevat geen data.`);
return {};
}
const headers = data[0];
const rows = data.slice(1);
const weekIndex = il20_getColIndex(headers, [
'hellofresh_week',
'hf_week',
'week'
]);
if (weekIndex === -1) {
console.warn(`IL2.0 - Geen weekkolom gevonden in "${IL20_ROUTES_DATA_SHEET_NAME}".`);
return {};
}
let hubIndex = il20_getColIndex(headers, [
'hub_code',
'hub',
'hubcode'
]);
if (hubIndex === -1) {
hubIndex = 2;
}
let badgeIndex = il20_getColIndex(headers, [
'badge_number',
'driver_id',
'driverid',
'hf_driver_id',
'hellofresh_driver_id'
]);
if (badgeIndex === -1) {
badgeIndex = IL20_KPI_BADGE_INDEX;
}
const scoreWeekSet = new Set(scoreWeeks || []);
const fiveStarCountMap = {};
rows.forEach(row => {
const week = row[weekIndex];
if (!week || !scoreWeekSet.has(week)) {
return;
}
const hubCode = il20_mapHubCode(row[hubIndex] || 'UNKNOWN');
if (hubCode !== IL20_TARGET_HUB) {
return;
}
const badge = il20_normalizeBadgeNumber(row[badgeIndex]);
if (!badge) {
return;
}
const fiveStarCount = Number(row[IL20_FIVE_STAR_COUNT_INDEX] || 0);
if (!fiveStarCountMap[badge]) {
fiveStarCountMap[badge] = 0;
}
fiveStarCountMap[badge] += fiveStarCount;
});
console.log(`IL2.0 - 5-sterren data gevonden voor ${Object.keys(fiveStarCountMap).length} chauffeurs.`);
return fiveStarCountMap;
} catch (error) {
console.error('IL2.0 - Fout bij ophalen 5-sterren data:', error);
return {};
}
}
function il20_getAllWeeks(rows, headers) {
const weekIndex = il20_getColIndex(headers, ['hellofresh_week']);
if (weekIndex === -1) {
return [];
}
const allWeeks = [...new Set(
rows
.map(row => row[weekIndex])
.filter(week => week)
)];
allWeeks.sort();
return allWeeks;
}
function il20_getColIndex(headers, possibleNames) {
const cleanHeaders = headers.map(header => {
return String(header).toLowerCase().replace(/[^a-z0-9]/g, '');
});
for (const name of possibleNames) {
const cleanName = String(name).toLowerCase().replace(/[^a-z0-9]/g, '');
const index = cleanHeaders.indexOf(cleanName);
if (index !== -1) {
return index;
}
}
return -1;
}
function il20_mapHubCode(rawHubCode) {
const hubMappings = {
OUDE_HUBCODE: 'HUB_X'
};
const upperCode = String(rawHubCode || 'UNKNOWN')
.toUpperCase()
.trim();
return hubMappings[upperCode] || upperCode;
}
function il20_normalizeBadgeNumber(value) {
return String(value || '').trim().toUpperCase();
}
function il20_normalizeScore(value) {
if (value === null || value === undefined || value === '') {
return null;
}
const score = Number(value);
if (isNaN(score)) {
return null;
}
return Math.max(0, Math.min(100, score));
}
function il20_round(value) {
return Math.round(Number(value || 0) * 100) / 100;
}
// NIEUW: eligibility-controle
function il20_isDriverEligible(driver) {
const weeksActive = Number(driver?.weeks_active || 0);
const routesPerActiveWeek = Array.isArray(driver?.routes_per_active_week)
? driver.routes_per_active_week.map(routes => Number(routes || 0))
: [];
return (
weeksActive >= IL20_MIN_ACTIVE_WEEKS &&
routesPerActiveWeek.length === weeksActive &&
routesPerActiveWeek.every(routes => routes >= IL20_MIN_ROUTES_PER_ACTIVE_WEEK)
);
}
// ==========================================
// AGGREGATIE
// ==========================================
function il20_aggregateDriverData(
rows,
headers,
scoreWeeks,
finesWeeks,
samsaraScoreMap,
fiveStarCountMap = {}
) {
const weekIndex = il20_getColIndex(headers, ['hellofresh_week']);
const badgeIndex = IL20_KPI_BADGE_INDEX;
const routesIndex = il20_getColIndex(headers, ['number_of_routes']);
const oneSidedIndex = il20_getColIndex(headers, ['number_of_one_sided_accidents']);
const multiSidedIndex = il20_getColIndex(headers, ['number_of_multi_sided_accidents']);
const immobilizationIndex = il20_getColIndex(headers, ['number_of_vehicle_immobilizations']);
const finesIndex = il20_getColIndex(headers, ['number_of_fines']);
const finesAmountIndex = il20_getColIndex(headers, ['fines_amount']);
let hubIndex = il20_getColIndex(headers, ['hub_code', 'hub', 'hubcode']);
if (hubIndex === -1) {
hubIndex = 2;
}
const scoreWeekSet = new Set(scoreWeeks || []);
const finesWeekSet = new Set(finesWeeks || []);
const allRelevantWeeks = new Set([
...(scoreWeeks || []),
...(finesWeeks || [])
]);
const driverMap = {};
for (const row of rows) {
const week = weekIndex !== -1 ? row[weekIndex] : null;
if (!week || !allRelevantWeeks.has(week)) {
continue;
}
const hubCode = il20_mapHubCode(row[hubIndex] || 'UNKNOWN');
if (hubCode !== IL20_TARGET_HUB) {
continue;
}
const badge = il20_normalizeBadgeNumber(row[badgeIndex]);
if (!badge) {
continue;
}
if (!driverMap[badge]) {
driverMap[badge] = {
badge_number: badge,
total_routes: 0,
total_fines_routes: 0,
total_one_sided: 0,
total_multi_sided: 0,
total_immobilization: 0,
total_fines: 0,
total_fines_amount: 0,
weeks_active_set: new Set(),
routes_by_week: {},
hub_counts: {}
};
}
const driver = driverMap[badge];
const routes = routesIndex !== -1 ? Number(row[routesIndex] || 0) : 0;
if (scoreWeekSet.has(week)) {
driver.total_routes += routes;
driver.total_one_sided += oneSidedIndex !== -1 ? Number(row[oneSidedIndex] || 0) : 0;
driver.total_multi_sided += multiSidedIndex !== -1 ? Number(row[multiSidedIndex] || 0) : 0;
driver.total_immobilization += immobilizationIndex !== -1 ? Number(row[immobilizationIndex] || 0) : 0;
if (routes > 0) {
driver.weeks_active_set.add(week);
driver.routes_by_week[week] = (driver.routes_by_week[week] || 0) + routes;
}
}
if (finesWeekSet.has(week)) {
driver.total_fines_routes += routes;
driver.total_fines += finesIndex !== -1 ? Number(row[finesIndex] || 0) : 0;
driver.total_fines_amount += finesAmountIndex !== -1 ? Number(row[finesAmountIndex] || 0) : 0;
}
driver.hub_counts[hubCode] = (driver.hub_counts[hubCode] || 0) + 1;
}
return Object.values(driverMap)
.filter(driver => driver.total_routes > 0)
.map(driver => {
const hub = Object.entries(driver.hub_counts)
.sort((a, b) => b[1] - a[1])[0]?.[0] || IL20_TARGET_HUB;
const badge = il20_normalizeBadgeNumber(driver.badge_number);
let samsaraSafetyScore = null;
if (
samsaraScoreMap &&
Object.prototype.hasOwnProperty.call(samsaraScoreMap, badge)
) {
samsaraSafetyScore = il20_normalizeScore(samsaraScoreMap[badge]);
}
return {
badge_number: badge,
total_routes: driver.total_routes,
total_fines_routes: driver.total_fines_routes,
total_one_sided: driver.total_one_sided,
total_multi_sided: driver.total_multi_sided,
total_immobilization: driver.total_immobilization,
total_fines: driver.total_fines,
total_fines_amount: driver.total_fines_amount,
five_star_count: Number(fiveStarCountMap[badge] || 0),
samsara_safety_score: samsaraSafetyScore,
weeks_active: driver.weeks_active_set.size,
routes_per_active_week: Object.values(driver.routes_by_week),
hub
};
});
}
// ==========================================
// LIFETIME STATS
// ==========================================
function il20_calculateLifetimeStats(rows, headers, weeks) {
const weekIndex = il20_getColIndex(headers, ['hellofresh_week']);
const badgeIndex = IL20_KPI_BADGE_INDEX;
const oneSidedIndex = il20_getColIndex(headers, ['number_of_one_sided_accidents']);
const multiSidedIndex = il20_getColIndex(headers, ['number_of_multi_sided_accidents']);
const immobilizationIndex = il20_getColIndex(headers, ['number_of_vehicle_immobilizations']);
const finesAmountIndex = il20_getColIndex(headers, ['fines_amount']);
let hubIndex = il20_getColIndex(headers, ['hub_code', 'hub', 'hubcode']);
if (hubIndex === -1) {
hubIndex = 2;
}
const lifetimeMap = {};
rows.forEach(row => {
const week = weekIndex !== -1 ? row[weekIndex] : null;
if (!week || !weeks.includes(week)) {
return;
}
const hubCode = il20_mapHubCode(row[hubIndex] || 'UNKNOWN');
if (hubCode !== IL20_TARGET_HUB) {
return;
}
const badge = il20_normalizeBadgeNumber(row[badgeIndex]);
if (!badge) {
return;
}
if (!lifetimeMap[badge]) {
lifetimeMap[badge] = {
accidents_1A_one_sided: 0,
accidents_1B_multi_sided: 0,
accidents_1C_immobilization: 0,
fines_amount: 0
};
}
const stats = lifetimeMap[badge];
stats.accidents_1A_one_sided += oneSidedIndex !== -1 ? Number(row[oneSidedIndex] || 0) : 0;
stats.accidents_1B_multi_sided += multiSidedIndex !== -1 ? Number(row[multiSidedIndex] || 0) : 0;
stats.accidents_1C_immobilization += immobilizationIndex !== -1 ? Number(row[immobilizationIndex] || 0) : 0;
stats.fines_amount += finesAmountIndex !== -1 ? Number(row[finesAmountIndex] || 0) : 0;
});
return lifetimeMap;
}
// ==========================================
// SCOREBEREKENING
// ==========================================
function il20_calculateDriverScores(
driverDataForScoring,
aggregatedDataForHubAvg,
lifetimeData
) {
const scores = [];
const avgRoutesPerHub = il20_calculateAverageRoutesPerHub(aggregatedDataForHubAvg);
for (const driver of driverDataForScoring) {
const badge = il20_normalizeBadgeNumber(driver.badge_number);
const routes = Number(driver.total_routes || 0);
const finesRoutes = Number(driver.total_fines_routes || 0);
const hub = driver.hub || IL20_TARGET_HUB;
const hubAvgRoutes = avgRoutesPerHub[hub] || 1;
const damageRawPoints =
(driver.total_one_sided * IL20_DAMAGE_POINTS.one_sided) +
(driver.total_multi_sided * IL20_DAMAGE_POINTS.multi_sided) +
(driver.total_immobilization * IL20_DAMAGE_POINTS.immobilization);
const finesResult = il20_calculateFinesPoints(driver.total_fines_amount, hub);
const finesRawPoints = finesResult.points;
const finesSurchargeRaw = finesResult.surcharge;
const drivingBehaviorRawPenalty = il20_calculateDrivingBehaviorPenalty(
driver.samsara_safety_score
);
const totalRawPenalty =
damageRawPoints +
finesRawPoints +
finesSurchargeRaw +
drivingBehaviorRawPenalty;
const damageRawPerRoute = routes > 0 ? damageRawPoints / routes : 0;
const finesRawPerRoute = finesRoutes > 0 ? finesRawPoints / finesRoutes : 0;
const finesSurchargeRawPerRoute = finesRoutes > 0 ? finesSurchargeRaw / finesRoutes : 0;
const drivingBehaviorRawPerRoute = routes > 0 ? drivingBehaviorRawPenalty / routes : 0;
const totalRawPenaltyPerRoute = routes > 0 ? totalRawPenalty / routes : 0;
const damagePenalty = damageRawPoints * IL20_WEIGHTS.damage;
const finesPenalty = finesRawPoints * IL20_WEIGHTS.fines;
const finesSurcharge = finesSurchargeRaw * IL20_WEIGHTS.fines;
const drivingBehaviorPenalty =
drivingBehaviorRawPenalty * IL20_WEIGHTS.driving_behavior;
const totalPenalty =
damagePenalty +
finesPenalty +
finesSurcharge +
drivingBehaviorPenalty;
const totalPenaltyPerRoute = routes > 0 ? totalPenalty / routes : 0;
const scaledPenaltyPer10Routes = totalPenaltyPerRoute * 10;
const scoreBeforeBonus = Math.max(
0,
Math.min(100, 100 - scaledPenaltyPer10Routes)
);
const fiveStarBonusResult = il20_calculateFiveStarBonus(
driver.five_star_count,
routes
);
const finalScore = Math.max(
0,
scoreBeforeBonus + fiveStarBonusResult.bonus_points
);
const lifetimeStats = lifetimeData[badge] || {
accidents_1A_one_sided: 0,
accidents_1B_multi_sided: 0,
accidents_1C_immobilization: 0,
fines_amount: 0
};
scores.push({
badge_number: badge,
hub,
routes,
weeks_active: driver.weeks_active,
hub_avg_routes: hubAvgRoutes,
damage_points: damagePenalty,
fines_points: finesPenalty,
driving_behavior_points: drivingBehaviorPenalty,
fines_surcharge: finesSurcharge,
total_penalty: totalPenalty,
total_penalty_per_route: totalPenaltyPerRoute,
scaled_penalty_per_10_routes: scaledPenaltyPer10Routes,
score_before_bonus: scoreBeforeBonus,
five_star_bonus_points: fiveStarBonusResult.bonus_points,
final_score: finalScore,
five_star_bonus: fiveStarBonusResult,
raw_points: {
damage: damageRawPoints,
fines: finesRawPoints,
fines_surcharge: finesSurchargeRaw,
driving_behavior: drivingBehaviorRawPenalty,
total_penalty: totalRawPenalty
},
raw_penalty_per_route: {
damage: damageRawPerRoute,
fines: finesRawPerRoute,
fines_surcharge: finesSurchargeRawPerRoute,
driving_behavior: drivingBehaviorRawPerRoute,
total_penalty: totalRawPenaltyPerRoute
},
penalty_per_route: {
damage: routes > 0 ? damagePenalty / routes : 0,
fines: finesRoutes > 0 ? finesPenalty / finesRoutes : 0,
fines_surcharge: finesRoutes > 0 ? finesSurcharge / finesRoutes : 0,
driving_behavior: routes > 0 ? drivingBehaviorPenalty / routes : 0,
total_penalty: totalPenaltyPerRoute
},
raw_data: {
one_sided_accidents: driver.total_one_sided,
multi_sided_accidents: driver.total_multi_sided,
vehicle_immobilizations: driver.total_immobilization,
fines_count: driver.total_fines,
fines_amount: driver.total_fines_amount,
five_star_comments: Number(driver.five_star_count || 0),
samsara_safety_score: driver.samsara_safety_score,
has_samsara_safety_score: driver.samsara_safety_score !== null
},
calculation_details: {
badge_match: 'Unieke chauffeurreferentie uit SAFETY_SCORES wordt gekoppeld aan DRIVER_KPI_DATA',
score_logic: 'final_score = 100 - ((total_weighted_penalty / routes) * 10) + five_star_bonus_points',
breakdown_logic: 'De gewogen penalty wordt gedeeld door het aantal routes en daarna vermenigvuldigd met 10.',
five_star_logic: 'five_star_ratio = five_star_comments / routes. Alleen eligible vanaf 4 routes en minimaal 1 five-star comment.',
removed_from_score: ['speeding', 'g-force']
},
lifetime_stats: lifetimeStats
});
}
return scores;
}
function il20_calculateDrivingBehaviorPenalty(samsaraSafetyScore) {
const score = il20_normalizeScore(samsaraSafetyScore);
if (score === null) {
return 0;
}
return 100 - score;
}
function il20_calculateFiveStarBonus(fiveStarCount, routes) {
const comments = Number(fiveStarCount || 0);
const totalRoutes = Number(routes || 0);
if (totalRoutes < IL20_FIVE_STAR_MIN_ROUTES) {
return {
eligible: false,
five_star_comments: comments,
routes: totalRoutes,
five_star_ratio: 0,
bonus_points: 0,
reason: `Niet eligible: minder dan ${IL20_FIVE_STAR_MIN_ROUTES} routes`
};
}
const fiveStarRatio = totalRoutes > 0 ? comments / totalRoutes : 0;
let bonusPoints = 0;
if (comments <= 0) {
bonusPoints = 0;
} else if (fiveStarRatio > 1) {
bonusPoints = 8;
} else if (fiveStarRatio >= 0.5) {
bonusPoints = 6;
} else if (fiveStarRatio >= 0.1) {
bonusPoints = 3;
} else if (fiveStarRatio > 0) {
bonusPoints = 1;
}
bonusPoints = Math.min(IL20_FIVE_STAR_MAX_BONUS, bonusPoints);
return {
eligible: true,
five_star_comments: comments,
routes: totalRoutes,
five_star_ratio: fiveStarRatio,
bonus_points: bonusPoints,
reason: 'Bonus berekend op basis van matrix: 0 comments=0, >0-0.1=1, 0.1-0.5=3, 0.5-1=6, >1=8'
};
}
function il20_calculateAverageRoutesPerHub(driverList) {
const hubRoutes = {};
driverList.forEach(driver => {
const hub = driver.hub || IL20_TARGET_HUB;
const routes = Number(driver.total_routes || 0);
if (!hubRoutes[hub]) {
hubRoutes[hub] = [];
}
if (routes > 0) {
hubRoutes[hub].push(routes);
}
});
const hubAverages = {};
Object.keys(hubRoutes).forEach(hub => {
const total = hubRoutes[hub].reduce((a, b) => a + b, 0);
hubAverages[hub] = hubRoutes[hub].length ? total / hubRoutes[hub].length : 1;
});
return hubAverages;
}
function il20_calculateFinesPoints(amount, hubOrCode) {
const amountNumber = Number(amount || 0);
if (amountNumber <= 0) {
return {
points: 0,
surcharge: 0
};
}
const hubString = String(hubOrCode || '').toUpperCase();
const isBelgium = ['BE_HUB_1']
.some(code => hubString.includes(code));
let points = 0;
let surcharge = 0;
if (isBelgium) {
if (amountNumber <= 50) points = 5;
else if (amountNumber <= 100) points = 10;
else if (amountNumber <= 150) points = 20;
else {
points = 35;
surcharge = 15;
}
} else {
if (amountNumber <= 100) points = 5;
else if (amountNumber <= 200) points = 10;
else if (amountNumber <= 300) points = 20;
else if (amountNumber <= 400) points = 25;
else {
points = 35;
surcharge = 15;
}
}
return {
points,
surcharge
};
}
// ==========================================
// HUB RANKINGS
// ==========================================
function il20_createHubRankings(driverScores, rankChanges = {}) {
const hubRankings = {};
const hubGroups = {};
for (const driver of driverScores) {
const hub = driver.hub || IL20_TARGET_HUB;
if (hub !== IL20_TARGET_HUB) {
continue;
}
if (!hubGroups[hub]) {
hubGroups[hub] = [];
}
hubGroups[hub].push(driver);
}
const hubStats = [];
for (const [hubCode, drivers] of Object.entries(hubGroups)) {
const sortedDrivers = drivers.sort((a, b) => b.final_score - a.final_score);
const topDrivers = sortedDrivers.map((driver, index) => ({
rank: index + 1,
badge_number: driver.badge_number,
final_score: il20_round(driver.final_score),
score_before_bonus: il20_round(driver.score_before_bonus),
five_star_bonus_points: il20_round(driver.five_star_bonus_points),
total_penalty: il20_round(driver.total_penalty),
total_penalty_per_route: il20_round(driver.total_penalty_per_route),
scaled_penalty_per_10_routes: il20_round(driver.scaled_penalty_per_10_routes),
rank_change: rankChanges[`${hubCode}_${driver.badge_number}`] ?? 0,
routes: driver.routes,
weeks_active: driver.weeks_active,
hub_avg_routes: il20_round(driver.hub_avg_routes),
raw_points: {
damage: il20_round(driver.raw_points.damage),
fines: il20_round(driver.raw_points.fines),
fines_surcharge: il20_round(driver.raw_points.fines_surcharge),
driving_behavior: il20_round(driver.raw_points.driving_behavior),
total_penalty: il20_round(driver.raw_points.total_penalty)
},
weighted_raw_points: {
damage: il20_round(driver.damage_points),
fines: il20_round(driver.fines_points),
fines_surcharge: il20_round(driver.fines_surcharge),
driving_behavior: il20_round(driver.driving_behavior_points),
total_penalty: il20_round(driver.total_penalty)
},
weighted_raw_penalty_per_route: {
damage: il20_round(driver.routes > 0 ? driver.damage_points / driver.routes : 0),
fines: il20_round(driver.routes > 0 ? driver.fines_points / driver.routes : 0),
fines_surcharge: il20_round(driver.routes > 0 ? driver.fines_surcharge / driver.routes : 0),
driving_behavior: il20_round(driver.routes > 0 ? driver.driving_behavior_points / driver.routes : 0),
total_penalty: il20_round(driver.routes > 0 ? driver.total_penalty / driver.routes : 0)
},
five_star_bonus: {
eligible: driver.five_star_bonus.eligible,
five_star_comments: il20_round(driver.five_star_bonus.five_star_comments),
routes: il20_round(driver.five_star_bonus.routes),
five_star_ratio: il20_round(driver.five_star_bonus.five_star_ratio),
bonus_points: il20_round(driver.five_star_bonus.bonus_points),
reason: driver.five_star_bonus.reason
},
incidents: driver.raw_data,
lifetime_stats: driver.lifetime_stats
}));
const damagePenTotal = drivers.reduce((sum, driver) => sum + Number(driver.damage_points || 0), 0);
const finesPenTotal = drivers.reduce((sum, driver) => sum + Number(driver.fines_points || 0), 0);
const drivingBehaviorPenTotal = drivers.reduce((sum, driver) => sum + Number(driver.driving_behavior_points || 0), 0);
const totalPenTotal = drivers.reduce((sum, driver) => sum + Number(driver.total_penalty || 0), 0);
const fiveStarBonusTotal = drivers.reduce((sum, driver) => sum + Number(driver.five_star_bonus_points || 0), 0);
const finesCountTotal = drivers.reduce((sum, driver) => sum + Number(driver.raw_data?.fines_count || 0), 0);
const finesAmountTotal = drivers.reduce((sum, driver) => sum + Number(driver.raw_data?.fines_amount || 0), 0);
const fiveStarCommentsTotal = drivers.reduce((sum, driver) => sum + Number(driver.raw_data?.five_star_comments || 0), 0);
const driversWithSamsaraScore = drivers.filter(
driver => driver.raw_data?.has_samsara_safety_score
).length;
const avgTotalPenaltyPerDriver = drivers.length > 0
? totalPenTotal / drivers.length
: 0;
const avgFiveStarBonusPerDriver = drivers.length > 0
? fiveStarBonusTotal / drivers.length
: 0;
hubStats.push({
hub_code: hubCode,
hub_name: IL20_HUBS[hubCode],
damage_pen_total: il20_round(damagePenTotal),
fines_pen_total: il20_round(finesPenTotal),
driving_behavior_pen_total: il20_round(drivingBehaviorPenTotal),
total_pen_total: il20_round(totalPenTotal),
five_star_bonus_total: il20_round(fiveStarBonusTotal),
avg_five_star_bonus_per_driver: il20_round(avgFiveStarBonusPerDriver),
five_star_comments_total: il20_round(fiveStarCommentsTotal),
fines_count_total: finesCountTotal,
fines_amount_total: il20_round(finesAmountTotal),
drivers_with_samsara_score: driversWithSamsaraScore,
drivers_without_samsara_score: drivers.length - driversWithSamsaraScore,
avg_total_penalty_per_driver: il20_round(avgTotalPenaltyPerDriver)
});
hubRankings[hubCode] = {
hub_name: IL20_HUBS[hubCode],
hub_code: hubCode,
total_drivers: drivers.length,
top_drivers: topDrivers,
statistics: il20_calculateHubStatistics(drivers)
};
}
il20_createHubRankings._lastHubStats = hubStats;
return hubRankings;
}
function il20_calculateHubStatistics(drivers) {
if (drivers.length === 0) {
return {};
}
const scores = drivers.map(driver => Number(driver.final_score || 0));
const scoreBeforeBonus = drivers.map(driver => Number(driver.score_before_bonus || 0));
const routes = drivers.map(driver => Number(driver.routes || 0));
const fiveStarBonusPoints = drivers.map(
driver => Number(driver.five_star_bonus_points || 0)
);
const fiveStarComments = drivers.map(
driver => Number(driver.raw_data?.five_star_comments || 0)
);
const samsaraScores = drivers
.map(driver => driver.raw_data?.samsara_safety_score)
.filter(score => score !== null && score !== undefined && score !== '');
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
const avgScoreBeforeBonus = scoreBeforeBonus.reduce((a, b) => a + b, 0) / scoreBeforeBonus.length;
const avgRoutes = routes.reduce((a, b) => a + b, 0) / routes.length;
const avgFiveStarBonus = fiveStarBonusPoints.reduce((a, b) => a + b, 0) / fiveStarBonusPoints.length;
const totalFiveStarComments = fiveStarComments.reduce((a, b) => a + b, 0);
const avgSamsaraScore = samsaraScores.length
? samsaraScores.reduce((a, b) => a + Number(b), 0) / samsaraScores.length
: null;
return {
avg_score: il20_round(avgScore),
avg_score_before_bonus: il20_round(avgScoreBeforeBonus),
avg_routes: il20_round(avgRoutes),
avg_five_star_bonus_points: il20_round(avgFiveStarBonus),
total_five_star_comments: il20_round(totalFiveStarComments),
avg_samsara_safety_score: avgSamsaraScore === null ? null : il20_round(avgSamsaraScore),
top_score: il20_round(Math.max(...scores)),
lowest_score: il20_round(Math.min(...scores))
};
}
// ==========================================
// RANK CHANGES
// ==========================================
function il20_calculateRankChanges(
rows,
headers,
allWeeks,
currentWeeks,
currentFinesWeeks,
lifetimeData,
inactiveDriverIds,
samsaraScoreMap
) {
const windowSize = IL20_WEEKS_TO_ANALYZE;
if (
!Array.isArray(allWeeks) ||
!Array.isArray(currentWeeks) ||
currentWeeks.length < windowSize ||
allWeeks.length < IL20_FINES_WEEKS_TO_ANALYZE + 1
) {
return {};
}
const lastWeeksSet = currentWeeks.slice(-windowSize);
const prevWeeksSet = allWeeks.slice(-(windowSize + 1), -1);
const prevFinesWeeksSet = allWeeks.slice(-(IL20_FINES_WEEKS_TO_ANALYZE + 1), -1);
if (
prevWeeksSet.length < windowSize ||
prevFinesWeeksSet.length < IL20_FINES_WEEKS_TO_ANALYZE
) {
return {};
}
const lastFiveStarCountMap = il20_getFiveStarCountMap(lastWeeksSet);
const prevFiveStarCountMap = il20_getFiveStarCountMap(prevWeeksSet);
const lastData = il20_aggregateDriverData(
rows,
headers,
lastWeeksSet,
currentFinesWeeks,
samsaraScoreMap,
lastFiveStarCountMap
);
const prevData = il20_aggregateDriverData(
rows,
headers,
prevWeeksSet,
prevFinesWeeksSet,
samsaraScoreMap,
prevFiveStarCountMap
);
let activeLastData = lastData.filter(driver => {
return (
!inactiveDriverIds.includes(il20_normalizeBadgeNumber(driver.badge_number)) &&
il20_isDriverEligible(driver)
);
});
let activePrevData = prevData.filter(driver => {
return (
!inactiveDriverIds.includes(il20_normalizeBadgeNumber(driver.badge_number)) &&
il20_isDriverEligible(driver)
);
});
if (IL20_REQUIRE_SAMSARA_SCORE) {
activeLastData = activeLastData.filter(driver => driver.samsara_safety_score !== null);
activePrevData = activePrevData.filter(driver => driver.samsara_safety_score !== null);
}
const lastScores = il20_calculateDriverScores(activeLastData, lastData, lifetimeData);
const prevScores = il20_calculateDriverScores(activePrevData, prevData, lifetimeData);
const prevRankMap = {};
const changes = {};
const prevGrouped = {};
prevScores.forEach(driver => {
if (!prevGrouped[driver.hub]) {
prevGrouped[driver.hub] = [];
}
prevGrouped[driver.hub].push(driver);
});
Object.keys(prevGrouped).forEach(hub => {
const sorted = prevGrouped[hub].sort((a, b) => b.final_score - a.final_score);
sorted.forEach((driver, index) => {
prevRankMap[`${hub}_${driver.badge_number}`] = index + 1;
});
});
const lastGrouped = {};
lastScores.forEach(driver => {
if (!lastGrouped[driver.hub]) {
lastGrouped[driver.hub] = [];
}
lastGrouped[driver.hub].push(driver);
});
Object.keys(lastGrouped).forEach(hub => {
const sorted = lastGrouped[hub].sort((a, b) => b.final_score - a.final_score);
sorted.forEach((driver, index) => {
const currentRank = index + 1;
const previousRank = prevRankMap[`${hub}_${driver.badge_number}`];
let delta = null;
if (previousRank !== undefined) {
delta = previousRank - currentRank;
}
changes[`${hub}_${driver.badge_number}`] = delta;
});
});
return changes;
}
// ==========================================
// WEEKLY PENALTY PERFORMANCE
// ==========================================
function il20_computeWeeklyPenaltyPerformance(
rows,
headers,
weeks,
aggregatedDataForHubAvg,
samsaraScoreMap
) {
const weekIndex = il20_getColIndex(headers, ['hellofresh_week']);
const badgeIndex = IL20_KPI_BADGE_INDEX;
const routesIndex = il20_getColIndex(headers, ['number_of_routes']);
const oneSidedIndex = il20_getColIndex(headers, ['number_of_one_sided_accidents']);
const multiSidedIndex = il20_getColIndex(headers, ['number_of_multi_sided_accidents']);
const immobilizationIndex = il20_getColIndex(headers, ['number_of_vehicle_immobilizations']);
const finesAmountIndex = il20_getColIndex(headers, ['fines_amount']);
let hubIndex = il20_getColIndex(headers, ['hub_code', 'hub', 'hubcode']);
if (hubIndex === -1) {
hubIndex = 2;
}
const driverWeekMap = {};
rows.forEach(row => {
const week = weekIndex !== -1 ? row[weekIndex] : null;
if (!week || !weeks.includes(week)) {
return;
}
const hub = il20_mapHubCode(row[hubIndex] || 'UNKNOWN');
if (hub !== IL20_TARGET_HUB) {
return;
}
const badge = il20_normalizeBadgeNumber(row[badgeIndex]);
if (!badge) {
return;
}
const key = `${week}|${badge}`;
if (!driverWeekMap[key]) {
driverWeekMap[key] = {
week,
badge_number: badge,
hub,
routes: 0,
one_sided: 0,
multi_sided: 0,
immobilization: 0,
fines_amount: 0
};
}
const driverWeek = driverWeekMap[key];
driverWeek.routes += routesIndex !== -1 ? Number(row[routesIndex] || 0) : 0;
driverWeek.one_sided += oneSidedIndex !== -1 ? Number(row[oneSidedIndex] || 0) : 0;
driverWeek.multi_sided += multiSidedIndex !== -1 ? Number(row[multiSidedIndex] || 0) : 0;
driverWeek.immobilization += immobilizationIndex !== -1 ? Number(row[immobilizationIndex] || 0) : 0;
driverWeek.fines_amount += finesAmountIndex !== -1 ? Number(row[finesAmountIndex] || 0) : 0;
});
const hubWeekValues = {};
Object.values(driverWeekMap).forEach(driverWeek => {
if (driverWeek.routes <= 0) {
return;
}
const badge = il20_normalizeBadgeNumber(driverWeek.badge_number);
let samsaraSafetyScore = null;
if (
samsaraScoreMap &&
Object.prototype.hasOwnProperty.call(samsaraScoreMap, badge)
) {
samsaraSafetyScore = il20_normalizeScore(samsaraScoreMap[badge]);
}
if (IL20_REQUIRE_SAMSARA_SCORE && samsaraSafetyScore === null) {
return;
}
const damageRawPoints =
(driverWeek.one_sided * IL20_DAMAGE_POINTS.one_sided) +
(driverWeek.multi_sided * IL20_DAMAGE_POINTS.multi_sided) +
(driverWeek.immobilization * IL20_DAMAGE_POINTS.immobilization);
const finesResult = il20_calculateFinesPoints(
driverWeek.fines_amount,
driverWeek.hub
);
const drivingBehaviorRawPenalty = il20_calculateDrivingBehaviorPenalty(
samsaraSafetyScore
);
const totalPenalty =
(damageRawPoints * IL20_WEIGHTS.damage) +
(finesResult.points * IL20_WEIGHTS.fines) +
(finesResult.surcharge * IL20_WEIGHTS.fines) +
(drivingBehaviorRawPenalty * IL20_WEIGHTS.driving_behavior);
if (!hubWeekValues[driverWeek.hub]) {
hubWeekValues[driverWeek.hub] = {};
}
if (!hubWeekValues[driverWeek.hub][driverWeek.week]) {
hubWeekValues[driverWeek.hub][driverWeek.week] = [];
}
hubWeekValues[driverWeek.hub][driverWeek.week].push(totalPenalty);
});
const hubWeeklyPenalties = {};
Object.keys(hubWeekValues).forEach(hub => {
const weekMap = hubWeekValues[hub];
const weeklyPenalties = {};
weeks.forEach(week => {
const values = weekMap[week] || [];
const average = values.length
? values.reduce((a, b) => a + b, 0) / values.length
: 0;
weeklyPenalties[week] = il20_round(average);
});
const values = weeks.map(week => weeklyPenalties[week]);
const finalAverage = values.length
? values.reduce((a, b) => a + b, 0) / values.length
: 0;
const improvement = values.length >= 2
? values[0] - values[values.length - 1]
: 0;
hubWeeklyPenalties[hub] = {
hub_name: IL20_HUBS[hub],
weekly_penalties: weeklyPenalties,
final_avg: il20_round(finalAverage),
improvement: il20_round(improvement)
};
});
const period1Weeks = weeks.slice(0, Math.floor(weeks.length / 2));
const period2Weeks = weeks.slice(Math.floor(weeks.length / 2));
const getWeekNumber = week => week?.split('-W')[1] || '';
const getPeriodLabel = (periodWeeks, fallbackLabel) => {
if (!periodWeeks.length) return fallbackLabel;
if (periodWeeks.length === 1) return `Week ${getWeekNumber(periodWeeks[0])}`;
return `Weken ${getWeekNumber(periodWeeks[0])}-${getWeekNumber(
periodWeeks[periodWeeks.length - 1]
)}`;
};
const twoWeekPeriods = {
period_1: {
weeks: period1Weeks,
label: `${getPeriodLabel(period1Weeks, 'Eerste periode')} (Eerste ${period1Weeks.length} weken)`,
description: `Periode 1: ${period1Weeks[0]} tot ${period1Weeks[period1Weeks.length - 1]}`
},
period_2: {
weeks: period2Weeks,
label: `${getPeriodLabel(period2Weeks, 'Laatste periode')} (Laatste ${period2Weeks.length} weken)`,
description: `Periode 2: ${period2Weeks[0]} tot ${period2Weeks[period2Weeks.length - 1]}`
},
full_period: {
weeks,
label: `Alle ${weeks.length} weken (${getWeekNumber(weeks[0])}-${getWeekNumber(weeks[weeks.length - 1])})`,
description: `Volledige periode: ${weeks[0]} tot ${weeks[weeks.length - 1]}`
}
};
const twoWeekAnalysis = {};
Object.keys(hubWeeklyPenalties).forEach(hub => {
const weeklyPenalties = hubWeeklyPenalties[hub].weekly_penalties;
const period1Values = period1Weeks.map(week => weeklyPenalties[week] || 0);
const period2Values = period2Weeks.map(week => weeklyPenalties[week] || 0);
const period1Average = period1Values.length
? period1Values.reduce((a, b) => a + b, 0) / period1Values.length
: 0;
const period2Average = period2Values.length
? period2Values.reduce((a, b) => a + b, 0) / period2Values.length
: 0;
twoWeekAnalysis[hub] = {
hub_name: IL20_HUBS[hub],
period_1_avg: il20_round(period1Average),
period_2_avg: il20_round(period2Average),
improvement: il20_round(period1Average - period2Average),
weekly_penalties: weeklyPenalties
};
});
let mostImproved = {
hub_code: null,
improvement: -Infinity
};
Object.keys(hubWeeklyPenalties).forEach(hub => {
const improvement = hubWeeklyPenalties[hub].improvement || 0;
if (improvement > mostImproved.improvement) {
mostImproved = {
hub_code: hub,
improvement
};
}
});
const summary = {
most_improved_hub: mostImproved.hub_code
? {
hub_code: mostImproved.hub_code,
hub_name: IL20_HUBS[mostImproved.hub_code],
improvement: il20_round(mostImproved.improvement)
}
: null,
analysis_note: 'Lagere strafpunten = betere prestatie. Verbetering = afname in strafpunten.',
target_hub: IL20_TARGET_HUB,
samsara_note: 'Weekly trend gebruikt dezelfde huidige Samsara Safety Score per chauffeur, tenzij je later historische weekscore-data toevoegt.',
removed_from_score: 'Speeding en g-force zijn verwijderd omdat deze al in Samsara Safety Score zitten.'
};
return {
hub_weekly_penalties: hubWeeklyPenalties,
two_week_periods: twoWeekPeriods,
two_week_analysis: twoWeekAnalysis,
three_week_periods: twoWeekPeriods,
three_week_analysis: twoWeekAnalysis,
weeks_analyzed: weeks,
summary
};
}
// ==========================================
// OPSLAAN & TESTEN
// ==========================================
function il20_saveJsonToDrive(jsonData, filename) {
try {
const jsonString = JSON.stringify(jsonData, null, 2);
const folderName = 'SCORE_ENGINE_OUTPUT';
const folders = DriveApp.getFoldersByName(folderName);
let folder;
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(folderName);
}
return folder.createFile(
filename,
jsonString,
MimeType.PLAIN_TEXT
);
} catch (error) {
console.error('IL2.0 - Error saving to Drive:', error);
throw error;
}
}
function il20_testScript() {
console.log('IL2.0 - Start demotest...');
const result = il20_generateHubBattleRankings();
console.log('IL2.0 - Demotest succesvol afgerond.');
return result;
}
Editor is loading...
Leave a Comment