     return rhs.id == lhs.id
 }
 
+// ViewModel candidates
+extension Model.Day {
+    public func description(forMinimumHours minimumHours: Float) -> String {
+        let hours = entries.reduce(0) { $0 + $1.hours }
+        let isMissingHours = hours < minimumHours
+
+        let hoursString: String
+        if isMissingHours {
+            hoursString = "Missing \(minimumHours - hours) hours"
+        } else {
+            hoursString = "Logged \(hours) hours"
+        }
+
+        let weekday = calendar.component(.weekday, from: date())
+
+        return "\(hoursString) on \(dateString) \(calendar.weekdaySymbols[weekday - 1])"
+    }
+}
+
 extension Model.Project {
     public var description: String {
         let billableString = billable ? "Billable" : "Non-billable"

import Runes

public protocol APIType {
    typealias UserHandler = (_: Result<Model.User, API.Error>) -> Void
    typealias ProjectsHandler = (_: Result<[Model.Project], API.Error>) -> Void

    func user(handler: @escaping UserHandler)
    func projects(handler: @escaping ProjectsHandler)
}

extension API {

        case .today:
            return "/daily"
        case let .day(date):
            let day = calendar.ordinality(of: .day, in: .year, for: date)!
            let year = calendar.component(.year, from: date)
            return "/daily/\(day)/\(year)"
        case .addEntry:
            return "/daily/add"
        }
    }
}

private let calendar = Calendar.current

public enum Model {
    public struct User {
