Skip to content

P3: Functional Design

Concepts

User

Purpose: authenticate accounts

OP: after a someone registers using a username and password, they are able to authenticate as that user anytime afterwards by providing the username and password pair

States:

  • accounts: set User
  • username, password: User → one String

Actions:

  • register (name, pwd: string, out u: User)
    If there are no accounts with username = name, instantiate an account with (username, password) = (name, pwd) and add it to accounts
  • authenticate (name, pwd: string out u: User)
    Return the account where username = name and password = pwd
  • deleteUser(name, pwd: string)
    Remove user from accounts where (username, password) = (name, pwd)

Session[User]

Purpose: allows user to be authenticated for longer period of time

OP: when a user is authenticated, the information related to the user account is retrieved and maintained until the user ends the session

States:

  • active: set Session
  • user: Session → one User

Actions:

  • start(u: User, out ses:Session)
    If the user is not in the set of active sessions, make a new session with user = u and add it to the active set before returning the session
  • getUser(ses: Session, out u: User)
    Look through active and if there is a session = ses, return session.user
  • end(ses: Session)
    Remove ses from the set of active

Membership[User,Team]

Purpose: keeps track of the organizations that an individual belongs to

OP: When a user becomes part of an organization, that organization gets added to the user's set of memberships

States:

  • allMemberships: set Membership
  • user: Membership → one User
  • orgs: Membership → set Team

Actions:

  • createMembership(u: User, out m: Membership)
    Generate an id i, create a Membership m = (i, u, {}), and add m to allMemberships
  • getMembership(u: User, out m: set Team)
    Look for user = u and return m.orgs
  • editMembership(u: User, o: set Teams)
    Find Membership with id = i and set orgs = o
  • deleteMembership(id: string)
    Delete the Membership with id = id from allMemberships

Team[User]

Purpose: allows association of a group of individuals as a single entity, wherein certain members (admins) possess authority of creating new member accounts

OP: After a group is created, only admins can add new members, promote a member to admin status, or remove other members from the group

States:

  • allTeams: set Team
  • name: Team → one string
  • admins: Team → set User
  • members: Team → set User

Actions:

  • createTeam(u: User, n: String, out t: Team)
    Generate an id i, create a Team t with (id, name, admin, members) = (i, n, {u},{}), and add t to allTeams
  • getTeam(id: string, out t: Team)
    Return team t with id = id
  • addMember(id: string, u: User, editor: User)
    Find the team with id = id in allTeams and if the editor is in the set of admins in the team,add u to the set of members
  • promoteMember(id: string, u: User, editor: User)
    Find the team with id =id in allTeams, and if the editor is in the set of admins in the team, remove u from the team's members set, and add u to the set of admins
  • demoteMember(id: string, u: User, editor: User)
    Find the team with id = id in allTeams, and if the editor is in the set of admins in the team, remove u from the team's admins set, and add u to the set of members
  • removeMember(id: string, u: User, editor: User)
    Find the team with id = id in allTeams, and if the editor is in the set of admins in the team, remove u to the set of members if it exists, and remove u from the set of admins if it exists
  • deleteTeam(id: string, editor: User)
    Delete the team with id = id from allTeams if editor is in the set of admins in the team

Patron

Purpose: offers individual patron information such as name, date of birth and photo identification

OP: Patron is stored on site and when retrieved by id, the information is displayed

States:

  • allPatrons: set Patron
  • name: Patron → one string
  • birthday: Patron → one Date
  • image: Patron → one Image

Actions:

  • createPatron(n: string, birthday: Date, img: Image, out p: Patron)
    Generate an id i, create a Patron p with (id, name, birthday, image) = (i, n, birthday, img), and add p to allPatrons
  • getPatron(id: string, out p: Patron)
    Get Patron with id = id
  • updatePatron(id: string, n: string, dob: Date, img: Image)
    Update patron profile information associated by their id
  • deletePatron(id: string)
    Delete the Patron with id = id from allPatrons

