• 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:
parent
d0a30a8f8d
commit
19592f2230
43
KAYA-2.md
Normal file
43
KAYA-2.md
Normal file
@ -0,0 +1,43 @@
|
||||
Kaya’s Transition Calculator — Friendly Guide
|
||||
============================================
|
||||
|
||||
This quick guide shows you how to use the calculator to move Kaya from kibble to gently cooked food at a steady, safe pace.
|
||||
|
||||
How to use it
|
||||
-------------
|
||||
1) Enter Kaya’s age in months (e.g. "5.5")
|
||||
|
||||
2) Check the energy numbers for your foods:
|
||||
- Eukanuba (kibble) defaults to 372 kcal/100g (replace if your bag shows a different number)
|
||||
- Fred & Felia (gently cooked) defaults to 115 kcal/100g (replace if your bag shows a different number)
|
||||
- Add or rename the foods (e.g., “Treats”) and enter their energy values from the label (kcal/100 g, kcal/kg, kcal/cup, or kcal/can).
|
||||
|
||||
3) Set your percentages. We will start small (e.g., GC 5%, kibble 95%), lock any values you want fixed.
|
||||
|
||||
4) Choose how to view amounts:
|
||||
- Per day or per meal; set meals/day if needed.
|
||||
- Pick your preferred units (grams, kg).
|
||||
- Use “days” to see totals for meal prep.
|
||||
|
||||
Regarding treats
|
||||
-----
|
||||
- Keep treats ≤10% (your plan is good). Larger shares can dilute the balanced portion of the diet.
|
||||
- Enter treat kcal from the package (as‑fed) for accuracy.
|
||||
- Monitor body condition and stool; adjust if needed.
|
||||
|
||||
|
||||
What you’ll see
|
||||
---------------
|
||||
- Exact amounts for each food based on your percentages and energy labels.
|
||||
- Totals per day (or per meal), and optional multi‑day batches.
|
||||
- Everything updates instantly as you change inputs.
|
||||
|
||||
Good to know
|
||||
------------
|
||||
- Gently cooked is less calorie‑dense than kibble. As you add more GC, total grams may go up. This is normal.
|
||||
- Treats count. I added a “Treats” source with its kcal so the plan stays balanced.
|
||||
- When in doubt, double‑check kcal values on the package (kcal/100 g or kcal/kg are most common).
|
||||
|
||||
Remember
|
||||
-----------------
|
||||
Every dog is unique. Monitor body condition, stool quality, and appetite. We will adjust percentages and amounts as Kaya grows and responds to the new plan.
|
||||
38
KAYA.md
Normal file
38
KAYA.md
Normal file
@ -0,0 +1,38 @@
|
||||
Kaya Transition Calculator — Quick Guide
|
||||
=======================================
|
||||
|
||||
This tool helps transition Kaya (30 kg adult) from kibble to gently cooked while keeping daily energy intake continuous. Enter Kaya’s age, set food sources and their energy values, then adjust percentages to see exactly how much to feed of each food.
|
||||
|
||||
How to use
|
||||
----------
|
||||
- Enter Kaya’s age in months (2.0–12.0). If outside this range, the tool adjusts to the nearest valid value.
|
||||
- Confirm energy values for each food source:
|
||||
- “Eukanuba, kibble” defaults to 372 kcal/100 g — change if your bag shows a different value.
|
||||
- “Fred & Felia, gently cooked” defaults to 115 kcal/100 g.
|
||||
- Add/rename sources (e.g., “Treats”) and enter their energy (kcal/100 g, kcal/kg, kcal/cup, or kcal/can).
|
||||
- Set percentages for each food; lock any you want fixed. The total always equals 100%.
|
||||
- Optional: switch units (g/kg/oz/lb; cups enabled only when kcal/cup is entered), choose per‑day or per‑meal, set meals/day, and use “days” to see batch totals.
|
||||
|
||||
What it calculates
|
||||
------------------
|
||||
- Daily energy target is derived from the 30 kg kibble feeding curve using Kaya’s exact age (month‑level interpolation), not from generic MER.
|
||||
- That daily kcal target is split across your foods by percentage and converted into amounts using each food’s energy density.
|
||||
- Results include per‑food amounts and totals, per‑day or per‑meal, in your selected units.
|
||||
|
||||
Formulas (at a glance)
|
||||
----------------------
|
||||
- Kibble grams/day (30 kg) by month (g/day):
|
||||
2: 250, 3: 330, 4: 365, 5: 382, 6: 400, 7: 405, 8: 410, 9: 410, 10: 410, 11: 408, 12: 405.
|
||||
Linear interpolation is applied between months (e.g., 5.5 months is halfway between 5 and 6).
|
||||
- Daily kcal target = kibble_g/day × (kibble_kcal_per_100g ÷ 100)
|
||||
- Per‑food kcal = daily_kcal × (food_percentage ÷ 100)
|
||||
- Per‑food grams (kcal/100 g) = per‑food_kcal ÷ (kcal_per_100g ÷ 100)
|
||||
- Per‑food grams (kcal/kg) = per‑food_kcal ÷ (kcal_per_kg ÷ 1000)
|
||||
- Cups (when kcal/cup provided) = per‑food_kcal ÷ kcal_per_cup
|
||||
- Internal assumptions for conversions: 1 cup ≈ 120 g (dry), 1 can ≈ 450 g (wet)
|
||||
|
||||
Notes
|
||||
-----
|
||||
- Age input is limited to 2–12 months; values are rounded for display (per‑meal amounts round after splitting).
|
||||
- Accuracy depends on correct energy values on your foods’ labels. When in doubt, confirm the kcal numbers on the packaging.
|
||||
- This guide supports professional planning; your nutritionist may fine‑tune based on Kaya’s body condition and response.
|
||||
119
iframe.html
119
iframe.html
@ -1981,6 +1981,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">
|
||||
@ -2091,7 +2097,13 @@ 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
|
||||
};
|
||||
|
||||
/**
|
||||
@ -2113,7 +2125,7 @@ const CALCULATOR_CONFIG = {
|
||||
this.showPerMeal = false;
|
||||
// Kayafied reference source tracking
|
||||
this.kibbleRefId = null;
|
||||
this.gcRefId = null;
|
||||
this.fredRefId = null;
|
||||
this.storageKey = 'kaya_calculator_state_v1';
|
||||
this.init();
|
||||
}
|
||||
@ -2138,11 +2150,13 @@ const CALCULATOR_CONFIG = {
|
||||
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,
|
||||
@ -2211,12 +2225,20 @@ const CALCULATOR_CONFIG = {
|
||||
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',
|
||||
@ -2224,13 +2246,13 @@ const CALCULATOR_CONFIG = {
|
||||
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();
|
||||
@ -2284,18 +2306,18 @@ const CALCULATOR_CONFIG = {
|
||||
// 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(),
|
||||
@ -2363,7 +2385,7 @@ const CALCULATOR_CONFIG = {
|
||||
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}`);
|
||||
@ -2774,7 +2796,7 @@ const CALCULATOR_CONFIG = {
|
||||
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' : '';
|
||||
@ -3065,8 +3087,8 @@ const CALCULATOR_CONFIG = {
|
||||
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());
|
||||
@ -3431,26 +3453,38 @@ const CALCULATOR_CONFIG = {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -3509,6 +3543,7 @@ const CALCULATOR_CONFIG = {
|
||||
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)) {
|
||||
@ -3537,6 +3572,16 @@ const CALCULATOR_CONFIG = {
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@ -3549,8 +3594,8 @@ const CALCULATOR_CONFIG = {
|
||||
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;
|
||||
}
|
||||
@ -3570,7 +3615,7 @@ const CALCULATOR_CONFIG = {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user