Ariver
2026-05-19 7c8c804dacd1747101c0949a3ca26c94c690af79
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import Foundation
 
// MARK: - Smart Start Draft Contract
 
enum SmartCategorizationDraftSource: String, Codable, Hashable {
    case localSmartStart
    case aiImprove
}
 
enum SmartCategorizationAssignmentSource: String, Codable, Hashable {
    case localCatalog
    case localHeuristic
    case manualSeed
    case aiImprove
}
 
struct SmartCategorizationDraft: Codable, Hashable {
    static let currentVersion = 1
 
    let version: Int
    let draftSource: SmartCategorizationDraftSource
    let categories: [SmartCategoryDefinition]
    let categoryOrder: [SmartCategoryID]
    let assignments: [SmartAppCategorizationAssignment]
    let unassigned: [SmartUnassignedApp]
    let warnings: [SmartCategorizationWarning]
 
    init(
        version: Int = Self.currentVersion,
        draftSource: SmartCategorizationDraftSource,
        categories: [SmartCategoryDefinition] = SmartCategoryDefaults.definitions,
        categoryOrder: [SmartCategoryID] = SmartCategoryDefaults.orderedIDs,
        assignments: [SmartAppCategorizationAssignment] = [],
        unassigned: [SmartUnassignedApp] = [],
        warnings: [SmartCategorizationWarning] = []
    ) {
        self.version = version
        self.draftSource = draftSource
        self.categories = categories
        self.categoryOrder = categoryOrder
        self.assignments = assignments
        self.unassigned = unassigned
        self.warnings = warnings
    }
}
 
struct SmartAppCategorizationAssignment: Codable, Hashable, Identifiable {
    let appName: String
    let bundleIdentifier: String?
    let path: String?
    let categoryIDs: [SmartCategoryID]
    let confidence: Double
    let source: SmartCategorizationAssignmentSource
    let reason: String
    let provenance: [String]
    let defaultNote: String?
    let defaultNoteCandidates: [String]
 
    var id: String {
        if let bundleIdentifier, !bundleIdentifier.isEmpty {
            return "bundle:\(bundleIdentifier)"
        }
        if let path, !path.isEmpty {
            return "path:\(path)"
        }
        return "name:\(appName)"
    }
 
    var primaryCategoryID: SmartCategoryID? {
        categoryIDs.first
    }
 
    init(
        appName: String,
        bundleIdentifier: String? = nil,
        path: String? = nil,
        categoryIDs: [SmartCategoryID],
        confidence: Double,
        source: SmartCategorizationAssignmentSource,
        reason: String,
        provenance: [String] = [],
        defaultNote: String? = nil,
        defaultNoteCandidates: [String] = []
    ) {
        self.appName = appName
        self.bundleIdentifier = bundleIdentifier
        self.path = path
        self.categoryIDs = Self.uniqueOrdered(categoryIDs)
        self.confidence = confidence
        self.source = source
        self.reason = reason
        self.provenance = provenance
        self.defaultNote = defaultNote
        self.defaultNoteCandidates = defaultNoteCandidates
    }
 
    private static func uniqueOrdered(_ ids: [SmartCategoryID]) -> [SmartCategoryID] {
        var seen = Set<SmartCategoryID>()
        return ids.filter { seen.insert($0).inserted }
    }
}
 
struct SmartUnassignedApp: Codable, Hashable, Identifiable {
    let appName: String
    let bundleIdentifier: String?
    let path: String?
    let reason: String?
 
    var id: String {
        if let bundleIdentifier, !bundleIdentifier.isEmpty {
            return "bundle:\(bundleIdentifier)"
        }
        if let path, !path.isEmpty {
            return "path:\(path)"
        }
        return "name:\(appName)"
    }
}
 
struct SmartCategorizationWarning: Codable, Hashable, Identifiable {
    let code: String
    let message: String
    let appName: String?
 
    var id: String {
        [code, appName, message]
            .compactMap { $0 }
            .joined(separator: "|")
    }
}