Project 3: Convergent Design
Table of Contents:
Functional design
Concepts
User
Purpose: Authenticate users and store eligibility data for clients
Operational Principle: After a user registers with a username and password, they can authenticate as that user by providing a matching username and password, and save information to check eligibility for food pantries
States:
users: set User
username, password, type: User → one String // type is client or administrator
information?: User → set (String → String) // maps any extra information fields needed for eligibility to values (differs by food bank, e.g. household size)
Actions:
register(username: String, password: String, type: String, information?: set (String → String), out u: User)
if there is no user already associated with the username, create new
user(username, password, type, information)
authenticate(username: String, password: String, out u: User)
if there is a user in users where user.username is username and
user.password is password, return that user, otherwise throw an Error
update(user: User, update: { username?: String, password?: String, type?: String, information?: set (String → String) })
if username is declared and there is no profile already associated with
the username, for each property declared on the update object, update
this property on the given user, and return this user
delete(user: User)
remove this user from users
User
Purpose: Authenticate users and store eligibility data for clients
Operational Principle: After a user registers with a username and password, they can authenticate as that user by providing a matching username and password, and save information to check eligibility for food pantries
States:
users: set User
username, password, type: User → one String // type is client or administrator
information?: User → set (String → String) // maps any extra information fields needed for eligibility to values (differs by food bank, e.g. household size)
Actions:
register(username: String, password: String, type: String, information?: set (String → String), out u: User)
if there is no user already associated with the username, create new
user(username, password, type, information)
authenticate(username: String, password: String, out u: User)
if there is a user in users where user.username is username and
user.password is password, return that user, otherwise throw an Error
update(user: User, update: { username?: String, password?: String, type?: String, information?: set (String → String) })
if username is declared and there is no profile already associated with
the username, for each property declared on the update object, update
this property on the given user, and return this user
delete(user: User)
remove this user from users
Profile[User (Generic)]
Purpose: Offer basic information about a food pantry, including eligibility requirements
Operational Principle: Store and retrieve by location and eligibility
States:
profiles: set Profile
administrator: Profile → one User
location: Profile → one String
name: Profile → one String
rules: Profile → set (User → Bool)
times: Profile → set Time
windowTime: Profile -> Number
Actions:
isEligible(user: User, result: Bool)
for rule in rules, if !rule(user), return False, otherwise return True
create(administrator: User, location: String, name: String, rules: set (User → Bool), times: set Time, windowTime: Number, out p: Profile)
if there is no profile already associated with the administrator, create new
profile(administrator, location, name, rules, times, windowTime), and
return this profile
read(query: {id?: ObjectId, administrator?: User, location?: String, name?: String, rules?: set (User → Bool), times?: set Time, windowTime?: Number}, out p: set profiles)
return all profiles in profiles that satisfy the fields in query
update(profile: Profile, update: { administrator?: User, location?: String, name?: String, rules?: set (User → Bool), times?: set Time, windowTime?: Number }, out p: Profile)
if administrator is declared and there is no profile already associated with
the administrator, for each property declared on the update object, update
this property on the given profile, and return this profile
delete(p: Profile)
remove this profile from profiles
Profile[User (Generic)]
Purpose: Offer basic information about a food pantry, including eligibility requirements
Operational Principle: Store and retrieve by location and eligibility
States:
profiles: set Profile
administrator: Profile → one User
location: Profile → one String
name: Profile → one String
rules: Profile → set (User → Bool)
times: Profile → set Time
windowTime: Profile -> Number
Actions:
isEligible(user: User, result: Bool)
for rule in rules, if !rule(user), return False, otherwise return True
create(administrator: User, location: String, name: String, rules: set (User → Bool), times: set Time, windowTime: Number, out p: Profile)
if there is no profile already associated with the administrator, create new
profile(administrator, location, name, rules, times, windowTime), and
return this profile
read(query: {id?: ObjectId, administrator?: User, location?: String, name?: String, rules?: set (User → Bool), times?: set Time, windowTime?: Number}, out p: set profiles)
return all profiles in profiles that satisfy the fields in query
update(profile: Profile, update: { administrator?: User, location?: String, name?: String, rules?: set (User → Bool), times?: set Time, windowTime?: Number }, out p: Profile)
if administrator is declared and there is no profile already associated with
the administrator, for each property declared on the update object, update
this property on the given profile, and return this profile
delete(p: Profile)
remove this profile from profiles
Expiring Item
Purpose: Stores relevant information for a particular item and controls item availability
Operational Principle: When the item reaches its accessibility date, it is available for users to claim, and once an item has expired, it is not available for users to claim
States:
expiring items: set Expiring Item
barcode: Expiring Item → one String
dropDate: Expiring Item → one Time
expirationDate: Expiring Item → one Time
Actions:
create(barcode: String, dropDate: Time, expirationDate: Time, out i: Expiring Item)
if expirationDate is before dropDate, throw an Error, otherwise
create new expiringItem(barcode, dropDate, expirationDate), and return
this expiring item
read(query: {id?: ObjectId, barcode?: String, dropDate?: Time, expirationDate?: Time}, out i : set Expiring Item)
return all expiring items in expiring items that satisfy the fields in query
update(expiringItem: Expiring Item, update: { barcode?: String, dropDate?: Time, expirationDate?: Time }, out i: Expiring Item)
if expirationDate is before dropDate, throw an Error, otherwise
for each property declared on the update object, update this property on
the given expiring item, and return this expiring item
delete(expiringItem: Expiring Item)
remove this expiring item from expiring items
Expiring Item
Purpose: Stores relevant information for a particular item and controls item availability
Operational Principle: When the item reaches its accessibility date, it is available for users to claim, and once an item has expired, it is not available for users to claim
States:
expiring items: set Expiring Item
barcode: Expiring Item → one String
dropDate: Expiring Item → one Time
expirationDate: Expiring Item → one Time
Actions:
create(barcode: String, dropDate: Time, expirationDate: Time, out i: Expiring Item)
if expirationDate is before dropDate, throw an Error, otherwise
create new expiringItem(barcode, dropDate, expirationDate), and return
this expiring item
read(query: {id?: ObjectId, barcode?: String, dropDate?: Time, expirationDate?: Time}, out i : set Expiring Item)
return all expiring items in expiring items that satisfy the fields in query
update(expiringItem: Expiring Item, update: { barcode?: String, dropDate?: Time, expirationDate?: Time }, out i: Expiring Item)
if expirationDate is before dropDate, throw an Error, otherwise
for each property declared on the update object, update this property on
the given expiring item, and return this expiring item
delete(expiringItem: Expiring Item)
remove this expiring item from expiring items
Map[Point of Interest (Generic)]
Purpose: Show nearby points of interest
Operational Principle: After a point of interest is registered, it will appear on the map if near current location
States:
maps: set Map
locations: Location → set Point of Interest
Actions:
create(locations: set Point of Interest, out m: Map)
create new map(locations), and return this map
read(id: ObjectId)
return the map in maps where map.id is id
update(map: Map, update: { locations?: set Point of Interest }, out m: Map)
for each property on the update object, update this property on the given
map, and return the map
delete(map: Map)
remove this map from maps
Map[Point of Interest (Generic)]
Purpose: Show nearby points of interest
Operational Principle: After a point of interest is registered, it will appear on the map if near current location
States:
maps: set Map
locations: Location → set Point of Interest
Actions:
create(locations: set Point of Interest, out m: Map)
create new map(locations), and return this map
read(id: ObjectId)
return the map in maps where map.id is id
update(map: Map, update: { locations?: set Point of Interest }, out m: Map)
for each property on the update object, update this property on the given
map, and return the map
delete(map: Map)
remove this map from maps
Order[User (Generic), Item (Generic)]
Purpose: Reserve items to obtain sometime in the future
Operational Principle: When a user wants to order items from an owner, they can place an order to pick up at some time in the future, and the owner can reject or fulfill the order
States:
orders: set Order
sender: Order → one User
recipient: Order → one User
items: Order → set Item
status: Order → one String
status ∈ [ placed, packed, picked up ]
pickup: Order → one Time
Actions:
create(sender: User, recipient: User, items: set Item, pickup: Time, out o: Order)
create new order(sender, recipient, items, placed, pickup), return this
order
read(query: {id?: ObjectId, sender?: User, recipient?: User, pickup?: Time, items?: set Item}, out i : set Order)
return all orders in orders that satisfy the fields in query
update(order: Order, update: { status?: String }, out o: Order)
for each property on the update object, update this property on the given
order, and return this order
delete(order: Order)
remove this order from orders
Order[User (Generic), Item (Generic)]
Purpose: Reserve items to obtain sometime in the future
Operational Principle: When a user wants to order items from an owner, they can place an order to pick up at some time in the future, and the owner can reject or fulfill the order
States:
orders: set Order
sender: Order → one User
recipient: Order → one User
items: Order → set Item
status: Order → one String
status ∈ [ placed, packed, picked up ]
pickup: Order → one Time
Actions:
create(sender: User, recipient: User, items: set Item, pickup: Time, out o: Order)
create new order(sender, recipient, items, placed, pickup), return this
order
read(query: {id?: ObjectId, sender?: User, recipient?: User, pickup?: Time, items?: set Item}, out i : set Order)
return all orders in orders that satisfy the fields in query
update(order: Order, update: { status?: String }, out o: Order)
for each property on the update object, update this property on the given
order, and return this order
delete(order: Order)
remove this order from orders
Inventory[User (Generic), Expiring Item]
Purpose: Tracks items for a given owner and provides analytics on item stock changes
Operational Principle: Users can add items to the inventory to indicate that that item is available, and when users want to remove an item with a specific property, the inventory chooses which item to remove by filtering additional properties
States:
inventories: set Inventory
administrator: Inventory → one User
items: Inventory → set (status: String, item: Item)
status ∈ [ unreleased, claimable, ordered, used, expired ]
Actions:
create(administrator: User, items: set (status: String, item: Item), out i: Inventory)
if there is no inventory already associated with the administrator, create
new inventory(administrator, items), and return this inventory
read(query: {id?: ObjectId, administrator?: User}, out i: set Inventory)
return all inventories in inventories that satisfy the fields in query
update(inventory: Inventory, update: { administrator?: user, items?: set (status: String, item: Item)}, out i: Inventory)
if administrator is declared on the update object and there is already an
inventory associated with this administrator, throw an Error
for each property declared on the update object, update this property on
the given inventory, and return this inventory
delete(inventory: Inventory)
remove this inventory from inventories
Inventory[User (Generic), Expiring Item]
Purpose: Tracks items for a given owner and provides analytics on item stock changes
Operational Principle: Users can add items to the inventory to indicate that that item is available, and when users want to remove an item with a specific property, the inventory chooses which item to remove by filtering additional properties
States:
inventories: set Inventory
administrator: Inventory → one User
items: Inventory → set (status: String, item: Item)
status ∈ [ unreleased, claimable, ordered, used, expired ]
Actions:
create(administrator: User, items: set (status: String, item: Item), out i: Inventory)
if there is no inventory already associated with the administrator, create
new inventory(administrator, items), and return this inventory
read(query: {id?: ObjectId, administrator?: User}, out i: set Inventory)
return all inventories in inventories that satisfy the fields in query
update(inventory: Inventory, update: { administrator?: user, items?: set (status: String, item: Item)}, out i: Inventory)
if administrator is declared on the update object and there is already an
inventory associated with this administrator, throw an Error
for each property declared on the update object, update this property on
the given inventory, and return this inventory
delete(inventory: Inventory)
remove this inventory from inventories
Request[User (Generic), Item (Generic)]
Purpose: Express interest in an item
Operational Principle: When a user wants an item that is not accessible from an specific owner, the user can send a request to the owner, and the owner can see the request
States:
requests: set Request
item: Request → set Item
requester: Request → one User
requestee: Request → one User
Actions:
create(requester: User, requestee: User, item: Item, out r: Request)
create new request(item, requester, requestee), return this request
read(query: {id?: ObjectId, requester?: User, requestee?: User}, out r: set Request)
return all requests in requests that satisfy the fields in query
update(request: Request, update: { item: Item }, out r: Request)
for each property declared on the update object, update this property on
the given request, and return this request
delete(request: Request)
remove request from requests
Request[User (Generic), Item (Generic)]
Purpose: Express interest in an item
Operational Principle: When a user wants an item that is not accessible from an specific owner, the user can send a request to the owner, and the owner can see the request
States:
requests: set Request
item: Request → set Item
requester: Request → one User
requestee: Request → one User
Actions:
create(requester: User, requestee: User, item: Item, out r: Request)
create new request(item, requester, requestee), return this request
read(query: {id?: ObjectId, requester?: User, requestee?: User}, out r: set Request)
return all requests in requests that satisfy the fields in query
update(request: Request, update: { item: Item }, out r: Request)
for each property declared on the update object, update this property on
the given request, and return this request
delete(request: Request)
remove request from requests
Synchronizations
Notes: Our User concept also contains a type state, which can be either client or administrator. Only clients can place orders, and only administrators can manage profiles and inventories. We assign one administrator account per food pantry. Each administrator account has exactly one profile and exactly one inventory.
app
include User
include Profile[User.User]
include Expiring Item
include Map[Profile.Profile.Location]
include Order[User.User, Expiring Item.Expiring Item]
include Inventory[User.User, Expiring Item.Expiring Item]
include Request[User.User, Expiring Item.Expiring Item]
app
include User
include Profile[User.User]
include Expiring Item
include Map[Profile.Profile.Location]
include Order[User.User, Expiring Item.Expiring Item]
include Inventory[User.User, Expiring Item.Expiring Item]
include Request[User.User, Expiring Item.Expiring Item]
getPantryMap(user: User)
make a new set called locations
for profile in profiles, if user satisfies profile.rules, add profile.location to locations
create new map(locations), and return this map
getPantryMap(user: User)
make a new set called locations
for profile in profiles, if user satisfies profile.rules, add profile.location to locations
create new map(locations), and return this map
createRequest(user: User, profile: Profile, barcode: String)
if !profile.isEligible(user), throw an Error
create new expiringItem(item: barcode, dropDate: null, expirationDate: null)
create new request(user, profile.administrator, expiringItem)
createRequest(user: User, profile: Profile, barcode: String)
if !profile.isEligible(user), throw an Error
create new expiringItem(item: barcode, dropDate: null, expirationDate: null)
create new request(user, profile.administrator, expiringItem)
getAnalytics(profile: Profile, out a: { name: String, data: set Item })
get the inventory with the same administrator as the profile
make a new set called expired by finding all items in inventory.items where the tag is
expired and the item.item.expirationDate is in the last month
make a new set called sold by finding all items in inventory.items where the tag is
used and the object’s last update was in the last month
make a new set called ordered by finding all items in inventory.items where the tag is
ordered and the object’s last update was in the last month
make a new set called requests by finding all items in requests where the request was
created in the past month
getAnalytics(profile: Profile, out a: { name: String, data: set Item })
get the inventory with the same administrator as the profile
make a new set called expired by finding all items in inventory.items where the tag is
expired and the item.item.expirationDate is in the last month
make a new set called sold by finding all items in inventory.items where the tag is
used and the object’s last update was in the last month
make a new set called ordered by finding all items in inventory.items where the tag is
ordered and the object’s last update was in the last month
make a new set called requests by finding all items in requests where the request was
created in the past month
availableTimes(profile: Profile, out t: set Time)
populate a set with profile.times
for each order where order.administrator is profile.administrator, remove order.pickup
from the set
return the set of times
availableTimes(profile: Profile, out t: set Time)
populate a set with profile.times
for each order where order.administrator is profile.administrator, remove order.pickup
from the set
return the set of times
orderableBarcodesAndQuantities(profile: Profile, out b: set (barcode: String, quantity: Number))
get the inventory with the same administrator as the profile
make an empty map
for item in inventory.items with the tag claimable, if item.item.barcode is not already in
the map, set map[item.item.barcode] = 1, otherwise increment map[item.item.barcode]
return the map of barcodes to quantities
orderableBarcodesAndQuantities(profile: Profile, out b: set (barcode: String, quantity: Number))
get the inventory with the same administrator as the profile
make an empty map
for item in inventory.items with the tag claimable, if item.item.barcode is not already in
the map, set map[item.item.barcode] = 1, otherwise increment map[item.item.barcode]
return the map of barcodes to quantities
placeOrder(profile: Profile, user: User, pickup: Time, barcodes: set (barcode: String, quantity: Number), out o: Order)
if !profile.isEligible(user), throw an Error
if pickup is not in availableTimes(profile), throw an Error
for barcode in barcodes:
if barcodes not in orderableBarcodesAndQuantities(profile), throw an Error
create new empty set items, and get the inventory with administrator profile.administrator
for barcode in barcodes:
get the number of items requested that have this barcode and are claimable in
inventory by sorting by soonest expiration after the pickup time, and add them to items
also, in the inventory, update these items from claimable to ordered
create new order(user, profile.administrator, items, pickup), return this order
placeOrder(profile: Profile, user: User, pickup: Time, barcodes: set (barcode: String, quantity: Number), out o: Order)
if !profile.isEligible(user), throw an Error
if pickup is not in availableTimes(profile), throw an Error
for barcode in barcodes:
if barcodes not in orderableBarcodesAndQuantities(profile), throw an Error
create new empty set items, and get the inventory with administrator profile.administrator
for barcode in barcodes:
get the number of items requested that have this barcode and are claimable in
inventory by sorting by soonest expiration after the pickup time, and add them to items
also, in the inventory, update these items from claimable to ordered
create new order(user, profile.administrator, items, pickup), return this order
updateOrder(user: User, order: Order, newStatus: String, out o: Order)
if order.recipient is not user, throw an Error
find the inventory where inventory.administrator is user
if newStatus is placed and order.status is picked up:
for item in order.items, find the item in the inventory and set its status to ordered
if newStatus is packed and order.status is picked up:
for item in order.items. find the item in the inventory and set its status to ordered
if newStatus is picked up:
for item in order.items: find the item in the inventory and set its status to used
set order.status to newStatus, return order
updateOrder(user: User, order: Order, newStatus: String, out o: Order)
if order.recipient is not user, throw an Error
find the inventory where inventory.administrator is user
if newStatus is placed and order.status is picked up:
for item in order.items, find the item in the inventory and set its status to ordered
if newStatus is packed and order.status is picked up:
for item in order.items. find the item in the inventory and set its status to ordered
if newStatus is picked up:
for item in order.items: find the item in the inventory and set its status to used
set order.status to newStatus, return order
deleteOrder(user: User, order: Order)
if order.recipient is not user, throw an Error
find the inventory where inventory.administrator is user
if order.status is picked up, throw an Error
for item in order.items, find the item in the inventory and set its status to claimable
delete the order
deleteOrder(user: User, order: Order)
if order.recipient is not user, throw an Error
find the inventory where inventory.administrator is user
if order.status is picked up, throw an Error
for item in order.items, find the item in the inventory and set its status to claimable
delete the order
addItem(user: User, inventory: Inventory, barcode: String, dropDate: Time, expirationDate: Time, out i: Inventory)
if inventory.administrator is not the user, throw an Error
create new expiringitem(barcode, dropDate, expirationDate)
add { status: unreleased, item: item } to the inventory, and call organizeInventory
addItem(user: User, inventory: Inventory, barcode: String, dropDate: Time, expirationDate: Time, out i: Inventory)
if inventory.administrator is not the user, throw an Error
create new expiringitem(barcode, dropDate, expirationDate)
add { status: unreleased, item: item } to the inventory, and call organizeInventory
removeItem(user: User, inventory: Inventory, item: Expiring Item)
if inventory.administrator is not the user, throw an Error
for order in orders where order.recipient is user:
if item is in order.items:
if there is an item in inventory that is claimable with the same barcode,
choose the one with the soonest expiration date, and replace the item in
order.items with the new item, and mark it as ordered in inventory
otherwise, decrement the quantity for the corresponding barcode
remove the item from the inventory
delete the item from items
removeItem(user: User, inventory: Inventory, item: Expiring Item)
if inventory.administrator is not the user, throw an Error
for order in orders where order.recipient is user:
if item is in order.items:
if there is an item in inventory that is claimable with the same barcode,
choose the one with the soonest expiration date, and replace the item in
order.items with the new item, and mark it as ordered in inventory
otherwise, decrement the quantity for the corresponding barcode
remove the item from the inventory
delete the item from items
system organizeInventory(inventory: Inventory, out i: Inventory)
// called on mount
for item in inventory.items,
if item.item.dropDate is before today, set item status to unreleased
else if item.item is not in any orders and item.item.expirationDate is after today,
set item status to claimable
else if item.item is in an order and the order status is not picked up, set item
status to ordered
else if item.item is in an order and the order status is picked up, set item status to used
else if item.item.expirationDate is today or earlier, set item status to expired
system organizeInventory(inventory: Inventory, out i: Inventory)
// called on mount
for item in inventory.items,
if item.item.dropDate is before today, set item status to unreleased
else if item.item is not in any orders and item.item.expirationDate is after today,
set item status to claimable
else if item.item is in an order and the order status is not picked up, set item
status to ordered
else if item.item is in an order and the order status is picked up, set item status to used
else if item.item.expirationDate is today or earlier, set item status to expired
Dependency diagram
Wireframes
The wireframes were created in Figma, and can be viewed through the embedded windows below.
Heuristic evaluation
Usability Criteria
- Error tolerance: We realized that in initial designs, there was no interface action for administrative users to recover from accidentally marking an order as picked up when it was still in progress. To address this issue, we changed the interface so that fulfilled orders remain visible, so that users can easily fix any mistakes they made when changing order status. Although we addressed this issue in our interface, we realized that another issue occurs in the order inbox that we did not address. If a pantry administrator rejects an order, there is currently no way to retrieve it in the interface. To address this, we could use a two-step confirmation process that requires administrators to confirm their rejection decision with a second prompt. We do not want users to be able to un-reject the order at any time, because by the time the order is un-rejected, the user who placed the order may have decided to utilize another food pantry instead.
- Accessibility: Our interface is designed without audio, which reduces any accessibility concerns for those with auditory disabilities. Including images of each food item in both the administrator and client interfaces prevents confusion for users who may speak a different language or have trouble reading. We also plan to include a Google Translate button to help users who do not speak English navigate the site efficiently. However, our current design does not address potential access issues for blind users. To enhance inclusivity, we could suggest that those users call the food pantry to place orders, and the pantries could have a way to add orders placed via telephone into their queues.
Physical Heuristics
- Situational context: For administrator users, there are three main pages that are accessible: the inventory page, the order page, the profile page. The user can access any of these pages through the navigation bar. The navigation bar underlines a given page when the user is on that page. To further improve the navigation, particularly for those who do not speak English, we could add icons next to each link in the navigation bar to indicate the type of page that following the link will take the user to.
- Fitts’ Law: Actions that need to be easily accessible (i.e. placing an order, adding/editing an inventory item, navigation) are located towards the top of the page, near the menu bar. This means that users can easily and quickly reach for interface elements, since they do not need to scroll to perform essential actions.
Linguistic Heuristics
- Speak a user’s language: To enhance inclusivity, particularly on the client’s side, particularly for non-native English speakers, we’ve added pictures for each food item in our interface. Administrative users are guided through actions with prompts that involve choosing from dropdowns or buttons so they are aware of possible actions that they can take. We also decided to use barcodes to categorize items, so that there are no potential misspellings of food names (which we will fetch from an API), and these names can be translated into other languages. In addition, to make sure users are aware of why they are eligible for specific food banks, we remind them of any eligibility information they have entered while they are on the page that displays accessible food pantries near them.
- Consistency: The interface uses a table format to track inventory, which is consistent with other inventory management systems. Similarly, we have designed the ordering system to be similar to the interfaces in Doordash and Uber Eats where users can select a quantity of a given item, and food items are displayed next to images, so that the ordering system will be familiar to users who may not have used a food pantry app previously.
Visual design study
Implementation plan
Due by 11/30: User
- Due by 11/27: Backend implementation - Luca
- Due by 11/28: Frontend implementation - Hannah
Due by 11/30: Expiring Item
- Due by 11/27: Backend implementation - Olivia
- Due by 11/28: Frontend implementation - Eghosa
Due by 11/30: Profile
- Due by 11/28: Backend implementation - Luca
- Due by 11/29: Frontend implementation - Hannah
Due by 11/30: Inventory
- Due by 11/28: Backend implementation - Olivia and Eghosa
- Due by 11/28: orderableBarcodesAndQuantities synchronization - Olivia
- Due by 11/28: addItem synchronization - Olivia
- Due by 11/30: Frontend implementation - Eghosa
Due by 12/7: Order
- Due by 12/4: Backend implementation - Luca
- Due by 12/4: availableTimes synchronization - Luca
- Due by 12/4: placeOrder synchronization - Luca
- Due by 12/4: updateOrder synchronization - Luca
- Due by 12/4: deleteOrder synchronization - Luca
- Due by 12/4: removeItem synchronization - Luca
- Due by 12/4: organizeInventory synchronization - Luca
- Due by 12/6: Frontend implementation - Hannah
Due by 12/7: Request
- Due by 12/4: Backend implementation - Olivia
- Due by 12/4: getAnalytics synchronization - Olivia
- Due by 12/4: createRequest synchronization - Olivia
- Due by 12/6: Frontend implementation - Eghosa
Due by 12/7: Map
- Due by 12/4: Backend implementation - Olivia
- Due by 12/4: getPantryMap synchronization - Olivia
- Due by 12/6: Frontend implementation - Eghosa
If something goes wrong, the concept implementations that are least important to the overall functionality of the app are request and map. If we run out of time, we can remove request without any major loss of functionality. The only thing that will change is that users will not be able to make requests for out-of-stock items. We anticipate that the map may be hard to implement in the frontend with geolocation and the Mapbox API. If there is an issue implementing the map feature, we can replace the map with a list of eligible pantries that users can search through. We also anticipate that there may be potential issues with using the Open Food API to fetch images and nutritional data for our items, which are tagged by barcode. If this implementation becomes untenable, we will change the data input type in the Expiring Item concept from a barcode to the item name (such as banana), and will implement the app without any additional information such as nutritional facts on the frontend.