Skip to content

Assignment 3: Convergent Design

a. Pitch

Ounce is a social media app intended for people who want to take control over their social media usage. Ounce does so in a variety of ways. First and foremost, users are limited to a single post a day. No more media companies swamping your feed. Next, Ounce has no backend ML algorithm to suck you in and keep you scrolling. A prof's feed consists only of accounts they have chosen to follow. For greater granulartiy, prof's can tag accounts under a custom label for seamless content filtering. Lastly, Ounce will put large emphasis on each prof's individual usage via a daily usage timer that will be present at all times. A person can get greater insight into their usage habits with a breakdown of varying timescales and metrics (i.e Total Weekly Usage, Avg. Monthly Usage, etc.). The motivations behind most popular social media companies have strayed away from prof experience, and instead focused on corporate profit. Ounce is here to offer an alternative for daily prof interavtions to be of quality versus quantity.

b. Functional Design

User

concept User
    purpose: 
        authenticate users
    principle:
        after a prof registers with a username and password, they
        can authenticate as that prof by providing a matching
        username and password.
    state
        registered: set User
        username, password: registered -> one String
    actions
        register(u: String, p: String): Void
            # if u not in registered.username,
            #   add User(u, p) to registered

        authenticate(u: String, p: String): User
            # if (u, p) in registered,
            #   return registered.username == u
concept User
    purpose: 
        authenticate users
    principle:
        after a prof registers with a username and password, they
        can authenticate as that prof by providing a matching
        username and password.
    state
        registered: set User
        username, password: registered -> one String
    actions
        register(u: String, p: String): Void
            # if u not in registered.username,
            #   add User(u, p) to registered

        authenticate(u: String, p: String): User
            # if (u, p) in registered,
            #   return registered.username == u

Label[Item]

concept Label[Item]
    purpose
        organize with overlapping
    principle
         - add and delete labels
         - find an item that associates with a set of labels
    state
        labels: Item -> set Label

    actions
        add (i: Item, l: Label): Void

        remove (i: Item, l: Label): Void

        find (ls: set Label): set Item
concept Label[Item]
    purpose
        organize with overlapping
    principle
         - add and delete labels
         - find an item that associates with a set of labels
    state
        labels: Item -> set Label

    actions
        add (i: Item, l: Label): Void

        remove (i: Item, l: Label): Void

        find (ls: set Label): set Item

Limit[Item]

concept Limit[Item]
    purpose
        impose limitations on an Item
    principle
        - use createLimit to add a Limit to an Item
        - use hasLimit to avoid redundancy in checking if an Item
        already has a limit
        - use validateIncrement to return whether increasing by a certain 
        amount exceeds the limit (and if it does not, then increment)
        - use delete to remove the Limit on an Item
        - use reset to change the count to 0 after some amount of time
    state
        const limit: Number
        counter: Number
        timer: Date
        items: set Item
        itemCount: items -> one Number
    actions
        underLimit(c: Number): Boolean
        #   True if c < limit else False

        createValidLimit(i: Item, c: Number): Void
        #   if underLimit(c)
        #       add i to items,
        #       add (i, c) to Itemcount

        validateIncrement(i: Item, count: Number): Boolean
        #   add count to itemCount[i] if belowLimit()

        delete(i: Item): Void
        #   remove i from items

        reset(i:Item, t: Date): Void
        # set itemCount[i] to 0 after some period of time
concept Limit[Item]
    purpose
        impose limitations on an Item
    principle
        - use createLimit to add a Limit to an Item
        - use hasLimit to avoid redundancy in checking if an Item
        already has a limit
        - use validateIncrement to return whether increasing by a certain 
        amount exceeds the limit (and if it does not, then increment)
        - use delete to remove the Limit on an Item
        - use reset to change the count to 0 after some amount of time
    state
        const limit: Number
        counter: Number
        timer: Date
        items: set Item
        itemCount: items -> one Number
    actions
        underLimit(c: Number): Boolean
        #   True if c < limit else False

        createValidLimit(i: Item, c: Number): Void
        #   if underLimit(c)
        #       add i to items,
        #       add (i, c) to Itemcount

        validateIncrement(i: Item, count: Number): Boolean
        #   add count to itemCount[i] if belowLimit()

        delete(i: Item): Void
        #   remove i from items

        reset(i:Item, t: Date): Void
        # set itemCount[i] to 0 after some period of time

Profile[User, Post, Label]

