Ariver
2026-05-18 440d4dc270ac33348f2aeed7fbafddfe284acabe
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
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?
 
    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
    ) {
        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
    }
 
    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: "|")
    }
}