Ariver
2026-05-28 9d87dbed3f81d07a81b1484a1317d7f7c0258610
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
131
132
133
134
135
136
137
138
139
140
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 SmartDefaultNoteProvenance: Codable, Hashable {
    let entryID: String
    let languageCode: String
    let notesVersion: Int
    let noteFingerprint: String
}
 
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]
    let defaultNoteProvenance: SmartDefaultNoteProvenance?
 
    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] = [],
        defaultNoteProvenance: SmartDefaultNoteProvenance? = nil
    ) {
        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
        self.defaultNoteProvenance = defaultNoteProvenance
    }
 
    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: "|")
    }
}