HouseholdProfile[Team,Patron,Stock]

Purpose: offers household information such as members, preferred language, specific requests, dietary restrictions, and number of previous visits

OP: When multiple people are listed together as a household, searching by household ID will retrieve information about the household, specific household needs, and number of visits

States:

  • allProfiles: set HouseholdProfile
  • org: HouseholdProfile → one Team
  • members: HouseholdProfile → some Patron
  • dietaryRestrictions: HouseHoldProfile → set string
  • language: HouseholdProfile → one string
  • requests: HouseholdProfile → set Stock
  • pastVisits: HouseholdProfile → one int

Actions:

  • createProfile(o: Team, m: set Patron, d: set string, l: string, r: set Stock, out h: HouseholdProfile)
    Generate an id i, create a HouseholdProfile p with (id, org, members, dietRestrictions, language, requests, pastVisits)=(i, o, m, d, l, r, 0), and add p to allProfiles
  • getProfilesByOwner(o: Team, out h: set HouseholdProfile)
    Get all profiles with org = o and return
  • getProfileById(id: string, out h: HouseholdProfile)
    Get HouseholdProfile with id = id
  • deletePatron(id: string)
    Delete the Patron with id = id from allPatrons
  • updatePatrons(id: string, m: set Patron)
    Update the HouseholdProfile with id = id with new set of members m
  • addVisit(id: string)
    Increment pastVisits of the HouseholdProfile with id = id by 1
  • resetVisits(id: string)
    Set pastVisits of the HouseholdProfile with id = id to 0
  • updateDetails(id: string, d: set string, l: string, r, set Stock)
    Update the HouseholdProfile with id = id with (dietRestrictions, language, requests) = (d, l, r)
  • deleteProfile(id: string)
    Delete the HouseholdProfile with id = id from allProfiles

LanguageAudio[Team]

Purpose: collects audio for a particular language in order to communicate specific phrases in that language

OP: When audio for a language is uploaded with the English translation, it will be added to the audio playlist for that language and will be played later to communicate to others

States:

  • allPlaylists: set LanguageAudio
  • owner: LanguageAudio → one Team
  • language: LanguageAudio → one string
  • audio: LanguageAudio → one Audio
  • translation: LanguageAudio → one Audio

Actions:

  • createAudio(l: string, a: audio, o: Team, t: translation, out p: LanguageAudio)
    Generate an id i, create a LanguageAudio p with (id, owner, language, audio, translation)=(i, o, l, a, t), and add p to allPlaylists
  • updateAudio(id: string, l: string, a: audio, t: translation)
    Update the LanguageAudio with id = id with (language, audio, translation)=(l, a, t)
  • getAudioById(id: string, out p: set LanguageAudio)
    Get all LanguageAudio with id=string
  • getAudioByOwner(o: Team, out p: set LanguageAudio)
    Get all LanguageAudio with owner = o
  • deleteAudio(id: string)
    Delete the LanguageAudio with id = id from allPlaylists

Stock[Team]

Purpose: allows the owner to monitor the quantity of a resource

OP: When the owner modifies the quantity of an item, the item will be displayed with the updated quantity

States:

  • allStocks: set Stock
  • owner: Stock → one Team
  • item: Stock → one string
  • count: Stock → one int
  • supplyLink → one string
  • image: Stock → one Image
  • maxPerDay: Stock → one int
  • maxPerPerson: Stock → one int

Actions:

  • createStock(o: string, i: string, cnt: int, l: string, img: Image, maxp: intout s: Stock)
    Generate an id, check that cnt >= 0, create a Stock s with (id, owner, item, count, supplyLink, image)=(id, o, i, c, l, img), and add s to allStocks. maxPerDay is automatically updated based on datetime calculations when stock is accessed.
  • getStockByOwner(o: string, out s: set Stock)
    Get all stock with owner = o
  • getStockByName(o: string, n: string, out s: set Stock)
    Get all stock with name n belonging to owner o
  • getStockById(id: string, out s: Stock)
    Get stock with identifier=id
  • updateStock(id: string, item: string, o: string, count: int, supplyLink: string, img: Image, maxd: int, maxp: int)
    Update the Stock with id = id with (item, count, supplyLink, img) if the owner = o and count>=0
  • deleteStock(id: string)
    Delete the Stock with id = id from allStocks

