Today's Log
Session Type
7
Nutrition
anchor: chicken + rice
Body Metrics (optional)
History
Recent Sessions
Connect your Google Sheet and click Refresh to load history.
Stats
This Week
—
Gym sessions
—
Circuits
—
Walk days
—
Day streak
—
Avg protein g
—
Walk mins total
Load data from Sheet
Sheet Setup
Google Sheet Structure
Create one Google Sheet with these tabs (exact names matter for the Apps Script):
Tab 1:
SessionsMain log
A: date | B: session_type | C: energy_level | D: bodyweight_kg | E: steps | F: sleep_hours | G: notes
Tab 2:
GymSetsGym detail
A: date | B: exercise_name | C: pool | D: top_set_kg | E: top_set_reps | F: backoff_kg | G: backoff_reps | H: rpe | I: notes
Tab 3:
CircuitsCircuit detail
A: date | B: combo_letter | C: combo_name | D: equipment_type | E: rounds | F: duration_min | G: notes
Tab 4:
WalksWalk detail
A: date | B: duration_min | C: incline_pct | D: speed_kmh | E: avg_hr | F: est_calories | G: notes
Tab 5:
NutritionFood log
A: date | B: lunch_done | C: protein_g | D: calories_total | E: food_notes
Apps Script (Code.gs)
In your Google Sheet → Extensions → Apps Script → paste this, then Deploy as Web App (execute as: Me, access: Anyone).
// NEO 2.0 — Google Apps Script
// Deploy → Web App → Execute as Me · Access: Anyone
const SHEET_ID = 'YOUR_GOOGLE_SHEET_ID_HERE';
function doPost(e) {
const data = JSON.parse(e.postData.contents);
const ss = SpreadsheetApp.openById(SHEET_ID);
const action = data.action;
if (action === 'log_session') {
logSession(ss, data);
}
return ContentService
.createTextOutput(JSON.stringify({status:'ok'}))
.setMimeType(ContentService.MimeType.JSON);
}
function doGet(e) {
const ss = SpreadsheetApp.openById(SHEET_ID);
const action = e.parameter.action || 'history';
if (action === 'history') {
return getHistory(ss);
}
if (action === 'stats') {
return getStats(ss);
}
if (action === 'ping') {
return ContentService
.createTextOutput(JSON.stringify({status:'ok',msg:'Connected'}))
.setMimeType(ContentService.MimeType.JSON);
}
}
function logSession(ss, data) {
const date = data.date;
// Sessions tab
ss.getSheetByName('Sessions').appendRow([
date, data.session_type, data.energy_level,
data.bodyweight_kg, data.steps, data.sleep_hours, data.notes
]);
// GymSets tab
if (data.gym_sets && data.gym_sets.length > 0) {
const gymSheet = ss.getSheetByName('GymSets');
data.gym_sets.forEach(s => {
gymSheet.appendRow([
date, s.exercise, s.pool,
s.top_kg, s.top_reps, s.backoff_kg, s.backoff_reps,
s.rpe, s.notes
]);
});
}
// Circuits tab
if (data.circuit) {
ss.getSheetByName('Circuits').appendRow([
date, data.circuit.combo_letter, data.circuit.combo_name,
data.circuit.equipment_type, data.circuit.rounds,
data.circuit.duration_min, data.circuit.notes
]);
}
// Walks tab
if (data.walk) {
ss.getSheetByName('Walks').appendRow([
date, data.walk.duration_min, data.walk.incline_pct,
data.walk.speed_kmh, data.walk.avg_hr,
data.walk.est_calories, data.walk.notes
]);
}
// Nutrition tab
if (data.nutrition) {
ss.getSheetByName('Nutrition').appendRow([
date, data.nutrition.lunch_done,
data.nutrition.protein_g, data.nutrition.calories_total,
data.nutrition.food_notes
]);
}
}
function getHistory(ss) {
const sheet = ss.getSheetByName('Sessions');
const rows = sheet.getDataRange().getValues();
const headers = rows[0];
const data = rows.slice(-30).map(r =>
Object.fromEntries(headers.map((h,i) => [h, r[i]]))
);
return ContentService
.createTextOutput(JSON.stringify(data))
.setMimeType(ContentService.MimeType.JSON);
}
function getStats(ss) {
const gymRows = ss.getSheetByName('GymSets').getDataRange().getValues().slice(1);
const circuitRows = ss.getSheetByName('Circuits').getDataRange().getValues().slice(1);
const walkRows = ss.getSheetByName('Walks').getDataRange().getValues().slice(1);
const nutRows = ss.getSheetByName('Nutrition').getDataRange().getValues().slice(1);
const now = new Date();
const weekAgo = new Date(now - 7*24*60*60*1000);
const inWeek = rows => rows.filter(r => new Date(r[0]) >= weekAgo);
const avgProt = nutRows.length
? Math.round(nutRows.reduce((s,r)=>s+(r[2]||0),0)/nutRows.length)
: 0;
const walkMins = inWeek(walkRows).reduce((s,r)=>s+(r[1]||0),0);
return ContentService.createTextOutput(JSON.stringify({
gym_sessions_week: [...new Set(inWeek(gymRows).map(r=>r[0]))].length,
circuit_sessions_week: inWeek(circuitRows).length,
walk_days_week: inWeek(walkRows).length,
avg_protein_g: avgProt,
walk_mins_week: walkMins
})).setMimeType(ContentService.MimeType.JSON);
}
Quick Setup Steps
1. Create a new Google Sheet
2. Rename tabs:
3. Add the column headers (row 1) exactly as shown above
4. Go to Extensions → Apps Script → paste Code.gs above
5. Replace
6. Deploy → New Deployment → Web App → Execute as Me · Anyone
7. Copy the Web App URL → paste in the bar at the top of this app
2. Rename tabs:
Sessions · GymSets · Circuits · Walks · Nutrition3. Add the column headers (row 1) exactly as shown above
4. Go to Extensions → Apps Script → paste Code.gs above
5. Replace
YOUR_GOOGLE_SHEET_ID_HERE with your Sheet's ID (from URL)6. Deploy → New Deployment → Web App → Execute as Me · Anyone
7. Copy the Web App URL → paste in the bar at the top of this app