concept Profile[User]
    purpose: 
        associate data to a specific profile
    principle:
        - create and delete profiles
        - get the user associated with this Profile
        - use followAccount to follow another user Profile
        - use addTimeActive to increment the total amount of time that a 
        person has been active

    state:
        user: one User
        following: set Profile
        posts: set Post
        labels: set Label
        timeActive: one Time = 0
    actions
        createProfile(u: User): Profile

        deleteProfile(): Void

        getUser(): User

        followAccount(p: Profile): Void
        #   add p to following

        unfollowAccount(p:profile): Void
        #   remove p from following

        addTimeActive(t: Time): Void
        #   timeActive += t

        addPost(p: Post): Void
        #   add p to posts

        removePost(p: Post): Void
        #   remove p from posts
concept Profile[User]
    purpose: 
        associate data to a specific profile
    principle:
        - create and delete profiles
        - get the user associated with this Profile
        - use followAccount to follow another user Profile
        - use addTimeActive to increment the total amount of time that a 
        person has been active

    state:
        user: one User
        following: set Profile
        posts: set Post
        labels: set Label
        timeActive: one Time = 0
    actions
        createProfile(u: User): Profile

        deleteProfile(): Void

        getUser(): User

        followAccount(p: Profile): Void
        #   add p to following

        unfollowAccount(p:profile): Void
        #   remove p from following

        addTimeActive(t: Time): Void
        #   timeActive += t

        addPost(p: Post): Void
        #   add p to posts

        removePost(p: Post): Void
        #   remove p from posts

Session[User]

concept Session[User]
    purpose:
        authenticate a user for extended period of time
    principle:
        - after a session starts (and before it ends) getUser action returns 
        the user identified at the start 
        - store the timestamp of when a session starts 
        - use end to return the total time of a session
    state
        startTime: one Time
        active: set Session
        sessions: active -> one User
    actions
        start(u: User): Session
        #   startTime = Time.now,
        #   add u to active,
        #   prof = u,
        
        getUser(s: Session): User
        #   if s in active, return sessions[s]
        
        end(s: Session): Time
        #   if s in active:
        #       remove s from active,
        #       return Time.now - s.startTime
concept Session[User]
    purpose:
        authenticate a user for extended period of time
    principle:
        - after a session starts (and before it ends) getUser action returns 
        the user identified at the start 
        - store the timestamp of when a session starts 
        - use end to return the total time of a session
    state
        startTime: one Time
        active: set Session
        sessions: active -> one User
    actions
        start(u: User): Session
        #   startTime = Time.now,
        #   add u to active,
        #   prof = u,
        
        getUser(s: Session): User
        #   if s in active, return sessions[s]
        
        end(s: Session): Time
        #   if s in active:
        #       remove s from active,
        #       return Time.now - s.startTime

Post[Content, Profile]

concept Post
    purpose: 
        enables people to share content
    principle:
        - use create to initialize a new piece of content
        - use delete to remove a post
        - getOwner will return the profile that shared a particular post
        - addComment adds a username-string mapping to a post's comments section
    state:
        content: one Content
        owner: one Profile
        comments: set Profile -> one String
    actions
        create(c: Content, p: Profile): Post

        delete(): Void

        getOwner(p: Post): Profile

        addComment(p: Profile, c: String): Void
        #   add u -> c to comments
concept Post
    purpose: 
        enables people to share content
    principle:
        - use create to initialize a new piece of content
        - use delete to remove a post
        - getOwner will return the profile that shared a particular post
        - addComment adds a username-string mapping to a post's comments section
    state:
        content: one Content
        owner: one Profile
        comments: set Profile -> one String
    actions
        create(c: Content, p: Profile): Post

        delete(): Void

        getOwner(p: Post): Profile

        addComment(p: Profile, c: String): Void
        #   add u -> c to comments

App-Level Actions: Ounce

app Ounce
  include User
  include Post
  include Profile[User.User]
  include Session[User]
  include Limit[Profile[User.User]] as LimitedProf
  include Limit[Post] as LimitedPost

sync registerProfile(u, p: String): Profile
#   when user:= register(u, p)
#       prof:= createProfile(user)

sync getProfile(s: session): Profile
#   return p where p.getUser == s.session

sync login(u, p: String): (Profile, s)
#   when user:= authenticate(u, p)
#       s:= Session.start(user)
#       prof:= getProfile(s)      
#   return (prof, s)

sync logout(s: Session): Void
#   when timeEnded:= Session.end(s),
#       sessionTime: Time = timeEnded - s.startTime
#       Session.getUser(s).addTimeActive(sessionTime)

sync getUsage(s: Session, p: Profile): Time
#   prof.timeActive + (Time.now - s.startTime)

