• feat(kaya): use current weight for Fred & Felia MER grams
- Add current weight input + validation to Kaya UI - Persist/restore weight in local storage - Compute Fred & Felia grams/day from MER using entered weight and age factor - Keep kibble chart-based; allow editing Fred energy density - Rebuild iframe.html from src/
This commit is contained in:
@@ -10,6 +10,12 @@
|
||||
<div id="ageClampNote" class="dog-calculator-error dog-calculator-hidden">Age adjusted to the supported 2–12 month range.</div>
|
||||
</div>
|
||||
|
||||
<div class="dog-calculator-form-group">
|
||||
<label for="weight" id="weightLabel">Kaya’s current weight (kg):</label>
|
||||
<input type="number" id="weight" min="0.1" step="0.1" placeholder="Enter current weight in kg" aria-describedby="weightHelp">
|
||||
<div id="weightError" class="dog-calculator-error dog-calculator-hidden">Please enter a valid weight (minimum 0.1 kg)</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="dog-calculator-form-group">
|
||||
|
||||
+69
-36
@@ -17,7 +17,7 @@
|
||||
this.showPerMeal = false;
|
||||
// Kayafied reference source tracking
|
||||
this.kibbleRefId = null;
|
||||
this.gcRefId = null;
|
||||
this.fredRefId = null;
|
||||
this.storageKey = 'kaya_calculator_state_v1';
|
||||
this.init();
|
||||
}
|
||||
@@ -42,11 +42,13 @@
|
||||
saveStateToStorage() {
|
||||
try {
|
||||
const ageInput = document.getElementById('ageMonths');
|
||||
const weightInput = document.getElementById('weight');
|
||||
const unitSelect = document.getElementById('unit');
|
||||
const daysInput = document.getElementById('days');
|
||||
const state = {
|
||||
version: 1,
|
||||
age: ageInput && ageInput.value !== '' ? parseFloat(ageInput.value) : null,
|
||||
weight: weightInput && weightInput.value !== '' ? parseFloat(weightInput.value) : null,
|
||||
unit: unitSelect ? unitSelect.value : 'g',
|
||||
days: daysInput && daysInput.value ? parseInt(daysInput.value) : 1,
|
||||
showPerMeal: !!this.showPerMeal,
|
||||
@@ -115,12 +117,20 @@
|
||||
ageInput.value = state.age;
|
||||
}
|
||||
|
||||
// Restore weight (used for Fred & Felia MER calculation)
|
||||
const weightInput = document.getElementById('weight');
|
||||
if (weightInput && (state.weight || state.weight === 0)) {
|
||||
weightInput.value = state.weight;
|
||||
}
|
||||
|
||||
// Restore food sources
|
||||
if (Array.isArray(state.foodSources) && state.foodSources.length) {
|
||||
this.foodSources = [];
|
||||
this.kibbleRefId = null;
|
||||
this.gcRefId = null;
|
||||
this.fredRefId = null;
|
||||
state.foodSources.forEach(saved => {
|
||||
// Back-compat: older Kaya state stored Fred & Felia as `chartType: "gc"`.
|
||||
const chartType = saved.chartType === 'gc' ? 'mer' : (saved.chartType || null);
|
||||
const fs = {
|
||||
id: saved.id || this.generateFoodSourceId(),
|
||||
name: saved.name || 'Food Source',
|
||||
@@ -128,13 +138,13 @@
|
||||
energyUnit: saved.energyUnit || 'kcal100g',
|
||||
percentage: typeof saved.percentage === 'number' ? saved.percentage : 0,
|
||||
isLocked: !!saved.isLocked,
|
||||
chartType: saved.chartType || null,
|
||||
chartType: chartType,
|
||||
splitByMeals: (saved.splitByMeals === undefined ? true : saved.splitByMeals)
|
||||
};
|
||||
this.foodSources.push(fs);
|
||||
this.renderFoodSource(fs);
|
||||
if (fs.chartType === 'kibble' && !this.kibbleRefId) this.kibbleRefId = fs.id;
|
||||
if (fs.chartType === 'gc' && !this.gcRefId) this.gcRefId = fs.id;
|
||||
if (fs.chartType === 'mer' && !this.fredRefId) this.fredRefId = fs.id;
|
||||
});
|
||||
this.updateAddButton();
|
||||
this.updateRemoveButtons();
|
||||
@@ -188,18 +198,18 @@
|
||||
// Food Source Management Methods
|
||||
initializeFoodSources() {
|
||||
// Seed three sources for Kaya's transition
|
||||
const gc = {
|
||||
const fred = {
|
||||
id: this.generateFoodSourceId(),
|
||||
name: 'Fred & Felia (Junior Huhn)',
|
||||
energy: '115',
|
||||
energyUnit: 'kcal100g',
|
||||
percentage: 5,
|
||||
isLocked: false,
|
||||
chartType: 'gc'
|
||||
chartType: 'mer'
|
||||
};
|
||||
this.foodSources.push(gc);
|
||||
this.renderFoodSource(gc);
|
||||
this.gcRefId = gc.id;
|
||||
this.foodSources.push(fred);
|
||||
this.renderFoodSource(fred);
|
||||
this.fredRefId = fred.id;
|
||||
|
||||
const kibble = {
|
||||
id: this.generateFoodSourceId(),
|
||||
@@ -267,7 +277,7 @@
|
||||
this.foodSources.splice(index, 1);
|
||||
// If reference IDs were removed, clear them
|
||||
if (this.kibbleRefId === id) this.kibbleRefId = null;
|
||||
if (this.gcRefId === id) this.gcRefId = null;
|
||||
if (this.fredRefId === id) this.fredRefId = null;
|
||||
|
||||
// Remove the DOM element
|
||||
const element = document.getElementById(`foodSource-${id}`);
|
||||
@@ -678,7 +688,7 @@
|
||||
const container = document.getElementById('foodSources');
|
||||
if (!container) return;
|
||||
|
||||
const isChart = foodSource.chartType === 'gc' || foodSource.chartType === 'kibble';
|
||||
const isChart = foodSource.chartType === 'kibble';
|
||||
const energyReadonlyAttr = isChart ? 'readonly' : '';
|
||||
const energyTitle = isChart ? 'Chart-based food: kcal locked' : 'Enter energy content';
|
||||
const unitDisabledAttr = isChart ? 'disabled' : '';
|
||||
@@ -969,8 +979,8 @@
|
||||
const addFoodBtn = document.getElementById('addFoodBtn');
|
||||
|
||||
if (weightInput) {
|
||||
weightInput.addEventListener('input', () => this.updateCalorieCalculations());
|
||||
weightInput.addEventListener('blur', () => this.validateWeight());
|
||||
weightInput.addEventListener('input', () => { this.updateCalorieCalculations(); this.saveStateToStorage(); });
|
||||
weightInput.addEventListener('blur', () => { this.validateWeight(); this.saveStateToStorage(); });
|
||||
}
|
||||
|
||||
if (dogTypeSelect) dogTypeSelect.addEventListener('change', () => this.updateCalorieCalculations());
|
||||
@@ -1335,26 +1345,38 @@
|
||||
return lowerVal + (upperVal - lowerVal) * t;
|
||||
}
|
||||
|
||||
// Kaya: GC chart interpolation across buckets
|
||||
getGCChartGramsForAge(ageMonths) {
|
||||
// Buckets per guideline with boundary rule (5.0 → later bucket, 7.0 → later bucket)
|
||||
// <5 mo (2.0–<5.0): 950→1350
|
||||
// 5–6 mo (5.0–6.0): 1250→1550; (6.0–<7.0): hold 1550
|
||||
// 7–12 mo (7.0–12.0): 1300→1500
|
||||
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
||||
const age = clamp(ageMonths, 2, 12);
|
||||
if (age < 5) {
|
||||
const t = (age - 2) / (5 - 2);
|
||||
return 950 + t * (1350 - 950);
|
||||
} else if (age < 6) {
|
||||
const t = (age - 5) / (6 - 5);
|
||||
return 1250 + t * (1550 - 1250);
|
||||
} else if (age < 7) {
|
||||
return 1550; // hold upper value until 7.0
|
||||
} else {
|
||||
const t = (age - 7) / (12 - 7);
|
||||
return 1300 + t * (1500 - 1300);
|
||||
}
|
||||
// Kaya: MER-based grams/day for Fred & Felia (kibble remains chart-based)
|
||||
getKayaMerFactorForAge(ageMonths) {
|
||||
if (ageMonths === null || ageMonths === undefined) return null;
|
||||
if (ageMonths < 4) return CALCULATOR_CONFIG.kayaMerFactorUnder4Months;
|
||||
return CALCULATOR_CONFIG.kayaMerFactorFrom4Months;
|
||||
}
|
||||
|
||||
getKayaCurrentWeightKg() {
|
||||
const weightInput = document.getElementById('weight');
|
||||
if (!weightInput) return CALCULATOR_CONFIG.kayaMerDefaultWeightKg;
|
||||
const weightKg = this.getWeightInKg();
|
||||
if (!weightKg || weightKg < 0.1) return null;
|
||||
return weightKg;
|
||||
}
|
||||
|
||||
getKayaMerCaloriesForAge(ageMonths) {
|
||||
const factor = this.getKayaMerFactorForAge(ageMonths);
|
||||
if (!factor) return null;
|
||||
|
||||
const weightKg = this.getKayaCurrentWeightKg();
|
||||
if (!weightKg) return null;
|
||||
|
||||
const rer = this.calculateRER(weightKg);
|
||||
return this.calculateMER(rer, factor);
|
||||
}
|
||||
|
||||
getKayaMerBasedGramsForFood(ageMonths, energyPer100g) {
|
||||
if (!energyPer100g || energyPer100g <= 0.1) return null;
|
||||
const merCalories = this.getKayaMerCaloriesForAge(ageMonths);
|
||||
if (!merCalories) return null;
|
||||
const kcalPerGram = energyPer100g / 100;
|
||||
return merCalories / kcalPerGram;
|
||||
}
|
||||
|
||||
|
||||
@@ -1413,6 +1435,7 @@
|
||||
this.showError(`energy-error-${fs.id}`, false);
|
||||
});
|
||||
this.showError('daysError', false);
|
||||
this.showError('weightError', false);
|
||||
|
||||
// Validate days input
|
||||
if (!days || !this.validateInput(days, 1, true)) {
|
||||
@@ -1441,6 +1464,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Fred & Felia (MER-based) needs current weight to compute grams.
|
||||
const needsWeight = this.foodSources.some(fs => fs.chartType === 'mer' && (fs.percentage || 0) > 0);
|
||||
const weightInput = document.getElementById('weight');
|
||||
if (needsWeight && weightInput) {
|
||||
const weightKg = this.getWeightInKg();
|
||||
if (!weightKg || weightKg < 0.1) {
|
||||
this.showError('weightError', true);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate per-food breakdown (chart-first)
|
||||
const foodBreakdowns = [];
|
||||
let totalDailyGrams = 0;
|
||||
@@ -1453,8 +1486,8 @@
|
||||
this.foodSources.forEach(fs => {
|
||||
const energyPer100g = this.getFoodSourceEnergyPer100g(fs);
|
||||
let chartGrams = null;
|
||||
if (fs.chartType === 'gc') {
|
||||
chartGrams = age !== null ? this.getGCChartGramsForAge(age) : null;
|
||||
if (fs.chartType === 'mer') {
|
||||
chartGrams = age !== null ? this.getKayaMerBasedGramsForFood(age, energyPer100g) : null;
|
||||
} else if (fs.chartType === 'kibble') {
|
||||
chartGrams = age !== null ? this.getKayaKibbleGramsForAge(age) : null;
|
||||
}
|
||||
@@ -1474,7 +1507,7 @@
|
||||
firstPass.forEach(({ fs, energyPer100g, gramsPortion }) => {
|
||||
let dailyGramsForThisFood = 0;
|
||||
let hasEnergyContent = !!(energyPer100g && energyPer100g > 0);
|
||||
if ((fs.chartType === 'gc' || fs.chartType === 'kibble')) {
|
||||
if ((fs.chartType === 'mer' || fs.chartType === 'kibble')) {
|
||||
if (gramsPortion !== null) {
|
||||
dailyGramsForThisFood = gramsPortion;
|
||||
}
|
||||
|
||||
+8
-2
@@ -7,5 +7,11 @@ const CALCULATOR_CONFIG = {
|
||||
defaultScale: 1.0,
|
||||
maxFoodSources: 5,
|
||||
minScale: 0.5,
|
||||
maxScale: 2.0
|
||||
};
|
||||
maxScale: 2.0,
|
||||
|
||||
// Kaya fork: Fred & Felia uses MER; kibble stays chart-based (30 kg column).
|
||||
// Used only when weight input is not present (back-compat).
|
||||
kayaMerDefaultWeightKg: 30,
|
||||
kayaMerFactorUnder4Months: 3.0,
|
||||
kayaMerFactorFrom4Months: 2.0
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user