Shift[Team, User]

Purpose: Allows people to sign up for a specific shift/time slot for an organization

OP: If a person selects their shift, the interval of time during which that person is scheduled to work will be displayed to others in that organization

States:

  • allShifts: set Shift
  • owner: Shift → one Team
  • volunteers: Shift → set Users
  • start: Shift → one Date
  • end: Shift → one Date

Actions:

  • createShift(org: string, s: Date, e: Date, out shift: Shift)
    Create a shift belonging to org, with start and end time s and e
  • getShiftByOwner(o: string, out shift: set Shift)
    Find all shift with owner = o and return
  • deleteShift(id: string)
    Delete the Shift with id=id
  • claimShift(user: string, s: string)
    Find the shift corresponding to id s, and associate user id with its volunteers
  • unclaimShift(user: string, s: string)
    Find the shift corresponding to id s, and disassociate user id with its volunteers
  • unclaimShiftsByUser(user: string, org: string)
    Find all shifts that have user id associated with them with owner org, and disassociate user id from all found shifts

App Level Synchronizations

include User
include Patron
include Session[User]
include Team[User]
include Membership[User, Team]
include HouseholdProfile[Team, Patron, Stock]
include LanguageAudio[Team]
include Stock[Team]
include Shift[Team, User]

registerOrganization(session: Session, name: string)
	user = Session[User].user
    org = Team[User].createTeam(user, name)
    newMembership = Membership[User, Team].getMembership(u).add(user)
    Membership[User, Team].editMembership(newMembership, org)

deleteOrganization(orgId: id, editor: User)
    user = Session[User].user
    {org.admins, org.members} = Team[User].getTeam(orgId)
    if user in org.admins:
    for u in org.admins | org.members
	    Remove the org from each u's membership
	    for shiftId in Shift[Team, User].getShift(orgId):
	    	Delete all shifts created by team with orgId
	    for household in HouseholdProfile[Team, Patron, Stock].getHousehold(orgId):
	    	Delete all Patron from each household
		    Delete all household
	    for stock in Stock[Team].getStockByOwner(orgId):
		    Delete all stock
	    for audio in LanguageAudio[Team].getAudioByOwner(orgId):
	    	Delete all audio
    Team[User].deleteTeam(org, editor)

getUserOrganizations(session: Session)
    user = Session[User].user
    return Membership[User, Team].getMembership(user)

registerUser(user: string, pass: string)
    User.register(user, pass)
    Membership[User, Team].createMembership(u)

deleteUser(session: Session, user: string, pass: string)
    u = Session[User].user
    User.deleteUser(user, pass)
    allMemberships = Membership[User, Team].getMembership(u)
    for membership in allMemberships:
        Membership[User, Team].deleteMembership(membership)
	Team[User].removeMember(membership, user)
	Shift[Team, User].unclaimShiftsByUser(u, membership)

addMemberToOrganization(session: Session, org: Team, u: User, admin: boolean)
    user = Session[User].user
    membership = Membership[User, Team].getMembership(u)
    Membership[User, Team].editMembership(membership.add(org))
    Team[User].addMember(org, user, u)
    if admin: Team[User].promoteMember(org, u, user)

updateMemberStatus(session: Session, org: Team, u: User, status: s)
	user = Session[User].user
    if status == ADMIN: Team[User].promoteMember(org, u, user)
	else: Team[User].demoteMember(org, u, user)

removeMemberFromOrganization(session: Session, org: Team, u: User)
	user = Session[User].user
	membership = Membership[User, Team].getMembership(u)
    Membership[User, Team].editMembership(membership.remove(org))
    Team[User].removeMember(org, user, u)

