-
Notifications
You must be signed in to change notification settings - Fork 0
Initial version #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,5 @@ | ||
| # make-exercise-javascript | ||
| Make Exercise in JavaScript | ||
|
|
||
| This is a public repository. | ||
|
|
||
| If you are from Make, check [this internal page](https://make.atlassian.net/wiki/x/oYBBmw) to see how to work with it. |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||
| const ACTIVITIES = [ | ||||||||||
| { person: 'Alice', distance: 12.5, type: 'run', week: 'W01' }, | ||||||||||
| { person: 'Bob', distance: 8.0, type: 'run', week: 'W01' }, | ||||||||||
| { person: 'Carol', distance: 15.0, type: 'run', week: 'W01' }, | ||||||||||
| { person: 'Alice', distance: 2.0, type: 'swim', week: 'W01' }, | ||||||||||
| { person: 'David', distance: 3.5, type: 'swim', week: 'W01' }, | ||||||||||
| { person: 'Alice', distance: 18.0, type: 'run', week: 'W02' }, | ||||||||||
| { person: 'Bob', distance: 22.0, type: 'run', week: 'W02' }, | ||||||||||
| { person: 'Carol', distance: 10.0, type: 'run', week: 'W02' }, | ||||||||||
| { person: 'David', distance: 5.0, type: 'run', week: 'W02' }, | ||||||||||
| { person: 'Bob', distance: 1.5, type: 'swim', week: 'W02' }, | ||||||||||
| { person: 'Carol', distance: 4.0, type: 'swim', week: 'W02' }, | ||||||||||
| { person: 'Alice', distance: 25.0, type: 'run', week: 'W03' }, | ||||||||||
| { person: 'Bob', distance: 15.0, type: 'run', week: 'W03' }, | ||||||||||
| { person: 'Carol', distance: 20.0, type: 'run', week: 'W03' }, | ||||||||||
| { person: 'David', distance: 12.0, type: 'run', week: 'W03' }, | ||||||||||
| { person: 'Eve', distance: 8.0, type: 'run', week: 'W03' }, | ||||||||||
| { person: 'Alice', distance: 3.0, type: 'swim', week: 'W03' }, | ||||||||||
| { person: 'David', distance: 5.0, type: 'swim', week: 'W03' }, | ||||||||||
| { person: 'Eve', distance: 2.5, type: 'swim', week: 'W03' }, | ||||||||||
| ]; | ||||||||||
|
|
||||||||||
| function getTotalRunDistanceForPerson(personName) { | ||||||||||
| return 123; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function getTopRunnerForGivenWeek(week) { | ||||||||||
|
||||||||||
| function getTopRunnerForGivenWeek(week) { | |
| function getTopRunnerForGivenWeek(week) { | |
| // TODO: Placeholder implementation; `week` is currently unused and should be | |
| // used to determine the top runner for the specified week. |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The startingWeek and endingWeek parameters are unused in this placeholder implementation. Consider adding a comment indicating this is a stub that needs implementation.
| // TODO: This is a stub implementation; use startingWeek and endingWeek to calculate the most improved runner. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "run": { | ||
| "calorieRate": 10.5, | ||
| "category": "cardio", | ||
| "displayName": "Running" | ||
| }, | ||
| "swim": { | ||
| "calorieRate": 14.2, | ||
| "category": "cardio", | ||
| "displayName": "Swimming" | ||
| }, | ||
| "bike": { | ||
| "calorieRate": 8.3, | ||
| "category": "cardio", | ||
| "displayName": "Cycling" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| [ | ||
| { "person": "Alice", "distance": 12.5, "type": "run", "week": "W01", "date": "2024-01-03", "duration": 75, "heartRate": 165, "elevationGain": 120 }, | ||
| { "person": "Bob", "distance": 8.0, "type": "run", "week": "W01", "date": "2024-01-04", "duration": 55, "heartRate": 152, "elevationGain": 50 }, | ||
| { "person": "Carol", "distance": 15.0, "type": "run", "week": "W01", "date": "2024-01-05", "duration": 90, "heartRate": 158, "elevationGain": 200 }, | ||
| { "person": "Alice", "distance": 2.0, "type": "swim", "week": "W01", "date": "2024-01-06", "duration": 45, "heartRate": 135, "elevationGain": 0 }, | ||
| { "person": "David", "distance": 3.5, "type": "swim", "week": "W01", "date": "2024-01-07", "duration": 60, "heartRate": 128, "elevationGain": 0 }, | ||
|
|
||
| { "person": "Alice", "distance": 18.0, "type": "run", "week": "W02", "date": "2024-01-10", "duration": 95, "heartRate": 168, "elevationGain": 180 }, | ||
| { "person": "Bob", "distance": 22.0, "type": "run", "week": "W02", "date": "2024-01-11", "duration": 125, "heartRate": 162, "elevationGain": 250 }, | ||
| { "person": "Carol", "distance": 10.0, "type": "run", "week": "W02", "date": "2024-01-12", "duration": 60, "heartRate": 155, "elevationGain": 80 }, | ||
| { "person": "David", "distance": 5.0, "type": "run", "week": "W02", "date": "2024-01-13", "duration": 38, "heartRate": 148, "elevationGain": 30 }, | ||
| { "person": "Bob", "distance": 1.5, "type": "swim", "week": "W02", "date": "2024-01-14", "duration": 35, "heartRate": 132, "elevationGain": 0 }, | ||
| { "person": "Carol", "distance": 4.0, "type": "swim", "week": "W02", "date": "2024-01-14", "duration": 70, "heartRate": 142, "elevationGain": 0 }, | ||
|
|
||
| { "person": "Alice", "distance": 25.0, "type": "run", "week": "W03", "date": "2024-01-17", "duration": 135, "heartRate": 172, "elevationGain": 320 }, | ||
| { "person": "Bob", "distance": 15.0, "type": "run", "week": "W03", "date": "2024-01-18", "duration": 88, "heartRate": 158, "elevationGain": 150 }, | ||
| { "person": "Carol", "distance": 20.0, "type": "run", "week": "W03", "date": "2024-01-19", "duration": 115, "heartRate": 165, "elevationGain": 280 }, | ||
| { "person": "David", "distance": 12.0, "type": "run", "week": "W03", "date": "2024-01-20", "duration": 78, "heartRate": 153, "elevationGain": 110 }, | ||
| { "person": "Eve", "distance": 8.0, "type": "run", "week": "W03", "date": "2024-01-21", "duration": 52, "heartRate": 145, "elevationGain": 60 }, | ||
| { "person": "Alice", "distance": 3.0, "type": "swim", "week": "W03", "date": "2024-01-21", "duration": 52, "heartRate": 138, "elevationGain": 0 }, | ||
| { "person": "David", "distance": 5.0, "type": "swim", "week": "W03", "date": "2024-01-21", "duration": 82, "heartRate": 130, "elevationGain": 0 }, | ||
| { "person": "Eve", "distance": 2.5, "type": "swim", "week": "W03", "date": "2024-01-21", "duration": 48, "heartRate": 125, "elevationGain": 0 }, | ||
|
|
||
| { "person": "Alice", "distance": 30.0, "type": "run", "week": "W04", "date": "2024-01-24", "duration": 155, "heartRate": 175, "elevationGain": 410 }, | ||
| { "person": "Bob", "distance": 28.0, "type": "run", "week": "W04", "date": "2024-01-25", "duration": 148, "heartRate": 168, "elevationGain": 380 }, | ||
| { "person": "Carol", "distance": 18.0, "type": "run", "week": "W04", "date": "2024-01-26", "duration": 102, "heartRate": 160, "elevationGain": 200 }, | ||
| { "person": "David", "distance": 10.0, "type": "run", "week": "W04", "date": "2024-01-27", "duration": 65, "heartRate": 150, "elevationGain": 90 }, | ||
| { "person": "Bob", "distance": 2.0, "type": "swim", "week": "W04", "date": "2024-01-28", "duration": 42, "heartRate": 135, "elevationGain": 0 }, | ||
| { "person": "Carol", "distance": 5.5, "type": "swim", "week": "W04", "date": "2024-01-28", "duration": 88, "heartRate": 145, "elevationGain": 0 }, | ||
| { "person": "Eve", "distance": 3.0, "type": "swim", "week": "W04", "date": "2024-01-28", "duration": 55, "heartRate": 128, "elevationGain": 0 }, | ||
|
|
||
| { "person": "Alice", "distance": 35.0, "type": "run", "week": "W05", "date": "2024-01-31", "duration": 175, "heartRate": 178, "elevationGain": 520 }, | ||
| { "person": "Bob", "distance": 20.0, "type": "run", "week": "W05", "date": "2024-02-01", "duration": 112, "heartRate": 165, "elevationGain": 220 }, | ||
| { "person": "Carol", "distance": 25.0, "type": "run", "week": "W05", "date": "2024-02-02", "duration": 142, "heartRate": 170, "elevationGain": 350 }, | ||
| { "person": "David", "distance": 15.0, "type": "run", "week": "W05", "date": "2024-02-03", "duration": 95, "heartRate": 158, "elevationGain": 180 }, | ||
| { "person": "Eve", "distance": 12.0, "type": "run", "week": "W05", "date": "2024-02-04", "duration": 78, "heartRate": 152, "elevationGain": 140 }, | ||
| { "person": "Alice", "distance": 4.0, "type": "swim", "week": "W05", "date": "2024-02-04", "duration": 65, "heartRate": 140, "elevationGain": 0 }, | ||
| { "person": "David", "distance": 6.0, "type": "swim", "week": "W05", "date": "2024-02-04", "duration": 95, "heartRate": 135, "elevationGain": 0 }, | ||
| { "person": "Eve", "distance": 4.5, "type": "swim", "week": "W05", "date": "2024-02-04", "duration": 72, "heartRate": 130, "elevationGain": 0 }, | ||
|
|
||
| { "person": "Bob", "distance": 45.0, "type": "bike", "week": "W01", "date": "2024-01-05", "duration": 105, "heartRate": 142, "elevationGain": 180 }, | ||
| { "person": "Carol", "distance": 60.0, "type": "bike", "week": "W02", "date": "2024-01-12", "duration": 135, "heartRate": 148, "elevationGain": 240 }, | ||
| { "person": "Alice", "distance": 55.0, "type": "bike", "week": "W03", "date": "2024-01-19", "duration": 125, "heartRate": 155, "elevationGain": 220 }, | ||
| { "person": "Eve", "distance": 50.0, "type": "bike", "week": "W04", "date": "2024-01-26", "duration": 115, "heartRate": 145, "elevationGain": 200 }, | ||
| { "person": "David", "distance": 70.0, "type": "bike", "week": "W05", "date": "2024-02-02", "duration": 155, "heartRate": 152, "elevationGain": 280 } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| import { readFileSync } from 'fs'; | ||
| import { fileURLToPath } from 'url'; | ||
| import { dirname, join } from 'path'; | ||
|
|
||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = dirname(__filename); | ||
|
|
||
| export function loadActivities() { | ||
| const filePath = join(__dirname, 'activities.json'); | ||
|
|
||
| try { | ||
| const rawData = readFileSync(filePath, 'utf-8'); | ||
| const activities = JSON.parse(rawData); | ||
|
|
||
| if (!Array.isArray(activities)) { | ||
| throw new Error('Activities data must be an array'); | ||
| } | ||
|
|
||
| const validatedActivities = activities.map((activity, index) => { | ||
| validateActivity(activity, index); | ||
| return activity; | ||
| }); | ||
|
|
||
| return validatedActivities; | ||
| } catch (error) { | ||
| if (error.code === 'ENOENT') { | ||
| throw new Error(`Activities file not found: ${filePath}`); | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| export function loadActivityTypes() { | ||
| const filePath = join(__dirname, '../config/activity-types.json'); | ||
|
|
||
| try { | ||
| const rawData = readFileSync(filePath, 'utf-8'); | ||
| const activityTypes = JSON.parse(rawData); | ||
|
|
||
| if (typeof activityTypes !== 'object' || activityTypes === null) { | ||
| throw new Error('Activity types must be an object'); | ||
| } | ||
|
|
||
| for (const [type, config] of Object.entries(activityTypes)) { | ||
| validateActivityTypeConfig(type, config); | ||
| } | ||
|
|
||
| return activityTypes; | ||
| } catch (error) { | ||
| if (error.code === 'ENOENT') { | ||
| throw new Error(`Activity types config not found: ${filePath}`); | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| function validateActivity(activity, index) { | ||
| const requiredFields = ['person', 'distance', 'type', 'week', 'date', 'duration', 'heartRate', 'elevationGain']; | ||
|
|
||
| for (const field of requiredFields) { | ||
| if (!(field in activity)) { | ||
| throw new Error(`Activity at index ${index} missing required field: ${field}`); | ||
| } | ||
| } | ||
|
|
||
| if (typeof activity.person !== 'string' || activity.person.trim() === '') { | ||
| throw new Error(`Activity at index ${index} has invalid person name`); | ||
| } | ||
|
|
||
| if (typeof activity.distance !== 'number' || activity.distance < 0) { | ||
| throw new Error(`Activity at index ${index} has invalid distance: ${activity.distance}`); | ||
| } | ||
|
|
||
| if (typeof activity.type !== 'string' || activity.type.trim() === '') { | ||
| throw new Error(`Activity at index ${index} has invalid type`); | ||
| } | ||
|
|
||
| if (typeof activity.week !== 'string' || !activity.week.match(/^W\d+$/)) { | ||
| throw new Error(`Activity at index ${index} has invalid week format: ${activity.week}`); | ||
| } | ||
|
|
||
| if (typeof activity.date !== 'string' || !activity.date.match(/^\d{4}-\d{2}-\d{2}$/)) { | ||
| throw new Error(`Activity at index ${index} has invalid date format: ${activity.date}`); | ||
| } | ||
|
|
||
| if (typeof activity.duration !== 'number' || activity.duration <= 0) { | ||
| throw new Error(`Activity at index ${index} has invalid duration: ${activity.duration}`); | ||
| } | ||
|
|
||
| if (typeof activity.heartRate !== 'number' || activity.heartRate < 40 || activity.heartRate > 220) { | ||
| throw new Error(`Activity at index ${index} has invalid heart rate: ${activity.heartRate}`); | ||
| } | ||
|
|
||
| if (typeof activity.elevationGain !== 'number' || activity.elevationGain < 0) { | ||
| throw new Error(`Activity at index ${index} has invalid elevation gain: ${activity.elevationGain}`); | ||
| } | ||
| } | ||
|
|
||
| function validateActivityTypeConfig(type, config) { | ||
| if (typeof config !== 'object' || config === null) { | ||
| throw new Error(`Activity type '${type}' config must be an object`); | ||
| } | ||
|
|
||
| const requiredFields = ['calorieRate', 'category', 'displayName']; | ||
| for (const field of requiredFields) { | ||
| if (!(field in config)) { | ||
| throw new Error(`Activity type '${type}' missing required field: ${field}`); | ||
| } | ||
| } | ||
|
|
||
| if (typeof config.calorieRate !== 'number' || config.calorieRate <= 0) { | ||
| throw new Error(`Activity type '${type}' has invalid calorieRate: ${config.calorieRate}`); | ||
| } | ||
|
|
||
| if (typeof config.category !== 'string' || config.category.trim() === '') { | ||
| throw new Error(`Activity type '${type}' has invalid category`); | ||
| } | ||
|
|
||
| if (typeof config.displayName !== 'string' || config.displayName.trim() === '') { | ||
| throw new Error(`Activity type '${type}' has invalid displayName`); | ||
| } | ||
| } | ||
|
|
||
| export function validateActivityReferences(activities, activityTypes) { | ||
| const validTypes = new Set(Object.keys(activityTypes)); | ||
| const invalidActivities = []; | ||
|
|
||
| for (let i = 0; i < activities.length; i++) { | ||
| if (!validTypes.has(activities[i].type)) { | ||
| invalidActivities.push({ | ||
| index: i, | ||
| type: activities[i].type, | ||
| person: activities[i].person | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| if (invalidActivities.length > 0) { | ||
| const details = invalidActivities | ||
| .map(a => ` - Index ${a.index}: type '${a.type}' (${a.person})`) | ||
| .join('\n'); | ||
| throw new Error( | ||
| `Found ${invalidActivities.length} activities with undefined activity types:\n${details}\n` + | ||
| `Valid types: ${Array.from(validTypes).join(', ')}` | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { loadActivities, loadActivityTypes, validateActivityReferences } from './data/loader.js'; | ||
| import { generateStatistics } from './services/stats-calculator.js'; | ||
| import { formatStatisticsReport, formatConsoleLog } from './reports/formatter.js'; | ||
|
|
||
| function main() { | ||
| try { | ||
| console.log('Loading activity data...\n'); | ||
|
|
||
| const activities = loadActivities(); | ||
| const activityTypes = loadActivityTypes(); | ||
|
|
||
| validateActivityReferences(activities, activityTypes); | ||
|
|
||
| console.log(`Loaded ${activities.length} activities`); | ||
| console.log(`Activity types: ${Object.keys(activityTypes).join(', ')}\n`); | ||
|
|
||
| const stats = generateStatistics(activities, activityTypes); | ||
|
|
||
| console.log(formatConsoleLog(stats.teamTotals)); | ||
|
|
||
| const report = formatStatisticsReport(stats, activityTypes); | ||
| console.log('\n' + report); | ||
|
|
||
| console.log('\n=== Full Statistics JSON ===\n'); | ||
| console.log(JSON.stringify(stats, null, 2)); | ||
|
|
||
| } catch (error) { | ||
| console.error('Error generating statistics:'); | ||
| console.error(error.message); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| main(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The personName parameter is unused in this placeholder implementation. Consider adding a comment indicating this is a stub that needs implementation.