-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from developmentseed/badge_db
Pull badges from the DB and calculate for each user on the backend
- Loading branch information
Showing
21 changed files
with
273 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const sequentializeDates = require('../utils/sequential_dates') | ||
const findLongestStreak = require('../utils/longest_streak') | ||
const getBadgeInfo = require('./get_badge_info') | ||
|
||
module.exports = (dates, badges) => { | ||
const sequentialDates = sequentializeDates(dates) | ||
const userTotal = findLongestStreak(sequentialDates) | ||
const key = 'daysInRow' | ||
|
||
return { | ||
[key]: getBadgeInfo(userTotal, key, badges.find((element) => { | ||
return element.metric === key | ||
})) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const getBadgeInfo = require('./get_badge_info') | ||
const { uniq } = require('ramda') | ||
|
||
module.exports = (dates, badges) => { | ||
// Truncate hours/minutes/seconds from timestamp | ||
const days = dates.map((date) => { | ||
date = new Date(date) | ||
return date.setHours(0, 0, 0, 0) | ||
}) | ||
|
||
const key = 'daysTotal' | ||
return { | ||
[key]: getBadgeInfo(uniq(days).length, key, badges.find((element) => { | ||
return element.metric === key | ||
})) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/** | ||
* Given a metricValue and a metricName to a badge | ||
* calculate the level of that badge and the percentage amount | ||
* needed to obtain the next badge | ||
*/ | ||
//eslint-disable-next-line no-unused-vars, consistent-return | ||
module.exports = (metricValue, metricName, badge) => { | ||
const { tiers, name, id } = badge | ||
let badgeLevel = 0 | ||
|
||
if (metricValue >= tiers[0] && metricValue < tiers[1]) { | ||
badgeLevel = 1 | ||
} | ||
else if (metricValue >= tiers[1] && metricValue < tiers[2]) { | ||
badgeLevel = 2 | ||
} | ||
else if (metricValue >= tiers[2]) { | ||
badgeLevel = 3 | ||
} | ||
|
||
const nextBadgeLevel = badgeLevel + 1 | ||
const currentPoints = Number(metricValue) | ||
let lastPoints = 0 | ||
let nextPoints = 0 | ||
let percentage = 100 | ||
|
||
if (badgeLevel < Object.keys(tiers).length) { | ||
if (badgeLevel > 0) lastPoints = tiers[badgeLevel - 1] | ||
nextPoints = tiers[nextBadgeLevel] | ||
percentage = (currentPoints - lastPoints) / (nextPoints - lastPoints) * 100 | ||
return { | ||
name: name, | ||
category: id, | ||
metric: metricName, | ||
description: badge.description, | ||
badgeLevel, | ||
nextBadgeLevel, | ||
points: { | ||
currentPoints, | ||
nextPoints, | ||
percentage | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
const getSumBadges = require('./sum_based_badges') | ||
const dateSequentialCheck = require('./date_check_sequential') | ||
const dateTotalCheck = require('./date_check_total') | ||
const { | ||
mergeAll, reject, isNil, filter, map, prop, compose, sum | ||
} = require('ramda') | ||
|
||
const getJosmEditCount = compose( | ||
sum, | ||
map(prop('count')), | ||
filter((x) => x.editor.toLowerCase().includes('josm')) | ||
) | ||
|
||
module.exports = (userData, badges) => { | ||
/* eslint-disable camelcase */ | ||
const { | ||
buildings_add, | ||
waterways_add, | ||
poi_add, | ||
km_roads_add, | ||
km_roads_mod, | ||
country_list, | ||
hashtags, | ||
editors, | ||
edit_times | ||
} = userData | ||
/* eslint-enable camelcase */ | ||
const sumBadges = reject(isNil)(getSumBadges({ | ||
buildings: Number(buildings_add), | ||
waterways: Number(waterways_add), | ||
pois: Number(poi_add), | ||
roadKms: Number(km_roads_add), | ||
roadKmMods: Number(km_roads_mod), | ||
countries: Object.keys(country_list).length, | ||
josm: getJosmEditCount(editors), | ||
hashtags: Object.keys(hashtags).length | ||
}, badges)) | ||
const consistencyBadge = dateSequentialCheck(edit_times, badges) | ||
const historyBadge = dateTotalCheck(edit_times, badges) | ||
|
||
const allBadges = mergeAll([sumBadges, consistencyBadge, historyBadge]) | ||
const earnedBadges = {} | ||
/* eslint-disable no-restricted-syntax */ | ||
for (const key in allBadges) { | ||
const val = allBadges[key] | ||
if (val && val.badgeLevel > 0) { | ||
earnedBadges[key] = val | ||
} | ||
} | ||
/* eslint-enable no-restricted-syntax */ | ||
|
||
const sortedSumBadges = Object.keys(sumBadges).sort((a, b) => { | ||
return sumBadges[a].points.percentage - sumBadges[b].points.percentage | ||
}) | ||
|
||
const mostObtainableNames = sortedSumBadges.slice(-3) | ||
const mostObtainable = sumBadges[mostObtainableNames[mostObtainableNames.length - 1]] | ||
const secondMostObtainable = sumBadges[mostObtainableNames[mostObtainableNames.length - 2]] | ||
const thirdMostObtainable = sumBadges[mostObtainableNames[mostObtainableNames.length - 3]] | ||
|
||
return { | ||
all: allBadges, | ||
earnedBadges, | ||
mostAttainable: [mostObtainable, secondMostObtainable, thirdMostObtainable] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const getBadgeInfo = require('./get_badge_info') | ||
const { keys } = require('ramda') | ||
|
||
/** | ||
* Given the userData object containing total amounts for each | ||
* metric, get the badge info for that metric | ||
* | ||
* @param {*} userData - User stats | ||
* @param {*} badgesDB - All badges in the database | ||
* @returns {*} keys - For every key in userData, get the badge info associated with that key | ||
*/ | ||
//eslint-disable-next-line no-unused-vars | ||
module.exports = (userData, badgesDB) => { | ||
return keys(userData).map((key) => getBadgeInfo(userData[key], key, badgesDB.find((element) => { | ||
return element.metric === key | ||
}))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
|
||
exports.up = async function(knex, Promise) { | ||
try { | ||
await knex.schema.createTable('badges', table => { | ||
table.increments('id'); | ||
table.string('name'); | ||
table.specificType('tiers', 'int[]'); | ||
table.string('metric'); | ||
table.string('description'); | ||
table.timestamps(); | ||
}); | ||
await knex('badges').insert([ | ||
{'id': 15, 'tiers': [25, 50, 100], 'description': 'Map early, map often. Map as many days as you can to achieve new levels.', 'metric': 'daysTotal', 'name': 'Year-long Mapper'}, | ||
{'id': 14, 'tiers': [5, 20, 50], 'description': 'Great mappers map everyday. Edit for a consecutive numbers of days in a month to achieve new levels.', 'metric': 'daysInRow', 'name': 'Consistency'}, | ||
{'id': 3, 'tiers': [500, 2500, 5000], 'description': 'Places of interest guide where you can go. Every community needs hospitals, schools, businesses mapped to enable access. Each new level is achieved by creating new places on the map.', 'metric': 'pois', 'name': 'On Point'}, | ||
{'id': 4, 'tiers': [100, 500, 1000], 'description': 'Frank Lloyd Wright knew buildings, and so do you. Each new level is achieved by mapping and editing buildings.', 'metric': 'buildings', 'name': 'The Wright Stuff'}, | ||
{'id': 6, 'tiers': [50, 100, 500], 'description': 'Transportation matters. Put communities on the map by creating new roads. Each new level achieved by creating new roads.', 'metric': 'roadKms', 'name': 'On The Road Again'}, | ||
{'id': 7, 'tiers': [50, 100, 500], 'description': 'Roads need maintainence. Existing roads are replaced by new roads and they need to be updated. Each new level achieved by editing existing roads.', 'metric': 'roadKmMods', 'name': 'Long and Winding Road'}, | ||
{'id': 8, 'tiers': [50, 100, 500], 'description': 'Waterways, rivers, streams and more. Adding water features to the map adds regional context and valuable information in the event of flooding. Add these features to reach new levels of this badge.', 'metric': 'waterways', 'name': 'White Water Rafting'}, | ||
{'id': 9, 'tiers': [5, 10, 25], 'description': 'You are famous around the globe. The more you edit in new countries, the more you can become world renown. Each new level is achieved by mapping in new countries around the world.', 'metric': 'countries', 'name': 'World Renown'}, | ||
{'id': 12, 'tiers': [1, 10, 100], 'description': 'JOSM is a tool used to edit OpenStreetMap. It is particularly useful for mapping larger areas more quickly and contains many additional, advanced tools. Map using JOSM to achieve this badge.', 'metric': 'josm', 'name': 'Awesome JOSM'}, | ||
{'id': 13, 'tiers': [5, 20, 50], 'description': 'Mapathons are entry points to mapping. They also provide structure to train and become a better mapper. Each new level is achieved by attending and participating in mapathons.', 'metric': 'hashtags', 'name': 'Mapathoner'} | ||
]) | ||
} | ||
catch (e) { | ||
console.error(e); | ||
} | ||
}; | ||
|
||
exports.down = async function(knex, Promise) { | ||
try { | ||
await knex.schema.dropTable('badges'); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
|
||
// returns the length of the longest array in an array | ||
module.exports = (array) => { | ||
const elements = array.length | ||
let count = 0 | ||
for (let i = 0; i < elements; i += 1) { | ||
if (array[i].length > count) { | ||
count = array[i].length | ||
} | ||
} | ||
return count | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
const { uniq } = require('ramda') | ||
|
||
// function takes array of dates and returns an array of arrays | ||
// containing each sequential date | ||
// http://stackoverflow.com/questions/16690905/javascript-get-sequential-dates-in-array | ||
module.exports = (dates) => { | ||
// Filter out non-unique dates | ||
const days = uniq( | ||
dates.map((date) => { | ||
date = new Date(date) | ||
return date.setHours(0, 0, 0, 0) | ||
}) | ||
) | ||
|
||
let k = 0 | ||
const sorted = [] | ||
sorted[k] = [] | ||
days.sort((a, b) => { //eslint-disable-line arrow-body-style | ||
return +a > +b ? 1 : +a === +b ? 0 : -1 //eslint-disable-line no-nested-ternary | ||
}) | ||
.forEach((v, i) => { | ||
const a = v | ||
const b = dates[i + 1] || 0 | ||
sorted[k].push(+a) | ||
if ((+b - +a) > 86400000) { | ||
sorted[++k] = [] //eslint-disable-line no-plusplus | ||
} | ||
return 1 | ||
}) | ||
|
||
sorted.sort((a, b) => { //eslint-disable-line arrow-body-style | ||
return a.length > b.length ? -1 : 1 | ||
}) | ||
|
||
return sorted | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.