From 65a36c0f429c67a714bc3e46e59f13bc49c017a8 Mon Sep 17 00:00:00 2001 From: Dominic Ferrando Date: Fri, 12 Dec 2025 15:00:25 -0500 Subject: [PATCH] Regoranize generator code --- src/lib/services/calculator/main.ts | 233 ++++++++++++++-------------- 1 file changed, 119 insertions(+), 114 deletions(-) diff --git a/src/lib/services/calculator/main.ts b/src/lib/services/calculator/main.ts index 0245868..98542af 100644 --- a/src/lib/services/calculator/main.ts +++ b/src/lib/services/calculator/main.ts @@ -412,128 +412,65 @@ export class Standards { ? this.activityStandards[activity].metadata.generators : []; - const ageGenerators = allGenerators.filter( - (g) => g.metric === "age", - ); - for (const ageGenerator of ageGenerators) { - for (const gender of Object.values(Gender)) { - const peakAge = this.cfg.activity[activity].peakAge; - const ageStep = 10; + for (const generator of allGenerators) { + if (generator.metric === "age") { + for (const gender of Object.values(Gender)) { + const peakAge = this.cfg.activity[activity].peakAge; + const ageStep = 10; - const minAge = 0; - const maxAge = 100; - - const referenceStandard = this.byActivity(activity) - .byMetrics({ - weight: getAvgWeight(gender, peakAge), - gender, - age: peakAge, - }) - .getOneInterpolated(); - - let currAge = minAge; - while (currAge <= maxAge) { - const newStandard: Standard = { - metrics: { - weight: getAvgWeight(gender, currAge), - age: currAge, - gender, - }, - levels: {}, - }; - - for (const lvl in referenceStandard.levels) { - const base = referenceStandard.levels[lvl]; - if (!base) continue; - - const s = this.cfg.activity[activity].ageModifier; - - const youngFloor0 = 0.5; - const oldFloor0 = 0.5; - - const youngFloor = 1 - (1 - youngFloor0) * s; - const oldFloor = 1 - (1 - oldFloor0) * s; - - const cy = - (s * (1 - youngFloor0)) / - Math.pow(peakAge - minAge, 2); - const co = - (s * (1 - oldFloor0)) / - Math.pow(maxAge - peakAge, 2); - - let f = - currAge <= peakAge - ? 1 - cy * Math.pow(peakAge - currAge, 2) - : 1 - co * Math.pow(currAge - peakAge, 2); - - const floor = Math.min(youngFloor, oldFloor); - const fCurr = Math.max(floor, Math.min(1, f)); - - newStandard.levels[lvl] = base * fCurr; - } - - // prefer real data over generated data - const overlappingStandard = this.byActivity(activity) - .byMetrics(newStandard.metrics) - .getOne(); - if (!overlappingStandard) - this.activityStandards[activity].standards.push( - newStandard, - ); - - currAge += ageStep; - } - } - - // now that we generated metric data, remove the old data - this.activityStandards[activity].standards = - this.activityStandards[activity].standards.filter( - (s) => s.metrics.weight, - ); - } - - const weightGenerators = allGenerators.filter( - (g) => g.metric === "weight", - ); - for (const weightGenerator of weightGenerators) { - for (const gender of Object.values(Gender)) { - for (const age of this.agesFor(activity, gender)) { - const weightStep = 12; - const referenceWeight = getAvgWeight(gender, age); - - const minWeight = Math.max( - referenceWeight - 3 * weightStep, - 1, - ); - const maxWeight = referenceWeight + 3 * weightStep; + const minAge = 0; + const maxAge = 100; const referenceStandard = this.byActivity(activity) - .byMetrics({ weight: referenceWeight, gender, age }) + .byMetrics({ + weight: getAvgWeight(gender, peakAge), + gender, + age: peakAge, + }) .getOneInterpolated(); - let currWeight = minWeight; - while (currWeight <= maxWeight) { + let currAge = minAge; + while (currAge <= maxAge) { const newStandard: Standard = { metrics: { - weight: currWeight, - age, + weight: getAvgWeight(gender, currAge), + age: currAge, gender, }, levels: {}, }; - // apply allometric scaling to generate levels for (const lvl in referenceStandard.levels) { - if (!referenceStandard.levels[lvl]) continue; + const base = referenceStandard.levels[lvl]; + if (!base) continue; - const scalingExponent = - this.cfg.activity[activity].weightModifier; - const coefficient = - currWeight / referenceWeight; + const s = + this.cfg.activity[activity].ageModifier; - newStandard.levels[lvl] = - referenceStandard.levels[lvl] * - coefficient ** scalingExponent; + const youngFloor0 = 0.5; + const oldFloor0 = 0.5; + + const youngFloor = 1 - (1 - youngFloor0) * s; + const oldFloor = 1 - (1 - oldFloor0) * s; + + const cy = + (s * (1 - youngFloor0)) / + Math.pow(peakAge - minAge, 2); + const co = + (s * (1 - oldFloor0)) / + Math.pow(maxAge - peakAge, 2); + + let f = + currAge <= peakAge + ? 1 - + cy * Math.pow(peakAge - currAge, 2) + : 1 - + co * Math.pow(currAge - peakAge, 2); + + const floor = Math.min(youngFloor, oldFloor); + const fCurr = Math.max(floor, Math.min(1, f)); + + newStandard.levels[lvl] = base * fCurr; } // prefer real data over generated data @@ -547,16 +484,84 @@ export class Standards { newStandard, ); - currWeight += weightStep; + currAge += ageStep; } } - } - // now that we generated metric data, remove the old data - this.activityStandards[activity].standards = - this.activityStandards[activity].standards.filter( - (s) => s.metrics.weight, - ); + // now that we generated metric data, remove the old data + this.activityStandards[activity].standards = + this.activityStandards[activity].standards.filter( + (s) => s.metrics.weight, + ); + } else if (generator.metric === "weight") { + for (const gender of Object.values(Gender)) { + for (const age of this.agesFor(activity, gender)) { + const weightStep = 12; + const referenceWeight = getAvgWeight(gender, age); + + const minWeight = Math.max( + referenceWeight - 3 * weightStep, + 1, + ); + const maxWeight = referenceWeight + 3 * weightStep; + + const referenceStandard = this.byActivity(activity) + .byMetrics({ + weight: referenceWeight, + gender, + age, + }) + .getOneInterpolated(); + + let currWeight = minWeight; + while (currWeight <= maxWeight) { + const newStandard: Standard = { + metrics: { + weight: currWeight, + age, + gender, + }, + levels: {}, + }; + + // apply allometric scaling to generate levels + for (const lvl in referenceStandard.levels) { + if (!referenceStandard.levels[lvl]) + continue; + + const scalingExponent = + this.cfg.activity[activity] + .weightModifier; + const coefficient = + currWeight / referenceWeight; + + newStandard.levels[lvl] = + referenceStandard.levels[lvl] * + coefficient ** scalingExponent; + } + + // prefer real data over generated data + const overlappingStandard = this.byActivity( + activity, + ) + .byMetrics(newStandard.metrics) + .getOne(); + if (!overlappingStandard) + this.activityStandards[ + activity + ].standards.push(newStandard); + + currWeight += weightStep; + } + } + } + + // now that we generated metric data, remove the old data + this.activityStandards[activity].standards = + this.activityStandards[activity].standards.filter( + (s) => s.metrics.weight, + ); + } } this.activityStandards[activity].standards = this.activityStandards[