Skip to content

Commit f67efd1

Browse files
author
Jay Gandhi
committed
init
0 parents  commit f67efd1

41 files changed

Lines changed: 17349 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
**/node_modules/
2+
npm-debug.log*
3+
yarn-debug.log*
4+
yarn-error.log*
5+
**/__pycache__
6+
**/dist/
7+
**/build/
8+
9+
.DS_Store
10+
.npm
11+
12+
# IDE
13+
.idea/
14+
*.swp
15+
*.swo
16+
17+
# Environment variables
18+
.env
19+
.env.local
20+
.env.development.local
21+
.env.test.local
22+
.env.production.local

README.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Carbon Emissions Benchmarking Exercise
2+
3+
## Overview
4+
5+
This is a fullstack development exercise for building a carbon emissions benchmarking dashboard for digital advertising. You'll work with real data from Scope3's carbon emissions API to create a system that helps advertisers understand and compare environmental impact across different properties.
6+
7+
## Project Setup
8+
9+
1. ** Use `nvm` to enable the correct Node.js version for the project:
10+
```bash
11+
nvm use
12+
```
13+
14+
2. Run `npm install` in the root directory to install dependencies for both the backend and frontend.
15+
16+
### Getting Started
17+
18+
1. **Setup Environment Variables**:
19+
```bash
20+
cd backend
21+
cp .env.example .env
22+
# Edit .env and add your API token (provided by interviewer)
23+
```
24+
25+
2. **Start the Backend**:
26+
```bash
27+
npm run backend:start
28+
```
29+
30+
3. **Start the Frontend**:
31+
```bash
32+
npm run frontend:start
33+
```
34+
35+
### Project Structure
36+
37+
```
38+
├── backend/ # TypeScript Express server
39+
│ ├── app.ts # Main server file with /forward endpoint
40+
│ ├── tests/ # Jest test files
41+
│ ├── .env.example # Environment variables template
42+
│ ├── jest.config.js
43+
│ └── package.json
44+
├── frontend/ # React TypeScript application
45+
│ ├── src/
46+
│ │ ├── types.ts # TypeScript type definitions
47+
│ │ ├── api.ts # API service functions
48+
│ │ ├── constants.ts # Available channels and countries
49+
│ │ └── App.tsx # Main application component
50+
│ └── package.json
51+
├── .gitignore # Git ignore rules
52+
└── README.md
53+
```
54+
55+
## Understanding the Data
56+
57+
The backend provides a `/forward` endpoint that proxies requests to Scope3's API:
58+
59+
```bash
60+
# Example API calls:
61+
POST /forward
62+
{ "channel": "web", "country": "US" }
63+
64+
POST /forward
65+
{ "channel": "app", "country": "GB" }
66+
```
67+
68+
**Available Options:**
69+
- **Channels**: `web`, `app`
70+
- **Countries**: See `frontend/src/constants.ts` for the full list
71+
72+
The API returns carbon emissions benchmarks measured as emissions per thousand impressions, organized by percentiles.
73+
74+
## What You'll Be Building
75+
76+
The frontend includes a starter DataGrid displaying sample property data (Property Name, Channel, Country, CO2 values) to give you a visual starting point.
77+
78+
Your interviewer will provide specific tasks, but generally you'll be creating a dashboard that allows advertisers to:
79+
- View carbon emissions data for different advertising properties
80+
- Compare emissions across channels and countries
81+
- Organize and analyze collections of properties
82+
83+
## Resources Available
84+
85+
- **TypeScript Types**: Starter types in `frontend/src/types.ts`
86+
- **API Service**: Basic fetch function in `frontend/src/api.ts`
87+
- **Constants**: Predefined channels and countries in `frontend/src/constants.ts`
88+
- **Backend Testing**: Jest setup with sample tests in `backend/tests/` (run with `npm test`)
89+
- **Scope3 API Docs**: Find our documentation of the `/v2/measure` api [here](https://docs.scope3.com/reference/measure-1)
90+
91+
### Available UI Libraries
92+
93+
- **Material-UI Core**: Buttons, Cards, Typography, Forms, etc.
94+
- **MUI Data Grid**: Professional data tables with sorting, filtering, and selection
95+
- **MUI X Charts**: Bar charts, line charts, pie charts for data visualization
96+
- **MUI Icons**: Comprehensive icon library for UI elements
97+
98+
## Approach
99+
100+
- Take your time to understand the requirements and ask questions
101+
- Build incrementally - get each piece working before moving to the next
102+
- Focus on functionality over styling - Material-UI is provided for easy UI development
103+
- Consider user experience and real-world usage
104+
- Don't hesitate to ask for clarification on any requirements
105+
106+
Your interviewer will guide you through the specific features to implement. The goal is to demonstrate your fullstack development skills, API design thinking, and ability to build user-focused interfaces.

backend/.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Environment variables for the backend server
2+
# Copy this file to .env and fill in your actual values
3+
4+
# Scope3 API Token - get this from your interviewer
5+
SCOPE3_API_TOKEN=your_scope3_api_token_here
6+
7+
# Server configuration
8+
PORT=4000
9+
NODE_ENV=development

backend/app.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import express from 'express'
2+
import type { Request, Response } from 'express'
3+
import axios from 'axios'
4+
import type { AxiosResponse, AxiosError } from 'axios'
5+
import cors from 'cors'
6+
import dotenv from 'dotenv'
7+
8+
// Load environment variables
9+
dotenv.config()
10+
11+
const app = express()
12+
const PORT = process.env.PORT || 4000
13+
14+
// Middleware to parse JSON bodies
15+
app.use(express.json())
16+
17+
// Enable CORS for localhost:3000
18+
app.use(
19+
cors({
20+
origin: 'http://localhost:3000',
21+
})
22+
)
23+
24+
// Get API token from environment variables
25+
const token = process.env.SCOPE3_API_TOKEN
26+
27+
if (!token) {
28+
console.error('SCOPE3_API_TOKEN environment variable is required')
29+
console.error('Copy .env.example to .env and add your API token')
30+
console.error('Contact your interviewer to get the API token')
31+
process.exit(1)
32+
}
33+
34+
// Interface for the basic benchmark request
35+
interface BenchmarkRequest {
36+
channel?: string
37+
country?: string
38+
ymd?: string
39+
[key: string]: any // Allow additional properties
40+
}
41+
42+
// Helper function to map frontend channels to API parameters
43+
function mapChannelToApiParams(channel: string): { [key: string]: any } {
44+
switch (channel) {
45+
case 'web':
46+
return { device: 'desktop' }
47+
case 'app':
48+
return { device: 'mobile' }
49+
default:
50+
return { device: 'desktop' } // Default fallback
51+
}
52+
}
53+
54+
// POST /forward - Forward requests to Scope3 API
55+
app.post(
56+
'/forward',
57+
async (req: Request<{}, any, BenchmarkRequest>, res: Response) => {
58+
try {
59+
// Transform the request body to use the proper API parameters
60+
const { channel, country, ymd, ...otherParams } = req.body
61+
62+
let apiBody: any = {
63+
country,
64+
ymd,
65+
...otherParams,
66+
}
67+
68+
// Map frontend channel to API device parameter
69+
if (channel) {
70+
const deviceParams = mapChannelToApiParams(channel)
71+
apiBody = { ...apiBody, ...deviceParams }
72+
}
73+
74+
const response: AxiosResponse = await axios.post(
75+
'https://api.scope3.com/v2/measure?fields=all&includeRows=true&framework=scope3&latest=true',
76+
apiBody,
77+
{
78+
headers: {
79+
Authorization: `Bearer ${token}`,
80+
'Content-Type': 'application/json',
81+
},
82+
}
83+
)
84+
85+
// Send back the response from the external API
86+
res.status(response.status).json(response.data)
87+
} catch (error) {
88+
console.error('Error forwarding request:', error)
89+
90+
// Handle error responses
91+
if (error && typeof error === 'object' && 'response' in error) {
92+
const axiosError = error as AxiosError
93+
if (axiosError.response) {
94+
res.status(axiosError.response.status).json(axiosError.response.data)
95+
} else {
96+
res.status(500).json({ message: 'Network error' })
97+
}
98+
} else {
99+
res.status(500).json({ message: 'Internal server error' })
100+
}
101+
}
102+
}
103+
)
104+
105+
// Candidate will add additional endpoints here as needed
106+
107+
// Only start server if not in test environment
108+
if (process.env.NODE_ENV !== 'test') {
109+
app.listen(PORT, () => {
110+
console.log(`Server is running on http://localhost:${PORT}`)
111+
console.log('Available endpoints:')
112+
console.log(' POST /forward - Forward requests to Scope3 API')
113+
})
114+
}
115+
116+
// Export app for testing
117+
export default app

backend/jest.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
roots: ['<rootDir>/tests'],
5+
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
6+
transform: {
7+
'^.+\\.ts$': 'ts-jest',
8+
},
9+
collectCoverageFrom: [
10+
'**/*.ts',
11+
'!**/*.d.ts',
12+
'!**/node_modules/**',
13+
'!**/tests/**',
14+
],
15+
coverageDirectory: 'coverage',
16+
coverageReporters: ['text', 'lcov', 'html'],
17+
}

0 commit comments

Comments
 (0)