Fix string weight causing 40k kcal, navigation, remove egg hardcode
Critical kcal fix:
- Profile weight is stored as string ("88"), not number
- "88" + 12 = "8812" (string concat) → mass 8812kg → 40000 kcal
- Fix: parseFloat(weightKg) at top of computeStats
- Also clamp gradient to +-50% to prevent NaN from GPS spikes
Navigation:
- Chart fullscreen on Today screen now has popstate handler
- Global popstate: collapse any visible .chart-full before skipping stale entries
- Both X button and back gesture properly collapse chart
Removed egg hardcode:
- product_units no longer auto-seeded with egg data
- User adds per-product unit mappings manually
- Eliminates slow LIKE query on 13000 products at every startup
Noted bike settings from user: weight 16kg, wheel 24", chainring 40, cog 18, mixed
This commit is contained in:
parent
606481103c
commit
9b5c9a204c
3 changed files with 22 additions and 25 deletions
|
|
@ -906,22 +906,8 @@ async function runMigrations() {
|
|||
console.log("[db.js] Seeded 5 serving units");
|
||||
}
|
||||
|
||||
// Seed product_units for eggs (one-time)
|
||||
try {
|
||||
const puCnt = await select("SELECT COUNT(*) as cnt FROM product_units");
|
||||
if (puCnt[0].cnt === 0) {
|
||||
const eggProducts = await select(
|
||||
`SELECT id FROM products WHERE name_lower LIKE '%яйц%' AND name_lower LIKE '%кур%'`
|
||||
);
|
||||
for (const ep of eggProducts) {
|
||||
await execute(
|
||||
`INSERT OR IGNORE INTO product_units (product_id, unit_id, grams_per_unit)
|
||||
VALUES (?1, 1, 60)`, [ep.id]
|
||||
);
|
||||
}
|
||||
if (eggProducts.length > 0) console.log(`[db.js] Linked ${eggProducts.length} egg products`);
|
||||
}
|
||||
} catch (e) { console.error("[db.js] egg unit seeding error:", e); }
|
||||
// product_units: user adds per-product unit mappings manually via UI.
|
||||
// No auto-seeding — product names too varied for reliable matching.
|
||||
|
||||
// --- Bikes table ---
|
||||
await execute(`CREATE TABLE IF NOT EXISTS bikes (
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ GPS.computeStats = function(points, weightKg, opts) {
|
|||
const rho = 1.225; // air density at sea level (kg/m^3)
|
||||
const efficiency = 0.25; // human mechanical efficiency (~25%)
|
||||
const Crr = opts?.surfaceCrr ?? 0.006; // rolling resistance (mixed surface default)
|
||||
const mTotal = (weightKg ?? 80) + (opts?.bikeWeightKg ?? 0);
|
||||
const wKg = parseFloat(weightKg) || 80; // ensure numeric (profile stores strings)
|
||||
const mTotal = wKg + (parseFloat(opts?.bikeWeightKg) || 0);
|
||||
const isBike = (opts?.activityType ?? "bike") === "bike";
|
||||
|
||||
let totalKcal = 0;
|
||||
// Basal metabolic rate per minute (MET=1 sitting, formula: MET * 3.5 * kg / 200)
|
||||
const basalPerMin = (1.0 * 3.5 * (weightKg ?? 80)) / 200;
|
||||
const basalPerMin = (1.0 * 3.5 * wKg) / 200;
|
||||
|
||||
// Cadence tracking
|
||||
let totalPedalRevs = 0;
|
||||
|
|
@ -111,8 +111,8 @@ GPS.computeStats = function(points, weightKg, opts) {
|
|||
const rawAlt = sorted[i].altitude ?? sorted[i].altitude_gps_m ?? 0;
|
||||
const rawAltPrev = sorted[i-1].altitude ?? sorted[i-1].altitude_gps_m ?? 0;
|
||||
const altDiff = rawAlt - rawAltPrev;
|
||||
const gradient = d > 2 ? altDiff / d : 0; // rise/run
|
||||
const sinG = gradient; // for small angles sin ~= gradient
|
||||
const gradient = d > 2 ? Math.max(-0.5, Math.min(0.5, altDiff / d)) : 0; // clamp to +-50%
|
||||
const sinG = gradient;
|
||||
const cosG = Math.sqrt(1 - sinG * sinG);
|
||||
|
||||
// Power required (watts): gravity + rolling resistance + aerodynamic drag
|
||||
|
|
@ -149,7 +149,7 @@ GPS.computeStats = function(points, weightKg, opts) {
|
|||
else if (speedKmh < 16) met = 12.5; // fast running
|
||||
else met = 15.0; // sprint
|
||||
|
||||
totalKcal += (dt / 60) * (met * 3.5 * (weightKg ?? 80)) / 200;
|
||||
totalKcal += (dt / 60) * (met * 3.5 * wKg) / 200;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -607,11 +607,14 @@ async function renderStatsCharts(container, days, foodTotals, bmr) {
|
|||
const closeBtn = document.createElement("div");
|
||||
closeBtn.className = "chart-close-btn";
|
||||
closeBtn.innerHTML = "✕";
|
||||
closeBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
function collapseChart() {
|
||||
wrap.classList.remove("chart-full");
|
||||
closeBtn.remove();
|
||||
};
|
||||
window.removeEventListener("popstate", chartPopHandler);
|
||||
}
|
||||
closeBtn.onclick = (e) => { e.stopPropagation(); collapseChart(); history.back(); };
|
||||
const chartPopHandler = () => { collapseChart(); };
|
||||
window.addEventListener("popstate", chartPopHandler, { once: true });
|
||||
wrap.appendChild(closeBtn);
|
||||
};
|
||||
});
|
||||
|
|
@ -3218,6 +3221,14 @@ function esc(str) {
|
|||
window.addEventListener("popstate", (e) => {
|
||||
// If a modal is open, don't handle here (modal has its own handler)
|
||||
if (document.querySelector(".modal-overlay")) return;
|
||||
// Chart fullscreen on screen (not in modal) — collapse it
|
||||
const fsChart = document.querySelector(".chart-full");
|
||||
if (fsChart) {
|
||||
fsChart.classList.remove("chart-full");
|
||||
const btn = fsChart.querySelector(".chart-close-btn");
|
||||
if (btn) btn.remove();
|
||||
return;
|
||||
}
|
||||
// Stale modal/chart entries left after app restart — skip them
|
||||
if (e.state && (e.state.modal || e.state.chartFull)) {
|
||||
history.back();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue