Project 3: Convergent Design
Concepts: Functional Design
Map [Bin]
Purpose: to represent a geographical area and the location of things within it
OP: User inputs a geographical location -> all items (bins in our app) associated with that location are shown to the user
State:
- Markers: one Bin → one (Float, Float) Coordinate
Actions:
addBin(loc: Coordinate, bin: Bin)
Add a location marker for the given bin to the map at location loc
removeBin(loc: Coordinate, bin: Bin)
Assert the given bin exists at the given location Remove the given bin’s location marker from the given location
getBinsByLocation(loc: Coordinate, out: bins: set Bin)
Return all bins with location markers within a 1 mile radius from loc
getBinLocation(bin: Bin, out location: Coordinate)
Assert the given bin exists on the map Return the location of the given bin
updateBinLocation(bin: Bin, location: Coordinate)
Assert the given bin exists on the map Change the given bin’s location marker to be the new given location
Bin [Material]
Purpose: to represent a waste disposal location and the sort of items it accepts.
OP: Bin locations are shown as markers on the map → User selects a single bin marker from the map → User is able to see what type of items the bin accepts for proper waste disposal
State:
- Bins: set Bin
- Type: Bins -> one “Compost” | “Recycle” | “Trash” | “Donation”
- AcceptedMaterials: Bins -> set Material
- CommonlyMisdisposedMaterials: Bins -> set Material
- Status: Bins -> one “Full” | “Not Full”
- LastStatusUpdate: Bins -> one Date
Actions:
addBin(acceptedMaterials, commonlyMisdisposedMaterials: set Material, type: “Compost” | “Recycle” | “Trash” | “Donation”, out: bin: Bin)
Create and return a new bin with status = “Not Full”, LastStatusUpdate = the current date and the given acceptedMaterials, commonlyMisdisposedMaterials, and type
deleteBin(bin: Bin)
Assert the bin exists Delete the given bin
updateBin(bin: Bin, acceptedMaterials, commonlyMisdisposedMaterials: set Materials, type: “Compost” | “Recycle” | “Trash” | “Donation”, status: “Full” | “Not Full”, out: bin: Bin)
Assert the bin exists Update the given bin to have the given acceptedMaterials, commonlyMisdisposedMaterials, type, and status Update the bin’s LastStatusUpdate to the current date-time Return the updated bin
updateBinStatus(bin: Bin, newStatus: “Full” | “Not Full”)
Assert bin exists Update status of bin to newStatus and update the bin’s LastStatusUpdate to the current date-time
getBinType(bin: Bin, out type: “Compost” | “Recycle” | “Trash” | “Donation”)
Assert the bin exists Return the given bin’s type
getBinInformation(bin: Bin, out: acceptedMaterials: set Materials, commonlyMisdisposedItems: set Materials, type: “Compost” | “Recycle” | “Trash” | “Donation”, status: “Full” | “Not Full”, lastStatusUpdate: one Date)
Assert the bin exists Return the states of the bin
getBinsByMaterial(material: Material, out: bins: set Bin)
Return all bins that accept the given material
getBinsByType(type: “Compost” | “Recycle” | “Trash” | “Donation”, out: bins: set Bin)
Return all bins with the given type
getBinsByStatus(status: “Full” | “Not Full”, out: bins: set Bin)
Return all bins with the given status
Material
Purpose: to represent what any physical item can be made of
OP: When users search for bins to dispose of their item, only bins that accept the material the item is made of will be shown.
State:
- Materials: set Material
- Image, Description, Name, RIC: materials → one String
- Type: materials → “Recyclable” | “Compostable” | “Solid Waste” | “Donatable”
Actions:
getMaterialsByName(name: String, out materials: set Material)
Return all materials with the given name
getMaterialsByRIC(RIC: String, out materials: set Material)
Return all materials with the given RIC
updateMaterial(material: Material, image: String, description: String, name: String, RIC: String, out material: Material)
Assert material exists Update the image, description, name, and RIC of the given material to the new values Return the updated material
removeMaterial(material: Material)
Assert material exists Delete the material
createMaterial(image, description, name, RIC: String, out material: Material)
Assert no material with the given name or RIC exists Create and return a new material with the given image, description, name, and RIC
getMaterialType(material: Material, out type: “Recyclable” | “Compostable” | “Solid Waste” | “Donatable”)
Assert the material exists Return the type of the material
Score [User]
Purpose: Signify how well something is doing
OP: User starts with having properly disposed of 0 items (i.e. score 0) → user self-reports that they have properly disposed of something to increase their score → users can earn badges for achieving certain scores → their scores will be personally displayed in their profile
State:
- scores: set Score
- value: scores -> one Int
- name: scores -> one String
- userScores: scores -> one User
Actions:
setScore(user: User, name: String, newScore: Int, out: score: Int)
If no score with the given name exists: create a new score with name name set to newScore Else: Update the score with name name to have value newScore Return the score’s updated value
getScore(user: User, name: String, out: score: Int)
Assert score with name name exists Return the score’s current value
Achievement [User, Score, Community]
Purpose: Show a goal has been reached
OP: Users earn different tiered achievements based on their score for how many items they have properly disposed of. Members in a community can earn achievements when the community earns an achievement.
State:
- Achievements: set Achievement
- Requirements: achievements -> set Score
- Name: achievements -> one String
- Type: achievements -> “User” | “Community”
- UserAchievements: achievements -> set User
- CommunityAchievements: achievements -> set Community
Actions:
getAllAchievements(out: achievements: set Achievement)
Return all achievements
getAchievementRequirements(achievement: Achievement, out: requirements: set Score)
Assert achievement exists Return the set of Scores needed to earn the achievement
createAchievement(name: String, requirements: set Score, type: “User” | “Community”, out: achievement: Achievement)
Assert an achievement with the given name, type, and requirements does not already exist Return an Achievement with the given name, type, and requirements
deleteAchievement(achievement: Achievement)
Assert the achievement exists Delete the achievement and remove it from all users’ or communities’ achievements
addUserAchievements(user: User, achievements: Achievement, out: newAchievements: set Achievement)
For each achievement in achievements: Assert achievement type is “User” Assert user does not already have achievement Add the user to the achievement’s set of users who have earned it Return all new achievements added
removeUserAchievement(user: User, achievement: Achievement)
Assert user has the achievement Remove the achievement from the user’s set of achievements
getUserAchievements(user: User, out: achievements: set Achievement)
Assert user exists Return all the achievements the user currently has
getNewUserAchievementsEarned(user: User, out: achievements: set Achievement)
Assert user exists Get all “User” achievements and their requirements Get the scores and achievements of the user Return all achievements that the user meets the requirements for but does not already have
addCommunityAchievements(community: Community, achievements: Achievement, out: newAchievements: set Achievement)
For each achievement in achievements: Assert achievement type is “Community” Assert community does not already have achievement Add the community to the achievement’s set of communities that have achieved it Return all new achievements added
removeCommunityAchievement(community: Community, achievement: Achievement)
Assert community has the achievement Remove the achievement from the community’s set of achievements
getCommunityAchievements(community: Community, out: achievements: set Achievement)
Assert community exists Return all the achievements the community currently has
getNewCommunityAchievementsEarned(community: Community, out: achievements: set Achievement)
Assert community exists Get all “Community” achievements and their requirements Get the scores (sum of all member scores) and achievements of the community Return all achievements that the community meets the requirements for but does not already have
Fact
Purpose: Represents a statement or piece of information that can be objectively verified as true.
OP: On the landing page, the user views a random fact related to sustainable waste management. The fact changes to a new random fact each session.
State:
- facts: set Fact
- fact: facts -> one String
Actions:
createFact(statement: String, out fact: Fact)
Assert a Fact with the given statement does not already exist Return a Fact with the given statement
deleteFact(fact: Fact)
Assert the given Fact exists Delete the given Fact
editFact(fact: Fact, statement: String, out editedFact: Fact)
Assert the given Fact exists Update the fact to the new statement Return the updated fact
Community [User]
Purpose: Group users together based on a commonality
OP: User joins a community -> user properly disposes of items and earns scores for their community -> when a community earns an achievement, all users in that community will also earn the achievement
State:
- Communities: set Community
- Name: communities -> one String
- Members: communities -> set User
Actions:
createCommunity(name: String, out: community: Community)
Assert community with name does not exist Create and return new community with name name
deleteCommunity(name: String)
Assert community with name exists Delete community with name name
getAllMembersInCommunity(community: String, out: members; set User)
Assert community with name exists Return all users in the given community
addUserToCommunity(user: User, communityName: String)
Assert community with name exists and user is not already in the community Add user to the community
removeUserFromCommunity(user: User, communityName: String)
Assert community with name exists and user is in the community Remove user from the community
getCommunitiesByUser(user: User, out: communities: set Communities)
Assert user exists Return all communities user is a member of
User
Purpose: Authenticate users
OP: User registers with a username and password → they can later authenticate with same username and password combo.
State:
- registered: set User
- username, password: registered -> one String
Actions:
register (username, password: String, out u: User)
assert username, password not in registered add new User with username, password to registered return User
authenticate (username, password: String, out u: User)
assert username, password combo in registered return User
AccessList [User]
Purpose: restrict actions on the app to a subset of users
OP: Users all have an access level -> “admin” users can do everything -> “organization” users can only update bins -> all users can view the map and update the bin status
State:
- Users: set User
- AccessLevel: users -> “Admin” | “Organization” | “None”
Actions:
getUserAccess (user: User, out: access: “Admin” | “Organization” | “None”)
Assert user exists Return user’s access level
updateUserAccess (admin, user: User, access: “Admin” | “Organization” | “None”)
Assert admin has Admin access Assert user exists Update user’s access level accordingly
UserSettings [User]
Purpose: store a user’s preferences
OP: User sets a default location -> user opens map which initially loads to their default location
State:
- Users: set User
- defaultLocation: users -> lone Coordinate
Actions:
setDefaultLocation(user: User, loc: Coordinate)
Set the user’s default location to loc such that when the user opens the map, they initially see loc and it’s surrounding area
getDefaultLocation(user: User, out: loc: Coordinate)
Return the user’s default location
Session [User]
Purpose: Authenticate users for extended period
OP: User authenticates with the password and username combo they registered with → user stays logged in until they log out again → can perform other actions on the app as long as they are logged in
State:
- active: Session → one User
Actions:
start (user: User, out: s: Session)
assert no active session return new Session whose active user is User
end (session: Session)
assert active session end the user session (i.e. remove the active: session → user relation for the given session)
getUser (session: Session, out: u: User)
assert active session return the User that session is mapped to by active
Synchronizations
App: WasteWise
include User
include Session [User.User]
include AccessList [User.User]
include UserSettings [User.User]
include Achievement [User.User, Score.Score, Community.Community]
include Score [User.User]
include Community [User.User]
include Map [Bin.Bin]
include Bin [Material.Material]
include Material
include Fact
sync register (username, password: String, out user: User)
User.register(username, password, user)
Score.setScore(user, ”Compost”, 0, compostScore)
Score.setScore(user, “Recycle”, 0, recylceScore)
Score.setScore(user, “Trash”, 0, trashScore)
Score.setScore(user, “Donation”, 0, donationScore)
sync login (username, password: String, out user: User, out session: Session)
when User.authenticate(username, password, user)
Session.start(user, session)
sync logout (user: User, session: Session)
when Session.end(session)
sync authenticate (session: Session, user: User)
Session.getUser(session, user)
sync createBin (user: User, acceptedMaterials: set Material, commonlyMisdisposedMaterials: set Material, type:“Compost” | “Recycle” | “Trash” | “Donation”, location: Coordinate, out bin: Bin)
AccessList.getUserAccess(user, access)
Bin.addBin(acceptedMaterials, commonlyMisdisposedMaterials, type, bin)
Map.addBin(location, bin)
sync deleteBin (user: User, bin: Bin)
AccessList.getUserAccess(user, access)
Map.getBinLocation(bin, location)
Map.removeBin(location, bin)
Bin.deleteBin(bin)
sync updateBin (user: User, bin: Bin, acceptedMaterials, commonlyMisdisposedMaterials: set Materials, type: “Compost” | “Recycle” | “Trash” | “Donation”, status: “Full” | “Not Full”, location: Coordinate, out bin: Bin)
AccessList.getUserAccess(user, access)
Bin.updateBin(bin, acceptedMaterials, commonlyMisdisposedMaterials, type, status, bin)
Map.updateBinLocation(bin, location)
sync updateBinStatus(session: Session, bin: Bin, newStatus: “Full” | “Not Full”)
Session.getUser(session, user)
Bin.updateBinStatus(bin, newStatus)
sync getBinsByMaterial(material: Material, out bins: set Bin)
Bin.getBinsByMaterial(material, bins)
sync getBinsByType(type: “Compost” | “Recycle” | “Trash” | “Donation”, out bins: set Bin)
Bin.getBinsByType(type, bins)
sync getBinsByStatus(status: “Full” | “Not Full”, out bins: set Bin)
Bin.getBinsByStatus(status, bins)
sync getBinsByLocation(location: Coordinate, out bins: set Bin)
Map.getBinsByLocation(location, bins)
sync getBinInformation(bin: Bin, out: acceptedMaterials: set Materials, commonlyMisdisposedItems: set Materials, type: “Compost” | “Recycle” | “Trash” | “Donation”, status: “Full” | “Not Full”, lastStatusUpdate: one Date)
Bin.getBinInformation(bin, acceptedMaterials, commonlyMisdisposedItems, type, status, lastStatusUpdate)
sync createAchievement(user: User, name: String, requirements: set Score, type: “User” | “Community”, out: achievement: Achievement)
AccessList.getUserAccess(user, access)
Achievement.createAchievement(name, requirements, type, achievement)
sync deleteAchievement(user: User, achievement: Achievement)
AccessList.getUserAccess(user, access)
Achievement.deleteAchievement(achievement)
sync removeUserAchievement(user: User, user2: User, achievement: Achievement)
AccessList.getUserAccess(user, access)
Achievement.removeUserAchievement(user2, achievement)
sync removeCommunityAchievement(user: User, community: Community, achievement: Achievement)
AccessList.getUserAccess(user, access)
Achievement.removeCommunityAchievement(community, achievement)
sync recycleAtBin(session: Session, bin: Bin, out: achievementsEarned: set Achievement)
Session.getUser(session, user)
Bin.getBinType(bin, type)
Score.getScore(user, type, score)
Score.setScore(user, type, score + 1, newScore)
Achievement.getNewUserAchievementsEarned(user, newAchievements)
Achievement.addUserAchievements(user, newAchievements, userAchievementsEarned)
Community.getCommunitiesByUser(user, communities)
Achievement.getNewCommunityAchievementsEarned(community, communityAchievements)
Achievement.addCommunityAchievements(community, communityAchievements, communityAchievementsEarned)
sync recycleMaterial(session: Session, material: Material)
Session.getUser(session, user)
Material.getMaterialType(material, type)
Score.getScore(user, type, score)
Score.setScore(user, type, score + 1, newScore)
Achievement.getNewUserAchievementsEarned(user, newAchievements)
Achievement.addUserAchievements(user, newAchievements, userAchievementsEarned)
Community.getCommunitiesByUser(user, communities)
Achievement.getNewCommunityAchievementsEarned(community, communityAchievements)
Achievement.addCommunityAchievements(community, communityAchievements, communityAchievementsEarned)
sync createCommunity(user: User, name: String, out: community: Community)
AccessList.getUserAccess(user, access)
Community.createCommunity(name, community)
sync deleteCommunity(user: User, name: String, out: community: Community)
AccessList.getUserAccess(user, access)
Community.deleteCommunity(name)
sync getAllMembersInCommunity(community: String, out: members; set User)
Community.getAllMembersInCommunity(community, members)
sync addUserToCommunity(session: Session, communityName: String)
Session.getUser(session, user)
Community.addUserToCommunity(user, communityName)
sync removeUserFromCommunity(session: Session, user2: User, communityName: String)
Session.getUser(session, user)
Dependency Diagram
Wireframes
Here is a link to our Figma wireframes: https://www.figma.com/file/SY8pwlYwSjEkIm32CZoSz5/[6.170]-Final-Project?type=design&node-id=0%3A1&mode=design&t=Or1kUAKhAPqymVZK-1.
The prototype can be viewed in the window below:
From the site's landing page, users can immediately begin searching for recycling information on certain items (and even view a fun fact!).
The search page allows users to search our database by name and query.
After clicking on a database entry, users can find more information on the material that they have found.
The map will have markers showing the locations of neaby disposal bins. Clicking on a marker will open up a panel with more information on what can be recycled in said bin along with its last reported capacity.
From their dashboard, users can view their recycling metrics alongside any badges that they have earned for their recycling habits.
Heuristic Evaluation
Usability Criteria
Learnability: Since learning to use a new application for disposing waste may seem like a nuisance for many users, we made our app’s most essential tool, the search bar, the easiest to find. Having the largest font on the home page, our search bar requires almost no effort to locate. Using the search bar immediately directs the user to a search page that only contains the search bar, other methods of searching, and the search results. With one more click, a user can find more information about a materia/item or start locating a disposal bin, each with their own dedicated pages. We use text to clearly indicate to the user what actions can be taken. We decided to have an expandable navigation bar rather than one with all the options constantly present at the top of the page to prevent cluttering as much as possible. Since most users these days are aware of expandable navigation bars, we think this is a good tradeoff to take. By having the most powerful tool readily available and having clear guides towards next steps, we believe our app’s interface can be rapidly and easily learned.
Efficiency: We believe our interface can be used very quickly and efficiently for reasons similar to why we believe it is very learnable, and to make it even more efficient, our app includes suggested and related content/functionalities based on users’ actions. Firstly, as mentioned before, the essential search bar is readily available, and the flow to finding key information, e.g. bin locations, is very short, with user guidance at each step. Our home page displays commonly misrecycled items and commonly searched items, anticipating our users’ needs, possibly saving them from typing and just clicking once to search. Similarly, when looking for more information on an item, related items are displayed, possibly saving them from having to constantly return back to the search results. We hope to even include a list of items recently searched by the user themselves, perhaps on the home page, in case a user needs to remember how to dispose of an item again.
Physical Heuristics
Situational Context: All of our pages (except the leaderboard) do not display their names. Instead, our interface conveys to the user where they are on our site based on the functionalities displayed. For example, the page with the map does not have a title, but the only component on it is the map and the map’s functionalities. We think that this helps the user quickly depict what the main purpose of the page is, and therefore it might not need a name for efficient navigation, while keeping the text at a minimum. On our search page, the search icon and bar serve as the “page name”, since they give a clear idea on what the page is about. Despite this, we will certainly consider displaying page names if it does speed up navigation since we want as much as possible for the user to not feel that our app is too inconvenient for sustainability. On pages displaying information about a certain material/item, we can add leftward arrows that bring back the user to the search results page so that they do not have to expand the navigation bar and search their material again.
Fitt’s Law: The essential elements on our pages are all very large, making them easily reachable by users. The search bar on the home page and the search page is huge and is located at the top center of those pages. The user needs only the slightest bit of accuracy to click it, and they can always count on finding it at the top of the page. For the search results, we currently have users click the name of the item to navigate to its information page, but it is probably better to allow the entire block containing the image and option to see disposal locations to be clickable and navigate the user to the corresponding page. For the navigation bar, making it vertically longer, perhaps taking up the whole page, might be a better option, as well as using borders or different colors to clearly mark the area of each tile and to which destination it corresponds to.
Linguistic Level
Consistency: For the most part, our interface uses icons and names consistent with users’ expectations of websites. In the navigation bar, all the destinations have icons and names that are prevalent on the web. To show users can go further into a particular list or item, we have the familiar SEE [X] followed by an arrow, such as under the “Commonly Misrecycled Items” section on the homepage, or on the search results page. We use the reuse, reduce, recycle sign to show that an item is recyclable, staying consistent with the field of waste disposal. There is some room for improvement when it comes to the button that says “Log Recycle” present on an item’s information page and on the map. Firstly, not all items can be recycled, so we need to either use a more general word, perhaps like “Disposal”, or adjust for each type of item. Secondly, recycle is commonly used as a verb, so “Log Recycle” can be a little difficult to comprehend. Lastly, adding an icon, perhaps one that implies writing something down on a list, can make it more clear to the user that clicking the button will allow them to add their disposal of the item to their personal log (if they are logged in).
Information Scent: Our interface uses icons, text, and direction to guide our user towards the information they want to look up. On the homepage, our search bar lacks a search icon but instead leaves a sentence to be completed by the user, showing example search prompts to imply to the user to fill in the question and begin their search. We describe an action in text and include a rightward arrow next to it in the search page and home page to signify that these actions propel the user forward into their search. We believe our interface does a great job with concise naming of actions and mapping them to pages/effects that are expected. Like mentioned above, we can improve on how we present the “Log Recycle” button to be informative and clear to the user about what that action does.
Visual Design Study
Color Design Study
Typography Design Study
Project Plan
Our concepts are listed below in order of dependency and importance to the core functionality of our application:
- Session
- Backend implementation: A direct copy/paste from the starter code—no additions need to be made. (login/logout/register syncs) - LJ (EOD Nov 27th)
- Frontend implementation - Diego (Nov 30)
- Navbar/menu
- login/register form
- User
- Backend implementation: After copying/pasting from the starter code, incorporate “permissions” (admin, org, none) into the state of User - LJ (EOD Nov 27th)
- Frontend implementation - Grace (Nov 30)
- Settings Page (updating username/password)
- Dashboard (only name & title)
- Material - Dana + LJ
- Backend implementation - (Nov 28)
- Populate with realistic data - (Nov 28)
- Frontend implementation - (Nov 30)
- Material Page (Disposal locations & log recycle button do not have to be working yet)
- Search Page
- Fact - Grace + Diego
- Backend implementation - Grace (Nov 28)
- Populate with realistic data - Grace (Nov 28)
- Frontend implementation - Diego (Nov 30)
- Fact component on landing page
- Bin - LJ (Dec 4)
- Backend implementation
- Populate with realistic data (must be done after 3b)
- Frontend implementation (bin component on map page)
- Map
- Backend implementation - Dana (Dec 3)
- Populate with realistic data (must be done after 4b) - Dana (Dec 3)
- Frontend implementation (finish map page) - LJ (Dec 7)
- Score - Diego
- Backend implementation (Dec 3)
- Frontend implementation (Dec 7)
- User metrics on Dashboard
- Global metrics on landing page
- Achievement
- Backend implementation - Grace (Dec 4)
- Populate with realistic achievements - Grace (Dec 4)
- Frontend implementation - Diego (Dec 7)
- Community - Dana (Dec 7)
- Backend implementation
- Populate with realistic data
- Frontend implementation
- Chips on dashboard page
- Syncs with Map
- AccessList - Diego (Dec 7)
- Backend implementation
- Frontend implementation
- UserSettings - Dana (Dec 7)
- Backend Implementation
- Frontend Implementation
- User Testing - Everyone
- Find and plan interviews - Everyone (Dec 9)
- Write up - Grace and Diego (Dec 10)
- Review and Revise - Everyone (Dec 12)
- Final Review - Everyone (Dec 13)
Our goal is to have the first four concepts on this list (User, Session, Material, and Fact) completed end-to-end by the alpha release. Should certain concepts take more time than expected, or we otherwise find ourselves low on time, we’ll limit the scope of our application by excluding the concepts listed later on the list. Specifically, we will first start by removing the AccessList and UserSettings concepts, then the Community concept and all features that depend on it such as community type achievements. If after redistributing tasks, we find that we still do not have enough time, we will forgo the Achievement concept and put more emphasis into the score concept to still gamify our app and promote its use amongst users.