diff --git a/.github/workflows/generate-weekly-rewards.yml b/.github/workflows/generate-weekly-rewards.yml new file mode 100644 index 0000000..c42ad18 --- /dev/null +++ b/.github/workflows/generate-weekly-rewards.yml @@ -0,0 +1,159 @@ +name: Generate Weekly Rewards CSVs (Daily Cron Job) + +on: + # A new interval every day (midnight) + schedule: + - cron: 0 1 * * * # 01:00 UTC + push: + branches: + # Allows us to test this workflow + - "generate-weekly-rewards-trigger-*" + +jobs: + development_guild: + runs-on: ubuntu-latest + defaults: + run: + working-directory: development-guild/rewards/ + steps: + - uses: actions/checkout@v3 + + - name: Pull changes + run: | + git fetch + git pull + + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: npm + cache-dependency-path: development-guild/rewards/package-lock.json + + - name: Install Node.js Packages 🔧 + run: npm install + + # Generate weekly rewards CSV files 🧮 + - run: node generate-weekly-csvs.js + + - name: Commit Changes + run: | + git config user.name 'NationCred bot' + git config user.email 'nationcred.bot@nation3.org' + git add weekly/*.csv + git commit --allow-empty -m '🧙 development guild - generate weekly rewards csvs' + + - name: Push Changes + run: git push + + ops_guild: + needs: development_guild + runs-on: ubuntu-latest + defaults: + run: + working-directory: ops-guild/rewards/ + steps: + - uses: actions/checkout@v3 + + - name: Pull changes + run: | + git fetch + git pull + + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: npm + cache-dependency-path: ops-guild/rewards/package-lock.json + + - name: Install Node.js Packages 🔧 + run: npm install + + # Generate weekly rewards CSV files 🧮 + - run: node generate-weekly-csvs.js + + - name: Commit Changes + run: | + git config user.name 'NationCred bot' + git config user.email 'nationcred.bot@nation3.org' + git add weekly/*.csv + git commit --allow-empty -m '⚙️ ops guild - generate weekly rewards csvs' + + - name: Push Changes + run: git push + + marketing_guild: + needs: ops_guild + runs-on: ubuntu-latest + defaults: + run: + working-directory: marketing-guild/rewards/ + steps: + - uses: actions/checkout@v3 + + - name: Pull changes + run: | + git fetch + git pull + + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: npm + cache-dependency-path: marketing-guild/rewards/package-lock.json + + - name: Install Node.js Packages 🔧 + run: npm install + + # Generate weekly rewards CSV files 🧮 + - run: node generate-weekly-csvs.js + + - name: Commit Changes + run: | + git config user.name 'NationCred bot' + git config user.email 'nationcred.bot@nation3.org' + git add weekly/*.csv + git commit --allow-empty -m '🎥 marketing guild - generate weekly rewards csvs' + + - name: Push Changes + run: git push + + ecoride_network: + needs: marketing_guild + runs-on: ubuntu-latest + defaults: + run: + working-directory: ecoride-network/rewards/ + steps: + - uses: actions/checkout@v3 + + - name: Pull changes + run: | + git fetch + git pull + + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: npm + cache-dependency-path: ecoride-network/rewards/package-lock.json + + - name: Install Node.js Packages 🔧 + run: npm install + + # Generate weekly rewards CSV files 🧮 + - run: node generate-weekly-csvs.js + + - name: Commit Changes + run: | + git config user.name 'NationCred bot' + git config user.email 'nationcred.bot@nation3.org' + git add weekly/*.csv + git commit --allow-empty -m '🌳 ecoride network - generate weekly rewards csvs' + + - name: Push Changes + run: git push + \ No newline at end of file diff --git a/development-guild/rewards/.gitignore b/development-guild/rewards/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/development-guild/rewards/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/development-guild/rewards/README.md b/development-guild/rewards/README.md index f05098e..275b7b6 100644 --- a/development-guild/rewards/README.md +++ b/development-guild/rewards/README.md @@ -1,3 +1,15 @@ -# Coordinape Rewards - 🧙 Development Guild +# Contributor Rewards - 🧙 Development Guild -... +## Generate CSVs + +Generate weekly rewards CSV files: + +``` +cd ops-guild/rewards/ +npm install +node generate-weekly-csvs.js +``` + +## Weekly Rewards + +See [`weekly/`](weekly/) diff --git a/development-guild/rewards/generate-weekly-csvs.js b/development-guild/rewards/generate-weekly-csvs.js new file mode 100644 index 0000000..b94df07 --- /dev/null +++ b/development-guild/rewards/generate-weekly-csvs.js @@ -0,0 +1,131 @@ +const fs = require('fs') +const Papa = require('papaparse') +const { stringify } = require('csv-stringify'); + +generate() + +async function generate() { + console.log('generate') + + // Load the Coordinape contributions + const contributionsFilePath = `../contributions/coordinape-contributions.csv` + console.debug('contributionsFilePath:', contributionsFilePath) + let contributionsData = [] + if (!fs.existsSync(contributionsFilePath)) { + console.error('File does not exist') + return + } else { + contributionsData = await loadCSVData(contributionsFilePath) + } + // console.debug('contributionsData:', contributionsData) + + // Iterate every week from the week of [Sun May-29-2022 → Sun Jun-05-2022] until now + const weekEndDate = new Date('2022-06-05T00:00:00Z') + console.debug('weekEndDate:', weekEndDate) + const nowDate = new Date() + console.debug('nowDate:', nowDate) + while (nowDate.getTime() > weekEndDate.getTime()) { + const weekBeginDate = new Date(weekEndDate.getTime() - 7*24*60*60*1000) + console.info('week:', `[${weekBeginDate.toISOString()} → ${weekEndDate.toISOString()}]`) + + // Extract this week's contributions + let contributionsDataThisWeek = [] + contributionsData.forEach((dataRow) => { + const updatedAt = dataRow.updated_at + // console.debug('updatedAt:', updatedAt) + if (updatedAt != undefined) { + const updatedAtDate = new Date(updatedAt) + // console.debug('updatedAtDate:', updatedAtDate) + if (updatedAtDate.getTime() >= weekBeginDate.getTime() + && updatedAtDate.getTime() < weekEndDate.getTime() + ) { + console.debug('updatedAtDate:', updatedAtDate) + contributionsDataThisWeek.push(dataRow) + } + } + }) + console.debug('contributionsDataThisWeek:', contributionsDataThisWeek) + + // Summarize hours spent for each citizen + let rewards = {} + contributionsDataThisWeek.forEach((dataRow) => { + if (rewards[String(dataRow.passport_id)] == undefined) { + const reward = { + passport_id: Number(dataRow.passport_id), + profile_id: Number(dataRow.profile_id), + hours_spent_total: Number(dataRow.hours_spent) + } + rewards[String(dataRow.passport_id)] = reward + } else { + const reward = rewards[String(dataRow.passport_id)] + reward.hours_spent_total += Number(dataRow.hours_spent) + reward.hours_spent_total = Number(Number(reward.hours_spent_total).toFixed(2)) + rewards[String(dataRow.passport_id)] = reward + } + }) + console.debug('rewards:', rewards) + + // Calculate hours spent by all citizens combined + let hoursSpentAllCitizens = 0 + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + hoursSpentAllCitizens += rewards[passportID].hours_spent_total + }) + console.debug('hoursSpentAllCitizens:', hoursSpentAllCitizens) + + // Calculate each citizen's reward percentage + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + const reward = rewards[passportID] + const rewardPercentage = 100 * reward.hours_spent_total / hoursSpentAllCitizens + reward.reward_percentage = Number(rewardPercentage.toFixed(2)) + }) + + // Export to CSV + if (Object.keys(rewards).length) { + exportToCSV(weekEndDate, rewards) + } + + // Increase week end date by 7 days + weekEndDate.setDate(weekEndDate.getDate() + 7) + } +} + +async function loadCSVData(filePath) { + console.info('loadCSVData') + + const dataFile = fs.readFileSync(filePath) + const csvData = dataFile.toString() + console.debug('csvData:\n', csvData) + + return new Promise(resolve => { + Papa.parse(csvData, { + header: true, + complete: (results) => { + console.log('complete') + resolve(results.data) + } + }) + }) +} + +function exportToCSV(weekEndDate, rewards) { + console.log('exportToCSV') + + const filename = 'weekly/' + weekEndDate.toISOString().substring(0, 10) + '_rewards.csv' + const writeableStream = fs.createWriteStream(filename) + const columns = [ + 'passport_id', + 'profile_id', + 'hours_spent_total', + 'reward_percentage' + ]; + const stringifier = stringify({ header: true, columns: columns }) + for (const passportID in rewards) { + const reward = rewards[passportID] + console.debug('reward:', reward) + stringifier.write(reward) + } + stringifier.pipe(writeableStream) + console.log('Finished writing data to CSV:', filename) +} diff --git a/development-guild/rewards/package-lock.json b/development-guild/rewards/package-lock.json new file mode 100644 index 0000000..0f94d04 --- /dev/null +++ b/development-guild/rewards/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "rewards", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rewards", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } + }, + "node_modules/csv-stringify": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz", + "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==" + }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + } + } +} diff --git a/development-guild/rewards/package.json b/development-guild/rewards/package.json new file mode 100644 index 0000000..d6c4eaa --- /dev/null +++ b/development-guild/rewards/package.json @@ -0,0 +1,15 @@ +{ + "name": "rewards", + "version": "1.0.0", + "description": "Contributor rewards", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Nation3 DAO", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } +} diff --git a/development-guild/rewards/weekly/2023-10-29_rewards.csv b/development-guild/rewards/weekly/2023-10-29_rewards.csv new file mode 100644 index 0000000..80fb213 --- /dev/null +++ b/development-guild/rewards/weekly/2023-10-29_rewards.csv @@ -0,0 +1,6 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +171,237874,0.66,0.19 +172,80204,0.99,0.28 +233,77877,324.24,91.7 +280,229591,27.58,7.8 +282,3893302,0.1,0.03 diff --git a/development-guild/rewards/weekly/2023-11-12_rewards.csv b/development-guild/rewards/weekly/2023-11-12_rewards.csv new file mode 100644 index 0000000..74f9ec3 --- /dev/null +++ b/development-guild/rewards/weekly/2023-11-12_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,4.5,100 diff --git a/development-guild/rewards/weekly/2023-11-19_rewards.csv b/development-guild/rewards/weekly/2023-11-19_rewards.csv new file mode 100644 index 0000000..3b165ac --- /dev/null +++ b/development-guild/rewards/weekly/2023-11-19_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1.5,60 +280,229591,1,40 diff --git a/development-guild/rewards/weekly/2023-11-26_rewards.csv b/development-guild/rewards/weekly/2023-11-26_rewards.csv new file mode 100644 index 0000000..1ad6bd9 --- /dev/null +++ b/development-guild/rewards/weekly/2023-11-26_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1.5,81.97 +272,3780245,0.33,18.03 diff --git a/development-guild/rewards/weekly/2023-12-03_rewards.csv b/development-guild/rewards/weekly/2023-12-03_rewards.csv new file mode 100644 index 0000000..b04e87a --- /dev/null +++ b/development-guild/rewards/weekly/2023-12-03_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,3,100 diff --git a/development-guild/rewards/weekly/2023-12-10_rewards.csv b/development-guild/rewards/weekly/2023-12-10_rewards.csv new file mode 100644 index 0000000..c7cd9e0 --- /dev/null +++ b/development-guild/rewards/weekly/2023-12-10_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,7.5,95.79 +280,229591,0.33,4.21 diff --git a/development-guild/rewards/weekly/2023-12-17_rewards.csv b/development-guild/rewards/weekly/2023-12-17_rewards.csv new file mode 100644 index 0000000..38ecdd0 --- /dev/null +++ b/development-guild/rewards/weekly/2023-12-17_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,22.1,100 diff --git a/development-guild/rewards/weekly/2023-12-24_rewards.csv b/development-guild/rewards/weekly/2023-12-24_rewards.csv new file mode 100644 index 0000000..dc94fb3 --- /dev/null +++ b/development-guild/rewards/weekly/2023-12-24_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,32.33,100 diff --git a/development-guild/rewards/weekly/2023-12-31_rewards.csv b/development-guild/rewards/weekly/2023-12-31_rewards.csv new file mode 100644 index 0000000..7d057d7 --- /dev/null +++ b/development-guild/rewards/weekly/2023-12-31_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,21.66,100 diff --git a/development-guild/rewards/weekly/2024-01-07_rewards.csv b/development-guild/rewards/weekly/2024-01-07_rewards.csv new file mode 100644 index 0000000..575fa3e --- /dev/null +++ b/development-guild/rewards/weekly/2024-01-07_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,24.99,83.33 +285,4031919,5,16.67 diff --git a/development-guild/rewards/weekly/2024-01-14_rewards.csv b/development-guild/rewards/weekly/2024-01-14_rewards.csv new file mode 100644 index 0000000..31642a5 --- /dev/null +++ b/development-guild/rewards/weekly/2024-01-14_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,13.83,100 diff --git a/development-guild/rewards/weekly/2024-01-21_rewards.csv b/development-guild/rewards/weekly/2024-01-21_rewards.csv new file mode 100644 index 0000000..6b72179 --- /dev/null +++ b/development-guild/rewards/weekly/2024-01-21_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,26.15,100 diff --git a/development-guild/rewards/weekly/2024-01-28_rewards.csv b/development-guild/rewards/weekly/2024-01-28_rewards.csv new file mode 100644 index 0000000..1a00aff --- /dev/null +++ b/development-guild/rewards/weekly/2024-01-28_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1.82,5.08 +288,4033422,33.99,94.92 diff --git a/development-guild/rewards/weekly/2024-02-04_rewards.csv b/development-guild/rewards/weekly/2024-02-04_rewards.csv new file mode 100644 index 0000000..9811ea9 --- /dev/null +++ b/development-guild/rewards/weekly/2024-02-04_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,4.16,36.78 +288,4033422,7.15,63.22 diff --git a/ecoride-network/README.md b/ecoride-network/README.md index 3c7a183..1fb9225 100644 --- a/ecoride-network/README.md +++ b/ecoride-network/README.md @@ -7,3 +7,7 @@ Coordinape Circle: https://app.coordinape.com/welcome/291168b1-df0f-4d1c-b696-03 ## Coordinape Contributions See [`contributions/`](contributions/) + +## Contribution Rewards + +See [`rewards/`](rewards/) diff --git a/ecoride-network/rewards/.gitignore b/ecoride-network/rewards/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/ecoride-network/rewards/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/ecoride-network/rewards/README.md b/ecoride-network/rewards/README.md new file mode 100644 index 0000000..5321779 --- /dev/null +++ b/ecoride-network/rewards/README.md @@ -0,0 +1,15 @@ +# Contributor Rewards - 🎥 Marketing Guild + +## Generate CSVs + +Generate weekly rewards CSV files: + +``` +cd ops-guild/rewards/ +npm install +node generate-weekly-csvs.js +``` + +## Weekly Rewards + +See [`weekly/`](weekly/) diff --git a/ecoride-network/rewards/generate-weekly-csvs.js b/ecoride-network/rewards/generate-weekly-csvs.js new file mode 100644 index 0000000..b94df07 --- /dev/null +++ b/ecoride-network/rewards/generate-weekly-csvs.js @@ -0,0 +1,131 @@ +const fs = require('fs') +const Papa = require('papaparse') +const { stringify } = require('csv-stringify'); + +generate() + +async function generate() { + console.log('generate') + + // Load the Coordinape contributions + const contributionsFilePath = `../contributions/coordinape-contributions.csv` + console.debug('contributionsFilePath:', contributionsFilePath) + let contributionsData = [] + if (!fs.existsSync(contributionsFilePath)) { + console.error('File does not exist') + return + } else { + contributionsData = await loadCSVData(contributionsFilePath) + } + // console.debug('contributionsData:', contributionsData) + + // Iterate every week from the week of [Sun May-29-2022 → Sun Jun-05-2022] until now + const weekEndDate = new Date('2022-06-05T00:00:00Z') + console.debug('weekEndDate:', weekEndDate) + const nowDate = new Date() + console.debug('nowDate:', nowDate) + while (nowDate.getTime() > weekEndDate.getTime()) { + const weekBeginDate = new Date(weekEndDate.getTime() - 7*24*60*60*1000) + console.info('week:', `[${weekBeginDate.toISOString()} → ${weekEndDate.toISOString()}]`) + + // Extract this week's contributions + let contributionsDataThisWeek = [] + contributionsData.forEach((dataRow) => { + const updatedAt = dataRow.updated_at + // console.debug('updatedAt:', updatedAt) + if (updatedAt != undefined) { + const updatedAtDate = new Date(updatedAt) + // console.debug('updatedAtDate:', updatedAtDate) + if (updatedAtDate.getTime() >= weekBeginDate.getTime() + && updatedAtDate.getTime() < weekEndDate.getTime() + ) { + console.debug('updatedAtDate:', updatedAtDate) + contributionsDataThisWeek.push(dataRow) + } + } + }) + console.debug('contributionsDataThisWeek:', contributionsDataThisWeek) + + // Summarize hours spent for each citizen + let rewards = {} + contributionsDataThisWeek.forEach((dataRow) => { + if (rewards[String(dataRow.passport_id)] == undefined) { + const reward = { + passport_id: Number(dataRow.passport_id), + profile_id: Number(dataRow.profile_id), + hours_spent_total: Number(dataRow.hours_spent) + } + rewards[String(dataRow.passport_id)] = reward + } else { + const reward = rewards[String(dataRow.passport_id)] + reward.hours_spent_total += Number(dataRow.hours_spent) + reward.hours_spent_total = Number(Number(reward.hours_spent_total).toFixed(2)) + rewards[String(dataRow.passport_id)] = reward + } + }) + console.debug('rewards:', rewards) + + // Calculate hours spent by all citizens combined + let hoursSpentAllCitizens = 0 + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + hoursSpentAllCitizens += rewards[passportID].hours_spent_total + }) + console.debug('hoursSpentAllCitizens:', hoursSpentAllCitizens) + + // Calculate each citizen's reward percentage + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + const reward = rewards[passportID] + const rewardPercentage = 100 * reward.hours_spent_total / hoursSpentAllCitizens + reward.reward_percentage = Number(rewardPercentage.toFixed(2)) + }) + + // Export to CSV + if (Object.keys(rewards).length) { + exportToCSV(weekEndDate, rewards) + } + + // Increase week end date by 7 days + weekEndDate.setDate(weekEndDate.getDate() + 7) + } +} + +async function loadCSVData(filePath) { + console.info('loadCSVData') + + const dataFile = fs.readFileSync(filePath) + const csvData = dataFile.toString() + console.debug('csvData:\n', csvData) + + return new Promise(resolve => { + Papa.parse(csvData, { + header: true, + complete: (results) => { + console.log('complete') + resolve(results.data) + } + }) + }) +} + +function exportToCSV(weekEndDate, rewards) { + console.log('exportToCSV') + + const filename = 'weekly/' + weekEndDate.toISOString().substring(0, 10) + '_rewards.csv' + const writeableStream = fs.createWriteStream(filename) + const columns = [ + 'passport_id', + 'profile_id', + 'hours_spent_total', + 'reward_percentage' + ]; + const stringifier = stringify({ header: true, columns: columns }) + for (const passportID in rewards) { + const reward = rewards[passportID] + console.debug('reward:', reward) + stringifier.write(reward) + } + stringifier.pipe(writeableStream) + console.log('Finished writing data to CSV:', filename) +} diff --git a/ecoride-network/rewards/package-lock.json b/ecoride-network/rewards/package-lock.json new file mode 100644 index 0000000..34846b2 --- /dev/null +++ b/ecoride-network/rewards/package-lock.json @@ -0,0 +1,25 @@ +{ + "name": "rewards", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rewards", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } + }, + "node_modules/csv-stringify": { + "version": "6.4.5", + "license": "MIT" + }, + "node_modules/papaparse": { + "version": "5.4.1", + "license": "MIT" + } + } +} diff --git a/ecoride-network/rewards/package.json b/ecoride-network/rewards/package.json new file mode 100644 index 0000000..d6c4eaa --- /dev/null +++ b/ecoride-network/rewards/package.json @@ -0,0 +1,15 @@ +{ + "name": "rewards", + "version": "1.0.0", + "description": "Contributor rewards", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Nation3 DAO", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } +} diff --git a/ecoride-network/rewards/weekly/2023-12-24_rewards.csv b/ecoride-network/rewards/weekly/2023-12-24_rewards.csv new file mode 100644 index 0000000..278cff3 --- /dev/null +++ b/ecoride-network/rewards/weekly/2023-12-24_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1,100 diff --git a/marketing-guild/rewards/.gitignore b/marketing-guild/rewards/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/marketing-guild/rewards/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/marketing-guild/rewards/README.md b/marketing-guild/rewards/README.md index b20b1f2..5321779 100644 --- a/marketing-guild/rewards/README.md +++ b/marketing-guild/rewards/README.md @@ -1,3 +1,15 @@ -# Coordinape Rewards - 🎥 Marketing Guild +# Contributor Rewards - 🎥 Marketing Guild -... +## Generate CSVs + +Generate weekly rewards CSV files: + +``` +cd ops-guild/rewards/ +npm install +node generate-weekly-csvs.js +``` + +## Weekly Rewards + +See [`weekly/`](weekly/) diff --git a/marketing-guild/rewards/generate-weekly-csvs.js b/marketing-guild/rewards/generate-weekly-csvs.js new file mode 100644 index 0000000..b94df07 --- /dev/null +++ b/marketing-guild/rewards/generate-weekly-csvs.js @@ -0,0 +1,131 @@ +const fs = require('fs') +const Papa = require('papaparse') +const { stringify } = require('csv-stringify'); + +generate() + +async function generate() { + console.log('generate') + + // Load the Coordinape contributions + const contributionsFilePath = `../contributions/coordinape-contributions.csv` + console.debug('contributionsFilePath:', contributionsFilePath) + let contributionsData = [] + if (!fs.existsSync(contributionsFilePath)) { + console.error('File does not exist') + return + } else { + contributionsData = await loadCSVData(contributionsFilePath) + } + // console.debug('contributionsData:', contributionsData) + + // Iterate every week from the week of [Sun May-29-2022 → Sun Jun-05-2022] until now + const weekEndDate = new Date('2022-06-05T00:00:00Z') + console.debug('weekEndDate:', weekEndDate) + const nowDate = new Date() + console.debug('nowDate:', nowDate) + while (nowDate.getTime() > weekEndDate.getTime()) { + const weekBeginDate = new Date(weekEndDate.getTime() - 7*24*60*60*1000) + console.info('week:', `[${weekBeginDate.toISOString()} → ${weekEndDate.toISOString()}]`) + + // Extract this week's contributions + let contributionsDataThisWeek = [] + contributionsData.forEach((dataRow) => { + const updatedAt = dataRow.updated_at + // console.debug('updatedAt:', updatedAt) + if (updatedAt != undefined) { + const updatedAtDate = new Date(updatedAt) + // console.debug('updatedAtDate:', updatedAtDate) + if (updatedAtDate.getTime() >= weekBeginDate.getTime() + && updatedAtDate.getTime() < weekEndDate.getTime() + ) { + console.debug('updatedAtDate:', updatedAtDate) + contributionsDataThisWeek.push(dataRow) + } + } + }) + console.debug('contributionsDataThisWeek:', contributionsDataThisWeek) + + // Summarize hours spent for each citizen + let rewards = {} + contributionsDataThisWeek.forEach((dataRow) => { + if (rewards[String(dataRow.passport_id)] == undefined) { + const reward = { + passport_id: Number(dataRow.passport_id), + profile_id: Number(dataRow.profile_id), + hours_spent_total: Number(dataRow.hours_spent) + } + rewards[String(dataRow.passport_id)] = reward + } else { + const reward = rewards[String(dataRow.passport_id)] + reward.hours_spent_total += Number(dataRow.hours_spent) + reward.hours_spent_total = Number(Number(reward.hours_spent_total).toFixed(2)) + rewards[String(dataRow.passport_id)] = reward + } + }) + console.debug('rewards:', rewards) + + // Calculate hours spent by all citizens combined + let hoursSpentAllCitizens = 0 + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + hoursSpentAllCitizens += rewards[passportID].hours_spent_total + }) + console.debug('hoursSpentAllCitizens:', hoursSpentAllCitizens) + + // Calculate each citizen's reward percentage + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + const reward = rewards[passportID] + const rewardPercentage = 100 * reward.hours_spent_total / hoursSpentAllCitizens + reward.reward_percentage = Number(rewardPercentage.toFixed(2)) + }) + + // Export to CSV + if (Object.keys(rewards).length) { + exportToCSV(weekEndDate, rewards) + } + + // Increase week end date by 7 days + weekEndDate.setDate(weekEndDate.getDate() + 7) + } +} + +async function loadCSVData(filePath) { + console.info('loadCSVData') + + const dataFile = fs.readFileSync(filePath) + const csvData = dataFile.toString() + console.debug('csvData:\n', csvData) + + return new Promise(resolve => { + Papa.parse(csvData, { + header: true, + complete: (results) => { + console.log('complete') + resolve(results.data) + } + }) + }) +} + +function exportToCSV(weekEndDate, rewards) { + console.log('exportToCSV') + + const filename = 'weekly/' + weekEndDate.toISOString().substring(0, 10) + '_rewards.csv' + const writeableStream = fs.createWriteStream(filename) + const columns = [ + 'passport_id', + 'profile_id', + 'hours_spent_total', + 'reward_percentage' + ]; + const stringifier = stringify({ header: true, columns: columns }) + for (const passportID in rewards) { + const reward = rewards[passportID] + console.debug('reward:', reward) + stringifier.write(reward) + } + stringifier.pipe(writeableStream) + console.log('Finished writing data to CSV:', filename) +} diff --git a/marketing-guild/rewards/package-lock.json b/marketing-guild/rewards/package-lock.json new file mode 100644 index 0000000..0f94d04 --- /dev/null +++ b/marketing-guild/rewards/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "rewards", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rewards", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } + }, + "node_modules/csv-stringify": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz", + "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==" + }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + } + } +} diff --git a/marketing-guild/rewards/package.json b/marketing-guild/rewards/package.json new file mode 100644 index 0000000..d6c4eaa --- /dev/null +++ b/marketing-guild/rewards/package.json @@ -0,0 +1,15 @@ +{ + "name": "rewards", + "version": "1.0.0", + "description": "Contributor rewards", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Nation3 DAO", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } +} diff --git a/marketing-guild/rewards/weekly/2023-10-29_rewards.csv b/marketing-guild/rewards/weekly/2023-10-29_rewards.csv new file mode 100644 index 0000000..e18316f --- /dev/null +++ b/marketing-guild/rewards/weekly/2023-10-29_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +71,3894108,2.64,61.54 +272,3780245,1.65,38.46 diff --git a/marketing-guild/rewards/weekly/2023-11-26_rewards.csv b/marketing-guild/rewards/weekly/2023-11-26_rewards.csv new file mode 100644 index 0000000..00cf884 --- /dev/null +++ b/marketing-guild/rewards/weekly/2023-11-26_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +272,3780245,0.33,100 diff --git a/marketing-guild/rewards/weekly/2023-12-03_rewards.csv b/marketing-guild/rewards/weekly/2023-12-03_rewards.csv new file mode 100644 index 0000000..d5062d7 --- /dev/null +++ b/marketing-guild/rewards/weekly/2023-12-03_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,0.33,33.33 +272,3780245,0.66,66.67 diff --git a/marketing-guild/rewards/weekly/2023-12-10_rewards.csv b/marketing-guild/rewards/weekly/2023-12-10_rewards.csv new file mode 100644 index 0000000..00cf884 --- /dev/null +++ b/marketing-guild/rewards/weekly/2023-12-10_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +272,3780245,0.33,100 diff --git a/marketing-guild/rewards/weekly/2023-12-17_rewards.csv b/marketing-guild/rewards/weekly/2023-12-17_rewards.csv new file mode 100644 index 0000000..176181e --- /dev/null +++ b/marketing-guild/rewards/weekly/2023-12-17_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,2,100 diff --git a/marketing-guild/rewards/weekly/2024-01-07_rewards.csv b/marketing-guild/rewards/weekly/2024-01-07_rewards.csv new file mode 100644 index 0000000..135bcae --- /dev/null +++ b/marketing-guild/rewards/weekly/2024-01-07_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +272,3780245,2,100 diff --git a/ops-guild/rewards/.gitignore b/ops-guild/rewards/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/ops-guild/rewards/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/ops-guild/rewards/README.md b/ops-guild/rewards/README.md index 12638ac..44ccda5 100644 --- a/ops-guild/rewards/README.md +++ b/ops-guild/rewards/README.md @@ -1,3 +1,15 @@ -# Coordinape Rewards - ⚙️ Ops Guild +# Contributor Rewards - ⚙️ Ops Guild -... +## Generate CSVs + +Generate weekly rewards CSV files: + +``` +cd ops-guild/rewards/ +npm install +node generate-weekly-csvs.js +``` + +## Weekly Rewards + +See [`weekly/`](weekly/) diff --git a/ops-guild/rewards/generate-weekly-csvs.js b/ops-guild/rewards/generate-weekly-csvs.js new file mode 100644 index 0000000..b94df07 --- /dev/null +++ b/ops-guild/rewards/generate-weekly-csvs.js @@ -0,0 +1,131 @@ +const fs = require('fs') +const Papa = require('papaparse') +const { stringify } = require('csv-stringify'); + +generate() + +async function generate() { + console.log('generate') + + // Load the Coordinape contributions + const contributionsFilePath = `../contributions/coordinape-contributions.csv` + console.debug('contributionsFilePath:', contributionsFilePath) + let contributionsData = [] + if (!fs.existsSync(contributionsFilePath)) { + console.error('File does not exist') + return + } else { + contributionsData = await loadCSVData(contributionsFilePath) + } + // console.debug('contributionsData:', contributionsData) + + // Iterate every week from the week of [Sun May-29-2022 → Sun Jun-05-2022] until now + const weekEndDate = new Date('2022-06-05T00:00:00Z') + console.debug('weekEndDate:', weekEndDate) + const nowDate = new Date() + console.debug('nowDate:', nowDate) + while (nowDate.getTime() > weekEndDate.getTime()) { + const weekBeginDate = new Date(weekEndDate.getTime() - 7*24*60*60*1000) + console.info('week:', `[${weekBeginDate.toISOString()} → ${weekEndDate.toISOString()}]`) + + // Extract this week's contributions + let contributionsDataThisWeek = [] + contributionsData.forEach((dataRow) => { + const updatedAt = dataRow.updated_at + // console.debug('updatedAt:', updatedAt) + if (updatedAt != undefined) { + const updatedAtDate = new Date(updatedAt) + // console.debug('updatedAtDate:', updatedAtDate) + if (updatedAtDate.getTime() >= weekBeginDate.getTime() + && updatedAtDate.getTime() < weekEndDate.getTime() + ) { + console.debug('updatedAtDate:', updatedAtDate) + contributionsDataThisWeek.push(dataRow) + } + } + }) + console.debug('contributionsDataThisWeek:', contributionsDataThisWeek) + + // Summarize hours spent for each citizen + let rewards = {} + contributionsDataThisWeek.forEach((dataRow) => { + if (rewards[String(dataRow.passport_id)] == undefined) { + const reward = { + passport_id: Number(dataRow.passport_id), + profile_id: Number(dataRow.profile_id), + hours_spent_total: Number(dataRow.hours_spent) + } + rewards[String(dataRow.passport_id)] = reward + } else { + const reward = rewards[String(dataRow.passport_id)] + reward.hours_spent_total += Number(dataRow.hours_spent) + reward.hours_spent_total = Number(Number(reward.hours_spent_total).toFixed(2)) + rewards[String(dataRow.passport_id)] = reward + } + }) + console.debug('rewards:', rewards) + + // Calculate hours spent by all citizens combined + let hoursSpentAllCitizens = 0 + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + hoursSpentAllCitizens += rewards[passportID].hours_spent_total + }) + console.debug('hoursSpentAllCitizens:', hoursSpentAllCitizens) + + // Calculate each citizen's reward percentage + Object.keys(rewards).forEach((passportID) => { + console.debug('passportID:', passportID) + const reward = rewards[passportID] + const rewardPercentage = 100 * reward.hours_spent_total / hoursSpentAllCitizens + reward.reward_percentage = Number(rewardPercentage.toFixed(2)) + }) + + // Export to CSV + if (Object.keys(rewards).length) { + exportToCSV(weekEndDate, rewards) + } + + // Increase week end date by 7 days + weekEndDate.setDate(weekEndDate.getDate() + 7) + } +} + +async function loadCSVData(filePath) { + console.info('loadCSVData') + + const dataFile = fs.readFileSync(filePath) + const csvData = dataFile.toString() + console.debug('csvData:\n', csvData) + + return new Promise(resolve => { + Papa.parse(csvData, { + header: true, + complete: (results) => { + console.log('complete') + resolve(results.data) + } + }) + }) +} + +function exportToCSV(weekEndDate, rewards) { + console.log('exportToCSV') + + const filename = 'weekly/' + weekEndDate.toISOString().substring(0, 10) + '_rewards.csv' + const writeableStream = fs.createWriteStream(filename) + const columns = [ + 'passport_id', + 'profile_id', + 'hours_spent_total', + 'reward_percentage' + ]; + const stringifier = stringify({ header: true, columns: columns }) + for (const passportID in rewards) { + const reward = rewards[passportID] + console.debug('reward:', reward) + stringifier.write(reward) + } + stringifier.pipe(writeableStream) + console.log('Finished writing data to CSV:', filename) +} diff --git a/ops-guild/rewards/package-lock.json b/ops-guild/rewards/package-lock.json new file mode 100644 index 0000000..0f94d04 --- /dev/null +++ b/ops-guild/rewards/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "rewards", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rewards", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } + }, + "node_modules/csv-stringify": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.5.tgz", + "integrity": "sha512-SPu1Vnh8U5EnzpNOi1NDBL5jU5Rx7DVHr15DNg9LXDTAbQlAVAmEbVt16wZvEW9Fu9Qt4Ji8kmeCJ2B1+4rFTQ==" + }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + } + } +} diff --git a/ops-guild/rewards/package.json b/ops-guild/rewards/package.json new file mode 100644 index 0000000..d6c4eaa --- /dev/null +++ b/ops-guild/rewards/package.json @@ -0,0 +1,15 @@ +{ + "name": "rewards", + "version": "1.0.0", + "description": "Contributor rewards", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Nation3 DAO", + "license": "GPL-3.0-or-later", + "dependencies": { + "csv-stringify": "^6.4.5", + "papaparse": "^5.4.1" + } +} diff --git a/ops-guild/rewards/weekly/2023-10-29_rewards.csv b/ops-guild/rewards/weekly/2023-10-29_rewards.csv new file mode 100644 index 0000000..095ab77 --- /dev/null +++ b/ops-guild/rewards/weekly/2023-10-29_rewards.csv @@ -0,0 +1,5 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,68.59,83.31 +272,3780245,6.6,8.02 +280,229591,6.64,8.07 +282,3893302,0.5,0.61 diff --git a/ops-guild/rewards/weekly/2023-11-05_rewards.csv b/ops-guild/rewards/weekly/2023-11-05_rewards.csv new file mode 100644 index 0000000..f29ad2c --- /dev/null +++ b/ops-guild/rewards/weekly/2023-11-05_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,0.5,60.24 +280,229591,0.33,39.76 diff --git a/ops-guild/rewards/weekly/2023-11-12_rewards.csv b/ops-guild/rewards/weekly/2023-11-12_rewards.csv new file mode 100644 index 0000000..91af87d --- /dev/null +++ b/ops-guild/rewards/weekly/2023-11-12_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,0.5,100 diff --git a/ops-guild/rewards/weekly/2023-11-19_rewards.csv b/ops-guild/rewards/weekly/2023-11-19_rewards.csv new file mode 100644 index 0000000..8ec89df --- /dev/null +++ b/ops-guild/rewards/weekly/2023-11-19_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,2,85.84 +272,3780245,0.33,14.16 diff --git a/ops-guild/rewards/weekly/2023-11-26_rewards.csv b/ops-guild/rewards/weekly/2023-11-26_rewards.csv new file mode 100644 index 0000000..d9e717b --- /dev/null +++ b/ops-guild/rewards/weekly/2023-11-26_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,5.5,76.92 +272,3780245,1.65,23.08 diff --git a/ops-guild/rewards/weekly/2023-12-03_rewards.csv b/ops-guild/rewards/weekly/2023-12-03_rewards.csv new file mode 100644 index 0000000..f1cc737 --- /dev/null +++ b/ops-guild/rewards/weekly/2023-12-03_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,16.5,87.72 +272,3780245,2.31,12.28 diff --git a/ops-guild/rewards/weekly/2023-12-10_rewards.csv b/ops-guild/rewards/weekly/2023-12-10_rewards.csv new file mode 100644 index 0000000..51eb21d --- /dev/null +++ b/ops-guild/rewards/weekly/2023-12-10_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,12.5,90.45 +272,3780245,1.32,9.55 diff --git a/ops-guild/rewards/weekly/2023-12-17_rewards.csv b/ops-guild/rewards/weekly/2023-12-17_rewards.csv new file mode 100644 index 0000000..28e1bdc --- /dev/null +++ b/ops-guild/rewards/weekly/2023-12-17_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,10.23,63.03 +272,3780245,6,36.97 diff --git a/ops-guild/rewards/weekly/2023-12-24_rewards.csv b/ops-guild/rewards/weekly/2023-12-24_rewards.csv new file mode 100644 index 0000000..6596ab5 --- /dev/null +++ b/ops-guild/rewards/weekly/2023-12-24_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,2.82,100 diff --git a/ops-guild/rewards/weekly/2023-12-31_rewards.csv b/ops-guild/rewards/weekly/2023-12-31_rewards.csv new file mode 100644 index 0000000..6bb5e5e --- /dev/null +++ b/ops-guild/rewards/weekly/2023-12-31_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1.82,100 diff --git a/ops-guild/rewards/weekly/2024-01-07_rewards.csv b/ops-guild/rewards/weekly/2024-01-07_rewards.csv new file mode 100644 index 0000000..26911a7 --- /dev/null +++ b/ops-guild/rewards/weekly/2024-01-07_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,5.97,94.76 +272,3780245,0.33,5.24 diff --git a/ops-guild/rewards/weekly/2024-01-14_rewards.csv b/ops-guild/rewards/weekly/2024-01-14_rewards.csv new file mode 100644 index 0000000..0de5796 --- /dev/null +++ b/ops-guild/rewards/weekly/2024-01-14_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,7.32,82.99 +272,3780245,1.5,17.01 diff --git a/ops-guild/rewards/weekly/2024-01-21_rewards.csv b/ops-guild/rewards/weekly/2024-01-21_rewards.csv new file mode 100644 index 0000000..c6f225e --- /dev/null +++ b/ops-guild/rewards/weekly/2024-01-21_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,0.66,100 diff --git a/ops-guild/rewards/weekly/2024-01-28_rewards.csv b/ops-guild/rewards/weekly/2024-01-28_rewards.csv new file mode 100644 index 0000000..9412745 --- /dev/null +++ b/ops-guild/rewards/weekly/2024-01-28_rewards.csv @@ -0,0 +1,3 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,0.66,33.17 +272,3780245,1.33,66.83 diff --git a/ops-guild/rewards/weekly/2024-02-04_rewards.csv b/ops-guild/rewards/weekly/2024-02-04_rewards.csv new file mode 100644 index 0000000..9f349c1 --- /dev/null +++ b/ops-guild/rewards/weekly/2024-02-04_rewards.csv @@ -0,0 +1,2 @@ +passport_id,profile_id,hours_spent_total,reward_percentage +233,77877,1.83,100