createHouseholdProfile(session: Session, o: Team, name: string, birthday: Date, img: Image, diet: string, lang: string, req: set Stock)
	u = Session[User].user
	p = Patron.createPatron(n, birthday, img)
    if Membership[User, Team].getMembership(u).includes(o):
		HouseholdProfile[Team, Patron, Stock].createProfile(o, {p}, diet, lang, request)

updateHouseholdDetails(session: Session, id: string, diet: set string, lang: string, req: set stock)
    u = Session[User].user
    h = HouseholdProfile[Team, Patron, Stock].getProfileById(id)
    if Membership[User, Team].getMembership(u).includes(h.owner):
	    h = HouseholdProfile[Team, Patron, Stock].updateDetails(id, diet, lang, req)	

resetAllVisits(session: Session, orgId: string)
    u = Session[User].user
    org = Team[User].getTeam(orgId)
    if org.admins.includes(u):
	    for h in HouseholdProfile[Team, Patron, Stock].getProfilesByOwner(org):
		    HouseholdProfile[Team, Patron, Stock].resetVisits(h)

getHouseholdAndAddVisit(session: Session, id: string)
    u = Session[User].user
    h = HouseholdProfile[Team, Patron, Stock].getProfileById(id)
    if Membership[User, Team].getMembership(u).includes(h.owner):
	    h = HouseholdProfile[Team, Patron, Stock].addVisit(id)
	return h

removeHouseholdProfile(session: Session, householdId: string, patronId: string)
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		for patronId in HouseholdProfile[Team, Patron, Stock] .getProfileById(householdId).members:
    p = Patron.deletePatron(patronId)
    HouseholdProfile[Team, Patron, Stock].deleteProfile(householdId)

addPatron(session: Session, householdId: string,  name: string, birthday: Date, img: Image)
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		p = Patron.createPatron(n, birthday, img)
	members = h.members.add(p)
	HouseholdProfile[Team, Patron, Stock].updatePatrons(householdId, members)

updatePatron(session: Session, householdId: string, patronId: string, name: string, dob:  Date, img: Image) 
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		p = Patron.updatePatron(id, name, dob, img)	

removePatron(session: Session, householdId: string, patronId: string)
	u = Session[User].user
	p = Patron.getPatron(patronId)
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
	members = h.members.remove(p)
	HouseholdProfile[Team, Patron, Stock].updatePatrons(householdId, members)

getOrganizationLanguageAudio(session: Session, o: id)
	u = Session[User].user
	t = Team[User].getTeam(o)
	if t.admin.includes(u) || t.member.includes(u):
		return LanguageAudio[Team].getAudioByOwner(o)
		
addNewAudio(session: Session, orgId: string, language: string, aud: Audio, translation: string)
    u = Session[User].user
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].createAudio(language, aud, orgId, t)

editAudio(session: Session, audioId: string, language: string, aud: Audio, translation: string)
	u = Session[User].user
	o = LanguageAudio[Team].getAudioById(audioId).owner
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].updateAudio(id, language, aud, translation)

deleteAudio(session: Session, audioId: string)	
	u = Session[User].user
	o = LanguageAudio[Team].getAudioById(audioId).owner
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].deleteAudio(audioId)

getOrganizationInventory(session: Session, orgId: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId)
	inventory=Stock.getStockByOwner(org)
	return inventory

addItemToInventory(session: Session, orgId: string, i: string, cnt: int, l: string, img: Image, maxp: int)
	u = Session[User].user
    org = Team[User].getTeam(orgId) //member verification
    stocks = Stock.getStockByName(orgId, i)
	if item not in stocks:
		Stock.createStock(orgId, i, cnt, l, img, maxp)
		return
    updateStock(stocks[0].id, i, orgId, cnt, l, img, maxp)

removeItemsFromInventory(session: Session, orgId: string, id: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId)
	inventory=Stock.getStockByOwner(org)
    stock = Stock.getStockById(id)
    if stock in inventory:
	    Stock.deleteStock(id)