// limited number of daily posts + post char limit
sync postContent(c: Content, prof: Profile): Void
#   if LimitedPost.belowLimit(len of c) 
#   AND LimitedProfile.belowLimit(__daily posts by prof__):
#       post:= Post.create(c), prof.addPost(post)

sync deletePost(prof: Profile, post: Post): Void
#   prof.removePost(post), post.delete()

sync makeComment(prof: Profile, post: Post, c: String): Void
#   post.addComment(prof, c)

sync markItem(prof: profile, i: Post|Profile, l: Label): Void
#   prof.labels.add(i, l)

// not only feed on labeled accounts/posts but also
// covers the concept of "favorites"
sync filterFeed(prof: Profile, ls: set Labels):
#   display prof.labels.find(ls)
app Ounce
  include User
  include Post
  include Profile[User.User]
  include Session[User]
  include Limit[Profile[User.User]] as LimitedProf
  include Limit[Post] as LimitedPost

sync registerProfile(u, p: String): Profile
#   when user:= register(u, p)
#       prof:= createProfile(user)

sync getProfile(s: session): Profile
#   return p where p.getUser == s.session

sync login(u, p: String): (Profile, s)
#   when user:= authenticate(u, p)
#       s:= Session.start(user)
#       prof:= getProfile(s)      
#   return (prof, s)

sync logout(s: Session): Void
#   when timeEnded:= Session.end(s),
#       sessionTime: Time = timeEnded - s.startTime
#       Session.getUser(s).addTimeActive(sessionTime)

sync getUsage(s: Session, p: Profile): Time
#   prof.timeActive + (Time.now - s.startTime)

// limited number of daily posts + post char limit
sync postContent(c: Content, prof: Profile): Void
#   if LimitedPost.belowLimit(len of c) 
#   AND LimitedProfile.belowLimit(__daily posts by prof__):
#       post:= Post.create(c), prof.addPost(post)

sync deletePost(prof: Profile, post: Post): Void
#   prof.removePost(post), post.delete()

sync makeComment(prof: Profile, post: Post, c: String): Void
#   post.addComment(prof, c)

sync markItem(prof: profile, i: Post|Profile, l: Label): Void
#   prof.labels.add(i, l)

// not only feed on labeled accounts/posts but also
// covers the concept of "favorites"
sync filterFeed(prof: Profile, ls: set Labels):
#   display prof.labels.find(ls)

c. Dependency Diagram

Note: this diagram is under assumption that this represents the dependencies IN RELATION TO MY APP. For example, in theory Post + Profile do not necessarily need to be connected. However, there will not be anonymous posts in Ounce

d. Wireframes

e. Design Tradeoffs

1. The "Profile Problem"

The notion of a Personal Profile threw me for a loop. I wanted to store various data points for a user (i.e posts, following, usage, etc.) and struggled to find the balance between contextualizing concepts versus focusing on reusability.

Options

  • "jam" all of this information into a "User" concept
  • break down into "User", "Session", and "Profile" concept
  • meet somewhere in the middle i.e have a "Profile" concept and store the "Session" info in "User"

I ended up choosing the 2nd option of separating this notion of a Profile into 3 concepts. This came down to the disctinct purposes of each. "User" authenticates, "Session" tracks usage, and "Profile" stores relevant info. Although I likely won't reuse any of these concepts, as described in lecture this is best practice in general design.

2. Permission to Follow?

Another choice I had to make was based on a Profile a following another Profile b. I had to decide whether to Options

  • b authorize a to follow them (and in turn view their content)
  • b have no control over who follows them (in this case being Profile a)

My decision ended up being the ladder, with any user being able to follow another without permission. In theory, I think that the users should have more control over their "profile security". However, I came to this decision purely based on implementation feasability. I think the cost of introducing a "Security" concept would introduce complexity, that isn't a central purpose of the app. Maybe I will change this decision down the line though.

3. Customizable Experience?

When ideating my concept, I liked the notion of each person being able to "toggle" (or rather customize) their experience with the app. For instance, being able to View Posts from "suggested" content, enable/disable a catalog of their posts, etc. Thus, my choice was between Options

  • a user being able to customize their app environment
  • all users have the same, default environment

I went with the last choice, where the user interface and features were identical across various profiles. One reason was similar to above -- because of time constraints. Further, after some deeper thought, I did not think this was actually going to provide much value to users, and instead create complications in users' interactions. The principle of customization may make sense in some contexts; however, I feel that in a time sensitive project, this is putting energy towards a niche community, and not focusing enough on optimizing the experience for common users.