Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
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.
72 changes: 72 additions & 0 deletions p1.js
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) {
Copy link

Copilot AI Jan 23, 2026

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.

Suggested change
function getTotalRunDistanceForPerson(personName) {
function getTotalRunDistanceForPerson(personName) {
// TODO: Implement this function to use `personName` and calculate the total run distance from ACTIVITIES.
// Currently this is a stub implementation that ignores `personName` and returns a placeholder value.

Copilot uses AI. Check for mistakes.
return 123;
}

function getTopRunnerForGivenWeek(week) {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The week parameter is unused in this placeholder implementation. Consider adding a comment indicating this is a stub that needs implementation.

Suggested change
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 uses AI. Check for mistakes.
return {
person: 'Name',
distance: 123
};
}

function get2TopPerformersByType() {
return {
run: [{
person: 'Name',
distance: 123
},{
person: 'Name',
distance: 123
}],
swim: [{
person: 'Name',
distance: 123
},{
person: 'Name',
distance: 123
}]
}
}

Copy link

Copilot AI Jan 23, 2026

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.

Suggested change
// TODO: This is a stub implementation; use startingWeek and endingWeek to calculate the most improved runner.

Copilot uses AI. Check for mistakes.
function getMostImprovedRunner(startingWeek, endingWeek) {
return {
person: 'Name',
improvement: 123
};
}

console.log('=== Task 1: getTotalRunDistanceForPerson ===');
console.log('Alice:', getTotalRunDistanceForPerson('Alice'));
console.log('Bob:', getTotalRunDistanceForPerson('Bob'));

console.log('\n=== Task 2: getTopRunnerForGivenWeek ===');
console.log('W01:', JSON.stringify(getTopRunnerForGivenWeek('W01')));
console.log('W02:', JSON.stringify(getTopRunnerForGivenWeek('W02')));

console.log('\n=== Task 3: get2TopPerformersByType ===');
console.log(JSON.stringify(get2TopPerformersByType(), null, 2));

console.log('\n=== Task 4: getMostImprovedRunner ===');
console.log('W01 to W03:', JSON.stringify(getMostImprovedRunner('W01', 'W03')));
17 changes: 17 additions & 0 deletions p2/config/activity-types.json
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"
}
}
46 changes: 46 additions & 0 deletions p2/data/activities.json
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 }
]
147 changes: 147 additions & 0 deletions p2/data/loader.js
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(', ')}`
);
}
}
34 changes: 34 additions & 0 deletions p2/index.js
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();
Loading