getAllocationByHousehold(session: Session, orgId: string, household: string)
	inventory = getOrganizationInventory(session, orgId)
	houses  = HouseholdProfile.getProfilesByOwner(orgId)
	total = 0
	for house in houses:
		total+= house.members.size()
	allocation = new set[Stock,int]
	for item in inventory:
		allocation.add([item,item.count/total/5])
	house = HouseholdProfile.getProfileById(household)
	members = house.members.size()
	new_allocation = new set[Stock,int]
	for [item,cnt] in allocation:
		new_allocation.add([item,cnt*members]
	return new_allocation

getOrganizationShifts(session: Session, orgId: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId) //member verification
	return Shift.getShiftsByOwner(orgId)
include User
include Patron
include Session[User]
include Team[User]
include Membership[User, Team]
include HouseholdProfile[Team, Patron, Stock]
include LanguageAudio[Team]
include Stock[Team]
include Shift[Team, User]

registerOrganization(session: Session, name: string)
	user = Session[User].user
    org = Team[User].createTeam(user, name)
    newMembership = Membership[User, Team].getMembership(u).add(user)
    Membership[User, Team].editMembership(newMembership, org)

deleteOrganization(orgId: id, editor: User)
    user = Session[User].user
    {org.admins, org.members} = Team[User].getTeam(orgId)
    if user in org.admins:
    for u in org.admins | org.members
	    Remove the org from each u's membership
	    for shiftId in Shift[Team, User].getShift(orgId):
	    	Delete all shifts created by team with orgId
	    for household in HouseholdProfile[Team, Patron, Stock].getHousehold(orgId):
	    	Delete all Patron from each household
		    Delete all household
	    for stock in Stock[Team].getStockByOwner(orgId):
		    Delete all stock
	    for audio in LanguageAudio[Team].getAudioByOwner(orgId):
	    	Delete all audio
    Team[User].deleteTeam(org, editor)

getUserOrganizations(session: Session)
    user = Session[User].user
    return Membership[User, Team].getMembership(user)

registerUser(user: string, pass: string)
    User.register(user, pass)
    Membership[User, Team].createMembership(u)

deleteUser(session: Session, user: string, pass: string)
    u = Session[User].user
    User.deleteUser(user, pass)
    allMemberships = Membership[User, Team].getMembership(u)
    for membership in allMemberships:
        Membership[User, Team].deleteMembership(membership)
	Team[User].removeMember(membership, user)
	Shift[Team, User].unclaimShiftsByUser(u, membership)

addMemberToOrganization(session: Session, org: Team, u: User, admin: boolean)
    user = Session[User].user
    membership = Membership[User, Team].getMembership(u)
    Membership[User, Team].editMembership(membership.add(org))
    Team[User].addMember(org, user, u)
    if admin: Team[User].promoteMember(org, u, user)

updateMemberStatus(session: Session, org: Team, u: User, status: s)
	user = Session[User].user
    if status == ADMIN: Team[User].promoteMember(org, u, user)
	else: Team[User].demoteMember(org, u, user)

removeMemberFromOrganization(session: Session, org: Team, u: User)
	user = Session[User].user
	membership = Membership[User, Team].getMembership(u)
    Membership[User, Team].editMembership(membership.remove(org))
    Team[User].removeMember(org, user, u)

createHouseholdProfile(session: Session, o: Team, name: string, birthday: Date, img: Image, diet: string, lang: string, req: set Stock)
	u = Session[User].user
	p = Patron.createPatron(n, birthday, img)
    if Membership[User, Team].getMembership(u).includes(o):
		HouseholdProfile[Team, Patron, Stock].createProfile(o, {p}, diet, lang, request)

updateHouseholdDetails(session: Session, id: string, diet: set string, lang: string, req: set stock)
    u = Session[User].user
    h = HouseholdProfile[Team, Patron, Stock].getProfileById(id)
    if Membership[User, Team].getMembership(u).includes(h.owner):
	    h = HouseholdProfile[Team, Patron, Stock].updateDetails(id, diet, lang, req)	

resetAllVisits(session: Session, orgId: string)
    u = Session[User].user
    org = Team[User].getTeam(orgId)
    if org.admins.includes(u):
	    for h in HouseholdProfile[Team, Patron, Stock].getProfilesByOwner(org):
		    HouseholdProfile[Team, Patron, Stock].resetVisits(h)

getHouseholdAndAddVisit(session: Session, id: string)
    u = Session[User].user
    h = HouseholdProfile[Team, Patron, Stock].getProfileById(id)
    if Membership[User, Team].getMembership(u).includes(h.owner):
	    h = HouseholdProfile[Team, Patron, Stock].addVisit(id)
	return h

removeHouseholdProfile(session: Session, householdId: string, patronId: string)
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		for patronId in HouseholdProfile[Team, Patron, Stock] .getProfileById(householdId).members:
    p = Patron.deletePatron(patronId)
    HouseholdProfile[Team, Patron, Stock].deleteProfile(householdId)

addPatron(session: Session, householdId: string,  name: string, birthday: Date, img: Image)
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		p = Patron.createPatron(n, birthday, img)
	members = h.members.add(p)
	HouseholdProfile[Team, Patron, Stock].updatePatrons(householdId, members)

updatePatron(session: Session, householdId: string, patronId: string, name: string, dob:  Date, img: Image) 
	u = Session[User].user
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
		p = Patron.updatePatron(id, name, dob, img)	

removePatron(session: Session, householdId: string, patronId: string)
	u = Session[User].user
	p = Patron.getPatron(patronId)
	h = HouseholdProfile[Team, Patron, Stock].getProfileById(householdId)
	if Membership[User, Team].getMembership(u).includes(h.owner):
	members = h.members.remove(p)
	HouseholdProfile[Team, Patron, Stock].updatePatrons(householdId, members)

getOrganizationLanguageAudio(session: Session, o: id)
	u = Session[User].user
	t = Team[User].getTeam(o)
	if t.admin.includes(u) || t.member.includes(u):
		return LanguageAudio[Team].getAudioByOwner(o)
		
addNewAudio(session: Session, orgId: string, language: string, aud: Audio, translation: string)
    u = Session[User].user
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].createAudio(language, aud, orgId, t)

