import Foundation import CoreData @objc(PredictionCache) public class PredictionCache: NSManagedObject, Identifiable { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "PredictionCache") } @NSManaged public var id: UUID @NSManaged public var sourceId: UUID? @NSManaged public var algorithm: String @NSManaged public var predictionData: Data? @NSManaged public var calculatedAt: Date @NSManaged public var validUntil: Date public override func awakeFromInsert() { super.awakeFromInsert() id = UUID() calculatedAt = Date() // Cache valid for 24 hours validUntil = Calendar.current.date(byAdding: .hour, value: 24, to: Date()) ?? Date() algorithm = PredictionAlgorithm.linear.rawValue } } // MARK: - Prediction Algorithm enum PredictionAlgorithm: String, CaseIterable, Identifiable { case linear = "linear" case exponentialSmoothing = "exponential_smoothing" case movingAverage = "moving_average" var id: String { rawValue } var displayName: String { switch self { case .linear: return "Linear Regression" case .exponentialSmoothing: return "Exponential Smoothing" case .movingAverage: return "Moving Average" } } var description: String { switch self { case .linear: return "Projects future values based on historical trend line" case .exponentialSmoothing: return "Gives more weight to recent data points" case .movingAverage: return "Smooths out short-term fluctuations" } } } // MARK: - Computed Properties extension PredictionCache { var isValid: Bool { Date() < validUntil } var algorithmType: PredictionAlgorithm { PredictionAlgorithm(rawValue: algorithm) ?? .linear } var predictions: [Prediction]? { guard let data = predictionData else { return nil } return try? JSONDecoder().decode([Prediction].self, from: data) } } // MARK: - Static Helpers extension PredictionCache { static func getCache( for sourceId: UUID?, algorithm: PredictionAlgorithm, in context: NSManagedObjectContext ) -> PredictionCache? { let request: NSFetchRequest = PredictionCache.fetchRequest() if let sourceId = sourceId { request.predicate = NSPredicate( format: "sourceId == %@ AND algorithm == %@", sourceId as CVarArg, algorithm.rawValue ) } else { request.predicate = NSPredicate( format: "sourceId == nil AND algorithm == %@", algorithm.rawValue ) } request.fetchLimit = 1 guard let cache = try? context.fetch(request).first, cache.isValid else { return nil } return cache } static func saveCache( for sourceId: UUID?, algorithm: PredictionAlgorithm, predictions: [Prediction], in context: NSManagedObjectContext ) { // Remove old cache let request: NSFetchRequest = PredictionCache.fetchRequest() if let sourceId = sourceId { request.predicate = NSPredicate( format: "sourceId == %@ AND algorithm == %@", sourceId as CVarArg, algorithm.rawValue ) } else { request.predicate = NSPredicate( format: "sourceId == nil AND algorithm == %@", algorithm.rawValue ) } if let oldCache = try? context.fetch(request) { oldCache.forEach { context.delete($0) } } // Create new cache let cache = PredictionCache(context: context) cache.sourceId = sourceId cache.algorithm = algorithm.rawValue cache.predictionData = try? JSONEncoder().encode(predictions) try? context.save() } static func invalidateAll(in context: NSManagedObjectContext) { let request: NSFetchRequest = PredictionCache.fetchRequest() if let caches = try? context.fetch(request) { caches.forEach { context.delete($0) } } try? context.save() } }