editAudio(session: Session, audioId: string, language: string, aud: Audio, translation: string)
	u = Session[User].user
	o = LanguageAudio[Team].getAudioById(audioId).owner
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].updateAudio(id, language, aud, translation)

deleteAudio(session: Session, audioId: string)	
	u = Session[User].user
	o = LanguageAudio[Team].getAudioById(audioId).owner
	if Membership[user, Team].getMembership(u).includes(o):
		LanguageAudio[Team].deleteAudio(audioId)

getOrganizationInventory(session: Session, orgId: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId)
	inventory=Stock.getStockByOwner(org)
	return inventory

addItemToInventory(session: Session, orgId: string, i: string, cnt: int, l: string, img: Image, maxp: int)
	u = Session[User].user
    org = Team[User].getTeam(orgId) //member verification
    stocks = Stock.getStockByName(orgId, i)
	if item not in stocks:
		Stock.createStock(orgId, i, cnt, l, img, maxp)
		return
    updateStock(stocks[0].id, i, orgId, cnt, l, img, maxp)

removeItemsFromInventory(session: Session, orgId: string, id: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId)
	inventory=Stock.getStockByOwner(org)
    stock = Stock.getStockById(id)
    if stock in inventory:
	    Stock.deleteStock(id)

getAllocationByHousehold(session: Session, orgId: string, household: string)
	inventory = getOrganizationInventory(session, orgId)
	houses  = HouseholdProfile.getProfilesByOwner(orgId)
	total = 0
	for house in houses:
		total+= house.members.size()
	allocation = new set[Stock,int]
	for item in inventory:
		allocation.add([item,item.count/total/5])
	house = HouseholdProfile.getProfileById(household)
	members = house.members.size()
	new_allocation = new set[Stock,int]
	for [item,cnt] in allocation:
		new_allocation.add([item,cnt*members]
	return new_allocation

getOrganizationShifts(session: Session, orgId: string)
	u = Session[User].user
    org = Team[User].getTeam(orgId) //member verification
	return Shift.getShiftsByOwner(orgId)

Dependency Diagram

dependencydiagram

Wireframes

Heuristic Evaluation

Usability


Error Tolerance

  • To enhance error tolerance and user awareness, the delete organization button currently shown with an 'x' symbol will be replaced by a conspicuous red button, acting as a visual cue to signal caution and prompt users to think twice before proceeding. Complementing this change, all delete-related actions will trigger confirmation prompts, adding an extra layer of protection against accidental deletions.
  • Most actions can be easily reverted in case of user error with no consequence, such as changing a member's status between volunteer and user, adding and deleting volunteers, and adding and removing inventory units. This allows users convenient and efficient ways to handle recovery when an error occurs. However, it will also make it harder to detect any errors quickly, and make it easier to commit erroneous actions in the first place.

Efficiency

  • To enhance efficiency and seamless user navigation, the app will implement a drop down that allows users to switch between organizations from the navigation bar, without navigating to the organization page itself. This approach not only enhances efficiency but also provides users with a quick visual indicator of which organization's inventories and patrons they are working with. However, this might be a tradeoff with Fitts Law because it might make the nav bar even more crowded than it already is and makes it harder to quickly get to the smaller links near it.
  • To improve the efficiency of the organization's patron experience, the audio recordings in each patron's preferred language are available right on the household's patron page, so volunteers will be able to immediately access the necessary recordings without needing to navigate to a special page for all audio files. This will ensure a more streamlined, efficient process for food distribution. However, this may cause the page to lag because we are storing audio files and would introduce the need to implement something for perceptual fusion to account for time delays.

Accessibility

  • The different language options for audio recordings should be displayed in that language, rather than just in English. This will allow for patrons who cannot read English to point to the desired language for the volunteer to play the recording, if necessary.

Physical Heuristics


Mapping

  • There could be some confusion about the placement of the functionalities relating to managing the members and their statuses in the organization. Currently, these functionalities are under Settings, but it would be more understandable if actions regarding people went under Organization. Moreover, moving them under the selected food pantry name in Organization would make it clearer that the volunteers belong to this specific organization.

Situational Context

  • While admin vs. volunteer separation is an important part of the functionality of the app, it may be difficult to determine which role a user is based solely on what the app conveys. This is important especially if a user is an admin at some organizations, and a volunteer at others, for example. To mitigate this issue, we can create some visual differences between the app's state when the user is an admin at the currently-selected organization versus when the user is a volunteer. Also, functionality only available for admins (e.g. resetting all patron visits) should not appear on the volunteer side. However, this may result in less consistency, since the two states will have some different functionalities and possibly appear slightly different visually.
  • For updating an organization member's status, the app could do a better job of conveying the member's current state by not displaying both the option to promote and the option to demote, but rather only the action that corresponds to the specific member's current status.
  • The wireframes context well in the form of pop-ups for adding or updating information, for example, in updating units on an inventory item or editing the status of an organization member; since the background behind the modal will still be the page that the user originally navigated from, where the user currently is will be easy to extract.

Linguistic Level


Consistency

  • The interface is inconsistent with the usage of words such as "add," "create," and "new." For example, we have a "New Organization" button, an "Add Item" button, and a "Create" button for new inventory. Using more consistent terminology for these similar actions will result in a more intuitive user experience.
  • Our symbols and icons, such as a microphone for audio recordings, a shopping cart for inventory items, and a pencil leading to an edit page, are consistent throughout our app, as well as throughout other platforms across several domains. This will make the navigation of our app smoother and more learnable for users.
  • Throughout the app, destructive buttons, such as cancel, delete, and remove, are in gray on the left, while constructive buttons, such as add, update, and create, or shown in red on the right. This provides consistency across the different actions in the app, allowing users to gain more intuition about which button to press without relying on reading the labels. However, by making the pages more consistent, and the buttons more similar, we are reducing situational context.

Speak a user's language

  • Currently, all inputs of patrons or volunteers are communicated through ID, which will be a string of numbers that identify a household or member. This is less understandable for users, however, since volunteers are likely to recognize patrons based on their appearance and name, not by their ID. It is also more error-prone, since it is easy to make a mistake in typing in a long string of numbers. It may be better to have a search feature that allows for ID inputs or household name/username inputs. However, this is a security tradeoff as volunteers can more easily search up targeted people and update their profiles more easily without an ID. This would contribute to discrimination as volunteers can mark people as visited or change their dietary restrictions without the person being there to prevent it.
  • Conventionally, red is associated with destructive or dangerous actions, such as removal, deletion, or cancellation. However, we use red to signify positive/constructive actions, such as updates, additions, and creations. This may be unintuitive to users and may thereby cause accidental destructive actions to occur. Therefore we can change the color of the buttons.

Visual Design Study

vds1
vds2

Plan your Implementation

Week 1

ConceptTasksAssigned
TeamCreate, edit, delete functionsAmanda
Add, promote, demote, remove member functionsAmanda
Test functionsPeilin
MembershipCreate, get, edit, delete membership functionsAmanda
Test functionsAndrew
HouseholdProfileCreate, get (by owner, by ID), updateDetails, deleteProfile functionsAndrew
updatePatrons, addVisit, resetVisitsAndrew
Test functionsPeilin
PatronCreate, get, update, delete patron functionsPeilin
Test functionsAnna
StockCreate, update, deleteAnna
Get (by owner, name, ID)Anna
Test functionsAmanda
SyncsTeam and Membership onlyAmanda
HouseholdProfile, Patron, Stock, Team, and MembershipAnna, Peilin
Test SyncsAndrew
General FrontendDiscuss general theme of website (color, font, button styles)All
Homepage+NavbarPeilin
Create Account / Sign In PageAnna
Organization PageAmanda
Component for each memberAmanda
Modal to promote/demote membersAndrew
Add/remove memberAmanda

Week 2

Concept/ComponentsTasksAssigned
ShiftCreate, delete, get shift functionsAnna
Claim, unclaim, unclaimShiftsByUser functionsAnna
Test FunctionsAndrew
LanguageAudioAll functionsAmanda
Test FunctionsPeilin
SyncsFinish all SyncsAndrew
Test SyncsAnna
Inventory PageStock Card componentAmanda
Edit Inventory ModalAndrew
Create New Stock PageAnna
Sorting, Search Bar, and StylingPeilin
Patron PagePatron ComponentPeilin
Household Info ComponentAmanda
Update ComponentAnna
TimeSheet PageTimesheet TableAndrew
Create Shift/Delete Shift ModalAnna
Claim Shift/Unclaim Shift ActionsPeilin
StylingAmanda
MediaPhotoAndrew
AudioAmanda

Week 3

TasksAssigned
Populate app with test dataAnna, Andrew
User FeedbackAll
StylingPeilin, Amanda
Finalize appAll

Contingency Plans

In the event of unforeseen challenges during implementation, our strategic approach involves a meticulous reassessment of priorities to ensure a successful delivery of our app. In particular, we will consider the removal of less pivotal functionalities, such as shifts, due to their isolated nature. In particular, we will forego the features with more technical intricacies, such as the calendar for creating and claiming shifts. Furthermore, we will deprioritize analytics charts for donations, patron tracking, and inventory distribution, allowing us to concentrate on refining the app's essential functionalities. In the face of potential difficulties with MongoDB and technical features like storing and displaying media files such as images and audio, our contingency plan involves presenting images and audio recordings as accessible links instead. This comprehensive strategy underscores our commitment to adaptability and resilience, with the ultimate goal of delivering a robust and user-friendly application.