From fcd752466ae6e2f59620f1d69de2a400488a2375 Mon Sep 17 00:00:00 2001 From: Krishna Kumar Date: Sat, 4 Apr 2026 09:30:56 -0500 Subject: [PATCH] SwiftDBAI: natural language queries for any SQLite database Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via AnyLanguageModel. Read-only by default with configurable allowlists. Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset. --- .gitignore | 8 + .../SwiftDBAIDemo/DatabaseSeeder.swift | 38 + .../SwiftDBAIDemo/DemoLanguageModel.swift | 253 ++++ .../OllamaWithSystemPrompt.swift | 70 ++ .../SwiftDBAIDemo/SwiftDBAIDemoApp.swift | 261 ++++ .../SwiftDBAIDemo/github_stars.sqlite | Bin 0 -> 679936 bytes Example/SwiftDBAIDemo/project.yml | 26 + Package.swift | 42 + README.md | 346 ++++++ .../Config/ChatEngineConfiguration.swift | 113 ++ .../Config/LocalProviderConfiguration.swift | 336 +++++ Sources/SwiftDBAI/Config/MutationPolicy.swift | 148 +++ .../OnDeviceProviderConfiguration.swift | 866 +++++++++++++ .../SwiftDBAI/Config/OperationAllowlist.swift | 54 + .../Config/ProviderConfiguration.swift | 609 +++++++++ Sources/SwiftDBAI/Config/QueryValidator.swift | 114 ++ Sources/SwiftDBAI/Engine/ChatEngine.swift | 677 ++++++++++ .../Engine/ToolExecutionDelegate.swift | 288 +++++ .../Models/ConversationHistory.swift | 143 +++ Sources/SwiftDBAI/Models/QueryResult.swift | 136 ++ .../SwiftDBAI/Parsing/SQLQueryParser.swift | 423 +++++++ Sources/SwiftDBAI/Prompt/PromptBuilder.swift | 238 ++++ .../Rendering/ChartDataDetector.swift | 423 +++++++ Sources/SwiftDBAI/Rendering/DataTable.swift | 255 ++++ .../Rendering/TextSummaryRenderer.swift | 301 +++++ Sources/SwiftDBAI/Schema/DatabaseSchema.swift | 164 +++ .../SwiftDBAI/Schema/SchemaIntrospector.swift | 153 +++ Sources/SwiftDBAI/SwiftDBAIError.swift | 215 ++++ Sources/SwiftDBAI/Tools/DatabaseTool.swift | 230 ++++ Sources/SwiftDBAI/Tools/ToolResult.swift | 108 ++ .../SwiftDBAI/Views/Charts/BarChartView.swift | 182 +++ .../Views/Charts/ChartDataPoint.swift | 21 + .../Views/Charts/ChartResultView.swift | 135 ++ .../Views/Charts/LineChartView.swift | 206 ++++ .../SwiftDBAI/Views/Charts/PieChartView.swift | 234 ++++ Sources/SwiftDBAI/Views/ChatView.swift | 225 ++++ Sources/SwiftDBAI/Views/ChatViewModel.swift | 137 +++ Sources/SwiftDBAI/Views/DataChatView.swift | 220 ++++ .../SwiftDBAI/Views/ErrorMessageView.swift | 362 ++++++ .../SwiftDBAI/Views/MessageBubbleView.swift | 214 ++++ .../Views/Presentation/DataChatSheet.swift | 114 ++ .../Presentation/DataChatSheetModifier.swift | 212 ++++ .../Presentation/DataChatViewController.swift | 81 ++ .../Views/ScrollableDataTableView.swift | 270 ++++ .../Views/Styling/ChatViewConfiguration.swift | 200 +++ .../Styling/ChatViewConfigurationKey.swift | 35 + THEMING.md | 131 ++ Tests/SwiftDBAITests/BinarySizeTests.swift | 254 ++++ .../ChartDataDetectorTests.swift | 293 +++++ Tests/SwiftDBAITests/ChatEngineTests.swift | 1091 +++++++++++++++++ .../ChatViewConfigurationTests.swift | 170 +++ Tests/SwiftDBAITests/ChatViewTests.swift | 164 +++ .../DataChatViewUsageTests.swift | 136 ++ Tests/SwiftDBAITests/DataTableTests.swift | 285 +++++ Tests/SwiftDBAITests/DatabaseToolTests.swift | 317 +++++ .../DestructiveOperationTests.swift | 745 +++++++++++ .../Helpers/MockLanguageModel.swift | 49 + .../LocalProviderConfigurationTests.swift | 337 +++++ .../MultiTurnContextTests.swift | 363 ++++++ .../OnDeviceProviderConfigurationTests.swift | 508 ++++++++ Tests/SwiftDBAITests/PresentationTests.swift | 247 ++++ Tests/SwiftDBAITests/PromptBuilderTests.swift | 254 ++++ .../ProviderConfigurationTests.swift | 325 +++++ .../SwiftDBAITests/SQLQueryParserTests.swift | 629 ++++++++++ .../SchemaIntrospectorTests.swift | 234 ++++ .../ScrollableDataTableViewTests.swift | 133 ++ .../TextSummaryRendererTests.swift | 301 +++++ .../ToolExecutionDelegateTests.swift | 246 ++++ .../UnifiedProviderTestHarness.swift | 617 ++++++++++ Tests/SwiftDBAITests/ViewInspectorTests.swift | 489 ++++++++ screenshots/GALLERY.md | 61 + screenshots/compact-theme.png | Bin 0 -> 28270 bytes screenshots/custom-results.png | Bin 0 -> 130782 bytes screenshots/custom-theme.png | Bin 0 -> 31898 bytes screenshots/dark-theme.png | Bin 0 -> 33847 bytes screenshots/iphone-results.png | Bin 0 -> 32527 bytes screenshots/presentation-modes.png | Bin 0 -> 70453 bytes screenshots/results-chart.png | Bin 0 -> 130107 bytes screenshots/sheet-presentation.png | Bin 0 -> 78846 bytes screenshots/tool-api.png | Bin 0 -> 147451 bytes 80 files changed, 18265 insertions(+) create mode 100644 .gitignore create mode 100644 Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift create mode 100644 Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift create mode 100644 Example/SwiftDBAIDemo/SwiftDBAIDemo/OllamaWithSystemPrompt.swift create mode 100644 Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift create mode 100644 Example/SwiftDBAIDemo/SwiftDBAIDemo/github_stars.sqlite create mode 100644 Example/SwiftDBAIDemo/project.yml create mode 100644 Package.swift create mode 100644 README.md create mode 100644 Sources/SwiftDBAI/Config/ChatEngineConfiguration.swift create mode 100644 Sources/SwiftDBAI/Config/LocalProviderConfiguration.swift create mode 100644 Sources/SwiftDBAI/Config/MutationPolicy.swift create mode 100644 Sources/SwiftDBAI/Config/OnDeviceProviderConfiguration.swift create mode 100644 Sources/SwiftDBAI/Config/OperationAllowlist.swift create mode 100644 Sources/SwiftDBAI/Config/ProviderConfiguration.swift create mode 100644 Sources/SwiftDBAI/Config/QueryValidator.swift create mode 100644 Sources/SwiftDBAI/Engine/ChatEngine.swift create mode 100644 Sources/SwiftDBAI/Engine/ToolExecutionDelegate.swift create mode 100644 Sources/SwiftDBAI/Models/ConversationHistory.swift create mode 100644 Sources/SwiftDBAI/Models/QueryResult.swift create mode 100644 Sources/SwiftDBAI/Parsing/SQLQueryParser.swift create mode 100644 Sources/SwiftDBAI/Prompt/PromptBuilder.swift create mode 100644 Sources/SwiftDBAI/Rendering/ChartDataDetector.swift create mode 100644 Sources/SwiftDBAI/Rendering/DataTable.swift create mode 100644 Sources/SwiftDBAI/Rendering/TextSummaryRenderer.swift create mode 100644 Sources/SwiftDBAI/Schema/DatabaseSchema.swift create mode 100644 Sources/SwiftDBAI/Schema/SchemaIntrospector.swift create mode 100644 Sources/SwiftDBAI/SwiftDBAIError.swift create mode 100644 Sources/SwiftDBAI/Tools/DatabaseTool.swift create mode 100644 Sources/SwiftDBAI/Tools/ToolResult.swift create mode 100644 Sources/SwiftDBAI/Views/Charts/BarChartView.swift create mode 100644 Sources/SwiftDBAI/Views/Charts/ChartDataPoint.swift create mode 100644 Sources/SwiftDBAI/Views/Charts/ChartResultView.swift create mode 100644 Sources/SwiftDBAI/Views/Charts/LineChartView.swift create mode 100644 Sources/SwiftDBAI/Views/Charts/PieChartView.swift create mode 100644 Sources/SwiftDBAI/Views/ChatView.swift create mode 100644 Sources/SwiftDBAI/Views/ChatViewModel.swift create mode 100644 Sources/SwiftDBAI/Views/DataChatView.swift create mode 100644 Sources/SwiftDBAI/Views/ErrorMessageView.swift create mode 100644 Sources/SwiftDBAI/Views/MessageBubbleView.swift create mode 100644 Sources/SwiftDBAI/Views/Presentation/DataChatSheet.swift create mode 100644 Sources/SwiftDBAI/Views/Presentation/DataChatSheetModifier.swift create mode 100644 Sources/SwiftDBAI/Views/Presentation/DataChatViewController.swift create mode 100644 Sources/SwiftDBAI/Views/ScrollableDataTableView.swift create mode 100644 Sources/SwiftDBAI/Views/Styling/ChatViewConfiguration.swift create mode 100644 Sources/SwiftDBAI/Views/Styling/ChatViewConfigurationKey.swift create mode 100644 THEMING.md create mode 100644 Tests/SwiftDBAITests/BinarySizeTests.swift create mode 100644 Tests/SwiftDBAITests/ChartDataDetectorTests.swift create mode 100644 Tests/SwiftDBAITests/ChatEngineTests.swift create mode 100644 Tests/SwiftDBAITests/ChatViewConfigurationTests.swift create mode 100644 Tests/SwiftDBAITests/ChatViewTests.swift create mode 100644 Tests/SwiftDBAITests/DataChatViewUsageTests.swift create mode 100644 Tests/SwiftDBAITests/DataTableTests.swift create mode 100644 Tests/SwiftDBAITests/DatabaseToolTests.swift create mode 100644 Tests/SwiftDBAITests/DestructiveOperationTests.swift create mode 100644 Tests/SwiftDBAITests/Helpers/MockLanguageModel.swift create mode 100644 Tests/SwiftDBAITests/LocalProviderConfigurationTests.swift create mode 100644 Tests/SwiftDBAITests/MultiTurnContextTests.swift create mode 100644 Tests/SwiftDBAITests/OnDeviceProviderConfigurationTests.swift create mode 100644 Tests/SwiftDBAITests/PresentationTests.swift create mode 100644 Tests/SwiftDBAITests/PromptBuilderTests.swift create mode 100644 Tests/SwiftDBAITests/ProviderConfigurationTests.swift create mode 100644 Tests/SwiftDBAITests/SQLQueryParserTests.swift create mode 100644 Tests/SwiftDBAITests/SchemaIntrospectorTests.swift create mode 100644 Tests/SwiftDBAITests/ScrollableDataTableViewTests.swift create mode 100644 Tests/SwiftDBAITests/TextSummaryRendererTests.swift create mode 100644 Tests/SwiftDBAITests/ToolExecutionDelegateTests.swift create mode 100644 Tests/SwiftDBAITests/UnifiedProviderTestHarness.swift create mode 100644 Tests/SwiftDBAITests/ViewInspectorTests.swift create mode 100644 screenshots/GALLERY.md create mode 100644 screenshots/compact-theme.png create mode 100644 screenshots/custom-results.png create mode 100644 screenshots/custom-theme.png create mode 100644 screenshots/dark-theme.png create mode 100644 screenshots/iphone-results.png create mode 100644 screenshots/presentation-modes.png create mode 100644 screenshots/results-chart.png create mode 100644 screenshots/sheet-presentation.png create mode 100644 screenshots/tool-api.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2016a05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.build/ +.swiftpm/ +Package.resolved +*.xcodeproj/ +xcuserdata/ +DerivedData/ +.DS_Store +.mcp.json diff --git a/Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift b/Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift new file mode 100644 index 0000000..99143d3 --- /dev/null +++ b/Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift @@ -0,0 +1,38 @@ +// DatabaseSeeder.swift +// SwiftDBAIDemo +// +// Copies the bundled GitHub stars database to the Documents directory. +// The database contains real star counts for ~2000 top GitHub repos, +// fetched live from the GitHub API. + +import Foundation + +enum DatabaseSeeder { + + /// Returns the path to the GitHub stars database, copying from bundle if needed. + static func seedIfNeeded() throws -> String { + let url = URL.documentsDirectory.appending(path: "github_stars.sqlite") + let path = url.path(percentEncoded: false) + + // If the database already exists, just return the path. + if FileManager.default.fileExists(atPath: path) { + return path + } + + // Copy bundled database to Documents + guard let bundledURL = Bundle.main.url(forResource: "github_stars", withExtension: "sqlite") else { + throw SeederError.bundledDatabaseNotFound + } + + try FileManager.default.copyItem(at: bundledURL, to: url) + return path + } + + enum SeederError: LocalizedError { + case bundledDatabaseNotFound + + var errorDescription: String? { + "Could not find github_stars.sqlite in app bundle." + } + } +} diff --git a/Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift b/Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift new file mode 100644 index 0000000..b7269ca --- /dev/null +++ b/Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift @@ -0,0 +1,253 @@ +// DemoLanguageModel.swift +// SwiftDBAIDemo +// +// A mock LanguageModel that returns canned SQL for common GitHub repo queries. +// Pattern-matches natural language questions about GitHub stars, languages, +// and repository metadata. + +import AnyLanguageModel +import Foundation + +struct DemoLanguageModel: LanguageModel { + typealias UnavailableReason = Never + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + let promptText = prompt.description.lowercased() + let responseText: String + + if promptText.contains("row") && (promptText.contains("column") || promptText.contains("|")) { + responseText = deriveSummary(from: prompt.description) + } else { + responseText = deriveSQL(from: promptText) + } + + let rawContent = GeneratedContent(kind: .string(responseText)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + let rawContent = GeneratedContent(kind: .string("SELECT full_name, stars FROM repos ORDER BY stars DESC LIMIT 10")) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } + + // MARK: - SQL Pattern Matching + + private func deriveSQL(from prompt: String) -> String { + let q = extractLastQuestion(from: prompt) + + // Specific repo lookups + if q.contains("react") && !q.contains("react-native") && !q.contains("react native") { + return "SELECT full_name, stars, forks, language, description FROM repos WHERE name = 'react' OR full_name LIKE '%/react' ORDER BY stars DESC LIMIT 5" + } + + // How many stars does X have + if q.contains("how many stars") || q.contains("stars does") || q.contains("stars for") { + return "SELECT full_name, stars, forks, language FROM repos ORDER BY stars DESC LIMIT 10" + } + + // Language breakdown MUST come before "most popular" to avoid collision + if q.contains("language") && (q.contains("breakdown") || q.contains("distribution") || q.contains("popular") || q.contains("most")) { + return """ + SELECT language, COUNT(*) AS repo_count, + SUM(stars) AS total_stars, + ROUND(AVG(stars)) AS avg_stars + FROM repos WHERE language IS NOT NULL AND language != '' + GROUP BY language + ORDER BY total_stars DESC + LIMIT 15 + """ + } + + // Most starred / top repos + if q.contains("most starred") || q.contains("most popular") || q.contains("top repo") || q.contains("top 10") || q.contains("most stars") { + return """ + SELECT full_name, stars, forks, language + FROM repos ORDER BY stars DESC LIMIT 10 + """ + } + + // Language-specific queries + if q.contains("python") && (q.contains("repo") || q.contains("project")) { + return """ + SELECT full_name, stars, forks, description + FROM repos WHERE language = 'Python' + ORDER BY stars DESC LIMIT 10 + """ + } + if q.contains("swift") && (q.contains("repo") || q.contains("project")) { + return """ + SELECT full_name, stars, forks, description + FROM repos WHERE language = 'Swift' + ORDER BY stars DESC LIMIT 10 + """ + } + if q.contains("rust") && (q.contains("repo") || q.contains("project")) { + return """ + SELECT full_name, stars, forks, description + FROM repos WHERE language = 'Rust' + ORDER BY stars DESC LIMIT 10 + """ + } + if q.contains("typescript") && (q.contains("repo") || q.contains("project")) { + return """ + SELECT full_name, stars, forks, description + FROM repos WHERE language = 'TypeScript' + ORDER BY stars DESC LIMIT 10 + """ + } + + // Count queries + if q.contains("how many repo") || q.contains("how many project") || q.contains("total repo") { + return "SELECT COUNT(*) AS total_repos FROM repos" + } + if q.contains("how many language") { + return "SELECT COUNT(DISTINCT language) AS total_languages FROM repos WHERE language IS NOT NULL AND language != ''" + } + + // Stars threshold queries + if q.contains("100k") || q.contains("100,000") || q.contains("100000") { + return """ + SELECT full_name, stars, language + FROM repos WHERE stars > 100000 + ORDER BY stars DESC + """ + } + + // Forks + if q.contains("most forked") || q.contains("most forks") { + return """ + SELECT full_name, forks, stars, language + FROM repos ORDER BY forks DESC LIMIT 10 + """ + } + + // Created / oldest / newest + if q.contains("oldest") || q.contains("first") { + return """ + SELECT full_name, created_at, stars, language + FROM repos ORDER BY created_at ASC LIMIT 10 + """ + } + if q.contains("newest") || q.contains("recent") || q.contains("latest") { + return """ + SELECT full_name, created_at, stars, language + FROM repos ORDER BY created_at DESC LIMIT 10 + """ + } + + // Microsoft / Google / Meta specific + if q.contains("microsoft") { + return """ + SELECT full_name, stars, forks, language + FROM repos WHERE owner = 'microsoft' + ORDER BY stars DESC + """ + } + if q.contains("google") { + return """ + SELECT full_name, stars, forks, language + FROM repos WHERE owner = 'google' + ORDER BY stars DESC + """ + } + if q.contains("facebook") || q.contains("meta") { + return """ + SELECT full_name, stars, forks, language + FROM repos WHERE owner = 'facebook' + ORDER BY stars DESC + """ + } + + // Compare + if q.contains("vs") || q.contains("versus") || q.contains("compare") { + return """ + SELECT full_name, stars, forks, language + FROM repos ORDER BY stars DESC LIMIT 20 + """ + } + + // Default + return """ + SELECT full_name, stars, language + FROM repos ORDER BY stars DESC LIMIT 10 + """ + } + + private func extractLastQuestion(from prompt: String) -> String { + let lines = prompt.components(separatedBy: "\n") + // First pass: find lines ending with "?" (most likely user questions) + // Take the LAST one (most recent question) + var lastQuestion: String? + for line in lines { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.hasSuffix("?") && trimmed.count < 200 && trimmed.count > 5 { + lastQuestion = trimmed.lowercased() + } + } + if let q = lastQuestion { return q } + + // Fallback: walk backwards looking for short non-SQL lines + for line in lines.reversed() { + let trimmed = line.trimmingCharacters(in: .whitespaces) + guard !trimmed.isEmpty, trimmed.count > 3, trimmed.count < 100 else { continue } + let lower = trimmed.lowercased() + if lower.hasPrefix("select ") || lower.hasPrefix("create ") { continue } + if lower.contains("integer") || lower.contains("text not") { continue } + if lower.contains("respond with only") { continue } + return lower + } + return prompt.lowercased() + } + + // MARK: - Summary Generation + + private func deriveSummary(from rawPrompt: String) -> String { + let lines = rawPrompt.components(separatedBy: "\n") + let dataLines = lines.filter { $0.contains("|") || $0.contains(",") } + let rowCount = max(dataLines.count - 1, 0) + let lower = rawPrompt.lowercased() + + if lower.contains("total_repos") || lower.contains("total_languages") || lower.contains("count(") { + if let countLine = dataLines.last { + let num = countLine.trimmingCharacters(in: .whitespacesAndNewlines) + .components(separatedBy: "|").last? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "\(rowCount)" + return "The count is \(num)." + } + } + if lower.contains("avg_stars") || lower.contains("group by") || lower.contains("total_stars") { + return "Here's the breakdown across programming languages." + } + if lower.contains("forks") && lower.contains("order by forks") { + return "These are the most forked repositories on GitHub." + } + if rowCount == 0 { + return "No repositories matched your query." + } + if rowCount == 1 { + return "Here's what I found." + } + if rowCount <= 5 { + return "Found \(rowCount) repositories." + } + return "Here are the top \(rowCount) repositories." + } +} diff --git a/Example/SwiftDBAIDemo/SwiftDBAIDemo/OllamaWithSystemPrompt.swift b/Example/SwiftDBAIDemo/SwiftDBAIDemo/OllamaWithSystemPrompt.swift new file mode 100644 index 0000000..83ee373 --- /dev/null +++ b/Example/SwiftDBAIDemo/SwiftDBAIDemo/OllamaWithSystemPrompt.swift @@ -0,0 +1,70 @@ +// OllamaWithSystemPrompt.swift +// SwiftDBAIDemo +// +// Wraps OllamaLanguageModel to prepend session instructions into the user +// prompt, working around AnyLanguageModel's Ollama adapter not forwarding +// system messages. + +import AnyLanguageModel +import Foundation + +/// Wrapper that injects session instructions into every Ollama request. +struct OllamaWithSystemPrompt: LanguageModel { + typealias UnavailableReason = Never + + private let inner: OllamaLanguageModel + + init(baseURL: URL = OllamaLanguageModel.defaultBaseURL, model: String) { + self.inner = OllamaLanguageModel(baseURL: baseURL, model: model) + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + let userText = prompt.description + let instructionText = session.instructions?.description ?? "" + + let combinedText: String + if instructionText.isEmpty { + combinedText = userText + } else { + combinedText = """ + [System Instructions] + \(instructionText) + + [User Message] + \(userText) + """ + } + + let plainSession = LanguageModelSession(model: inner) + let combinedPrompt = Prompt(combinedText) + return try await inner.respond( + within: plainSession, + to: combinedPrompt, + generating: type, + includeSchemaInPrompt: includeSchemaInPrompt, + options: options + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + inner.streamResponse( + within: session, + to: prompt, + generating: type, + includeSchemaInPrompt: includeSchemaInPrompt, + options: options + ) + } +} diff --git a/Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift b/Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift new file mode 100644 index 0000000..1596e8f --- /dev/null +++ b/Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift @@ -0,0 +1,261 @@ +// SwiftDBAIDemoApp.swift +// SwiftDBAIDemo +// +// Showcase app demonstrating all SwiftDBAI UI variants and presentation modes. + +import SwiftUI +import SwiftDBAI + +@main +struct SwiftDBAIDemoApp: App { + @State private var databasePath: String? + @State private var setupError: String? + + private let context = """ + This is a database of the top ~2000 most-starred GitHub \ + repositories. Each repo has: full_name (owner/name), stars, \ + forks, language (programming language), description, \ + open_issues, created_at date, and topics. \ + Star counts are real and current as of April 2026. + """ + + var body: some Scene { + WindowGroup { + Group { + if let path = databasePath { + ShowcaseTabView(databasePath: path, context: context) + } else if let error = setupError { + ContentUnavailableView( + "Database Setup Failed", + systemImage: "exclamationmark.triangle", + description: Text(error) + ) + } else { + ProgressView("Setting up database...") + } + } + .task { + do { + let path = try DatabaseSeeder.seedIfNeeded() + databasePath = path + } catch { + setupError = error.localizedDescription + } + } + } + } +} + +struct ShowcaseTabView: View { + let databasePath: String + let context: String + @State private var showSheet = false + @State private var showFullScreen = false + + var body: some View { + TabView { + // Tab 1: Default theme + DataChatView( + databasePath: databasePath, + model: DemoLanguageModel(), + allowlist: .readOnly, + additionalContext: context + ) + .tabItem { Label("Default", systemImage: "bubble.left.and.text.bubble.right") } + + // Tab 2: Dark theme + DataChatView( + databasePath: databasePath, + model: DemoLanguageModel(), + allowlist: .readOnly, + additionalContext: context + ) + .chatViewConfiguration(.dark) + .tabItem { Label("Dark", systemImage: "moon.fill") } + + // Tab 3: Compact theme + DataChatView( + databasePath: databasePath, + model: DemoLanguageModel(), + allowlist: .readOnly, + additionalContext: context + ) + .chatViewConfiguration(.compact) + .tabItem { Label("Compact", systemImage: "rectangle.compress.vertical") } + + // Tab 4: Custom styling + DataChatView( + databasePath: databasePath, + model: DemoLanguageModel(), + allowlist: .readOnly, + additionalContext: context + ) + .chatViewConfiguration(customConfig) + .tabItem { Label("Custom", systemImage: "paintbrush") } + + // Tab 5: Presentation modes + PresentationShowcase(databasePath: databasePath, context: context) + .tabItem { Label("Present", systemImage: "rectangle.portrait.and.arrow.forward") } + + // Tab 6: Tool calling API + ToolDemoView(databasePath: databasePath) + .tabItem { Label("Tool", systemImage: "wrench") } + } + } + + private var customConfig: ChatViewConfiguration { + var config = ChatViewConfiguration.default + config.userBubbleColor = .purple + config.userTextColor = .white + config.accentColor = .purple + config.inputPlaceholder = "Search GitHub repos..." + config.emptyStateTitle = "Explore GitHub Data" + config.emptyStateSubtitle = "Ask about stars, forks, languages, and trends" + config.emptyStateIcon = "star.circle" + config.assistantAvatarIcon = "sparkles" + config.assistantAvatarColor = .purple + return config + } +} + +struct PresentationShowcase: View { + let databasePath: String + let context: String + @State private var showSheet = false + @State private var showFullScreen = false + + var body: some View { + NavigationStack { + List { + Section("Sheet Presentations") { + Button("Show as Sheet") { + showSheet = true + } + Button("Show Full Screen") { + showFullScreen = true + } + } + Section("Navigation") { + NavigationLink("Push DataChatView") { + DataChatView( + databasePath: databasePath, + model: DemoLanguageModel(), + allowlist: .readOnly, + additionalContext: context + ) + .navigationTitle("Chat") + } + } + Section("Info") { + LabeledContent("DataChatSheet", value: "Nav + Done button") + LabeledContent("DataChatViewController", value: "UIKit bridge") + LabeledContent(".dataChatSheet()", value: "View modifier") + LabeledContent(".dataChatFullScreen()", value: "View modifier") + } + } + .navigationTitle("Presentation Modes") + } + .sheet(isPresented: $showSheet) { + DataChatSheet( + databasePath: databasePath, + model: DemoLanguageModel(), + additionalContext: context, + title: "GitHub Stars" + ) + } + .dataChatFullScreen( + isPresented: $showFullScreen, + databasePath: databasePath, + model: DemoLanguageModel(), + additionalContext: context + ) + } +} + +struct ToolDemoView: View { + let databasePath: String + @State private var tool: DatabaseTool? + @State private var sqlInput = "SELECT full_name, stars FROM repos ORDER BY stars DESC LIMIT 5" + @State private var result: ToolResult? + @State private var error: String? + @State private var showSchema = false + + var body: some View { + NavigationStack { + List { + if let tool { + Section("Schema") { + Button(showSchema ? "Hide Schema" : "Show Schema") { + showSchema.toggle() + } + if showSchema { + Text(tool.schemaContext) + .font(.caption2.monospaced()) + } + } + + Section("SQL Query") { + TextField("Enter SQL", text: $sqlInput, axis: .vertical) + .font(.footnote.monospaced()) + .lineLimit(3...6) + Button("Execute") { + do { + result = try tool.execute(sql: sqlInput) + error = nil + } catch { + self.error = error.localizedDescription + result = nil + } + } + .disabled(sqlInput.isEmpty) + } + + if let error { + Section("Error") { + Text(error) + .foregroundStyle(.red) + .font(.footnote) + } + } + + if let result { + Section("Result (\(result.rowCount) rows, \(String(format: "%.1fms", result.executionTime * 1000)))") { + Text(result.markdownTable) + .font(.caption2.monospaced()) + } + Section("JSON Response") { + Text(result.jsonString) + .font(.caption2.monospaced()) + .lineLimit(15) + } + } + + Section("OpenAI Tool Definition") { + Text(toolDefinitionJSON(tool)) + .font(.caption2.monospaced()) + .lineLimit(10) + } + } else { + ProgressView("Loading database...") + } + } + .navigationTitle("DatabaseTool API") + } + .task { + do { + tool = try await DatabaseTool(databasePath: databasePath) + } catch { + self.error = error.localizedDescription + } + } + } + + private func toolDefinitionJSON(_ tool: DatabaseTool) -> String { + let def = tool.openAIFunctionDefinition + if let data = try? JSONSerialization.data(withJSONObject: def, options: [.prettyPrinted, .sortedKeys]), + let str = String(data: data, encoding: .utf8) { + return str + } + return "{}" + } +} diff --git a/Example/SwiftDBAIDemo/SwiftDBAIDemo/github_stars.sqlite b/Example/SwiftDBAIDemo/SwiftDBAIDemo/github_stars.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a244d479e789162bafa84450ef2c7d26072e5de4 GIT binary patch literal 679936 zcmeFa33ycHy+3}kuqKnZ6p>QOiHH)JlgUCruogoIixLt@0IjujGINqlG8=Pdk_>9g znXoScih#0-DBuEEL`6X8{dKqde!Xq2MJ5To^=f->`)liM{eQmS_dRD$0=B<HP2gWh*6{{^%%6>47UfaHR*X^uU!KxY7exdf-YATANGVVrhDVMe<|4W%d{>Yi3WTI-x*$9oIQBt?IW|4K-jN$8Ows;?3a2I z@d%$U<&Ioh#z_ul(@>gGgYes&S6_(4%>S$F=Dg1q+*q(D?|8va^S$}WywAISoByM{ z$6Q9i9}B;mUtjQU;l#qIYi0h&3*OA1Q@FnHkNN9e7xVV#KkxG7-Cb~dq2j7{?J4+L zUT5CF=RfQ^mw%@4se;Oa?-riT|FUaN!N$UI`GNdD;d^TW^|iczEf`%8%YVmpx2r90jqCpW`~tUYTR~0U8~J9zXY)t6o-Vki z;Az*O!jBd#$=gy`Q)m`G?E1Ltp9{M4r|18?V4Lf^1;g^*FYL;nod39MO2Pbsp2DGd z=kpJ^ysnoEcjY~ke|O<03SXk$y7G6W2d?zMl^(d#16O+BN)KG=fh#?5r3WtQfwf=V z|F!A3TEFRE-@X~?;8zabgv7i5%r}YTHoo}d-3g?{4RWdqjsE0+Z}lL( z`athr??<}ix$iywS)>v3Df9h#$Sm)E{zpG1b>8j2-}YS<=@Sos@WzcuSEYXXi{qb1 zX8POa4@VF4?c;^HuJTOKb?V0-kQ@dehlenFaGl{u0wj=msXrcqEx1OpFU3N zz?RhUzPAlzVr&2M@6||$uX^UY%aJy8fAH4HdyyGt?tY0nq+s8%U&WEm{MIjiUWas| z`IXi8Aa$G1{^(Eynf&!9cHfEg)7w@ZxCQC#)O)Y~x(u0HyU+gqgb$gIZhZN#)Ti4I zum0bSNbh>|jo*zyn%K4KSCvQ~ec?ZTTa0va>YvV2N#j!|zw|@{G6%nYa4YdTZRU!L z?^9=0Kk@v_Gz_`h_R{?-^WD?0j6&vPzxdP2TBLJdS^Fe)(b&|}KmG|lIOxEmbCDS>^>L}ZazT0T4R21*B5C(=KN{XD^#}f z)ED1fM(IQ5j`Rm!WESqYuQhKRWA01s3?ft4z3w|dy9JpCQ>l%osR_54 zCsuroYWDe`z4IITj`HwdHa|-f`o_1;eVJyT(w;i{>>utxX2za>|7WVw&8airdyEEp z$oEgu=af&FZ(TfJgUm-xy?cmy%5&uLU4FW=|DV2cEDz~TTOa<~LZtWa{ONg`RgeAl z7ez=uwRXq9Pz9HDKl0{_^x~bXe)SxctOQqX_yRrgz(a4I`W({L-TU78Jq_f@uN*!3 zAX4RC^N)XBF%+5j-h(Ts8oO7&_$}(m1N+~5e+W`#>B=o{uBA5ZNp1bgm!~4LIeq*T zt>PVTeDA3Wq{@`nU;aTk($+I?9i{P7npeNMbr#Yssh@rOpJ+VpPVM-^!J*Y$0^ z2I=C|-e3Q<3K@U*TYr6@8q>A?)m7AbrRnLl-BgpuSMGWLFEmzLAN$6u`A9>DHoQP< zUHRO~*SAs~*Z<}hsoRit96SD>G{cp~A3y)rWTZRKp7?49(hch$d%G^%HC!po|K-PV zzQ*;M>q*ze{9hK77tU~9UGPA`uELMF+FU<&O)Z#La1u{k`Mc5sS9;(|4_xViD?MBL%IgVZQQtO zM`5w+F@;$=MY&^KPEBreFc6LEe$AVRMfjz#){)RPyb}oL&uCkLHiz9gOL7f8)*1+C z!ARKejd@6&s~M3*%%^K1uTP5@nl~CX2CrO35|MScY7ca~B7~K0O?t?Tw)W5}v3x77zG>x>3?*WVMx=1ySV& zJo>l)O0UTom+Ncw2HQP+R8FKx&V#>=V{>lLMe~B4&^ZUaO@_xh@fpV81;?isd|M%I z)%u(N(VU9hRRgiiL&pb&DZ;QnI-f)vQ5c7NH`t%7?#}fCf zD4kd3jLpTEpoust$NzG$;2mWY-};b4;q3}@{vUGNarr)KM=)u0wkG19V3!t37y+LK zYk+nr-17_1v(!Q<46^=QnKnLWZmz$*)f+Mb;g)`rARuQz*4Oj%3V*7!IKJ@@&Z_@M z<-73IHOdEy@C`Cn6%%XAG*;z$qOT)}1dehtxj3h3X^JFeo8OgA(H5Lg+5{4R}7gRq? zHi8k7a{R;p%#S~|Fkcxnw(PDVs`vdc74gSox*m<`SlAtbke$IQ6}PHXdO+2?qQOWE zmL<_(q9qU}4+<5}mn;s1)CRU0ncqI>sd43{rMGIO6iaw6F>S_Yxa(=~QhT0U1bRu7pDbHV(w@UdUR~U6J zuLQ`c(5&~}GJ{1--+@7oyVdK%a&FRN&AJhHx1nCb*vFC8WEI^ZtEK;#xsGmBJAs!} zpVx;*8i{B$5{s)|>aay~c^xed=$*J7*IOd7WL;AmHKIe;s{Owm+4O+j=rS(A?E_eE z#Y}ViBEE<>8u1(MmO!(+RmXuBnHzw~-3F}T2AV=PVi)LhN5d^{!{4bli6@QDKyw^Z z(mT!pp%G1@&emf$UvAu* z|84m29G~5UQuF$Ue{%zhE%Mwyg63JRU&q(=RvnG2%?j#W0oahKuy$(2 z;tHUj$Yjfi1QVR0)wvL8$q>D~#2fSL;gW@krsPuBh}Fs)*2l{x_bVC6n{WY@>KYEP(oCycKoBgKBdOMr)lg599gn zb%MgM9;X|?C)P4?tC3g$pvD^vCb3#N^dQ#0U&Ydy6$|KL|14eimx#ibbc{Ht{Mc$# z=~QRQ?kInMW$D)QRRzlE(GyoqADM%8q^3q%L&@cU2mKO{JbiE9W8JX8tHPIqftJ=d ztmjk=Mko;u#FLsohPfD4C-59DcTXxUort^Pcr4Ha`?Hu)>}V>D1;asVTN1RAijUIM z&4CtBX*6CX>fEp|m;k<2E2yXlTi|(v64)B*1Ti2gpr9Lt`RW?nDu_Baa0g8@oB+Nx z#q>_M8pg_XtI^gd4%~9Ls>glBZnbO@O2%{v;VbcoYQT^V;3X=m6vBw1er_=^VVYPG zEfMu&{o&zAP{6E^x1AtUT-DLTR7g8e2bID7K;Q}#9)Jrj(VGT(ArfLRW_MjlL#rMP zX8+{2=C5a8D@N>At^8JBFyO|Is7H6CRSP9GbW$c~AqxAJ#tp#(JcMq6M>KnW$qR-% z8jJW71UWEjZi7DU2GIqY){6}Fg*X~+`W${*zi~0gK6IufUl~2tbAJ)dnOa~$z-B`x zZJCTmUYg|vXjKzom??%au;>Io1`PR-HyA+2^j`%uHE6sXKt0Pf>Yl$U-xobISu39` z3w8i$H@(tkxCvwHv1J>750vg1b^E7#^6eM!xl zg>&R=v{KK2`|s_0c!k=C>4FXj0Ts}DYNT1UpQj5noz)3`qD=@5_={VAH?h&KLaEug zVSm0$88zx=AH$`T=F?$Sn{*yM;$$Xzo>wMX+UPaf)ddMXp;OrmRERtQdi8=XD;Dr1 zyy4na+5aiNHAyR<!ultiAWU&N}&jrk44nP0zOGQ00g336$6ZA4#G(lt3td3=$PQqCGx5R6a;F^U8U+_ zU>3ckI&18yrT;ekZPEM+t=y(3uygVL`Oir_3 zXfr@@wd+Z(!y8QKnH(5!kkM`cVFUDv2C9zWw{@2e2R%3AaS};BS(Nhf`XfwRCgl z40WdLhU$qE!==})94)%5Oe?GO5o9*_km)R0j%a{6v->ap0-CJmxDhmy%pq-hdombw z9zEh@T1yc0=~BWbO92-G{s4pq)6_fpc|lDy1tTq*x0B`=i82B{)gXuN79FdEqzgnh z6P-gp_ieAW2a*z&SCwy=pYk1SHU^MZ%zH z*e*SQ8Yi(>;ILt{}S(%qP0>D+b$N^74bmGns z0j=>+&>iaVImlV!|F6yZS0(3E&NbPch4*LuYrz`@e%A@tb@`voYa9NhVXqGT$=*^;cQtWQ_hKQ20uYFHh@%JRRm@-jRe`t&UrKgk^%C730KEio0J(smy0PfzU{TMG z-1FqngIPCQl_{;n^I(Em_UnxX+$|BeMOC>8*gzo6z{VW6+{}Cvyf~O+ zI0+@yr2wwMc;;5yn59OUt}MOC92>sCfFvm(B> zmJt3S2d5KEY;X_;7)`=|;N!sk-~u?LIUq#AEGdVV6ug;RWY@fu__#=zo`$GJKI^u~ zEP#4`D}zl%v2O7-Fk_H1h6!tfHj>B?ORD?+Cx5Y^Kp8P&?8^IrShI>!mA*tQX#G5t zvDXF=p0zfJsIx?Bgq2vuK(r$*Q>F@;EnT#5o(f4r+}q{01Wok7%(>N#Zgtk&c{MmT z*7E^e_3XO(IW-Hd6c^?+(oH(z1U`5;p(9S_&7>1L;-q##BZ{LVPU`FGE5s2e3ma?k z7#;bf0VfUBbl|Tx)XqS*b_VV>)Y6yeh?B;edGl*fVk1Abq=9MRB@GQF)MV-rAYinW z1ZJUREa3GMLXJi{A>D(d!jb}3D^I*N`dj0z@hiL4M_e|>z)eNCo8wWyg=i}%(Eh?f zA|Egl!4Z=949B3~8wIVzU#67gnC{TBc-#$%Y=ztKMcoFKW{m-ppY|Dzh`2)HObvuD zYJh2Kkn|#Me*DQbAIn$9jwx$rE_;c%3_3+*GO5SDvk;4hxe5m-kpa!>g!v%BO73is z+(2;#-Y9<f=70Xk7#0bi4*gqPrQfKgef|kMRex47|0Xr=b;QC z3nEk0`dFj|axO6aE$Te!IY~F}eR0Isv#Z31fh?x@+--o+mT#q$NNSLIF<2nUg;k7Z zKSUp564iOhh?}2z;_zn#jXio4ulDH>?Ruk0=g}ih0CsT${1LNPYAb|WT96oFmQCPh zC)1TTsr)!K!p3+^`w0$N3f`1|d(^!rd!5~=a(*Av$tuT5jr9{hOWBn55tz} z!F!@KZYA`ZI_5G9QwfvvN&~zxBF%ATJ~B+N)fI=nbM@m{FIintHrcN?C0f8`w(vsY zLpdO0SOeb(3SJ8Z{Qe*~L8}!s;$2Bg%wXtPp)9mAAgO{dHnUuA*!$Pd=PNglp7J1* z&$W7x=oz2j@8pS0JpJy?B1H~C%{nwsGAnM?Asqpjs%dB>0MfwBqFTdjG9kFETXy?@ zJ>mnad8N1ZE1B^X1X9FdYJ$Nsm+g7ux&p;BVyZctr4(i%qc*K60?kk?N|M6X%BCp& zJeS<5qOpLUf`sruPmm*q<0u%CDm}!n@+jF-29gxG)mWq{VTlX809fp2&C?oC!hkrL zc}2s4nw7WeO7lmFMuGa6)P$kdqC8oAV5Ol^VSLPoN^x}7--)Kh$IY@mC+ZS_zriO{4L$r4KYyol+}349Wob7$C!7@qXK) zt8254$cq+oX0?)4?0 zR7aQH%jnESI3VbafqaxXnRqfcs>CBeSQ;w?A)jhRzks5qZlfyG3@*x4Oi91fHYMxZ zR?SK$n`3tU`A!Vs=!w(8?SN(-Z6rg@k#Mqq;*qC>ts2Aw%;MNE9!yPg*iE0xzSS;z zQWL0Y04n>D0knc-dK|)c`3<*BHBo@6?ELcQ!1j+RCPpKx=;k(~e&#F~WI(~=?Xpsg zC9YO8kpjXWGb__*6?ZI259S~D4KPc)O~vcfIv{61?12I*|FDvjS-Xs;FJ2Dc`}bJ!Lq2afDumy!`nfSgh(4O zzJ$koktmC*yu@1rBtTS1t0@W+lsHldj`^!%(UFNlW*RM6I_Ub(i&X%NfN}^5B7$`> z+)}e*r7>`GP?zEBx?8I0-DKkd@vKNR8)g#u4O0lnxFcOw3L`5&;B#As5Cv&ZY!0z| zBt&;dP)ktS(2}D8psqkD>43qlkvMQJICbcpDbq~fVdRB(2Apfl10d1EpylZ;^nr$& zkb%U|{R+7g(VtoR)Y(q(y+sw~7zVU8EXj=+dX+#UpfWN!AtuJnS}7$KI&}fZX&9i+ z478B^65LR>ATY-YZm%tTSUD~Fev(!?nX3Vt1W2R(O_usA;t#noL(E&A zdIj7AB-)<`U6@srYz8^h9tBX*n5Ep^`iG&FqTY~$Pa)1&gIx~Hy@1I0 z45jP9He?$?Wr@1b#IvY$n)pm(c29d@?-C}%yNB=;NreoOBLyM;xisUkFU=BWEgCCs z>K4}Waur4HfXnPwO@VXY$!LkzVZ+Mkw~-mqYvRJz~UP&c37pv(-5v4@(wHi?Ij z0Ns0M5^v2BUvZ&PXPvlbYNT|S=&s3H*<^yjMEqyCUiyxi_uScqF5>^qLY`BuW^WTF3J4~V$)q0p z4#8>BCbgOcXO`d%L%Vo3q^_F0JmDtNrr=?^hFisIJwg}hvCN<*YYQf3LE}z?IvIvv z$>-m}3cYsZSgSg)C}q=9+rG3u(W0JT@rccE00y^!#s@ViW`WAn+-6k-L&Ov65(;xs z`d_xY@xhKJ(j7A^7wmW8nR7M zol3225@^O&cPm6C%$%`EL9*;ZRxX4LT$rP(FcqS5!4cyl;s|wi=2Dws#-2VlxPX;6 zv)OdaoEz>4;a@Xa#nFR=R<^nraw6hPYT53Uw7>Or4Rh~?RS}3!sGQ;|bWg1$S_3d9 zU**z z4L)cv527+C=?Kv;20KTPkzL}LsgN{^k{_~dPhbj_eEF0CTj5a?+Zj`gk3k?7X@>r6 zSpwR3afA_wxCkp@AQ_Or%`|8br3o=J ^~^g#$^N0@@9)t!rL$>>#iuKQ!Bm1jhA zrULZhK0U`{lN$Td!6?)hV>P1cV6E3cnuAyp%IyIe&=_=MCW$af+@OKFhNTG90Knfw z4YN%leH%A{)6wIYSfC|I_YT}9s3hf?Utd!^^lDL=TeXT(Z!|J+hjNKuGRy14kcm^QXbR2;rYN%Y4rb-Xtb{CI)cQ@*yI0W#w!FjN1q+pcY7{ zghg8w0J@F(h-)w9hn?!e7r`+a+Es6QWvnY#3Bh*56#K| zbc8~7;#9GS&VpqjOMQqkfYSr8A4F9BtZ)qQRiOZc9yG@n^34(^h*iNPK=8sg7DQ8$ zcrOu_qzX@xPy*IHFs#w#*qr%UZOpaZ8XK5n!6K7oUV?hHD&C)#ZI(8&tbBrAO^~vr z|Gzrxpi;QJ;BnWAydUO$V)*`H-yXVssB6f+!P|0Q%N?1sGp9J)lXWorXIXD%EmWHR zdm#AATmLsbVCEtW1{*b!MkuSu1$YBPF3W%g&{SLyM*yJ#cNg*n!;%$YdcvOtla-(k zM3~T*gG*vqzyz`br~E!V=&SZeUJ3Me>P*<82wGQY=`9O)Vk+uq&ai3*TmhLoxcp>eWMKmNT0IWQH4NG& z+(ZkY7#M{32C}g7#;L|d)#JRk6x^)*%C6)jAMg+=11KcFnaRZ*St*x?wl)skhD=C* z?uLO4G(vzn5qA`91e-Xwf9E+5j3-7~61=BdS*ac*nB_On?pTc0nRWEOq%=tqHW#+8{huqMTf z1AEg2F%2J?H*#d5RiT?Td=zqfe=_U^iw6!sf-*ogOKZnS(3UP30C+az1S>8mreTfD z#{3SWNKtl6oomNOtXh^%?Oyo$;rT*Qp2b4m)ID8IA)od0Q0A_PKfqOjpaqI5Kb+{o zEEZ_Cls``TiVbhEDxiyrc|zzp$jN|e(3KV=#{iixQ9Vh~K!)esBtNY4L3*hCUWvK{ zA~)EE!{Y%$yLf=4Z>)K1_5$BB{fw0EUvSAneJ=qDtE- zhzDl54q+G!t)X9o7znI@W1)H{6>L_{+g)1LPnzzwD?#K@n@#@P{Jq91Z1CIA1&{@X zX1{>08+Zw2w0+|T*RZ{Pg8&2QF4)V4Wzq)M96pCpy3AFnL}#IqG-ui%Oi;Z|5tvi5 zz#>k1B}ZG9es;)|?4f8&Ddf3Y`BXFa8~e&(crbEeHCu=UqENa(Uj{`@ERgi_0k&ZB zS_2d9JVECGV#9~jP-h6hgQ;iSc-@@^cF|L)m!;kfQ;8pjHRG@Z>*2Il-tJE z>Cjq2=j9>~j>}9dx=fZQhMn#Bm_EVIrX4oMJZ&?}iKqygYM(M%g{pGN z%Y#0pe8=v!O0ageXrA3oE>j)b+2k_!%*`66q0XTsL!u=DEJe?tK z!r~Z~xdd&o-iV~}fVw8r1NPm-?9BR9SlRK5>{qOMmQ9rfxSgQU7fVLtFa(D6eb@(s zG(JU%(N>rxlt;~LzWUIDd_|jBtBs(l)frJX?6D=?@*K2C49OfvHI?g?N1k8+n%Ho9unR9?oewgu$%pphy`TLiaN z$X{WD57I;?o=YE~XI1em;3eBoGuX5lbJ7jLnkXoJDcRETCbn~wq#ZLw5K#jvG5q)Gg`8RFQ7sBmoiIBRfu>EZibkb@2W-{m!1T=G?%0gU zGQD=Q0+yY~N{T^2!~agU7)+kjqf7M}$WxX3$*K-Y?Yi^h$w~HTmZe5~`C$l#pvzDB zt|i7EvV?%QJah15vVyW=EY_)$51tT{5C@D|3ZJx_b*G1?&vGCHT&rB4KAefo$Y}(@JVubmP0OKiYLF_XT`@ve|?rBLO=IP$WSz z{=_nOUc)>}5|QV;>VPXi1uW@kiA5qE$pMrT;eWVQaMj$=KolPc>j(&8K-%Qj&8#o} zry;F4SfJzrDu^dD2f}OdV?v&uKcJ{n7vrZc`+w1d-328@;46t^q_wkG(YzQP4faOoK9Q&|%4-x66by=HhDDZwT)hJi}&G z1U7*cgtaL$K=_d^xmbmRgie=})5z0@+Oo zC<5|Na7T@pR$-3&;kIdz(O)}$k&qsakleZvHJb5nG51Cl3G+bmxz!|Gi)>vNGx$GB(KnmpB_|h(AY=?5;5|Tj z@GhL5-}&gfCpW9ruvS2<6&No9ToOVDXG=;^U@5XY&44o|J4wkvdI6dXB+Qb64+3?9 zJ-Nr5_h*Id|1cFQprdZ``)!jHg3qGoAnQR*#8GMrEOkK~FVKhikRh`8l%`LAx0WG4 zJ$8S-GG$cFbe5V-6%ufaAr#6s;Ra$7)fPw3WuL$}s8#iIg$imWOVLZz_x3$>QcxuE z{~W#VF1tQ?Q-hS_dfTNKSFCuZiYjI*Xd^qkrqT}fj>wj$m4IOTayOF1! zg}Eg$^tAS{?m#SLdd|U3DyKJ(Zp)fr*Q?C@_)mT^f~&BEfq+@ql32bRvM0yUBTi=X zCHk6WPDyePtYoM11gH1z8di6MH4)(@Pe%Kq1YSX8!$ZiLR+G$H%X^Bwe%NwY!V0tP zM;q@#6~|0m#sg-4J_vf>h>K$Yz=_V_ASg0RV1W#(*OFKfCC#&vI|W^2o0?#-9^V14 z?!#ihf*;MlpZIJEkD4&{t>g+q6Npznm6Y+U1Z=`7&pJCtkJ=~jKj4bF9V#4Wp5JBQ zCL$gPFdB%MQ_p`HMv>$r&Wcin7~-aD*>uZl*|6C6QQ*M`j`N zs#Y%aRr+&8`=E!LB-D#Ow|E2zMns3(A?YGPmw_s6$6||4!jA%wkgiV%iDEEdcgJ9+ z1dmhLdXdY3)I$QOgv>kMI$Po5Fhdg=n$*?9b}Q1*3DU|RfZYuybEG4a^JH#k0D_D$ zgg`g2yAZbRp-2<)3_R0CgeNQkD3zeDf&9|q#xU#`6>d=1j&F~+4za2N>nF4)LK z2=LY?8XNC%t9JvOu=*M3_wI#c7PL?&cMdPSap)z;Ak@2ORoCE@OcTK+0QEpjsV*je z9Z@XEf46!U*Z_E1)JB@TcTmY2(r&5LQH7A9dyG~nxq_RL!8~b)T*Et zIFE~)jc7ESp#cQwO0ueqHN>rpM@2qn4^A2PjVAgA=DRu^Zv0wD+>I8XGJGs|x69Vc z6o{0DMRuq=2>VLePZ6UF>r9-8@EcI7C8#ER9Ppx5fb|9thkSM59jh$)0QDRXqZOG0 zl0FupS_QbfVo*|a*@I+W|1T%*fdgaFZJlFD3gi(;L2H3uH+EU`2E}+P`AS(ff#f_u#~7X$Sayjwg*NQDk;(92zph?ukg!~oB<_Fy9r$O z&#d^-S8m|-UCR+H%+UZAV8G%asxFuFT<+pD@VgOq)fRGI(H?QUdxzRF=zUSUat+q& zWcg>=`tgY+OJg~OyCvRE@Jdzzh7qD**%bFO5C!e-&aRa?Z=Sn_rIo8jGewrl!dnp_ zS3bgp32wD?PC2SHvJP9adObpx4mFU@lIcj2aK{RmQ*DFQ1U*V@e`|mwOGWCPkbFUR zr=DN^unm_*fee!=riSA!qKF`J0<&au-6|Q0k+l4lEkXNm0i9g5$lKf`e2&=<( z!|-P(xd3VhtVe8d3_=E4Wu&}`!*2&OB+vm(Qi)mvM_g1Li-jaYASQ%R5|JYK1)-7_ zF)sj85d;SSox%wi5L$R)$`2tAcF?1yLCzvolIM?XA&QVK%j)6x3a}$6V7weez4RiY z5E}O7>)88;yADGCPB?iF9M8 z9m@rbeKlXQWIQ4;8m%$J7{bs)tPfm>KsucPuuvq(qJCDHDPnksUIppn4<<{s!-H=a zv^{@G-XnuPn)?7O|2qmltxU>JWxb;Op`gwDgt==qoO;HLt85okeO?qqJ7JPGSi<-r z#YeCYM;5jg;x?;8F(nWWsEMe0nt;#@I74<(lOis zLWQ{bSk3`LU^PP%KHvuwM!XozNUI8vs)vH+ZL}o`hjTHnh42Lt3k#Qbp_?-Y4`)Ah zH}(C58$N~TJ6^S#YP^j*lRv|=fxGjwxohU0iZv17gReiFZ7kW5Z zid&|DMJNrLAc94hkujJj5eea57DGD_#wr2X7}v|JF?TcWQ67p0_TWmD}G#^6mVcpW>kk?{yRvC7PD{>B^xOAY@ zVeba63msB|=d)&f=aaA0Iol{-XRk3-1q;S15aeAF{C%qq>m@U6?%ITaDC8nmhUtW{ zs08(bXeZ#wIB6pcN#|e{Wv&sBP58LkW-Sb7Bs1R64H}*_Z0sP(18K;EVWow_Z{1xx z&uP;DtcbC| z-1G-12H~o7dtpuvtN~d4i-tg`@23DhoO<}`Jkks!5E>%21TC{gikMlQ({WsR_jSo8 z;6szl>&;Euuf`-Ep^5l`<`B^Gpd{vipaTW!Z=;{baZO|q9*bhaAWn_1J(w)Y7%sOg z89poPF}o$@+}ltMBG3a~5JzDRA@bCHZi7FGP+jzhIN^M!x#9hr@|DU_)s-xsy_qD` z;DaOZO|Y2)3~RtYMI1esdw|5^;-!pUhtvtmBj22w&aM%4CHuO5Sjrr1u6}r;FJrjvl^J#aGn3=q_*wqVr|qGRc4wHYa5(0tg~~?qk#Xvg)nRmsK*D z;UeKo1A~kV1J-V?ej@;K^6H68d3Cy^rU0Qg+l+WyybZP|tUBNe1dvG}Bd>|b9V9T1 zk=cU*Y(^F-031{aA)*PrI{15}DF$;4cZ^A<&otk@5$5kBip>bC{5nBJ#?cHouy!^X z^z+zP2oP{2EV$hh!^Vr+P4G()9+}TLI61;G5Zse}BrO_H&QU-)AS2@Ym=_c_la}I+ zXcD5OfagnUXWpHC_{OYBd$UN%i8{mWVX9pn8`jw~grS1d6ueqk_paQ1XLJup_|!(03r2j!n^FZKceNBQeGwUy3?1Dkk7!Eg*T{NA59fFgPBSARO4xwE~ ztCh0%vb={Omg--rh0D|{vu+<~xb`EtWJk^22~Tl=+BVD&DV#$h%Sai2IXebka^t3^ zikb3Al^LC0vUkuD0V_xTsPZDR9xI;+G>EI}qPc>~lEf-$B!Th>8wlN=n3iJxFd~FB zlXzr~QY-A5m`<0Gupo7`4ScM~AB;Bzq3c!UcY(sm7>tabA=`5dP;|m}&rtha0vZh*>mygu+8OnYmzpiSYO$!xx~^DmEm6hLourL@1B1ys=he?|g& z5KKd_os@~kc?+_1nn#r?`94+|6N**)?f@6;%sN;+^s3)0KgkTg`AKu#*9QXtj;&ZK zi%@F}vTK4^NVrx~OgJu`WYqYAdTU7+lEtrFO<^b) zzaXFz-i2Q%7X%Y^u9YTV9B^vgwu+^G5I%YuWg`*p7V4T?=|Fa(sS&@uPz=r2sN(xs zhLNhpTA%>`KnU#&zpGrUR44^GuImOp zntL#(B!5EQimda)PYwId(6vL}P%4I8qZ}T*xNvd7^SQLYA+{S`wf(suXw#A7mM{+1 zAR$Na2>XC=g*cyv<21znAqO51lukQf0(}CtXMo^m#tnK*c^DOil5R4>jyljk7nho} zYTvIB6btdrQX&q%>7C(iNbvPn zKK#cy)kY^n+Qgk8kS&xV>Ll(3oyu5Rh+LQcX!vzQ zM6)X?thM;Co1YX%4W)q3HRPFmTb@Bkjm{S*cmgrf_+Y8Z2$VrOM$tF~%*nl0q0Kk{ zQGe#rl@||#03A1Wis>q1S;fS;VKAcD2m+sOx`uT87hNc$g{}3WkpYj`$kX(dlEb#BYMkJ6OiED!!Mt&h1H}AX@Zhj(at??V0gX)Yg?-H|O{EfH9l#~J+Y<%}?C;-Mx=vKLPRuLp>utXFXh!W+cqK|z1e!E@e)($Q*#o7jsXT1zvGlB>d zuX^}gR=!d^<~9Q{>9dRAkk3NrrL2y_sDed;tkFs#9d_QX5piT7pQk0HH3;|P*`z`g z^1~|eIF^m{Gd03rzP?>~%Bo}8R9PTEllT(Wfdj{JN$xn@0st_BM(U1Lo6mRheiY`- z%w(p>6p|)G|Aed}=_=tT84sPv{#F7oRdY3-xaeef+hBr#t7(UaB>*YxKRMJX>~?9R z3c_5_j6tjeD+9R7WR+dK{)4O$cIBtQ*dK(hbaS;Pk=9>5Xw6h=7`wZQy(eHG-V7Az zhDTj6(FN74W4ej;4w-8Iup%LZiv%vQ4l=z%WXb z&bS`lY`QN=@jam+##o!emV08M8Fd)=qXxdW^=}`SaL! z6(y6P>?R+sTJi&{#w_-c83MfG8-dfHd9&=k5#`$q zs1^ci0AYLrG*$}RLtLdf z@8snPNYL;|RqjB7A%jnIPQ9pn^_#h8MFl~KO+sTJEoBvO25w}C1v{iEf7hJ|n5`?{ zIe#ad{zs2P6c7%nxilIor%2GcuT>@pviu%bLji5)7h3J?R| zo&ljV9MPToIVp#)8@@jGPP@{u-^d`heiRP}mBz$^bUrDpq?%-Lw5+V8SVv=uP1;fdt~5n#X3`i-_Del{HaBA#1c2geWusQhwg~hq z^yDJaJ#o@-4-5hQz>N(a@|Z$Rf}RIaD9jka;u%o51M@lE&XjL$b3c_e$m)yINff%D z?N@!^)mvyu^dtKKub~GIz#NlW4Z)tH2ADPw``Ihkzw(F2;bAj&l(q_m4`R2fKz4_jt$OCoDhw z3G>EPhgL5V{PaZD$&_J32_LND+6Sf{>jGgfYa4>O@aBtCxNBN7RX)d@B(_VP1)NZe z&cbk2WqL;pN`tf)E|7lt##INlxp+sC0J8}X>IOhR z$cxO;BTi<4ec=i<2fE12A9jx+X6|`2fy@#H*+3C|ZLLY$@N0doq7EQ*rs}?sB`z1` zI%fla6v8^*AA&W>8kQo*yK-fpRjyLnmE1oU%+2{{moNXuyz7Sth8}{oi#6e z?BGp<&gJwMj(+$M{jEFZy=x9{nk;nIOA3i3@o^9+!MR;Ni@3GWQHv+oK1^_xygZGm6vJa+xg2T20=zCp73ZW@B0C5;wgIVxjOxag8)gk5#Td+&$`Ndn z7OUS6%rl?_aV}~mnNPEJ%?rQIaVZlg-X+L(6BgtE+e9)7{ng(-6+k}oG%Y4pdl6L+ z4HX&$Unm);w+1sg58r77;87=l5duUi#>7VaXAxpf0&l_F zVxp5LEU~vNL?LQzHuN$Muq5CLYQ%4dLKb}h2LDAg5kc@E_AJA6YxrAJ?!R%*tXgyW zMUel{`2!Ia`q@P9yjd-7Ah3>D=!DuwtP0u~#4O1JIxIuadO0 z6FAjNCqcIgAOz<{OG?F@0EFBWMrYaspDMYJ0IFzyrK}jUl-91ZvVN(Lt=aqY2VKg7 zqL$P>SZo06pdPfUSX`@X*6=ave%NrfXfu-9orz#lD-ZkIkOIu$Y9{CT`>*xve*WVA zS9^CI?b&rUz4~~1?F;EuhkLhl_dfspg_A4ZJ+m?W>hZo4hc50~eR12tzC$KI|I)=h zPxkCykv{e4#qPuO%;tmXqZ@l3J<)soY~Pv_y^pW#J$170#Npm`Yu{e6zW2=b-oqO% zyt<+1#Yf*g^Elq^JN9V$;f=jVci>9;TzC5Hj<;8ANWXGCz5DQmQ~2&~Px`e-@PFat z`it9Eq_-WdZoFo6_zr7%K+imV&5onMCXisv6)1jD8s5_)OCCi2d#| z&=-cOFbjv_OGb&QeBt_0zf=BXf1s4K+-?{uVYVBiq*1 zk^!~`s#LfMmd|8-gsmG`1}aJ+pV?=2c_++jw5Q*szbm)|rH^p>_?kFMktW-F+n;kz z-qeIOuk45FAO0fU)hsZtoP(`&VspLWdD!+#CLX>vP?`osZG~7g0Oqaq7U2c-JsUS; z?;r*(xCej(e2C~=0HyFsgzpWx%tK5=aq?jAEh5&zCzAG}6?g($rqZ5-HNwV`3gXTF z7(>I6kz9ZX+n@+&hXTzAuqu7;$ySP3O$b(^0pW=ZTPi!Q14Q_2sL((r0o>{HYOte0 z11`pXc3tT$c2}3fT!2gqG}$B&84y$yt~vH-C+pS(~j{T|OCfr>`B(GH}4PzTrj?s#Xj! z`9!B`*Btq&Qou1&hp=H}3bMD1J=2!fRWA%B5&Ss< z#a=S%Z?;m8b;C+kgwmxstl|Q?uq%!rEZ~o65eQpDKgW?S` zOmbl?feEZCobm~l*!m=xDzUP`Y2K4uSnx!4jx{1MYR5<2{k|;Ds1BTKVXrzmjdCs~ zYD|M?poM(k8(byu{;8>J_8*)D-P=elC4$_Vo!F|w2w=NEJywVfW z8_BxI+uusd8|#{Up1Ko^Hg+sFKfC6M?gt8#;^Nt69^xfp>W_?)IjEs^BD642)!#aU z?X>gJdOj1DMFg^mGe|&IfaS~~i|K;clb6;n=zGCjLirCzHbaEy$a?Jf^~baAUa7Q% z$yQmmO9TyX0yYl(O%j(_L?f{betiMwEYnk|1yT~)Kk>-Z zM%11tYMu}D>4?EIqMw+PZ<3tVawrh{GuWn#CDJblL^k9qFqkIq1)y_@PAS|W$*alX zz>ZvD$=f-e2`FL4l?l8t4^FvmwFV|MZQb$>jasR@G~k=GbCWv zn8He5AqB5x77RP`St4X+x7oJ>J92>#0iTrqa_0U!ivZ8c9vr~e z+45$qBOIbH>Chls;E))*vgQC=Z&HSA9y}s-TTJu|TO9QzJR3)M!gjK|?8Ls@DhAE|_X7^>{nY%JnZC2}2Y$wqhB> z+y}9{7{p~>ANIExn1LW$UV_n84L#$4lLTdHc^L6TQxmpwCgVxk;!bWuE*_r;x7J0E z=YKjog%JZwH3b|$6{_(U-Hs!J0eg39affs+Frw*zg$s5$NB?~BztBhj;~wq4cm0Xo zknf`FyRT-AU3VpxKqpK|n}NXxW)NglUTP4TSq)Nq=deE}!*I~hQnbRDPlm*|?o4Q? zK!kFlJ}mi*AcNu@Q3x&S0SWuw-^*d1UwxvwvVu0+Ov_^CeMxY)}BDzKFJwv7y2?b@WlW z^=-fF)_bP#hL9=3G>3ML)aw;7}Kd?y{=_5DAYE7+_dR z7q?M?fBU{YDDF6RMeJWkvv~c{UnXJ1Ill5ig~Yrqa!mpp`AG?hNCZT|*?Fu-Tn*{( z?c4c~S|#$5h7lpfKrO0dd;yoOW)kG!i_DV(@n9=;V{m${vhs!7>|QOC<+)`lbe2pM zp-B=bQ>;ZqE(jZ`&h`7A34!J)n$j&I5SkO)5F(3IV0!FwIZx)|op_KWwm^$y@dHCC zlH7<-KpO$)HaMYUHeD(Mo={&^TWn(&L5l$I5pFj~)maDC7|p1;jY4y138|nN21XFL zmYu{vs)+dqEZopA2et$=5m4x^jN;keLzv5hLv19kS0Mlq)XZ#aM}$x;l4xm#q?7bK z(n+U8(BT~=*UgAnT?=IZp{z)lm1J&T5^DzvYAYr{reA=`5j9`Bgi~ZN?jv}j205g! z?UI^`uiv|`8S0O*6U`CKadZRLd8?C0Nuh6sXx;6 ziIxyfu}PNxq1nKcL*9>>MlpEE-EV;26)`HYO5{_tU2Y4MMQENI8a1l7y{nv)!xVQb^5$?G5g^L*Y8!z8H` z#C8xCB^VCEX6M859_Nj@)(aHi8CzPwSvSsMQ$QP9_D6ybA$gY2K4G77+CdzSM4;>- zw6H|#KA|jOUJIsR@uY*eR?JThseHg5sq)lS>yN%!?ox`!SEq{Ea3W+ab)kcDLKkP= zf;5_VY}#^fbQ%_$Eq0qvnh#j-Nds=N0&S3?-aDhFx@u8FjaoNLZLF(PYZq0|Q5&$3 z)vEc}ezb0hwqQ}!yt$3{s11u|)Xr^am|Hi$0o+LDM6Fs_qt;UyGu1`&8|Thb8*1ts zYieiIEWA(o;K$d`7;Uu@B%OeS{h?S!QU?wbEG0EWD&A66>!1JPy)YRYRc4kk^`Ba5 zbqh9f>;P0+?iEM7A8;MauQV5-0$JBCK`?2qF;fLfp};tn?^j(D38vx2qE$0YfwNR| zoE97hpMw_yPL*%``{*(C1q@I+cq?JPgPSQkTWlsY&af-&<0k+&sR@yp2VJ zsj*f!^20D8jO!3SU=JU1owW0Ir}9%mY6$!#RNsuxoSoaieP*|k`_Z0hCF(34K5Y~@ zfS4=RCIuiw)QiZ{R*&SDl8dL+vzMDER*ePLv(C6?+~Gnz!Ba>)B5v{VzuTAQl=aU% zjlGuP%V6Hc%hF=_SitOUhwcS^CK9{1Xhwhd9JB=zpw0SsmK`S7qQD8pkUY^ENIME} ziSp{BV=hW<28{fs%I>gcAZC&ge_;K=FWj0>ti%#_({)*WCR5_1n!HJ%UId7esoe(0 zGaud*$3yIwmntb3Qf>`n>8<8x*B>~G2r<}Q(!3A7mt7RGs=(%P;P9G!_^?RqPxHMG z|0pr(s56=fnTE47rL`owRaj~91Z)W`hc=C3c90YqbPG0BwJHI(gA!%)>0u|8t#+lS z$eaCoQd%VPZd=$Tf#;Yo@I&NJ^ONhJ{N+ecTcgX&JeDP!Io20iB5-9vMXkhcV@u@2 z8we4zvsss)BeNsf+qPCrQl7JGKKZgWCytl)>S&RP4P*K_O8xiQP9}4CJ6_3}P6N*w z;lNn3K1{5s4N}D7uz&^_;FzvFP6SMa)d!F$wvUCd8+!^!m3~Qd>8Ob$bYb{V3f-L* zZNchj9ug*IAa^Emr9WX~Q#rKr6bXqhkrVW%efo9IrKFE+Dj3mgsu~umeaDVm-1lP7 zqc8MqItoRdI#EsUJ9XjILp`s2A${s#`pCfxuRZ~t+=Y`zdbY0-5BD8gonHG~?~A8< zPw#tsh1vW3*7SiFdJe4Ud1*uXvBS8+3VQ+w>8BpYD}9Gop#1ca9iH^2M=!j#$&)^q z>e=yh&+dIaJ6^uHS&@#dp%19_rct0*-hiz3QZ>TKd!@>CNlm$2I}a^=x=P zeez`Qw!=L~)}*&Qgiqkjp0!&0rZr^e7$<^t7`+E01O$BgUQ1HbaM|+QKvj;pS#t-@4;HPps*A@i2|(sSOuSzkK23y7a2<-qS1XYCGRe zuU*}E7q|_Pw-AT*v5NIDMe^3A5)5YkF3kwjaX?_db8n zt_=CWV&`30Kw-nZ*fjOR>4(m**o{wOii(juzhV!{O0Rku*Kvg=CuYa)=W+eQsY93- z813E@PxPKTgh?(YX!_Wz7hl_A_gwmwr_-xmK@F;)Lki4>7ZqIcoh|9xf4KLlQ)2B* z5Is_}ULj`Q!+WXK8T2J??o|XH=1k=R25M9xGWZk=G)}aCbqyBO_ zpix#Y_H2Lk!r5~@J0HPhqbghQ1?6f~HU?_fYCJ!=y!Q}60gSGv!d*ETQ}|i?VLT=J z&|cSgvS<5a0w!41eH>E)XqgNlLukJqFd+Hnp1y|E+cco9C5-C33E(I7?EV86 z_w4Q6wY7K80jiD#MeHSf7u23D2p^20yYSjMbX7|= z-`-6<>mTiVX{A_4J)5@xgxS+v45nyf&(@uN2Te4I2ECD3MqXsAdbSoGE-H?NM)0j^c>mPv+KBA zM5i91$}{*&zpw{pA)1z6zptd3UNFt4RNYoNl>D_hm!kIGwl<6Z|(rceeAAMZh zO27ItI`qQXU42K7^q%gvaR_0kmo}i@_7!{qgVl&IMFyy7n2NyqrSA{}Jsvg88!R1s zs^^JUfv)F}KN^DOz-|;n)cf2Mm?{^a+$jc1Oy<60EBlU~66F$L=sUNoZ~w;hsmS*BFkx2}QL6ASNBt6jLX-FC ze%R>HJlMVlkg)gA)(fXkr`PYt2g*uIVfvFkc~I0qOq%oyoBNJ|6rj06^?mFtkssC^ zMaQSttiEt=e~)=gOmiFK^*#1@+FS$Ja&gCLyLu>LmNdGVrp~LXt#VF&0d!z`B!7vqyd&DVpsHu%)}FOH@w|YnPLx0^kee?WLU_u;HR&yYc8Aido=^cB zbP?8D=|@+mA2FTJ+eLvSgHcfvQ8p=t{;PL^h`E$166Mvn6_#H)^@zF*TtYGtR>j;*uQ>(6 z3*9OoGuNk|dZuUhDG=i*)^0^#|LfQ}b#~Q!-0j_Mrcb7T{VzVc2Ed_b+c~BL9+qKP^+@qEvtkM*UF%e}`;P6g ztEV}KnmQ6>21pWsJvr7o;wB$OgeH8 z3jmpZ^~LmNBIji7F9Fq}P=S#}zri?Dus1-#U`f0^TaJKo!Sr&LixS1tjl4g}EZ7eL z=u_44VDHiW7tKe~XEqB$hrn2W)=19q7dC_4!t;H{%=Frm&Q6!4t>~ikQ=58s?d(|x z;I@vS4G7tncHyj_^x8=r~WG#&2A}=Ry%5rNwWd736FyJ#ahDDA&OM2M@IKl z(LJ!3HCS0Bu&$1PSnH(Z63Nyojd5`l(EmT^<`LjkoK23#*vRZ@P%t%8+La-{* zRw5Q%EsQE*NFI(vBMIoI-0E!F_a1wHMEnQ^<&6a+YCY&+82Z4w7ZO9zf!N@w*1LQ; z!t~7#AaF+TOUGf~v3z+1g04Wr`yW{ZK@A1f(8@|329H!>2wtMJwOj{`9bpK7$bIb@ zLr55ILoJ;|SQO~2$Y_8JP$;Yz%0<1Q{~=uuHlt2pLwnsJ42OoVqRl`rzxrS5>HjGY zr>@?2>{r-%2;p8#8O(|!{89Kt%X8uHU+`S=$SktYLqu>y6N1JJ&ZzVRkRIC-EF|ZJ z5?Gh9qBrv)?0dpdz#*Wwz70PsXe2qFG_P*6i>lT%an9a-n)4eCttlE=2Kkm5%p*3Uy3 zSg$zM)3(@DSPF{ZpU{d!nT^mZs`wn4yUBQ3vKMMlk{V0EhYSKMS{Jl&tjZh1VYag^ z4Ca`#fv<(M?Kv^Kg|;&Rigrv)sv$*)3F%_a5R}GP zLF0!y5!-*wtMSH2vN{cGpEbeiJXm>gV4Yc36Ct;Tu~6HzEEy3BQA${Sb}4OiYABPEGV;E`6y`wyqRRf_U8i{opp1|hXVkWr3Va8st)twP;%M6h zuvI4eNt>VAci{ax;g4M;w!z{b!sUQG^Zg!>!l7?tCto(-qGff`$Y7WfRtBf*DqpY3E0Z9pM-cM%Y3hW=xhp*D4JhE(IcQl@(b%tmb#OW96Qnf-L{Io z)lkt^AP%U*VeDeI?%R<{6e!b2*O`MfTAJn{@!^>etV>D%94sAz`EA~Iy~7`siRVL~ z6F2%R3#>Ax372%1=m9s~nLu@jYlqnfeZm-cK{@H4`+(A8x4*Paya6S-cmu7rBU8%; zqPq6RzA$7BK%XnNsWY?pZTgBgpSJ9#h`mFLYNG8eSa?1?D2^VS*F%m#CK~M4VC&rF z#zVqUi*{##F)y1VkYZ0bo-M7V3f&*fiv zZ{LwKW$Omw!TZjc zNAwf>8EljCJXCe|x$tTb5e=ZOBP$=clX&6SK(TnCO+{P1)Z0Ao4So?c2go&6>QNv+ z?8nL>j}aHWnat1G)Vrj5T7GCXSu~_og8lt4``zrfRJEfgZxflpy%+h$>9w zG{eS8II++!cgBQ76q-^XiemPiR=N)tpU9dc8Zw2pwU?xlOuO8)R6x`NNWdq>4dZMw z6ucI*7Qt&p%uIGJ=IFJ;*48#zwS&s= zx12N|AcJ@oE+QJ44s|538EJy7TP?b~`(t|#J`{oRQ_<8^HH@D?057xD-7v;$^Cmle zZR7!w$=T)wxc}~fb82HNOgI74dHXwL;fhVMPK7P7WzYsHf^mb@2Y44&QWluubqx3p zQ>NwUZ<;0{X>W|F0h$L|dvKHR++!z{xb3-1t^?$4LUGPvTMOEUi-Of|2^$J!Z48ci zSb4|l%Cadmc?c5ge588PJirwjCijQUR$manw;w?~3ZD(0sYDA@Q^{7Dj%(Nq2(J?n zL_ZDNl28izoR18m?VP-b*(j0J@T~DlC#-`Ce_VLF@X0J!VX{y!{QuZ{^Z2Ohd;fou z2?4U9q9UcpAZ3%8oFwd6v>{;$BqWjmTHDe|GD(IcGa)k}1bbU1fU@I)6>vdCMXP|j zvT1AWW?O65+r`$~mMmQDt=+!&-mkaz`+UDY=bV`Yy!ZS4J-+w%`2NvWGG{-Z^I6{e z`}Mwl#K%WW8gXgf-|~K)_m#ZI^X|yom{*^-JTE^lJNNC}*K!}pjpcqicS&v__tKod z<-C>i<(vaKk(|n$**PD{Nz3lZ{$}<=*_*Q0WS6FG%O0OSJnQ#aKgv3uwKwY@vVvJl zv!-OF4?jEnTWR+Xe`@%h!@Gu851%xA=&7P%pN}rpamiAKG@6uvvH>O?7rw{z! zC~qL6uqqe{wFuIaRT12IPoT!-&k@igI_&uug0Q6PZK3a5D_Rc3k77`jT;wGnJanTw!Sni9dWsLTE;@j zAzieP^6#2Unvs6%djao_84E?AT?81ZP+poXVub|IIw4>Rp%wFd_g61Y_lCGSxRIFt z=z4*V2Urol2eLp*OF&ReV=*DHu>e_b8#jhx*(YoM+2_43V0}(d|NeX@~^5lQo?F-L}6;ACyMH9Ex_4jbeT?ep&nx?{p3~HUwLuQtbsR z7L<8oD6RGTL$2^z8KvRqhK?A9+n80anIqUsi_}dMVc$2=GG#*trQfbaHL>b zMeZ2y%nXi3se3K2w}9LMh-GEji{Y|mQlL6T^kiz zUlZ*jrn9XLXkFdD;+CF^rh2QrrGwFV&&S@qYRREXy_0!S3zXD?=XJDq4Ec|=?@aVA z(v7^*=MN@|I+j2D>_fk)`C}G+F{Z8wkQe+Jl{eaCFX(@7%4Q zh~=(+iG04=SOQdzjTQd2>)L$pP23Lb7g&|Cl9rBIi^&xXn3Qt069zVtx`SZVHrjh@ ze42NqUXEFWC<>lG@bZ8pk;p8)`d@GSg3b)gc#B$y9Yw0JI>^Jvb*}jn?X4mUe>A7- zRvn>WG*Dc|&{+*ERpLts(jde<8X4OW+M=CPb|bziYXu?y9koO6JGfT|e8wuGbi*Q~ z2BF}Q#_8{GJSP*aq^q@r^bfE~W1$rWRjYj!v#b~!BhzLHNL-KpnD`_xhkb5cv7hH`8Hjd}{8e~Eo0``S}4e$+dgXQ0kS3-Fh9yvP0RzI4 z$(M^p#uZ)Vldj}B4PAjs$|&j9%CZSluAF2V=H!-J&^O6wXlV(i`#u^q4RcRAY381H znm3tK^`WXJOdi1-EXE>URJvgD*!iWSy@kB1KEx;%vsmm+&(qh>mq{wSLJ6HyvOVVA z|Ja}nfkihjp`dWcDseZxcbBc!=7*8|@LyWZ(Rh#;!$|)1-+Xkc_xg+yay?o@x+c+x zsB$8^!i02~0^q}4`>VGey_f9m89ZA7Rg-Cs9V^*A|o-uTM}$76f7}to6VGZjM2C0u`t(QC@~%7ch|%vG~Xd`mNQ~3m2DFbF~l} ztE6&OSt+UHc{7@K>0+%|8K@GINn0Ey8IT!GKce9aIS{ptxgQ#G{hK8dy=55-Fz(2| z#nC0}EEB443(I&E3a3`z-XJq<0a!Wl>JH2b7IEvK`L%hAZu*vd`vOpY&>qVwE$KVx zMkfsE>hB-?jLf11jF7<9S+({?_GgwqBa?YS3z=laWq@kdh62q&BBCSLoSO9`8IcQX zR^FdodYv{`Y&KmWS1`gLBE~{$cBx?L)fKmey+@zz8}BX7SP<%BoJt`M6w9E;s|7q! zbdA_Z|L4XFGhDzF3ex}-5!PSpl=N2Hn0IkT@%nIcU@2_TB7!6UPeEpC z2vn3aB=wOjq-$;cm9hX)%T&xEr<%=j#N`+{lYYg z$!n%GZ<9=Qr?rsTy7y+U`{#Pmv5TzN10H?ZK5>G*IPl|9CG@=L46|CP$G@;CQJS- z!h_P%m771!(%y=6;LNfrNCA|*udX4VysJdkk-Fk4W8vnYPF{yaPyFr|mR{|xmeZJF zyGjJ059ZGp2;^FTy6^!TdnJ}pb?iGrD2gL%qs;}gEiDj=AhkYE}oi))C7<=B1x+OYvEiGt}k;Ygxbt>ash(<9HMdN_)rEfg)nDFdk zQJ)s`>GC_Wa>XySOIku3VoUK0#VjUrLpZ)L@5m=~pj#l|Vvw+kovpN0$L}-`Dn-Q% zwpgT$)}<$||B?#z(5`lb1}i8VlP-GhoKW%N`t@SKtPd>0_J$0B!~|46gCml-3Yyp3 zg68nwO_-#;T2G!#dp)sstzf1smhC0*2=D24OSD;^43*4q1>VJE4z>-Q)pbzhpyHJ^ zl@-M`WhI6D?>x>bgt;R;uD!|9?y3Mob#ZI$5{qA3cv=BGV{5SOlJYUZao+2^#fzX3 zV$Y{nt<;?{eW&Du=gyrKuIsS)$uBEenYBL~R6&jw6ci#Av~^LuCf=2O-Kut(LbK;s z8^Xcp#-?z98a&gBijL*JCUj)htXYjsYnfW@(M91{Q}GYJc}gy~4=(_KFet|hhyL-H zP>h*Hg&R6TouM#dorToYf~_zP>-KiEWeV{daNeAnSyV9M`JF$PVVeP4uokm&MSf0O z__;fTEKRR1tVMW_gj>`8_{KNn3yLC96eJZAYHF$rE?c`mZZ49!Z>=u^Ol0}u?C~om zi;k6gFuSMdZap@AdM#>#UYYs8FWwZftFUf1{88Wlu(l>KB!%xASw3^l#jP?MQ1Br;zhE|3f4>1y5(vvYjlgGgEg3?4Aw?={ngo7 zXtJMA%lD*zBK@lLUz6+qyR;vr9Ubx3h%+M&kJvmSIAYm|tJ1$VVp!hq^L~OY;J)f`;TE~hrK@R`C$(a+d3>VZ1u3&!+cl;`i8zT^uD2;Lu-f59(v_aZ)R`i*E65W z+>!RF%%;rp%$aF>Gsg~jf5^{2ZiJ39SK>Hn6NpLWszo5Rrj$Zm$nuy$0(bH2oio7R!v0%b2dv9eZoyi2v~yAr!0|VctTK z#FAVMJlXsmmzK+9T7(=`EhSFzrnEML#Tp;B;F6VNz1MgbVUCWKkP{oTtFd)ewuTym zi~Qd#@UF>NgyJoA=E@>JHrV*iCZg*7q1aPJQ5aL zLg8nElZCu5PhPM%6e%uSQ!I~+75%B^9-(j_=IrqPB0g1w)8#42J(l^iA6<8ucbXUa-Ov)=U@Zaik`j}R*Zt}e?}UuyFdif{KwYClzjS1w zwpvFAwKd!pw};B?r2W(O9U_X!@I|31)%K`MqwBh?#-=cwJ*&ESpMF{}(i#@otlct5 z%0`-<^y5#b$-pfShHIm%#U!v0J8L!d^g`_yoftPpCd2aL70W6=X-O&*OcdV?4T+_H zBXL4>O2kuERpX5n*6NTbB?}E?Idgkaw7nu&m-V~DS4$%+IwFhV39gbPl7DT8?jbWI zmj2|=Mhlay02-yGtGFI~B?JwG64?dH%j}!koPNa>Rr){#OFZG~Sc$e+6$5rNt^41Q z1z3SH&I>Supqhm17DqE*|JR?2_*tRyPIx1|;cR96Y`zR4b+M}#aTI4^DByG{5`rCk z5zzB#->yy*drt*4C4|Tsjaucv^ho~}!>x2chTrM`j0l#9X1It$8$?AxNsQnChn@rw zXeK?q=d8H`{Vdc{!iQRO3q(4tY=9F2bMTQ>eGd!IMPCY*u&!=^$TdYVdvM0u%F0 zdk4{87rpcERWg^#p_0)?Om3ts(=So!hFJa!-wX*=EGPfIytk?Wc#&;RL_!eN~gVl$FOAbPB!eR>%bGd}b)o%4i{IF}0ATi2!S ze|}Ovwp@6D7*~WA6Ad7-6*xidgISL}@PKrhUKJ>Tq*^6a)Gv^KMxxC=HQ@zW2jyX6 z^FcR(J}2k6m|PiJQN2GF-(4olo>&+BQE{e={^rfRP9(naPzkbP(7ltk;~uBpYCM*? z_tQ->)aAjAU8};ami>Fx>yP|RJ3Lt5RU4+)D6@3;(V(l=r?i8Q9|KX#pT zsUAS)iMAqgd+TTYNH`ezbJ%;^nlNWj(jh|T(seg)QAu;zikfgciU)WVQQ?!97Q}xp zYk4V(-GZe{t%~;izzQt?SWrY-57bCuf{7R9q)U^Rc0{meN9zlUtZG>AM_)YruhOBw zriO#%Lb-rT4F^YG{;=4ymqweIK(IusTA*Dz0ldHk94IbDJFjaFtdKb=6bc!yee|D4 z-yw5M?kR~vcu*eIT%3OFzVBS_4S1J^;@JQBOtBat=gnBNR78ZOq59Q~5<0lCk{Vxj zTK$!83E5s6jFe(IuC2ui#<)bA0?g9U0M8=o5~U3})ZQ95>c{mmd&(LcTe@nAgLG$}AY|5c4Zn2NNxijBn%ELjn^g(% z$CcGrtbgJm8Tv8<2WoIYJ->VNVIe$AI^c5Er2Fx*78LH5vew4*9nH_kyUsgjTe-4t zUVpKy#3k!icLbJ{)s*0Ru|qjZY_S|MWKytx%$4|&MBSojQm2JefrbLMV)haEKkA*E zJnbAOjwIGRd&v%IvU{&Q!0Z3iSfOJ}LS0pY-DW9*Ti|H>LH!U)A!sg&#<|AOXWF9~ zvG2Si#DGk@aI9<{;P!LoSXCHemurL-41HMFy|>HIE|EfUoG+L$%l#ImC;4<~0mRP}GCicrP$W_e1=8#^mTj}w>n}qI&^(-Jj z)B#kgCM@z!-^mHcns-V4+xqRxR+b_U1*!Rsfn?31E&ACA?(ey|DFhgeE5vbYOu;#G@FEUJw*G()AQ-}JR}1c{Y-Rn^*IpHi zhOIoH^RuW!?P@%OB_W4JB`|qN*+sfI?90>&th_t6t|Q6<9@_L$hPF5wZG_`X_x=rF9Sed zRU-ibGGIXpg}r8c^PMC5O4X>y^wHBu$77wV0eeCMk(>DlbUK~Uq6B!zTKiXU#ZVoM}EXn`+yI&DGXHm!6FfN#2 zNfYRNB5Sc|h?-#8tSSmznzvYH*rJY5i|MA$VAsggOJA23xd$6K8wle7EI}_U zE>#Vrq_}kTEw{^X9iZ^K*zk8B-{F4D|BG+3bHv}>foftF*Or-A<={e zz(}z{n~3;Nf;onQDtt-gi-O|ABvH*IN(=6<02}NzLJi_gHg?wX9hXc@`?dycP&>15 zmh(;;;{?LCC)OVB``~89+zlFV@S8>37Fe~xzkHfGQDy(Sm}7WEkqaeMMP`b8wwRp< zmN9UG)-eemyFh(PJ#E=kex0mSh4Q#+fM>_D@C_$H1ugDf+JSa6l!R! zqhKZfEcZwa(2BYvs6wFX6$3-;V~#b1v@!#tvP7tx;29G-MNbe^7oj7hn>93FWBLhB zf=rpwMX1-B6iLgr=av&AW~Q46JLw*cRkH6R5d#OXbC20W1PDtRaM4Ol?3TREj^2`E z%pFmI073&9E7G{R^6>7R=|HoNp1n0&gEXj0#INVY(-;}bY z%gi!irhZc zDJ~@G56l_5Mry?XXT}Ii*_gIA7QXf{v1nYt!A|BHQ;bM!gQB>&Y!R@qkS^p=DoOYW zqY4FJG%ecYgw5JJ^_)sBsIY7z!@*GW<7(mNWZQs!UdLJtso7WHg|+yMPXZF`LYcMg zs=F`&L7iTUa;~v)`iubZV(7FQQ|Gi@NBQZCew*h^{=(_>i=Ww`xeK8UQM)_s8cL0z z=_l(b+2>+0LEpqXX#VO8ph}N#zZfu^%cdoUK$S>8x-Qrvl^Ehpt%PGni84X3O&&r` zMsaSTqao&WuLOh2`kA(^DfI|ifH)h)c_X7@8@yVp@d%z3SpD{2cde4>6r z{8-3}Yh~uG`y#Wz9WrE$OvGw|%1r)BXx-u4_tjH#am++hm3n+A}gRc0(xp$7!E;KVn8_h!spmMq5|n zro-R)ACibM-b!SfK*VCi7Yx_tuezyqU$!tH$*C$k5@|j2TV|>PN^38oE-O6)lujc5 zQ~Dy=tNFxlxBg=IGVk3^gNmeVy^)iZ1`5!x8?YDFV1bk;X;uK40RwuPxJ!azG&+$8 zb|n#}t=;nuZ-2K4Y_ak46U}24pxG2=lxra=^~vbO>DiC< z-ga-_a}Sv;XsK>-%hsM_Crn;!%C#U3^xU$Gl#u@86vmL`ag#H`1`p8e!0x7aye&4* z5=ku|$2|P4V?%{a;5|Fv8hvJk(?^9xtkNdG2_CS2lBi)a1Lb3sCsHvA_-5)Gn0ReF zYTLfTPfWI<8}^To52Ad<;^~j^1`SHkY#rS~GDvt2|8NiGKmm|irh>pSMq@+Qnl||T zS^@zI3uf3qA_>?h>~x7LIZ)*Y2Esdlxxxr&PJi+jmu04X-1)?!nYJ$+4oKy3eHL-H zNYI4U%1zyq4}b4KGgUpN&0I4|VCK8KM1hijDN5C#UDdG`idx5{Bv=|xicAk1+RVgC z3+&z`UwnOKwT6DK{(9!?QlG)F?HOYAGE>Ukk~{+}4s@~~=xtoif#j1xdZj@hg0Yv( zwz3iT3gvJ<2yW>#bAyN^NI749X`0DT zn;KA%`^A_p+MIu4M4^PMmH{DX7TFBpQ#d>2REdQpnmz;$D4L8$Uegj5sOBvQHnxdB zH1JrkS_Qpt$se5NQoAqrTj_$BZ91}OKm?0aPm#wvwHsOas1V4wC@(~Lz{gdp5h=&H zWN_G{Up_(qw1m16mmS`_Z<0n-FNWKb^K-EXf33+smToSRpkD%(sDUaimt{es8WQGv zhju+JC~-H$FPh~!=(M_Ux_bpQNrBcy)@VTQQ|2k=>v$AlssTxSKt=?n9oAYqPfWBO z{?VTn<$7jMt`>n4jQC<|EaC-{aZ0HK(+<{MYaJ6EyjXnhDLdFaFK~T{93%$-`I8_U z30@KIEa7F;$mBDV3?R9c))p>!)#YbJGizx!l+>ibF|GAILJXw#pRh;!kTg~K96?}8 zUc(WlIb4GGrUs6SFj^NDPTM`?@aumbkJvfJ>P|CRW~0!TaeHCgP-+A-5wb%YGlmZsgP{IhOXXbIqa=~p5GrZ=)kMnDPnItdT~AhEM=WJHt8TyK zUGI2iCMnCUXF`pK{KLn;G+&oSvjPBa9?v&~ad|s`EZxNE5s{n*L_v(*Fn4B^ z7%z;(X|VfMXESvyC?K=%wC{%Wq>wiUBaQ8lQu(kh++No~JR*iMtW4xx5Zv0)g3QkB zqyq^@0e2QYrP*MVP(nDxe6L8Stg#fV?3OOzMa@;9$`cf6lDABqHpe&Xx66t~Te=#V zVu92vsk{Uyl6Og!4Vm*qQ7Dz6SBVf5*A`q*Q^KefA*hLmpZhYU!p4lVLS)skW+zZp z6=dHo`)4h%^}5fv*<0@&+Ps$xFQT}`EGku45ucqxbu62Z4;5eLb2POQX4jU>zqu|s zfZXMm=jk6_C%?(Z4BRuNF%cfH(Ag~CRS$71!@Ea*lI1AzWCOsu$Mp(>f}$8EJ+C}*ZquRuGfyIe{&o{kA%b!N z)&wA0zumMOs0a?8+y6@M&h2MUA9s)QG8?ty&?6v77$BGa)W<3kR4P&gSO(@W{%s44(H)*q(U_)zQxh) z1|frXxp-QFxS+Uy@@ zA@xI3^NfL-0#U=D1@%%n?KZ*F>O1uukQ^?e40*+Z=AzFxUC1@n&}7zE{qpiEUqL=; zL4Zf}ojh*ZNIzWwaz&?rZEh~F3Ye`D+-!nm9TMQb{;eL;ad+bDp7}Vwz;EM;hzkoA=qq4 z;4ZEYt7mS{iRbKjB_KHi5DyKS2u}tM5TF==s(Sn+pp@tK9|93j5Yiq<@B#yNl8TKe z?N>}2+HbzpjMkv%Gu7=;Jb9;CTJ8t(1T1|;CK=qt+ajJ(MknA)%_X0LTU#6xZr=J- zt}(^%=q!Pl+O%1^l^LW6gMHm6c(Hk=K@H=rAS%wlHayRC?*)ch*F5umO79m170P1h zK&7$xM0kM{YDJgUA*}JSI6%`ikkY~KG4OPc7 z6vMt4XrZ244hVMWp+{)p4doS##3I{<$k&h`&vRI2F2;`8HFzuB0Ys7+8XJ11=jCJl zPi>Y?-+rR!<%C&|JH-UJmo|&v6?0E%*c!uFJ#eGig;Yh`o9A<3;{XtK*RxA+Oc$j#$-IC8EzF;B_T(!D42X7` z;|5g>EJZWV`(8QKfBbfZ3VPw}sV_2DO=t30>E4t3WTXZ7X`AUp{H&n}SET|Z%`!{!-Rq) z%F3imHXV@G5qwvhaqfk79R1IT zm4TrXSX9ia#3|z{jdwAEHXh^2M-3FA88%wR$FIeJ4zv@bM?U5G7Xf859p;jm;&LG< zrVHewYDAZYb4vYAAABtnxyYwxOa-=d?-N3-RWK`V z4Q`A^un7thNs2VZ#el^H1cy$!nhl|4z}lRdA#X4pWzXxA51iX~KzixEyIls>cS0tz z;2fWMvUm4Ga*ti7Y2XGHyl)!27i3S?a>bdtR5b}io z^qoRxIw{Qbw%s(_ED9G_*`3CwvG*cLiDmBSqf$*a1%8*Ru`K4k$6o*`v;X+Z2!T_K zKqWIN-)Cl3Du1H?^jkV53=<_-7XE3v;~+c-3^{W%x#Y<($6bV08+VX75AKvYeIK zsSIdlDAcFtX+e7K*$b<_k2jS@aTZD|c~j5U-Tlv?g|Cv*= z22MVF_T(LiutpwBoruy5Y!+eoiKv?*U5my^&Za=7w|B9H+gJ>_a+)ZSICy#V{SL|j zOSRuuo&W|_pntVD0pyudvH5U?*m;sfTvQ(SsC)l4J@$@1i#0Z|OGIfnu+;S8iQfT}L)?lN<1 zkW&GvK78)h9mQpQ5N8a1U|d+n^qwutI#AZ;k;xB2n03DfWLj7Q-&9grwNS+12Tu3A z4C}gEz;Iu_-S{^+jp|!RXo<{kL&gj(HBvdVWH&f_0Hitq^h#?6&uBciyjtcz%htif zrcZ3NNhO%VUUv)Zv(0CZ90n4dPht)*nh2HfJ9A2=n2{-TVi{aXT52e@nSch;kEe;` zZ~C0q%MMX{VIr=Aaqci*WQfNg&_DU2Gsk-F1S@nO0z&W3$CJt+U#*=tr5sp^P`lR8 zjq$ird|GfK*{ssijgUC;>L;d9k2hE=nDa=JX2{IiFq9fDM&a%$Mn#suGi5ya6 zF^UyaL|GYWhu503$N}(siF=>yPaI^9 zx`Qrba^Ky)YU;(|65tj<<@6q&VCFi{XNB7cpAP7%={&Gp`*&?tlSg7fdD-%1yhy0u z)@@K|2Hl9j2It(6B~f9ks8uT;r_225mZlDb(3CMTss&=W@>{O4Ys)LDGVAhy)Q|w?ySea5$ zSxqFK-?xB7Qqm9ozUrFFGU|pdtiGP@6%{K~IE4%pPM@=Qf$6P&1p}9%LNop1tt+B3 z@IjXzn^7_YF8w2C1E|&kIGe-^GhZ+?AnF;oQn~Q-UY;h`8A`*&Ot*7i5-(%#-49?+ zWAy?oFC-OcQD;|rK;4)08-ggtC}D0cn)}n+kqA)E=?4RQ&vyUuhj^xKSCMf$c}(mL z_uPN(!IwGVD={II)MXXK7Hpk(r~nPX>@L1_(>fan|9tUMCjKL5q+gV^vVdWsQ_M|f zXKG-O;n2vLuE1>T)F`~7uNjF?vEt1`3=LD#u^Uaz;vbW(f6t3R-ZL?URkMl|#ob)W zP>4qN!nWR{NBF#;9Vm+KN#Hk8Sz^Y;!Dd2Oq<2#)8%W=8-r9HSOFerI_1yD-AsQSs zvaX0(Pd`Z4nq6M!8XWKyha`cAaW+(^SK)652#tzBNKd&`)=*-Mp8aRez@d1L+u5u@ zE))3h)0+$#Hg-dqTZF1y-%(u3tESkxlv%RIu9`|hd!7>Q)a8_BJkGxK47?wvXvX2} zODB1>oMK5mFEfK}-eNQ;ye=>&1HFi_NFK?5Q6`W({}ZxnnFbr7tN-Quncwm%hdc_; zz*uqaRxx0K#3&LnSjk!?z*c%4>THXU+G{1{D;KhSe6E%Ie7)PZNUU*K4EU+%GnnhX zg-ix~g#bD@iow{k^QpsPR94<wd_{tDTmYv~lZDECM3-L-BDYZ47+jOx16)F?$f^Nv7 zzULM6U?WnQyRkL*?A_nLgLf$OK)#7fbwM88t}znEu9Mm+5Dg4@ zJv%5cpenJWxNu`hbXByvh6g!jYGX^GNk$ws<`*QyfZaqjZ1Qj;BFRM6l~`D4u4K7S zVP=;phQ8UC7_dp?8kZY*l&cb>6A**7_JmS+nWvNbnj z%8~xXda6a7Qn>t&!EQZ;@%Ba{GYMI*)J3 zy;FMb+;;ZNJ&^2Y5OJ{1W6wstkOiTJr?V$8y$vw;7*=J-n0)sa4_JodA?aI2jyu0Q z7NL*{UG7Uf-}~Gt3X(AQ&G@+Th?$HoMRwd-(n!MqjAaLb#n^^L1UPoLkag9Ms=CU9 z;UKSYBram~4w&fxuS3A$b*TcFYZ&4A!Uf3`1FS{vzhj#{k?wuca7ioKCrZN2%rxv# z)`>#$FChyv4hM2SlIObLDdcba<5>BOR5s=M;;BYk<{~rVX5ORMvbK#JX&PugJ#F@p zYw-92BqiwfMTNP!6Fnn5fAQpWXa6wk?X1AiuFTkw$d z1poK`*PZv$Klem2klq~ zz;tUTmWj9*u{vs63|E5x*g*&hp`+a< zw-ASD<3^Hb)@UNln&t@U7J@D;yPP9}D7i&G@`W$v6|?a%pFQzZ@3vQ{2Z$Gk8g&Q` z$U?T}*5zG!E}ryt1`IQb}3j>soB zNIak1M9`h2ToAP76L9EJQyJglg1~v{2Mn z##`G$=A{H;bt-aeZHQ2%P(ugLBbZQff04TKORB5GKfkoS(HXKL@mK5c_}wo`+<5Y~ z-Jb?1c)~S_xmp{xbG-xyktD0lA0=Qxn?C%|NYs}v8WZj(v&VYlFR zCk!0LO(OjnP0WhsQrkP_o9=U-&MCc?aKM#-dUOpAUX4vjTlsvNOIZqwy2qXP*`Gc_ zB>AZ6-9xB+LP%I;4OzIv9JVSZPqD!C=%0aDJw*jYvoLue!1Q)+>AB-e{MWnh1^6Jm z29zse5h)|(JgM*Hhm9P~`WJ@2m6`#!%iUTeO3Wr&711@PE~u<|@6f}}`^CnR+&s+`}YCv>>3y z_9IEd>S&8~drth|FDo=_tW^=dy9>!cjRxxK<%bx&wa%|)A1@7&&o4?NP}~F{qFZ)I zc1meTkT=EdqT{%XwC;99xKM0yboKLN8ZIh!K8o7XJlStQ&0kmVuM6`!{>mTsflR;_ z2?*^Pc+7bTIK1nlAohoi0GYsAf_mApS+>b8a%sC9CJ(TkxKLqC zYonwDka;9Ncge$X!8;s3V?A(O)aYQDa--=WB9ogUQS6Np_8Y-=uPm;GSuaV>k_x3M zI8KPae^9~?;jB*T4$Mo*lb>=HihCs`&m+yg7GSZYbvB^7yauu-qnE-#{mysDEY%&N znT0)Re6$ZTD5)-=CoIxD(#GG2nJBo5 znBYeJW63TKc7rA>y(5GYr zC?TRk?e~XRnV)k%YPL!NNFG?qCDK?@IwW}t0%~6BZUmgSpMNBln0n&O_D|$`Mo*iY zn5(hmxs+yx-#3#79qJOG^thxp4BEBKMIv8Iz(3i9T_Ti;03_0oM0@L}P}oBAtt1b` zfg>jfbG2TF5GeVT*ASd8WD}vVlN5kZK5+;vUosU#o)6Yv^2n`<{sGQ`eT528v=j~L6*^EQUm>!irZ<@E{lNm83S@VMGv4wxC0{9> z5x^_K=m}9bSZf9m*9c&Y`aO#Va~<)ugrX)}`21$HcR%zk+rjUyJFl`1{Ts6m`0(47c% z!HiPo3WhLR0f-;w9?#Yv4_}`y6SRsFf0?2Ux!dX)AVP)ZA4Oag2^JZ|Jk8xelR(00#E&moQR~88VgI^ylD;R7et*29wYH_* z&Xnb|nme42ll=dYo;y6$=_?)f*PQR>%*&pbb!OO%ym(q@#K(t!acFn$`iosXdCHH3=k1>gEpAW_946m(%Wu|o88fZ z9UxM^$aWAuAs1+z?S@+mJV8pP%<^xg-!q+FpP`liX1kBlOneCzG&3b&+7K|vrhc>M zZ)@rzMVcfMr$PSMdTM)gl75}IamP=7bzQD!fuAqWOtgJ6?K%oXe@B zW+q0f*3;;CN9&_;q$a>iHrDGO7wpHJC`RKZfY+7X?Av_#%NN!p$Iq{8gd}x0Gt$U` zPEV{jmpx^U^ZkXh?1v1jv{A?Oual-DO=+9_4Z4?@x8sEmhG7btxH>Tv3Ys=P*J=CU zRKm5S6ty)`+=VVo-FCle@C|$&UQJ>78k2x*>U>{CP@OVKA87K+M7kvSSTQezBs(8z zC!eK_{`tp0Hrf5yncY|H+_ZIdu4nv&xhoaGpnFYgeJ!{xQjvu%^kjshU|Y(jmApU{ zT|MoaNpfq2okogc6$41gM2Ltas;0yP)&W4i%+acA@5=(dd`7g7eC6ljNhmM62 zWjyL3%b#3EcOww3!=!8F5>bV`$iKA6;kEsqUpildDs2Zo$ef0R$V_ox zJV+as*DCag$@ZiKl2llbdZ_jW*{#me=Y`3Le34-SLJ*xApx8jL z1n2>$#--Vb3yPcy^9ET0MrnFElI4?7Uzmyjfn;&l@h25{kj%NyS89k(D?;^a_}X^=7_IaZt^YeAb+1K zU!ZP~H!m%2i=wP5D|D_*O?egYdD3{JqRNZd1}kCaOK%~)Qc^40(b(i`1htO~?9}D_ zPwgD>R)zUE;gpGyJ5D?m;6ug~Cr0rh!;{3{Rl#XZ7wnf5P5p7_TJnMqC-a2&BVR}x zNttd}q#FxS#A#2`#t_R7uFvjv8df-~d;E@{zdcfApbE-nrH$X5=AwY#5@TZNxZDG7ui=vVxLx*cENF*U~%7*{+~DMT(dxd2iOa-PBdY@RcxsY6Oc zxElhaG2jxazHm9Ea~*Rq7kC~yn-jgr>7k;c?h!j){bO;SXVjR<2}}7%_sDQ7)0{48 zr<#WSW9hL{hT2!w%5F*0e}KF5$|T;PZHz=BJH(+qWpq);MMdk{{z-syr4OAcN6C-2xeFu?O;x zFjq5JY(x0$v6JL=y+{6NKD&4xm{g4cm-D#ojqm9F^XGGfiWe#oFEoa2T(BU(QtK2& zxif~%oN46zN)|hIoYYHJh5QMkH2eC z_Hyj9M7F*woxeTndkWl?N4K0ygedxB>smzr&raLp$=jVfDf`~6UBgEW+cfkC zLylyG(tpI^=h6%QJ9PZNKl8#p(EW+8ZaU>LRkv4SDWG2FHI-$6tTu-S{#f?zJQ0?P z=J{50g*ZPd%dn!Cl~k4JB5)n*QoFqOkfu!Czq9wHmwLDDB|ZM^OFMc=8ocWf@fQ&& zf7=s%-A|m``l5J*?md3)=srSXC`B$6-KrIOz5bgQf8g1|tYck6t_p*7trYZatMfY> zZe6wibL6wZo-}=~{7F7j_Yi9}zu6xR^I!5L+s>)Y*{}ZO;N)D-^l^)4t6Z?9!ZEp~ zR0?v-2)P>CDd30F}rmSz^D=e67Y4=!-}pi*&lb?ffEZQK9 z3`Hpf&9+0H;k&jwz^rw^k;NM&@__f`jXfPpKR3kAca&HqegNgF_;@4Fk}`YdmQ-5@BFH zbSB!48Nh^_V*CPm%GP$eKmO`d+e?|&~Rhfk`3TS&cn=z zrQVDN@k-JIJi^v(T){`DyK69`r!ba6N{va)ak-`xptr3lt40qy;N5Npauy1KDf^`K zQjBrN@n)naj2lp5^~9+YSo~U)SVzniHyjNS@@>$NT3Z5gi2=iLfT;#F-1P#Z9qY$B zo$q|7-`TJY0$V1&dT=ixQ^jMeZ~21aL*(2v$3Ve^b1%tVEa(2u*Yjw%1ZxqFdmE9E_woXR)ga@rwu@Od^Am-$rc2ZXPdWd8l6G_7 zW)r+A_r7u;y9Dr%n`~cPL->|V*8GB5Vwy2T+4J_-udSA+LzJ1rvlCNZ-S?LUTtcHK zCq}5U(g@OSbA4^QMOKQH6nr4urFY=%)GlKYtX+w1- z79kWX#;%4}r0a8IF8-R=ZT-yFSfD-{>yEyo$YW}5%NkELD|`57x^x}}VL#cXA;iC^QZHU*i2`Ur^HuVtwauf>)qcRQ z5Dwj4dXMVmDuTl=>_=X}fY~NwGkJk=KpJ-{T0U^=2=@ReLqwftIL&i3&F-vMw>`WP zpwS71iPZ{1mZ&s!h*Appq-@$nim(_N*AFS#L=i=)G(hyDc-1hx0IVA#R0F-6kUiB( z#nF;fkbP3-$ToGvOy5%{#YdG+3^#bnCX4aDIg}5O+1VM{fABn-?CI1y9neiF4A&6d zpe7RHDk#DY|Ly>mSz0haG1ntaz!*wba$P$%bpUexaR329uE)U_+nn*058j=g=UF)J z#>A&pkm-(Luha6$QY;fWp4^Xs8#Y7mL#wdHE_cKF&n_QqJVXOQS7m_IT_KpjAe2*5 z1&p_3O%RkQUP8~Rz^w4*!tmG^hf%=1i~TGG8(178x7F)l<>#j+SsV~AwL2~*OR=)%-J z$uM#+T#<}}VsbK8bi)}QxYURy23X0nChjbj7XB<7ZCrnmYJ~mi;SqGP-+XhTfg#?EuT<0?(VTh{h6t9@wyg!xWMn` zG3Ku|@Dy2F8)foJc!=~6A5RxxhgY|yLG^)`QS1w09x_S{GwU12FigrUd}=iTzh~$2 zNeB?^Poe`2ydkxP7_e|7#kG9Q@+Jwkl?OR2%~0uBeo|ff+^OuVhc9-zvT%-hq);e* zq}cFa6H<#5S}l9E;YKwdxG?X|7s^Rz8|xu4iYx=5tXn{W*B%k|p2E|$c%Nf&nUXT! zO>8Di7D}P_5bK~%5uu$5nxb*2{_p)q&yXP_>hr#q`-t}+b0S%5hd(pygUs&@c{{`J zdHTYd>few4y$Al@1OLzVK=+i_p4%77^At|MF|m-CB?8f}AAog=EYeD#k(QBZiFVXi z+1CCG9HQ`B5>6HAm>a9roe9I|P7^Sa8VzWuX<>nHz>Rnrqn*_D5E`8f)%N?!1Yy<) z>y0a_N|3acFPZpnQu6_Mf&ZpA*0uQ?n?nRQNo%oK@KUHDW+#!3qZbadj%f?UG3&%A z`pqF&dNb9gsS_umlkGvo+tx(QS9iPz_%d}&ZknKOgVo?Y%gy!rws6$>wd~`_P6NY2 zla>e!uY4h)XZdSmm9+{~i?yZ1^WeWxIrU-lSs2fX96R&C2N9VQjp5a?eJ@RF(+IV1 zP+2m?`z*SFN*}>iKW&nK(q8$MACO(;-}J=VuN`~oA5gExTVgiAA@XTTAIF1j9U+3{ zQFOwwJr>|H)k+ z?lr=?AF*}QlyDv-J`)z}8u$G^Sh*PWRCJ5cgpd(RY}v|1Pp@nr{;nPTJSR}-1PBc9 z${X91%nPwo4KOzjf>9UcPs`XiwkbUK6#mlJpVu{D%%_}e`EW7NF>Ye}(#UZMELTg| zz&$xAlD!>&iDZnlP1rn*KL619t+~)OE!_E`Lp$JHi{o;Z6t6%qoXG#$rrQ>PdVLvC z@ERWe52ev%;dp)6{NK_&!qa=7eA&(hefA!ZJVCELd-6!noe9B#1VfJuoPCERJNb4X zt`3Qv?5=It$*?tn1&7s0tTmP2&RL&!iL)dMXE^I$ltdXdS+=dX*Ba@Z6lV>cDI`iU z)YbKxD0=PRZ@WoL6D2OD#6kM_#`Tl{t|#MhRkhfslfRa8f-3HkZ9(WZVZ11|m`hew zo3LqdI|Fl#`NW60IU<$_;-4f~ohV|eO%Ng|1P#w#OUs6;%vB!oIw@r?oNljb46_s+ zLQzY_wnr{LYqJT+Uo~8)H%DQ%!H~Hv}nzO$1f>t5QIoH6@y<_E7=|fgGCkz>VWYS+v8zOH#!dbz?2cu zQ>x0@Tk@EcOH9SGZ<^_E`06Gx0mCY8DF-LdK2V4g)iWlNmhYPZheSv zCLq3<6sL|!fyRY}lGETGmT0ky6lL`Da=Y1VdN;ldS8 z#zRQ^2fuibl{+}F&$ff9#s%^O+OgZ|k`xnd7q^D%DS>}q;L7fgy!Pa_Ym8fPrUt2Y zkAh^>$Lebb{vfL9(d)$t?ZhaG`IA{+EYL*aKPD_iB8$oCXlX!4m-=Z8n2%_Gm@WCh zB7h{-W66k99oMW3&)x4|F|N+-$Rcx>KZq*pJpVj9qitq%wcA<|k%Y$&utGX|^k7Gz zldL^O-Hl5Tj%Fuy)!9t1Egl_n3PImt%O-~R^HbYsixg4k!%@o7> zCl5_r{?&({SWBGJnE91z)u>ot$MXjkQa@{bs2-b4;he%E`^Or5I+^`{=f0jhAMY`L zfm=C!vVRN6a!)304sHFJKS=wv)2hN*ZstH7ff$pr6u;{7O9Z?LV-)>A&-*9O(AAko zy?@G_HRQ64RU@|N9m;(x=TOejtkCeghgGEibNWA|{V;8Wr{e!vedq5D`+E=ky$AmH zdf+oV6Fq5eEcW$%*C9XWJ<# z=qKbI?%Z+;g_^i~yD~~zM1)u}P1@T)X$ZE9qXLuVfE_Xr?Q53X8;Ghf3dx{YT3xw3 z1*-y8iX>tj2CW*TZaksa-|P)a(v+s}6cvckw1K3oRIx|vWIC@Qa@BdE(Ld2iiOuTR z&^>17=AX9%zA#oQ-ig}WeI0T$x(7kWgMKaNK(Rz_N>gKcozK>nC;8gK8$vCjB^#&I zl-0(&BmR}T%4)x_Ts%aQIN;fnb>+Ve?R37NkWe)9$bfld3?3^7B%I56>v^$(lxFPo zH-;OGy;u{H3?xFeb}RA-=6ffjzpgtx4Sh`sy5HdhKh|9WOYi^2bN*p4>*kQ70I$E+`weASvIhbl> z5}Rq1_IQ7wBJHzo>*fp$u9N6zj8O8t)R`rZF*Xf(ccN|QraxSYvNLj8!Y&}2>t?8Q zP-0X5vr>*!Fm)tTz_+q&9*S8jYH^dDGp&nJyfwxiVjC1B#+`^uHQL)=( zH-haTlL*Iq%BGciiP^>wN#GpQQWtWG(Qq9o2QA5v@3WCEZr^XfxIxwGz3&ykYrtVc zi#MlACqm=90<5s>KuwZCnL{ z2IP)9?X3HbfqPc!g~bpjvMD-4np2USS1!#G0!+{XJPh5+y)${?`U98zHZAJRzk*ra zvv)r7=LX_%M$PUn#E&s-e4@<6ps#VF#JeOjUH{n8>FyZ=sAB@urTQxP^E&{^IgZUm zz}oLUB*32!ZUbf-vjMgt&@{P)5dk>K4PCuAeMQH6xW{GI#0buDg<(NnukBv@{XM84G5 z8dr+>YuU%j<&+%^rcQL9r0fv*31TzgHK^GH(wL+JH(ebDWVnuDMp%&~6H5XS2+^Xu zAkGL4r2XKQhbi+MjW6NBniXa5>N_;7-LrqvxId*y=}9BQ%}&(sy#4!^V(gqYKV}j* zD|rdUG&>sfm;6CC3GG5|;c#+S%FOCzRrYa=9(o;c3ofHn*S}iB4bc~}0+~e=OS|mm zYu`$9vjK}{fmTDlFdVMLIR^E0l0J zzCBp&J8wJlDHhV=W^=vkitTLEQX`tw5Y`oIj0S5Z&Z#b*+_Eloq4bMlnHZ*( z5|~6P4FQBkixH^^v<#V~;5Tef5+l#Ssly)i+(KtTB8${zXN%uR;&$4R<6P3L8=E(p z9|wZQ=9o6TNCS&I;M`2Iv$=Nb)Hh%L`J_D0%n21+1ji$7{FXuthY~X!;FUOMTHygL zv8E0!yDoqaAeoGe?h{8x3=$b%6bWl3wE9_t5tvJ|VWW%7XLQszP? z(a+%sMd%gK9t)_6qLixiT7cegeu$n*Ts9Oj%igd6>W&2f!j6%O=r_MN)cAc16}}OM zqKB6+(w|l2)gzkJNHCEsL#XPMRpFYceqTitBUXruR?SG+39On<%KEAzd2XXVp|onb zABm?*cgS=c@G$NSZs2)b5iK?^hJ3GI9=d^+N!uk<%{FoF`O~GB6+5F|P!#NFYz=P^ zOtAG4aYClT3~BEOO$a&P6ya0NkU63C0)~xTsR!k3YjDGQ^5}S`?&;St4^s03TJ<2O z1M7=$2Fdrcvwpfi(!g)b)m!z+nqMe^na1@OB@k1P#sFb)hr^yesYmPqlE8DB4VY0u zrQ%CPt!WR%q-mI|X}CPu84~+BiPAUsyae{*_;E866E*Qk!W-)h9+Dfc>uy-dleX^b zD{(K^j9(MK*%gYL{3rqGNZw<33j#cxRFfp_hd`z`eChJWwC|dsDhw3OAsNm=dFbe* zdcCb_+rI$sIEP4H@oxyWwsfa|@BRDRVUg3WF>+7h<_2QC5HV|8h#ch)QI0sn+XwAh z_EpPQtw^$aVF4!A)E8C9Kki;T{Yt5ESruQ!#15v=${_;>5 zgp#onJ5A*6gyy+1I5@?gfP%MwEsni+=(ZP9;x~K(VN(-jb&2jJKs@A15+sf47=wzK z+-~F^Pj|+!m7edpA38Is@;A?k`T2)Vt+)AkNvf=qEb%K^cgh*(hFEvS_n!OuBrrUz zV0Tod*Xv7ZcUPcH9NmJ*7Qi7Qu_BRTr&RMItWSJ?{doth54#oDqLli2`M%!m)W)Df z#$zC`_TEPUpzR2hlvFr-nhI+#N`O?KhYX3&HBgET@K9?VNXPGL5jhz09$FrW+Y0Q@GReAP(&cbD;Qg|kdc)% zZW8RsCJCfe>PvVic1O7=dUog=8H?PuPM7zXEasG`Z9Tw#N-DGX8~x&AOtio8vnNS5 zl8l0jP3Y6QX7Yui$PIx(8fsWs5eb+qia=d#Q4|d?7GD+-{cXUGmS0a^xC$|! zXcoKJJ(3gh6Tz~?9fx599j)_Ld^=~pz|=T1PPH4{rFP0xV(9mG?Z_waV)VqsSj`9< z)z}sf6iIV}Ve{A0BSbV9bzk}&Kndi6tIF*lVnhRximy!HvC*AiW4C6%v217d|!1(n`V(XcE50) zuf#Y1THh*(qIcrY^L?^#V)HCZ8=fDIuJYWmV%TY~z+IR=oa3rsPP;`rbBxq(e7b*q zcgvgaJh%p2ma+4o#=*>Q(BY|~*SSE{az+oZ;r3+B6BB2K0yyb$X4j%b=Qu30luz)H=g;MGLwq5Y9 z-g}KRdyw=Cyu&Zg|1Y%A-&tb*aU410B$ zCv*Cc^%-Zq?|SRg-%MZ4Wq<$Q1$v-6ch?IaOeQ>MJb7y-%61`QKJ>5N{t2v&CATbl zGg%2qv~nk~1oB75ngj)5lR`?zkOng#Ektq`-HX(kCLyshAMk{lib;@M@U}sujj`3< zEFh6d%THR(imTc)>zOVy(0&jEorj^&nwS<83=;{3!7&Ai!fi{cyGsI%Fzxx{fCJdV zV+Vm$&ZFYHo_}aVj^v0tqs*p!jqv2(MU@dsdaiEc2IdkB5&6dKez_Lb?oHCOkH@n(tfd+h_TiA zg?>aki|gxSnj-}-;X8z9!IYEaN%qi8F>2EDVjY#pR&h6I7%@7$)KdT;SR|BnVs?#p zDMZbxFAx1pdaY@90a|Gyeb?R(!s>AsMv^|hLgnm0JzjS)&<)tI4SSp`#pJ3lC~c3n zDVbg&S0_ibrhDi=rv2P`PvKl#zl{M}M5bu84I4pR^S%W2N0xnEJkh>u`yGUljUN?A z%+`h^=0>^z03ggZ+@f3h$4VZY*TToIfdfG7(9{uOHi*)w6%sIAH3NYzd>;39TboUw zFyaU^5aA!lD+K&}pAS)zWU0p`n%!0xL7q6B~^fS_idGvWc zd?VrDJjeK*Y^iW@cU;W(I?8?`XXgh8X5coSSXj+xr;V>{0C>A;DGGi`6Jb&4g{j+? zo-IbNmn_VD-AkPnZPAu4kc?YWJgK2?WanMxe#UgYC3Uqpw@4$^Jv@8o2Yct67~Y`< zIYxR@CgtE?%Q;XDIRFrewI0FEE5>;(?5Z=Q6*zn`bt>AZ&jF)W5lw0_sBJxX^Th`| zr`<*tIS=qV8#bo2*sAn-=5NYTY(nBk+`M?_pHId>!x}wt%LEgRIATdKQeK7Qv{^7V z<*%hj3@;@+pE6GWWhp0A>zfTa)DxO*talwzzxe)pzAMgHSNnm2m+_!^40?LHD(= zcv-anU-Bp=Fp^wKn<6?sXnC~g3c@h3ni#|nW0p*OeCMCGH05|^k6yS=5wp5P2Nu7C zZJG{*F?^yrOKgGdFuLFs74~hmPuZup3G9{aAe!P{P~Fv9ixq#W=X=*qd_V0Dw=?EI zx&#EuueWN#i}R+z4rM1y#SeK&T)Xq#dnV{wiW=s78GapbY$LJ3zm{|0H+!E|-4Ozn zBt{bDN;w)LU4*hbuCCwgy65*V?M)J0&z@65_j~VlTRltelH3b@Bg0n*Q6#sf0X;gE z1V9f3Vh*ybaU$w2GVj7?NMOs`Z(N$=8GqTdRwc~Q7`VZ4(m~qe@fq=%^3O^hDrTu; z?hA{Z6li}z85Fy%@`&X35D9XXKH;il>sJ?M{n+zOZG6Gp!1Mw=F9HCpb5LusIs#?f z0ZN106j0xqcE0syX^v;Y__-A$gq7vWe8UhaGp4SAAoCWI0fNabu&>cA{2l`dVrt(`Qd#G$-A51I`u1$by}}*g74% z#i$91ToZz%JS+Lf(oL+S5BL@cH#7J(j?$?w7qD*;HpH9YBkCFhlGr9wrebO;)f1a8 zKau`dXUK}?hzhRnz$0Nh6GM_|HPau8&4O75*8{eIiOY8W;42@=^;~@MsE~?d zi4p7P$p5YNb86t#ZY89Fg|t@9kpf#X!|d7}h-AUeiw+w4#r!Fn;Wk0diQ;g8gsn|# z9?4BRf!lFWJDELecD`|7SdM4pmL}Wtvyk!2xJUc(g`Hom#@n_Rtg^uUXdB3_1Qyl*K zEE^^-AOVc#?TF=WOn_bjahCcD>aDRa2~pS*p(*u{x}2Ox*qI6LZW3P3egJS%BvgX` zXz#mbBWGVj49t#Ha=^&MRKz>gWiTNVpWFGZgKLO+qaxP?oCxHcu0-Voy-d+8fXYN< z>4-zqQZ}uWD~c%>aUtDTX`4frW%aH$PsB~h2TS4};a~$0^s&c!p7~wgt2J)V7hqsT zJHkhy9V9POQVedDCg3q2D+Fjo5$6M>zt{U%!h0c|~S zq}axA!w&p$uy9L(w%{qo3J!#@aV0U<4VYI&-xC8ta$cw1nznX$##-l_02D9}PhD_r zoj`~rbu##)@W_<-E!j)SDa#$Y+moBN)04g;ZRgN8M@-LLmp3Ul%loO!UuF-_C?7sD z{n-mBynjFR-_Zk!Pb3aJ^%c;{Cyt&T!zMm_d}53scP)awmorbOFf8#k`d1;noD-;p zEdg~tihAoK6v(KHpa8O-fk_r-!?Mxh7UEcKi5Y3M^WBWcs619cXag~j;(~5KC-Tcv z*ST5D!N+;x2`_L+kAW zY!Mx)=ZbwuOVEi1g3l;rQ%~3cDseKbHs(lt$@RA3m0@oK&RQhK5&#b|{57is9BGd& zlw1^Rg#oeE&6IU#(C5R6gKs?s5FxmC-I9%rxFj~2<}Rpb7#x!>$#u3j2wu}OCb{SA zOFO0J){!UeqhiTS$^DTET~8bUNA280x7(L$l~XS3-FLiqR|3?sbGPm_r`&^cmJ#5% zq;v&;d+)oq_xL^@(0A7^IjG;7P1iF2@t$(VZd^Z;CIJ7rYbWw3I_}z zUu+qvFU2h`kY3I!N&$r-yw?A}*n1cFD9bbNeP+xJ1;GQJEsEl?iuEiM-L2hfYhSmAZFk>RU26%4Zfotf z-Q9M3_Q7r2YA(Id%*q#sVJX>6Dda%JU^w-lMyYj_=Jj~rk8 zn%nZKN#79m6`n@u+-^7+rB!Al^(Ce(F+(JgA;h04A}+I=i1}Jqwtkqt-CKz`3W9w| z_4^}wb=?gOR#QftP5>t{LxF<^=LQ;AD9ly_P|6pPB6(e-J$L&%zX0e)?7&82pIgs= zkqxaG^BY~io*Z0|bc<-o_YmjN+S(>;N`iLB;UI2fD+;QlWs^XOLA>jWg+DAJ%F~W$ zb92*kckX_nLwVG57nu$y*1B|6$HxBSSEBB&L;G(O>b`?F9DCrFLkAAJ2->4p4&dFu zxG=c=R=N$Og4Kr(ZXbN^NfP1Vo+lRg(7~GquYH!-UWxGCy4^VSz3X!~!wpDoXsB1| zKwYnwZ4E&CZpvFNRJ*nRhciq5qq*C^`0tZ2^3ABpP1S5g`Nd7bm9Nmbg>e$Bwk^W^ ziV-i>Gi)>Jo%r7FJMRI?0O~Lcyd#x_`5QNm03e>f5|%}i;{E7z519~mdD;8#FuAmW zeDJi8JDBjEwUc9eM=UNPSkjqm?1+wt+Z$%QPtpow?g|e7KT3)*G>?l(=OQAdm<;re z#``JvRx6?I>)s#|ofOUdsut-8ZhFSX-gdSfDs6Bt6UX{FoGkc-`Cwr4! zw048K)g7#XfY#MUvyetA22kJV64to~hYf6BE9hn7-f=+%;ZR3Hox-qDF_K=O}pYt)?OX9t4kzEQA40G{bZ_6n%QlSDbDRRguvZIHz z#jn^qq4Q(zm#)GgC0vPO9TbH>=`cvX&wio3Q#IIy%gUgx*@4Yd)r8^Ob}lenCJw!> z2&Qgb1E=+ExfdKKst(`boQVZa?5j}DxrGA)Nmk+f>A5=&?gjHimWO;oL2$e$DsGMZ z)TQhqs{7vCeQO>w6GA1T9Fk)u8(kM~SyZ#uiAFK=72Vf>7F?6OMbaazI85+jA{8ed z-M1-)F=IVFJqj{FhFK%b$RhrRo#uPau$Bf0k=)vOIVKK_FPYqu-0fff#TED%$4}0!iwIh#9uy?G>3rBF z*&%>>T-xYsW&TY~<-s&GvG=;z`7f3@J;5qFiPgR4q9 ziXSViEO=(@!NAp=@|XYq)EdaOzWI$k=zLQr&d*mXvaJ--vsNg6V>$`AsAzWuc(m_$ zvjFM%Clfum`ySE>Gts4xH(F6zC49h&!ivyG+hG`U&`S(NK$p>OjSj?Ja2}$6aUp6` zya(SB_5stp)H|CL1{oQ7+T%zJpb+GkwXTJlfiTc>leQCfy-~P0a0kXG&^qv!Rcv_U zmZwi5b?3y&n0hcLt;&KnZSC!EAxx|v&UiA7+m)zusK$7BFqS$N3m}Ay2mR>bN{Z>G zyTjCc*~6s|6@1b>aCW4snsTap=(6m_H-GZVY*MdIs+mGiP;kw~sI@SXZo&Mp^jB?*@rk7mDLmjYIIdjW~H;+BMzAUh6 zVtmVdrGgz;hrHQE*9Az~gC&n#|<@ntJK|cx6CX)6Hk9^rlY+_~AY|G%dC<1Dvg!@mrJB7&g$5XOWk^qPH=i z@Y9(E6ciGhI~HJRa!Mzvr-$+3F<+U15qJMEqmY@&NK}kSFr6l7UJHi{crr#VaKFiy zpM~Cu5#6NDma1}Y;|U~sru%}rMPU@RZJM+5&c=4t8?E}uY3}!;acG5vy&4F^r_IFN z8IA+=7sxGbKa38HJ;<5uhC1tpY{~dJ)S1T~fG2+U9($^e+f#kqo|(t(nI-s4Z87eO z$KKD>lh~|3zJ2<6Gr7c@gFk=}`pij@T&>CfHzgYrY^VH)5`)0(tVMOzQvE=^AW>%u z;FP8Gd5L%dT7*AC;4KErVfIVB$q?z^2!zn&0@nt5q{w-Nx&&|kMd|&;60m7Tg=%{v zRj4G>wBOb&z5)?DL@_g_7F{*SZ8cJxJB!dE$n@OeH}3m&p?TeLYAeW{3f?yxYonMk zBuG?QcP*+%-~#W*Q?e-{&t^32w1K8V+AHFQHubhddpoTjzm};8Vmk=jfCLTERLlt3 zuLge{++^N~fd?m7lFSwijgqQTAA{M2Tgc7$!xuxDP%^~8qAxSN4DodSf;Yc;aFk(` zSgelIf#T*xhVeZcONpnN|IIS$99TNM{&0GctW7K;V(k_24d1axK`YRKp+I#fB!khp2X_`y)P^`riQ>nW zlZ9ZxZ-KHBm#7Cuu1iRqfm4o1B1LEveT*`zOCG8C`exiN2S z{{aLE6VI;9O@IXvboOdmC>}A!xlK&93=Yl`CINe-L7;px#vvjRBFC_%u1TTsW?BP| zpq1_bqJ*(Foh5mPnSaRf&?}(^u?)e5?b+VS!fowt)mG+ve(;BT>EV)gqhF0i`F^-m zGTkx%FJZ~DON>AfpXYz&>INO2R`8w0ICPHRz;ii??v>P=R2+mc_L~L0IJRhESo3o< zX|RB<6L2CylFZ(Oa?6|6qOzn@%-M?;dPbwblNXKr|JkFU-{nC*AzfrTXTKzpsI2gM8%l9dCkRMmm?I^kc}%*FPajZI7V;xWc}U@I-^@&16Rhs{)&Y69WaCioR3yY|-wbtBbmd8jGrnMi%~4;olbC zQ+P#TTj7#|Hw!;J?zwTdkLww?VB8tw0tHLTj+MP$_QkS0%5r6$W#^ZL%T6o(b?IBB zUkgqweWvv8(ygU^rJpDbl@^Wt`Pgq3%ozK^*n7r)ZtVK8YsP+b?1#n{jyXK$YhxZ8 zvt!JLG3SjrXUu7%|9$k|kA89V{iC;xPK<6I{jt&GM;DEHd(_{Ix@FYmqb@Co72Gmv z;i#!tTK;BKaOAO(Umdx3O$L3P2%;J1PYf_DZx zg7v|w(uUHRBVtAG1_ITE@uFA@n)oS&lZ#eujQ1>Wh{|GGh3!XvMVLz%{qh(LHe{FK%;^8+@-!sk*y;=mGTr<} z&4NVn}_W=8nC2kiARU;sQUi>yQRj(TKkDO8QRfKHQsZx-JrhBkd-O?!NL(AES#gKF6YxU z<4;p7uWZcU^Nl;@UFAMY8Cx@YMAss}{_xArp;{(6R6sq*`uyKM68b=4RIggturS)d zbXVKh5J`m}fBs}?J#sraP>Tn6QGCO+moI(8DZ;{qj%`-&ws7I8HO;@DQdqCo&`{Lp zsjq_A>j+$_bji;)JSU}Di(Hs!@2{!ndv(OonBtq}eR#yl(xleL;AL&HL5tBwA$sX4 z$^PIxT<#*^4p|b2HtFK;n2R0`LCsyX5(tl7!`3gvh8} zFaAt^m(-QK@cD6eo8{@F)|@uJu(GJRGhUN)@;^pJ#~t~`2lmK&4n1N_D}}G#c$xHy zp~t*QuJ~)omB-&zju*aq>s`{MhhAA55!0Oha6(ecIix_f>>eX6kn19=`+6dcoe5yX zsou{1$jWpFQMQKA2v+c(58iq5nf4KpnzV{bOEJpOJEEO4g_f!Kf$fL1bX1Q7f9?*;2p>0}$NA6slQC6_R!(r^7Pp-m{((H=zGR6*I5+=y$No;fcN2g!Ni)#~dkoz+DgZRy4xYNUM_(nI zNHcJC5VuWq0Z<0J$!Iru{h4o{Sr{z@nSkqLA$a+&sPw$dc{6?_qXl)S`glLC-l(IN z;rEdkretR7w4w_6l)g#ASq&V!x3n3EY{{>3je=R1K^OX@t$$k$RNd56Pk@3+QK>Kx#%^$h{rft$X3_#05 zvrf%kH$ox9K|*QqUXcZF@VZTBLfmFp>0jMv%lGc=;y%F0}aG zdVxuZag>wT4BVFc>yry76?s9rqXr*%PezR+Qa|)Xlt&6v)`!MRQ7QMPmyXrznCQ#J*Ha%F8 z(+~)(&jjvhK3fjTsBgx1odC@0$Z^4b`7*0hsahI4v`I0;@I2V`Qi36zgb>*QBh+8y(QKYS%eieDzF;DW#bXkzarIq|5O%O zbZ^PIFTX3}dL!X&&7@Ry8S7Rc{YMsJFz=yK1s9O{*d^!uMynGs==@lJw4t%a_&(`* z>1^;L6}yaPLsM++V(4|ENpR(4U~rK*v+8MMzJT^Q+GwCg9*T80W&0RE)k%q6vf$-V zqs0H06)p^nyR_`9r8kcK!kD*4-!Qs#`1{}=g0lj#|Esjw zzr4&}YT(aO1NqI{Z_1ql;CAAy{6>W% z2Nnkcu*6X%)H-V3z?Krm5(!`sqWg4ts5(4by**q7UTPMOA9KjSLdfePiV$7^IM{%B z$Q&++K5^y??@nE^zqiwOYtCOn2}3oO$#OU=bVy3ab5=6b(EG%PFIA?J1IoODwkgbI*qpMVjbKo~$XEvx zvGn4IKrpl3A|dCNEzKAUN3`y?gR*$aGpqYr`UCe~eCEZ4bKQDz<5uJZzt7DVyyui~ zC`P~pKdZMKk1_#vn7HFWwmVbVOm5-!Yrfk8OU|S@tJHj+Lzn4}x1@(*S1yHs`(59HllSHXlk%ZSNso5F>|1gS*4 z-XX#mAwzb>u@3wn5`Z_JwOSnb?$|OD#?d-4ne@IzwHm<=VnK15cW_t0b=1K8?Uy z03+fO#RewV#o_A`2#H>edz*Dj)8-<?N;Kc87cVOas++YgbLfH6K!2uOlpiMG-9^ zygAdEo4D;iepEeHK!um6!}$s^-WvnH`HjkAg?TZ*ufmcKhu*pCA;OOLNYiH3GuH%P zSsvKBZ}b%RZD+4X3pGxYFm*&Hf-wyLrzWC-uW(Q;(8FqJdXv*apz#{j`pR#Q*_c6TtXHLwGGA^jaR0;pq zXY?n^uT#_FB?(avLad`fD2R8O8s6cAF>R0`%^$VtRsX8q;?JJzl$e-JR7D7+AWZCO zHNO#nevE-K*z#CyeY8N)vBgz{Oo!6k+CSWl=X@blL)?=zB+yOd^#nN~iA^(EpUYl1 z%^UdrKZH#1NO1%?{-P_ky>oQFHo?oNm4Zo?5>;X1E6gu8BBqiRi<}jRk;fPoG$; zjNe;=UAVKC*}tf4ytn&-?I9VxG4Yy_xIR>eWt%~Kd0#B>Q0u9`(lmuibW}Mw~D2UsXXI9>g+OxbeY`=#lnu_lu6d* zehys1*>5nSxV=#_htWQYVVJjKwB6K?Cs#(4xZN^-a+H8fZt1o+uY>`9#^l=EL=AoK za5}8b@`p#W3#FoR-rRY!c0Kncmcj>k_{_yz` z7-UWm9V$D;h!~Fdleid*7*mKo5Z6%~(w*{bnwU~um2>mu+rIrwT7$^*{Xp#tCVje< z5req5p^K>9tThJ(GrV}o5=cL?*gr*M;_JX;H*H))O%c@;sKzAS(e?*JvVx;LtBm3Xc#0dQ!;@ z>Zr^ih>1)*R84KeVgW0waNq#@ul!2s7XnYYm0T5T1)?uM4lKmJWLTDJ!vqQ<12C;A z-QP<}Bb{4wr)~SzK_<_$$Is}RAXT=84;vE+7`F{}Rr4Elc4|Hxu^0ML)e8Ngd{_xJ z52gpEu1%)a1Ck7$cXVtj_=sEBN)>xu$TfHt_gVlFbYTHv=g!*p%3uGLMnhkyLe3Qe zM5Tg;F0e)EUdMbucd~>J0FxZn0vAvQ+w5n?c8>j}Q<2do?n*3ERfLi^rW2_`LJJh&k%U(!8LE|G0P(DaZdRYHeFwWj%Gzk!F?3NGIS1 zA!1l9VE$8a`}bD$GF`^HV=`$Xsdl#jdXbEvBob}qK_}ulr+t$ZMg3y161KNb#v2G%Vz3VMS&A)Ady(k_f+5w1n>!Z{Tz!kp=I4i-Mh}?`c zXUH9Gk`C@9RYFzE+hlTQ=kGu8(-{aJ5Yx`nDH$qYMv~_dw&uBXyh zd~Dd^Kr%(#YMB z#%EUu!-R(v)MQUb{;APhnh}s5B>m2g2&QL=hG^xJ4VB~e1P;1I ztr!|hClS`^K2_Z?S|x#9@@>OTQ#0PzwWpL5OVrT;VJa=h zSw`I>49Z}-owy%LUmGPviGRUuRSOOmU+NV|uFWsY-}n1h&cXsVeSYpdwH@Rai*HF9 zp!_l8$tIqup<4hF%t@FF5bX(;(8^eX@l5(&jnnDM9rEpSL)?QFDh9C2u@?$r`f`{w zG%kxZZh|R+6v(*$CFKJIS^nr?XgyY;+}#Q zxII2@Z_#3>$|?w{x3h!_Rt#9W^fxW6On57`CNm@*v@J#_FljHdoF-!Cul&aRp4WZ~ zmqs+&l+PHAbH21CMtqu>qUo@q{HHsx_z$~gaQFU0`>%JUN-<*yg~qiv!jpRBr2|Lz z-EjDs9Y-I$R#Mg}XFT!TF4@N6d$*A#6O+nhC0nUE=2HL|KA0!wt%HSAd(Kfk^^WbB4fgP!Wk8ATUH>9dYdLB1d~! z0Q(7M$1{skAd`!@4p;>`%>4J^vv zb7bEP1l9>Nt~%K?%Muy18RwZIg=NStqIR!cn6Tomp43Ki`jAu11Ti?VIx2Z$-7)md zMlXOEl0k)7WHHHjxcN*U`j3dU!{QbEflc@r$T5a}K&DfXTw!LCafp0fHFc;Cd`5v$ znEBVnKGDgYhysEp{MssEqFUKfflh_mAz16!cvQ5Qxb3Na6kVEfgtmqIh{qBaDBv3t zuR*a@hVF@LNHxN^=`zFsU?=YD)%Ys0=XOBSCHL{Q{;szsv;|&tKU=km!p__!L$nSo zs;8i#I5UqL>%s!!K=Fnz&_*_0p5Oh--AkcUojgBd=!t4m z7mr>ts(s{^l7B2&G2(+opDQdJSIgm-|G47*m!p4T4Gfg+eC)eKk29#{`%IMB*`B%f zL~DYSW4ab=?mKtfOPWV#%VW$7y>sPFl2i|mo}9*5W0Xi4XN-0zsvD_yU@%3w7nedv zGp*q9!8Ev$!8DY>pkVr<)#_0_d(g#`=#Y$DSOrm(&{L%=)vRlxRY_pE_B~_oGiEB- z@Fmd^-AHZL86QV_;JGztbQZmi&1zPpVy1GTN*cvXq^%#eYk<_cfrKUj>%e`|L?F42 z5yL!U1waZx@T`h==8`QT<0ezT*@IXqodZB>t|WT?e}D}l2*=$Rez`DDgo95Xw- zp_Ov!HNC)-#g}fvdYvRqct5k@cs41%K|$`iF$dvpVnmUrhYZmOo0z@uRGZgKyMvs& zVvFW&54pj>*qzrOyut)fUZ|?lK{vxwy=EFO; zUUd>4dU$Svs+Z@lXv0BHX|sSy^cNRPrgM=@@ij8qoD*8awk9Sp$yJRgR*pm7@3LO) zSPsqMOZrB>T`<+H2$ zxHcp(x3QaWW=w}XuN#Td8YhAqelu=TaF0`M6?1K26?Bk0XXlmwl2T=>?o2VH=O(P) zq~KW>H0!Pq%QfVPpoUy8h}cd9)UQ96e)6P6CGG>~I0G?a9FziY|CdE1SaU zO&Ph1a4PeQ->?lwn3_&v$=oM*{Kv76Gbv(TtE8|PU{7Dh*Ach{l(#+c4OoEeBI+F& zl58A)5}7fyln6jAX*#KPb|Fv|=05JHoxD}YvJPY8wtg|bi6$!1t3P#g^j8Cqxi2|e zE)c(6QY@*)JZ+8CR;XnVmA9AfYDz5QB|FTkZW7aE?h`xSy{n9|ZNl{2R1>H?sc#|p z*e;;7fIKBOM;1}D)BJ{3VrQfJnPJGg;^dbN0ZkUqj=r3FGC{@bn^|9GxtCE2KWvYG z2L9#zad$$GY37!SN_(g9i4-{8h1}P1SNg#ReR^5gEU;fm8S^Lac>DX&QX$33kJ9&> zV^iVPWV#}&92%DhrpHJVvE&zJ~mL~{CR)mB64p)fi`&kZhoQPJhFT5 z;Lhy|e4|Ph3wWdnCr7NG`L!){c(G&gZB(RkUw;A*Wt(RJF;iL zu&I&YPsH#&DS~4`+Vujy9K(|QsOiXCi|Xnk z;pkgVzFEobr=M7psyWiZub*w07(By!st$kdSm-Cl(Cjc98AKO*t?myFk~I zr%fFrz(S-Ssa2SQC>c-}y^pgbMBYSty0D}rJASYQLbh4sYsnKKJOCG3e;e^k5@~I? zH`dc8Qd#rsIt%?LSba)x0{FI9F#Dhp${^3+BX~xOdaA*GMf%H-J7dRdch`}u==4c(Z8MToFVS7yQrRMZLT8=mt`X5( z1Uf6jXiE_>0-1se!@Wnqr}5v}rW0H_@`DqDKXgk|B{##OiH8p<+_{|upF8&;Xae3! zdxn`>P;y48qLVM#@yf1`mIY=^n4dFHpjI07@7T5ZRRB zK=SRTd>M7OSST#<|1WqKG#8GZ z9gqYrrjb>u<5kQnHod8j=)kwCFXi8s$ymTs{kb_iZ$7e`srKwh%vfV4NG2|5|68zd zw8A9lETeY+xv8#882Q@H4N0W278etGuQ>B8voRRW&OUG(-hi|A;gZ_|?qpwC?KaD> z+!V3Trv1__xts21Z0^9RjGr3eyUjn06B)yV6Duv96Brw~D=>EB=#Pyn9aUViqvWI! zmlnMixU1miU`tu!pQzXVkDQiUkbmfwYYR&?mG3eYK zwS@vdRwoQ5ei?bzDmxHSe$e9>j*7t7F1q+Pg|pmZRH`M)=gei*u`{{e-S;st8oRq^ zFZTgN84fx)oahNB#hm2AU2Slr%lU&do}9Z{221lRfLKIioT%1zDV8rC>U`bEQwGIG zVFtA=71Y@4$+AG@^p#ttGYFEgrDVBNjz}#|0x7Wb7f}??(5>k4rysuuntKF^q5JXa zLT+#E^X?(&Sgo~wc|$lPOc1(fx%iVp@ZlfzIcNoEA|_6`q(;78$lB>IS5qA2BoemP zmOnI2)Evw^B%B)Zf|LVFP((c}Fs{YcTIR*pJWbO?%56B)7D739@terHaiCy*mJM;Fpu&<4UI&#J&;(LNWb`|z3&ky` zT9#=qUdyp-2EfDplIn?5D`_IR0Sft)+2dMBT?26>*4H5#F;RSuw@a)HntlRepztQw zJ2g<2f9P}nu^d0>_!;@rO|zLup=ZhfkAb(VtKa-e+?wu`*~gJ3Le5Ui157s}cl6`Q z6hD|z!JLo`v3o1HTijCej)E(&-efKyWmYCMvwXJPFEp#XI@CpO&QMkPEao|591d-$ zF0aDnEC#kvwHZ=^!DnaHx;~YvdHxT<2T>i^I7!Wih|F|8iDEA7=Rwm2Lu5B*dTuV~ z$$GB4k*;(dUo%~IDK#VOGxb#G?Mz>Hy0PiadV*bbTE_`Y4!P<12mfv_piO4S+$=Tg z%#r(J8dI`fOj*uSKMVVDW6Z2cCeKx@lA@Z-(2cZH0}N>qN^vXkEGxYkTW3(P(+Q?r z)MT;o$n1K@N2hUI5seK!komJBC?kv zaztVdc|sPHAcCG&M zIr&un!KYs#aqxwcFZ+y&fBEh|E#%Y}r0QG!6XR7E8y6M6IP}d}2y1H_Nf#_gOo5T= z%r(B8kDeE*tPt~XW|C>Vfvq1nHCFIV?=6Nou4x{4hy|ReE^4U_ffyX~r@`wm18Kl=dbK%=Vc$@OGHO8Rz~vhp#+v~&4VQi>0M8l>`Hx!?E~B< z?9MpSEKHP@XP}(L(-55xrz{uja?f6wkoiXO=!q7 zJd69=dFqtE54{|myta1{MvT8nRz&C#zj7bLEqzNLK75wOKm>&Z5k=ZR zb*x^SXzlAU?kss4Q*Nk+up0*l7P#ZAQ=TpSsq@;E^8$h3`2P`|=l}0F&CSj~^w+l) zlm_NZsLM^(BpUM?(&drW1YII|q#-(qG$ZM!&nHf9;rZv=Mf8N{kO&CggqflT=`~`Q zCivb=@X%Rcqui`BJ_;Osp9W3r*f7G4zsd2#Tbl`rw{{j z$@~MaJv*{AaNhW3xs9rD&etBt3Es4X@e8%uh(6|5(Mocc;U~!)Anl3Qpv_sxL#R=2 zAdm(VM`i&cSV2e}Y2QR32%^1(96TeLkMJcDyHBfj;0>DI3X6{z_1s2oy#uTNp)3+4 z^P~~&tE%mLIGs~Wc)UfXbaZK;H1J4Z^rr)ljH(*>rICRV>x(xQJyH0pg09jpj(sHf zN?_{$H`-+W%y+lln1%6m@|=~%85I-PDyl&OEBc96qwY@m4$g-8=%J1z*jqu92nfX1 z*{TbQe^+!imZ5nP*GzU?dw<(boXsxryW-PzJWjqSFfB(_*eskF7Q1Df(qS?jx9X33 zm>q&<6!9P}?x*7ex?*s%XZVRE+Q`Q%D;buqGcS|Medy=+-j~)C^Zg2JS_eoisUH91 zS3p|ZV^S~D`asL*SWjfI;RCnf&>{}3!%lAKuX0gN>HBcDa6_=)D6!M62fm=HWgBFM7M-=3T#=wW2DsA({wRCp$?%?H`iJq8JdJp z4#{8OiyKegI`*hjZU68Hy8M|9rIOAxt4dA!tRA~nvrb*?jh8TB63LnVpigQlN1m5z> z>zo@V1j#6tg!H*WJn&7a(!5Q5v3e-YIU zU_X-4da>b!MP11B(xuug89y(s^Td$+w*=(eaZ$+^B}PJ9YUOO^LTQy^;Fl$6XffU3 z-{YaU%1xa}mIQM<)(DMgOY)V#P^ZKYFpQyKcqW7j=T(`(#HJ;9A#^taktHYu|M7smOq8Rn^QyLNiPX!_!slgX4iG1ix#=Hqjb|!-t8c2k<207&WU*IT8bGaPDeqTJ}I=As_K!-QavcP^x8l2x)Z z)h6l`8S6TXz6S7G-j(2;bPA^^odm)kZeG4nOq7ymso>7~kIyJ{zXd^#`mQ*(IyaK; zzKdLE{8i6o2(Yj0tpUDqUfEl#o()o2-^wm@hrWf6b|9NE@8f_w?2^C#9f0vRD&j zQWZT0hcNXk4N|;95xG$hAcih{V_4LwOMfKAcfV4-Y79xBO;uJ@5%mR|1M?0lTnofp zD$+XFi4%zLKD_fHWOx`CuP_LDJ^$|sdt0Xe^p)>lRu-6ZcKw!Gt;u|>x+1bD)}MjP zH{!XzY9Jsa(4o^=Mo)NFbp__Beq!I+{YWF;)P=Ipgqb!mMHv|aJaBKMr9UD|&-17r z-S@kLV-RH!4%z3GLqtGyo_FtVVUj`YV7dWIh7{6+xy+P}9ADU9MDbkDRK};5m?4Ap z0i^<)kJvStu{go_UGd`*cNtIiVw*{ME~r z@ER2hm^&oiHXnTN>pL$%e4JRFD={69R8!<*%VK{&ekJPeTIiglpWOfleIt}~K*h^S z|Ff8Kuq48dLzuhI-*)A1Ke(h|gwye=X1WguxAwHQBilJagr45%+iC6>Zre0$9z_5I z45D#l2z$dw9+c@>^ads{5-drHw$#7(;sX*D4EVpn=s_0?(H(qtjfURNE*zFS$TDu$ z3=%Nk*&5)xV)2fEYVg0X&Do(Nl3O95_yxExz79KRkg{Z*x=o~#%s9e~2^vzddM1~> zw<-CkWah*YN}4Yu>~xy?#&BccjZQl55NJKg;B2Z+KD!ymh#~;V+s_dZHeqQZ2myDD zOwys+MU7^zriWo6aLd92B{&d>f6DY7#bKImrtV1cdL+n@ZW4iGNu~P5JSL{2F3~Hf z9&nqRV=V@~U+Rvhw02q5;Ir3_dZ=`q+c>IYEe@%QaF2NP>!%g_Qp<&=&|s0)6Ak z%WfDuSaRc-6{FXWI&WlX#0AAgMGXb#{@JI5TxBlztCvP#HaB^vQwKRZ5B}u%emO)0j@K`3SE%G{c0QEE%;I3@60!tO(G0nq%tphlnAjj zb72^`38F@vQKb#;qn1`nfCK@J>=BEOEQJiR1Bk05D}^^Fdh;W4x$FL}7#!b}>RhW) zKc*-9T2WceZ$z2W{OeJ(u`rIwAR)cz9Kj<<3l-0>oGwGVq*rFF$1#ABCyUA=x`l-& z_{Cb!}ON1xvLJnI04w^#93R+fIKi}F=}*EuNX2` z#1bv3b<8F1O4K`8Ba8zg2w8*x9~us5`6xPmz}8EMQaXyF!<`P_cGIzIt~qw+)j#{v z9X9*)j(sGj$BOSp$%*~`bK)L2{LFPn_w9;`*?#-u=CbIKYrk;x_D7E%+(+hAEa`(g zSlN5{)_V>exbMh4xxqVM96Y#(OL5#EeO$Z(*!B-^eGpr`x$w||1B0Kt7ikbj17Jf+ z(PT#Cb?^zJ?GTJNxbuc}@fPFf7g*u#`;QJBl+xU_ol6Iw$Q`}!sv}Qf^WV*F&c{<} zAsk;=blR`gNQah2>Xx}JqB4jr$O)D*J1ke1TS;U;)e17>hmS4L$;~FPXEReLdPr*K zw@)w%qnnYf%(YmgQ<%F2XPxei3DKOuVF&}2swdi0K9gNI_AaxNQaX~A%-CUsl3YdZ zsvrIHXO+Au7tuNTqHMai)%lCc%6q#Xy#1Xkx4(1ym7>+++5-0_rhjeJ3mL@7kxfX6 zXi!kqw1H5MpoxZb0E@LN=M$>D^cAt$ATN1{m^)uD{$1cprol^fismU1SWXz0O*}MQ z8P7N5uKLGoDo8XpeU`#ku}qBtb|(l}elS0@XJwEcx)s&UHFAcLC&f6E?g5A_DQR2# z5r>#H&sE5+btVnm@%NQOd$Ca5(B*@qX+IUynY1KE~ z2nwF@OIi%?myWE5Z?Y%eFO9%ED_@iO5hWr1arTl0lxs4OH|9*pABzBT`Va?gA9R+^o#bsrFv+&s=qG_~5S0;H^8 z6}EN(l_C&!j(rUS=WdSmf}l0gRLJc8E{zBAhtg|RlR;0`w4d}o zlo0uxK2U9NX67@Sg0P&5F=Y-kO*S@b*msuxIb@`Ac9_AzICdoTa^R%gmR(n$r!rA4 zrrytiRR{?%7qmsXlkq+o5D0s4SEAlQ5|!Oyu&5G7B|%0K>d~QoKas(}>*C7bQ)oCd zqgcXv2oWJwE3t6^Tx{d@JO_z1Eo<^2n7OyP><`w7!8^}%ilEAf6HMUjJTCN4iXtBt zWVq+!z`@eWJWl3dy@|2m`$7=GKr>_6qs}A5hvN$=)VLh_rVW0O46z!l;MiRJVv+YU ze2G@j0xVFZ31Tqq*u9Ct>I_WD4g9Z1&ITDcc~&l_a>)R(>b;q6MrCJ-?bo*|A4U&1 zI%+ZVvZ^5duFAw9(UnZy!nmh|Ofo@B9GQuMqeI&0OTL}<=&D&W^m9q@y~!(yCNqp- zA#g|g$$L)Dc%!iCx$bSlW?M%K(EGiZv25N%vAyRY9XOc+R&Vt}(YG#ZNv zs=S?v$kp$#S_3i4qjq0SNK+e=nLTQtq5n0uZ<|I8 zgPssz;&as{uiXhR~ItzB($+zD6huLVV z6Q;~jz{Hl7ZK-5d{*V;xA}Y&8{3*ji9X1*<7B9jQS(jS#g@W%CNHC?=PSs2-g$PjH z3{}j6{tc(gi5XDNVZg?W!aZaLF!QCrV)=khuN|^eiag|Oxr{X2$6w(;pjY`h7QH7T zbD6B=P7&Clrh9pc9^ob3)6obyoFQv(0*e%dCV#tI9P}5H6}VqckiTK&f@j7(JGc1F z-~JqzH#OdGlDW^vs@*1k5njeDqOwE<7{UP-s>7w|z&hj$2&O=0Uh4?QX0|xlEj#*Yy(rOl+PnMqjbp^u_ZV-`@YLQ#CI_ z{&cPId^bvecBAB@8Magan;h^P8A`kDT;qu2_FEx#;OV~Cu2vOn9=3NI$-oE~4P&5H3&t0h2kNjttCV_oz5xs)L@V1dYhi@a+_)g#gRTvq9E%w8PZHtSMXhEEWRtt76Qd6o=#^NLScg52J ziJ{Fi?qhc1L>;MpHl<5{Q(PZB$NdN-2^V^PXj)~miENA~`MN@$sV!dK=s5AxZKo1Z zK&nm{6e5HT;o}=Rg8S;+t7@iR6+Oi&CJLA9F7)b!-Ok%mo)$-)HLL256X7zq@}F)V zJd?Th%qh7Uinhs3+)$N{^{=P`E0oorsM!JHl(7bM0+;Dvzs@pagqAC{p-h&jL@S$+ z;Y@D`UBHcW5>;`*N!zWYIGNhX!L2vW{p?6*M5?Yp7zN%Z3N?Hi1oA@@AiuK+8&9pr zF1w+s9ng&yCcHsZ%J2p>RvMVY(81Hfo3hDFe)(H(-HY>4gYhN(IH2dnNvfDZ`)^M-Pj`xXv~sIMfc+4#&&(=@q3T#y!yy4p_~&F_|9#I zZ++H4@9SoLF-TJnk#J@EjyW<9vZysp(FcWA{PJ1EjI`tekfh@`L0zx z)-Ym0L4i}{RWog;=U#p`>@N3xBdkij<=fu+_E)i3N}ugAeRc{`n79O)CYW5{F#gZB zLCX!jph1no+CIcwE!JHDRW_|61l4P04gKs0tCv=W0Y=JI%hk{$=gCkhqMNoo%sl0$ zErwBYv?0oV+f03(cimTtzgPHMw-$gZc4#`#a4Vx0mq3x(O3O>9(7WMYa_4ln%hFrp zEs3=F$RPF9?oG?LOFQppWX|u_*k8f^sgH!7pCR+!9^2D%xD%01ruHExmW}#m5i5bg$ic z?6xNl-+2Gvou5B?@7_bt=ZL61eC-W`&pmnMz>Xt#T{n2`6NmQWw%caTIrPHr!%yBz z7?1AUT01##!`^Xe$(Lkg=t>?Rv6K&*{wdts8oul@26-v7U36zU!ql7!YPcM9dbUj~ zU;fs$KiCX1YvQbdVr8vpU3DSoVTz#i!^7_!UT3oi$%9tqPYV}D=trEnm9Hvc@_eTuAlAO;p zN3L5*ATQS$eu>b^7QJzUeE;d1+hkgeXO|hm%A>-S-d{o++h&b;u;`zidaRtyyevh* z&Mj%P%v+##>03DcP?l7U@&7*>D7&lVl+wK;FBp6Oh*e`Yjrr)PzbQIect^p$;12?i z{z(>q|M`>h)AP^2d=IeY$k_{WeX29(RxV8#J7oQ$Mv`NtGM|X|Cn9_TXBi!SfEFGnpC%sjEypcCebS%M(Bl#{7Jz9zS%qIO__bGLcY>4^ zVm}ZN$yRV2hP@m8zqiAg`bdbR69uEZx9V!d>M`{R=(_DfXY+OWXTQ5Y2uNnq`~i~) zrnG_4F;;7w|0`lW-HBQeFTAy={{*0|NA~YI^uhy2zHsx=$DbfJ{pdar4*LzN0f*q> z&)tH)36kR2o*RxoDv^*|uY13v3qwa9#gV9&{_Nmg!*9(bF}VGX!RK!od~9dv1jmou z{^H>N2aZ1d^x)11eP{W1o5C`+4Bo%p6gfm8A$&UY!u3Qq3MUg3P>J(I0CVJu zZjvHkpOGt(2{?;U=D3*PPjru-7W|A`(Q|X>=AU}@ewyvsQ|IM6bd)ZoSH>i)zmf1g zH$Cu>ji_^q2ppy)>bUcw-zcN*(Gx{m#ZqKF@)BLALKDzLcm}0|itB3wbF;aLIayu_ zB<>VGkc1i_M_V!QnB}QK}LBH$X8Ceg> znRFtm#_^-8{~QZCpQ?kgTBjpSnc&X>l*#CgL`#2r>Vd=K%K|5lpO~L#v?<6;QDlfF zFuxG0HZ$}}aHmDUiEYobdI^~mNvot&ko{74trXn7f)=W&9pwNQH!WIbdZNO|{HPj# z*K`4`#Gh)!f&gyFpMpd{~su3^j`8B7^yP+1>L+GHybGha6U%vbMjD+^4ST9fNm zYh?anhqNHV9UGXy@i1+QEN4y}vJv&4CZ7T|8`D0J7_C^wF*itnAQ*`PVH`^#2d~8A z?DZ*&cM7R_$MoGEvV)Ntu%7_~ztssDwLN`3@WXi@SQjjJhme_`rQEtNy&W=mD#5m} zs+bm6txb+JEvlfyN5oho?`_toLno=8@r4P#0rM5I+z=hiVJb3>SZHXfm8ly8Ag?@h z%2tdVuQogX^q#9fJ68Bn17@I0r{Kk{=x(dtV1A>r!;CT;E6)14C3vAF&CO5AKYjO) z$U!+}!mM1eiH59OE5PlUY>M-8dwmmnCHv&;TNMP3)#6NKbaQqwzxB z&UgTcOD4BD|J2j3&NJ@&S{+MB9f2_t!vylOS<)bVs{l&P?ss-PY!f0o-W7ZjGHfV2 z2AUX&1jHb%vm!8w+Ipr1iD=?1i^cU1DYf>tqVjno)n`9^(ochj-P$z1AgN9C&f{#N z>%kcd&x*1r5`wMG*Y}L@h73go4$DA*)mi4EZFuAHMcBRC(pjxYj*?@VUJh_?VVtmU3=l=8~g34?;=-gVxgKhUFK}AAR5y zw^pjPPf}_xTZ^OgGA`7=8c@K3d?0o|_J zMbvksL98pFnPR}U(y*iAIThdQCel=@(ap8q$^g~G5A1%M!l4TyVqKL8w%TxLsU!@* z&PBok{L_M72{juVZK$bfsc~7C5W_uQldPzVX zW;U&aWCOZk{Uxq2MnGw2xYK#4vr;8cBn=2RWf)>PRx2{LVDc&3SCAEXl?-<#LXLDT zV&@)aNw)FW;Y}PWEqF6f7U&F&SX(f%Y{%Gdjd{0t{g}?t*Nr|W&^hXkkvEn+R#H%W za^cSl-V9C;EFJUf|E`)paMlf1KA)lmPnvUye#tEt%Vf)(!ibByNQiEtW$7w!E$SU! zBDnB{c&8Y9V!feRp&A9tEpkvcp=JwdlSbFH?5wX&#}op&<1AtT6e(lzGM+og;Ah-z z54bk`0MoVErC1xg?jh);90H3&cQA$X)<`Df?v2bcU_kFV{zGmthFsu2#5>hr5iAtYh)W{5+|zO^cOH1`BLLSX&B+yN4yoKY4G7NlDJaN`8Sr!S?@dgZ;~Of2n~#YYpVucmCaPP9fxf{FHo5txsP0+r86w4cOw^yay+cbJ=7L2Yokg@E1qVeuvzMO@N^7$hd?^n5k=8|jc4FC03*FDY|h6Vfz`x)gixuo(clu@2yh z!uGss`B~?c{DaeJt1I*kV4r{XCyM|`OsVTu4SJOWy>{G7OjK=ni=aL4BVNy2cM;;w ziEA?O=O=;g8vnZi^{PH7b2loS#Z@l0kch;WN`B{3)T6N(&3!2J+qpNcEf^)H+;HP7 ztI279X5AXiY2S)sDV7%@-tb$}*C%1E&>zG5cl;R*8cAq0R_hH=6FfFl4H)djjZl#O z?YvimQ2v=2s;UCm4oMCCSqqKFO>Cezm5{(UIX3&;B{yu}xdh|qql0IF9xUs3 z3SOxkmG}^uzumC&cId)1w(kA?`$Cff^8v_PQEr)l1iF`zhz2o#rn9a2f*ZDej~Jqw znvESs)<4tvUV_Jm&ATNed*3WJUB&@i>}m9QYucvJ|t7`7gbj_#5&_d^oU(?ag%j=0iK3WB%b#4 z{`R{^e7@j9w+J(H>vq2VL?g^$)7MCBrMNVOu|>c#ZR86PvnUX*ws?c?tg-usZbkpd z70BY|HeRs*lE%<9`le@*neG+u;!sJeTe^zbUakw#B=E^-lJMg`Bo?kA$AP5bEFi~4 zVCQ$9`Cw__)CrSwQ^%?Kxuvx=0qGNCpj|}Sv#_=nN2?gb%t*JeaY;mged<>vw`ToH z249M6ZR%eD5UFZyp`d3-RyB+FPi35aYbeB;+_BN%hgEX%|gQc$CqRG}lI`>NJC1dvx_Qh=5(6AiQ{`^LC!fxFC8 zk>lo3oHVRgT37hW!@&zv|gSwnILiZigoWt^WVD^07LjLp#4$HSpcrre0*@M}fYbH^s$9-oUv zUpt^5nR-??HxPfmF5a@bS+vFFP5d-BH%68$Uu)Nz+10eVnHjioab09lP1922_^n%5 zHMeqZW!TutMAvV1Zmhj^X?YcSctWz66%N&`<;=B>^H{6lK`Y@GwznytQ&}nQ-`*K# zO#HB~y2VitZVf{!MmUfDs9pfl4Vu(W)J4Iy=?|zDUX-B({Tm=Z`Z8$Se(IW6c6?X{ zsUUkq6%_`YICmvf9l-?8Lps|nRAd5CiDc-V;Y=FLzk6Fh`Wc&y$!ROkGk|FCL_7F* z?flx)SjFllUy|!qn_7MX4T{-FICF%3Nyw)Km6%i}j|_GXe3)X5#b@uXM*rwFl~tI> zd}mlc0Dcb55~D{pB;p%AQV%R>9u8ckA9XTNdeZ+&VzKGvA$w zqKst-*u3bJ4=r=Q(p>j`Orc7sEuJ0RnEIOj5k{UQ-^o15F(+xzgj>?EyT;%nRYl+N zcDZzc@&VnEt5;bAN(0|P|6dsxxjOi6*)yfz8u87*%EEP{K0WrEWBzUQ=K}8*-&%C} ze?biXZ2pCNuB{+%$dr0R(pT!UeH$N56XGmcu(SP8LHgH4;a91H&uq@Db9XonXDo8= zB^dFHxSBXHwjkJS>?*VIQWTvUnD9PD&h_UH{KEj0hm|u{<(hN^?Cyw(1R5h2S0oDY zq$^@rfby8C7`umUMvuQ>r6$2aZh|EZ%b`#d93R#3;p#=9I{U0plk+U?@T)}-YDN$W zt;W@_nf8_>;v@iAtsrHJ^kMU=O=HIv6@1yes)TMyo6%q_Eu#r*7#f}>*a5jA;7e|Z zSLXSzy%;oVew|L``Mz3{6?a)8d!n_dw^Ia2TrzY=3|o8$LI%WRQzModF*W)gr!q5%;-#vOzq$E(6C=8gWyrA`f2&4SLJ_qTo; zA8({3khBpRgwd&mY1BspiTCM0f8O2s)AC>X=Iv8WwB*^QHI@jDFtJ%sf&?DmKI!hf z%w17R8*`9o$%lk0DWo_$<}H$^DBHW8u8m$x&(S)wQ*BIyP~s|ak>CLUaDM+SUY#Z1 zICX8{$8O)6dx8({v(nQBG!n*6mG0xTF2kY*;Xxpt-ljv8NlTD9g7!rkBu+$U*kPA> z?FUBkFMaJEQcp|ny(!9WmhbA1ZBAr1dB0Iv1u?%jv@ppU?<#@6gpQ^~IY{D=6HmW^ zeT97dfF^vxNR^1_z*2i!L-o;><#Hgf?drl0Ngy0nYKEA(j)bOXiClq8!><*yAqhog z)Fb3)NZf`qS0Nb~RFZ~HBROEYL~_tA2bCrpCPnWM?YAe6gxoFjmg|bNaT@j|#mbE= z(e0?n`A_Gc|K)EM!H+b#I$x&W@& z9IPb}vumj(IpWzuBc@5*T7EzJy9GeW&A3=q!I0O&w-7Hpo;kitYnS0h@>T`-D1)c- zM!M3GPJq;Jsp3yS5@~hpb&5!6ki0!HuoXmo;GL7=`+&WsN$an3XTZHi_*1}Y6`Q%% z{PSPEc4}!LI$=RQ#vhc7PdnBhXNbL{07>mE_&YOcl%<);`n{Yw0I0jn-@?1UZ{Oc%kLj321iQ z@3C)H(Ses>;Yox$)@mP?1A0p$lX293=-XopZ#42c8wNCXI>0e#vgv~s>7uRVX@+uvD- z<~CvSz^Qn|F#yCmy4v_BW_`PevNRD35cnLsAL;%Wt0Yt{*_y6LK!54NN$-gjo%>kkf2DjhjX6M0+QDZb!u(yp?Ib%cwgM!&J zPR<`}wY@UTxd9VC>T|&) z5bdJ?9ZR1BDpXhu#E(j5WfY^(3QJw?;Pm|vXCv?ii&ln?B%PiPBe%+e(m6#YLm7?9 zy1r&akAW4~fBTQoAjglNnrku<+I3>1?2h?=qh>=u-=uq&A>PTxp|)Hn zNlSq%FVp~bzafT?SS;;L1bKud!B0dcdd&yLi2{<)TWRc%gGj4O6>Oh0|IIO2VeG&? z+Xb;$T!ma%9Tq5+M|SGmTuaW&dN>M@E*eSDO=&RPVkk+5&D$t^B6o5A`JY}dahBsJ zuQ9liQcNWk@)twJkl&~*K#?%`XOI)vl4M63IcOyMdMnhMj}UmR?2Io3zVE((`3gg1 zZZHDNef-obR2|NZ+t8OJ)ZYIK zL6Yo}h%V`ESzanBs<^!0;+_o&Y!5OtWI_woBF<#GNZeoPh|Da^i)KqyiN=Cc?t%T> zd}e5U^fddO-C5J@mD71Ad9W<>dB#2!_a2#>@X*(e%{7CnexO_-mitYyM7pJ?#r#HP z$9ubX?h|gqO+eLI2*HI-OW;+pr*by_xZoFoU8b1TfK`y?F`f$3uGsvBT<;=5#d7Mq zuWW4!Z`hP8c=(YwgOG+zTp(Vvk&{YIY?LU{jGW2jVx+ruLWJ+GV0aMa!9BwSc1hF# z;I92U26urKzFzRe8t~=q2im{?R?kn;{GGF=IaMl5)F^$I7@mdd$WdCfEvtbZucQbvY9K?XG|oe&TuYND1j-Vy1?mb- z!BM+1kje^>4%w@AU8txs__|Zt3LiEzh(-1AOB8E zstvJCq9kyYAT3$BwjJ10NN6=o-cDQ*fh~kq2RodeSM~p}_a^Xh)#d*GOeRZ{CJikV zETwj0t!?d0CQBDuWJ%M7Licomg4Ri9l1#Hqnn}{6igKoGg|ZZot!NQ}Rw%2oQxFkE zz3zHluM0L^R21*!>h*H3_y2yL@AsTJlNPV{zV3bf{QrLT)y(;x{haUlF3<9LKF_R{ zIO>MVt$xxdD4ZZnE?I|96Oryny@GCB3F*)uFNMOoXx)u`C>OSJfZ6;rg~h9 zya!f3wWMpx?5I>+BvmFV6M@Sj=Zk`YLvz*8TDgf4-{P-Lc(h=d`$-5qW3c@5M&lNFwzjy$r}@8(Y~c?| z5Ks8QHLF6TxHzG3QFkOvA#_5Y6WDIxi=}#C7FX5KzOkDmcYD$Ow?1>um_V#*QFfBH z{_HyDnJ9XcjQ9pVw<_L!9^p@<{K=J+Q!nqR`F#YvN-(P`%80jI$8vTVZm9qEZ+l-~ zPiB5Bh7z+mMJq{mS7VxF-l$HckA05y{mI}5Kf85p(fhrspR4xOw?QL%fWP>l@D_Wp zJwAdGEC_}`1!MPZF?^+JPw%F|f&?(tWG!f2Om~br-9ue;C0<|tOciPgqmV3tl^CRf zyPQRAac0nI{q$Q$3o8Qi>f;v$LLtmI(=fkDWU6sQdp*^9nq(M8LzNbgInJio@lS4q z+fAfu*UXc5RM@*S#0mmGA>;-{FYsx~2&rxJ`9?j<9x5~dPC?&*D^4!=^wDX;HfHH$sm(|**N9eI3ksa|aI)A(!lX51Rw-P(%*rhpFYS<0f zM3EI#g^7SMswi!@uq0g4+0Nt z2)VBb7}t1$+$WC+gFe2b+&aM<9mphrYVFhIW)6=vusQDSJm&qx;tX)yJHV|^Z$n$! zD@tR@TQMdYZi-e?bVHt&1+&2fO8 zCae@rOiW&r+f$-fKyU-4Kh({+>>Mzi^d;dI(qGW-Z+F;h58j~6$|4jqeAqMyO zG|TmZ9|X=D3|i(QZp30vrVG6M!ODm*zi9j9!;!Z{L5Svr$lKpM=XQ>4PB&xU6I6Td z9h|0pPaQZ9IeJPoJ6esyXD)!`vTMuG7GAm)JFBl4lU>7CeD?732ZXqmc+!XNraMX2 z%UhlntKZ#(vwl)`?cPl+HU!(lSKSIdckVq7J^3gkBxVOaGPmR4Q#T#G`!;XKqNX`s z840PS&t1z7-8k+H$#-dMqTbE+6M@mL&4>PR>h|q@;XNQ8C!CQ|+`^DMd%Hu=P=EL4 zd!eW`TusP}}vUwNQ%PVtJOuNMA#%*7Swm9H9oL&>hua|<_@oE|C; z?hV%bm(;$$9sIW%_*)J9AE<%sm|d?tT+Bc_e)1(o^H|-tCDG;n#iU>el)=1W8iV??TiazXqH_!JQvpv8n8 zOqhWo5fbvf&Hao%?89P1!YYjSl$oKBbTu6Th^|h9P~z5J0ijcqV(J6=%l3Sm`vhT< zxdyETU?v6Q0awjCf~ss-f1HbU9eyzc_@{P*1g0a|j_vO@g!c)M85m9>{LgK1XYgaU zKuR?7G-JUGH`=S<4n|Q@kQq;;YMC(`E3?0;z}8q{XJD^efwgt+6G+_&E04>??`|lgxKfz;CKbmF07<0+yApZ9c@A~=vrR9OClja)abcGR3j5kj76%v~CyUUn=X9Elv zmRFkid%m0(JdXrnw0n^fXdGP`bw)R-KWp9g!jA-A^j@LXN$mR3cjl2ted3wRjhPG* zDW+44+YyeoZg5pV7zt)0pn@7@Xff7Qg= zeyze<#dblbwOP_3_Ox&5w2N47hvw&eTjbdiIc@bal__!}P)<%?**2;y0A0T6$@Mk% zx`sJZZ=5}K*R#JT5R;zcY#>MiPxVaKr!V}@hTi!v+2XnSbEoBe{Tk@k5!XIn$~ zyrw+pG;AJlY^ZciPU3VkvY)!UEO1A()ZCW}{PsW2DE6Wo>tM#_lFsT~xBgr_o|9%e zQ%qP$g~>l*erY$Fje?x0rm#l4q;X<)U3WHgT=O*eq-c!VKga z3Iad8ZrZ^hc?oS?ORWmX(!;Q?gE6rQF-!|K-3XX4{T{az>XBytPjYagwS=gXTZMQbPIIpJKorH%e}cVA#n4>rDwj^ z?Iuts=Gp%?djVh(p`-I|FJH`kF{kgw9e;wKiJFNU^ZywFNg2%j#Iz_VDotT3%Fc=< zOIKU~vK@`INg2`s(qdSZbSy*FjeULZye$X@zzk1hGB*|p!UdSh$a2)zqd#@}Td%)B zHld?JbijeyLhBRyS}_<6lI_vpuJj=wA4tLbi#-UwE3-d`j_hmc$v;H`t@{%rHcDeP zm&}&$dj0Q1fEkIP72_s?ia3HZ#I-BZDY&x z;;vxl5a68#o-tl9=W?paSw!>mtX+&dK!3!xTkZ1&26i5;ni^_$>lZJSpb5N?uHz>E z%{@4j4mMkePN{hlrgP@5qu)i@k4eD+ZDwX~;GULkhOF~_Ea z@f~84WC&+);HJisCj&QlFH-M2%G?ZSVFs8k4S^|6<9FdUNnB_wmWg zVGfA+E+8-+_QvCbkZxugLd|h#W z@!I0*;&DZXiykSuqi9>v;-cwAV+&ueJY4x|<=)C0D*G!p7G7JqsPdhaC1Vbad1cIl zV{RJrkuhCkR*!k#n0JpUsrXIBcPl<$@#%_f73~!(D(WgAS}OlZ`HSVBD(@}dSiZ2l zru?+>(CA-}{?_ONqwgPmMPc9Q^ypQi&mKLo?9H;TmE8|BLAvaMvNOxxT~=25>(cL) zexdaK(vOvXw6wXjp>#@Vpm5`;uZ?=V@ZrKo3a=~tK;gNCvkEIi{}K9S=$oO>h3*SE zp|znIp_2<2Edgw;K41YG6XBwh+jO;mgWE$RnhZnVQJr#qS8k3PB_Q*XiwywLw5EsK>yAzpAea zHHHXnZ6;cUIX)9J^PdEL>Fm7v+ZUf0nkp9+97tjy);d^n@;`rIMrd{6hUAK5GI1{9 zX|bk$;B>u1D_fVPxD`@k4RsBt|8n-{=7c&5H}-E!r`LBQ zJ}C8?=a5rCm^YQVvtOpL?$PXfLrt6p-Eev>2KkssJd&kZLN255QAatLxv{fePW<`@7syAx5Df=0IeKm~9ZR=IyM~06 z1hQV80$X1lIw3T<@WSPb7ZE|xlM3B5_&aH=7w{m=wfL4}3rRtSZ~i??Puh1{XfAu= z>E$UNcGDoGuGFS>8d}}2SFS!OG_CN03&imqW3eLH7kvJ=N9Ds^uy$EfOqk(mBE89R z*DXKsU7^Op3zn{{i{Yvl%u;3xz_T~Fm{ajjv9?~d^C;^1T4D`^f*8+SP zyXnb?P63v%)08L{@}sg6IQp686GHVNpgjq2Q<)4KZ7FhckG$4~R3|EJ0ytf3 zD=i&`t91W+-YHMMA=$eDOHXWR*SX2TaW@@3YgVYOaKpysOB+_oAMQh+_g_DS@$6I4 zmf*5t?)&zg8k!wiud&_hQ(ZArswN_%vi82Qp{m05L}j+c;B1PQ)DFKnJ+zW7oqY{J z2>QG(C`uV0Xlqo&!OqOWisFw*wXfFzXhBeoi(-Z*WcFxBsa8}v(8>N z`HI?5+&|{DF{Iv9ZJkhuG6LZ+P*X8v-n3TZ6 ziFg3l)RK;6GJPlfX~vyqJaso}l`;CvpMFJbTxg0poJ+9(#JAeMbP`fdoG?`JJ7(BS zCwt@SNue{?DCRsmJ}t$Tt0_-Ax!PP`WmU#K@O0gLqD;ZBepB#{5cO(=QO+^Je48^R zMb)Q<>bbEw#V$q6EvWnTk8~K#-xb_aJ6bv}mIMh0k7<%3yK>q$ru^oep)vfL_m2dsEj89yV-qn*r0!I9;QSRO^_J?Axn1?CS0BYsZAq32It39BDxGv;MB+ zm_wgDbOKyS#7_?b{_E;(S=iO0FVQ&=C#n&nLpla`N#`oy8R+hTm)|(7)zV220ZBt* zaV=CN-f@H83eqCNj!1f}j$JiIkpe?E7C-eb#rl=)F+KGyMf(SD)OKPwbqia_sM-zJ z=y$XmTS;(5yN|Y|OTYfqPfiX+L-v9(i4@xm@@vMdXUueLZk{B}D>(?&V@Y)Uz7s>! zyyFwSfCOgLf7HLNMF#!M7k;8MmAxYXM$$2jI*{p?(aODBlW3)UfRazP{aa0F6WiOc z$@H~%c_2qdv*T?HIj_vo{s;F>Op>fbC0`#iI)3qFci{UU`;&IB&SY~-e8~ISq_OJk z44(Vb^Q%G)+$-K)S64GP*373MZi1M?RzoCma_o(9+Js>r;c5D~fLjVpn7&uPRmT8R zNOv3g4}|M0bln-}XcO+>T0K494aG7NJa3Job-y{8>`NfJY>UUXr3ZmljJfk4FC812 z5{hq*50d942EeWIkvFSD7Zt{tbD8*&0C;`NG3y6crL5uU^`@`sR|0N|2c>-r)TvrJ z;V1tX*LI;eR7Ic`jq7{LJ+`|UUR^?()v_M^;hOV~-GQJ}7@K;P)}pzHJ;Qd)!q|H* zIwcefS&VDW1k^J9Ic{U5;>f49dU)yXA#kzQypJKVyYBh$S7h2{BLGYK zf6=d>Z_+D}e~H+QR7?^vO$wj1SSt~OS-P7U7p!5LJPVz5rC&R;N*{mZp3o_q-cngg zrWBk_JwTo0V(wbx%ol5PkZvQhLtAo)rr(m>qtiuNF=nLNKy zChcUym*3Pz2XR2Wv$Al3-0jXZeHBbwD!EFl`Fz^)iu?Fl8-J-K9z2)fuKX-KoJP4 zkTR+%RpKJ8HB9I_m@%-GC&chiNDuZM(qoHZGqGtxWRw^@YNPzGfx_32ku)PzMg8V( zAW%a0=mNHteqTmNLtA1>(iZWxaUfygl=IKX+r%Mxdq@8y@Zx`^`u^&Ubvm7oUr~T> zkCY>4D1~E%FB6K`@ZoE9Go$26v0PCN{TZ?RibWJmCLsl+d*kD|Nu0t8*g_jgP>OdY zh26#?%)*pA6V+i2vmn>6Qh;F_75acaAx73l!Kwxd;{g&`Ukz>gXa)nN*JGaq%_9=w zDQXo1Dc+@OaRT(6Gk)B1(^P7rwe?P!^YRrJs(p=_1cNSl#*nY>orzgD1#slUy2Oj#}NjC-4?!(p26aP-a z5-s8ovtMEa5Z?E&=AL2;ZcCF)$Ux^J-96TuYOKN{Iq`j5)2nRk6cl`mkR(CltFqz=!Ja<8d#m_Bwwk|~t~Fvw5aMc>-|6w2Noi(stb`Mo z!CPRDk~Y;9Ubu33Y(ugc)Z_q;+Z|9}SWqE!BbFybR4 zpN*S2R>yctN0ol&)Vkm=+`HD!?sq=^L@&O(s_2K5SFK{P(2}$gY6ZZe4`wN^6w^KW zW9Xt*ox93&-YTfl3Tuo$t&(wAY)LxiFbi>P`*~wV2YzPm%z!e>d7tyZ7dDVtU_wJ$ zonuar&#)Ey)}%tyNtLG>8-y0|_`|Ad29kC9#Cg zmeq}$FE09c0Wz?e10`Nw=%EZlAzZ0K^o;?AY)b?vn#&4wLdG$eh2`1o&62%NrSq9v z=aSsvlo`%c?UWU+$fJfk{qdwABC17C^*bQ^>7}Fz(jOq_5b2xaXlA)VNl3JoFVn6{ z^YphO=d4NV%Lqr6*P*-3bw1=g`lWv2=qJYmXEW|M&r1l+5n~65k2U`eQ1#G61R9NVln$fm->gn>|Nrj|R`T>rb|($nx6k5{I};G3&Oaod)Ng zLm$-_&Q4U}wL&%E5rV63J9^s-N3Yy@@R?nJkxBV9{PbsruYY7A6(f^PX1W_SA{jcX zIUfYD42$FzZo!Dq-M5J#Bn+vs+WNV3=G0dUD>JzccSB#WXZXrzhHoON+D>wN9NxX_ z&~KXcQO7d~<5x_b{jdYAN^?wYDwO39?)3cD3A2max&PLTYc zdZ28fv-jn4GK^GBbI!&K3wXX|YliLs$ByV=xl(LvrilrX*`Co6b2p=-pD;&yF5Rt{ zd<*T!3kb3TBHEB_Zqf_6i5lfFM0$op9R{qtksGisc)s+zWO%T3KX1g$?@sw5^%&CA z-qx1roLRGHOAj`WoGXbLjke+6Ku4CTA6v0+t9c%3w9;3r*@ey{`+MM?np}Tbg;F3^ z&=ie%_}D_uVC?N9@SP-xnq;CH*_NbtIXs3;gqvZ-Kyd0x_4a~DKz@*s+&ddC!)7rF zNGw|JJklzt5*JRSB{3K^DMA8f6pm4XIw(uv$$%F&f2({lw+q>5E9pJn-C|pLHLV%f zd9Nd}<`0z6huqm>iLXYgX{q*3sTOk#(+(dzo~UNVUNfrhiURWuMUZnYb`JcBl%SHR z^8JJ>lvX(*N%@uOWQI;+6$Be(3t+3*-XOsq1GJvkF1?u$G{GB4{pdbe8xx7B@(Og3 zD2>z;eJ#~K$QpHiF0o{gkPJy?CQ0-uMHl^H){IxD`hyJ>`*=(vY|fU;ADfCywu)ni z`CWIS=;+eWzt4{9LrMO>@`CmN-uf}+kB)w#?3L1^#lI;TF8W#Fm7!hi_}jm~)xiHi z4P@Wt-2a`uX(Oa{DQ3>W!_X%gX6=IU!mJZbX-kbl=?RG-;&iW7)D={XjOzlSHlTnk zfwDI7`G-t=VmQs<1DA=*j%$F8O-(D2)D}1B;D8F+O&}_Ipt_zl#tp!Arg};G1C@oZ z6#W{X0Jb6sB4%0mlNi=OtYcl{$rwJ!E{jAc2}@+8LkhDG92w|gc;=DFI}7UFD@EPy z@OU!VB#pkSEAADe=3$()bN`nwU#c`n&LRzkbrwNe$6H}Ds3U(AW9~Ja{ zqO*|MsAlbwE@KB^Qd5o_>{51sG|5d(^k^>w)=jG9B7^fCHm2Hw8^3+R!xNU~s)Aca z_!AOmU*MCkC<82Zu|apL%jy5BOBx`k+G-R$9@&;^lRpIUw(J_`{{4GPXdV-1X3sX@ z{7Xa?0XUeyh}qR9c3!YziP6ghv&%3paypDHSR=3&ijRo26f*pU4M;dHIu$@vrUhnF zqPo%A#5RDpCMD!z_8gg0J-o4Kbb^#+c7lnXphujtv@A>lhygO0bTHV1fI|~a3zG!1 zh>Gr=XOvwZ_y@1@>Y7p1BB)BRtR<2LxEjGy)ggb%`jduNMbMsoWAb()4 z1g6yNV?t-T#!{A}_3@eH1c+$f6KWMD#zDvHK~TWEtT0=ItX}g$#M*8t`n`07=2XdW zTMZqS)x1Q{7Y$Hk30Jw%mKs+E6Ki;|)f?DxSK$>wq0Z8NT64Mp<2);eM7yc_loGZZ zS5bl~(CKmZ{_HCRH;R3jtO zXfO!tI|GCOHh@J5!N#gxB7R_wr!ItfPVm&y42=HXnX(20Xu!UkcVXw-W9IDc) z*u|8Wv1xbchJp(7!h{UZaUa*CpzsQCQOF=eix62=o_PBu&c1u^C*ijAuJ^0je%Z{m zsQl+8`|=mD{8i~)@;yY0cTv+hd#*?}_IM#dit3@zC|X_F61)d^jBW*WC(&DhxVUxKH%P zj98WeW(1dq4jBmKd3SUlQLL33MDA^nXO85pWqjkGKfS!rX^WXgd9~GE;{)Qt$!#gw z+{1E4KL$^8qBRk!(-|wFKk&dehN3&Ta<3?pYRJK)XsawL%?{<8snuEU?7ixqK?Z^e zGqxCW>Pi}!sG=;q- z2@jw=N;}h}m)+2|Cx5fWnwRN?Mm=f#F#zMggdk)->iyF|*73hG9jc$@RHNX?f*U4D2$Lbwa^w zJ=KPee~oBw)Gpl2iy7E3bZD=a3U)`P8{hSgtAjVWFJC(^4@INwCW0T3u|m#5BImEQ zM-~QMPW@m8voO|DT6Sf#pOlF#|dA@w3v>+Jr3o%_?OE0Faj2OdRD7_^< z6-8A1IMrbrL*B$>B4Zf$7rOVbh+)#Us&c61z%Sla{;S|Sz4x81w=?0d$EpnpR{FCU!doJ!CFTH`7f$Pg@+Y}za-H#)=XmP(>PywNRkuu7xM z4018pY=y0eHdakmjjmfq{oJjInz@N|pVHcn@S9*Ik979O=?iiNyRg2G{59(daW{I` zGiOF~7r{PDlFdl>0I0VF3%M*m_M#@6sN3Iqg&dwA-b9IWIj)yHaY}UI^g8!k|ML&P zM=*I^OH}y?>K8IEMw=whBrM|C@}dj=46okXA6#3*Y?(xE4%gM>2=?Uou)VmBEDfEV zRU_H-0$1KRabKZVuk{IeA{TIIxUxKU2-%fSko9(ad2ZyXiVFJTKOTd3b8^E<^Wte{ zvj6OLM;=mJ=e^{t+)FZLiNJK+S9|<|sS|v!COm;}!(l7Q-K|&wYwx8 z?B^bQ1dhfNr^OnK`+r7qTb%#2$}Ylei6hHfiLnN04+8>jCcn*{@uxE*)YNNE4c-^r zL_1Jw>N<#wAd=4WV<@NeF@#-=as&i1*uc#Nj1iQ`NDgTcMJo0O|64Emf6GbEY1z*o ze61hZc;eKH%m*v!7LpPEqFcM{A|^|sg$X#4C?O2>V$x?&+z=m-znn zcL#^eH#?BCzl6mgTdI49~9>d2(h@xyXOk= zSa^XY9$V8mdYeQX*%n#fmi_#9x5EO0vbEB9Viu><*!eS^ms56XV|?x-`~}o^~lP z8Q{i4hVqqT-VsJ^qn7x72uQv(joca481Ri=$@N{O$Bp|#@JG4Zo7(m#Ua%>_@zjXS zWA}3{n;6okpFvkeyUODcgC5p>OfBiXl`>XcV|q1ZUvuX%(McFCiMm8HCXCP3`QmNP zyzGkyp9d&$;*^<9r)Z%68B1G|n=ur}m8~)1E5_KP34djtM}+RjMc!+JZ;hd!A$4U_ z)7DgLUusW5Hkk6NU>19DMCq?+5UB6n{mNG$1>q{NOWlb`+ehLY*=&khlFLRi@(V$N zVJaBn!+!4WFLWZ^O`MjUV!Gl~Tx4BI{n?zci`geNO@LqVX_s z*(51d84au4%$NK?n!9jR;YLX@G~k__wKCVMTj5Dl8-#YpbF7b8)2WA=q7Zd(hZ*(ql@ zV^z-DULuLg(SOtAf@YBIgYF`pX{J*7i5;_VskrgzR^op`=I@i30JWrWq*#w!A|*BK z+3OL>-UpKs$%AA$Qo0Oo`snO0;`4FG@INPlaYJ~mb0p6G011Y-+$)S2+K$KN{5EOR zVKpBS)`Q%FBH+<0DwmU3w;1qLIaK4SG)v5r+t+5F|N3X9(?hCix6jrJ&UVNbV|c

R%tv{di7=CTvwTVp9Eo6`r$|*K#yN4@SjFR|;-)GZ=Mopnv_>j6hsJ?37x6UUQ z_U!q=2}b+g3cXl=R|{A=Oi}iWm61L5w$50^I)in6lmn06);%my!Xq?n21VM1Ws}Gy zBpyv|`T&foGKn>_vnBE1hD-mkY;D-ZYL{;7>~J9uZuDu})*3ct(22T&VT32kXA!L3 z{o7Z-wh}zj;z&DNJ8~WQ@Gqm4b^JMKf{4b=&Kvmy1#}{$-jDQ=pJH-^+qC@%dixMBT>jHoszMbdKWrOR0YAj-M{=fVGik8 zE3i&E-a=>ANi%jCds$b@_Q78JiEN##Tkt|+wZ{@K^xZe+KGM&olR%*18e%Qr1ukmcR>p6(VUvKqiI zupd|{r68~nm4Oq!A{p8ZfnK(2aFxW=8P#L5VST1yb+U;B_c;4xw>=$B6v`jTaj=j^ zX(>3YNxj~a^DzH**lY-8DBt}yb<;J5Cr`U8m`l3g*GLm(sTglm~k6Q%S zu?^;-cM$$d%=F^KCyXbyPfDo34(hI@ZL4%P$+z|ts(1hU(||eW%$z?m-i5Z74RzoE z$Pcvjbhqj+KeT(@fT7U>e} zRy8g}lEIrwiIdiI-3*9`f$$+gfN@>DFG-B6)uqK@fUa|?tGtaHlcBuWd<=+ihzoVY z1ac9MpHy-!Uy78yDjY+BTu9uk_dV~tyX;F*?JtXsyK4I6Y{;iNwzbg(xApJ`w8=lD z=qmRWQFzPlpZpqXl@sPIFDx*tV=%cD$;AlfqZLM!a9`UtBzJ$jAzXn$P^bZEW$+jc zwk2KEkU$D|RA+EEqwj-Mnc z3DLGTG{RM569H}V{l7S-@&x&gO+8+`EGj2dfI3-HYkZ0-dV(3L4r@)98vcKJq9HNk z^65>L|5kZ6TK}h~e0B0)Pb!CA@9i2W@(gS^f4Lfwqr_V}oILr3p>VC=zrM?Z3{ph};-^5F9i z)UO}C=Ht#gs{GW?zA$>tgJZkyI<#v9(zpXJuzByH2d)^s>gFHp3JyK?%4_=`8{4~w z6jBGDdg0&;cXQ6@eYd_g^5CIot{8j%M$Q?%>dx18-NXK|s~!V{=-N=5qXCJ>sN@fF?qOm)lKJ?NHukF4;rFi+3 zu|1zMNY-^@`#$NNxGz6;;FWv1@oRtfkps_tOe;5E%|BKSycmqWjB718>nwgqfOX)- zeWN#UgTS%e9eng^Ie)LbpGS_~@c3)Ht{%Jg<9cvCqg=Q0FlW|T=G)9_PiU4&DtC2e z-G;`@nJ^W>kJj5&zn;sfveEmlp!&d%jqQJgM;vA+M-R(&DoO30tf4%36!GUM*82j)OqmS+zz3Da_*}StG84?dnJn@>ciwzy) z@?C?DiRZdD1dNrv$o?g#C1mbhsrD>d@t`5^JRHXHE%6qhYzz>i#OTIp=1^Lizc{3Y z5MEv?sB>IPK2@^gPeBVsT+T1DFpNtlpqu79J;!C^;;AW+Cwi99iVB$gtkZTo~@mcu0s zP*JtGw51i*I(E0X?4S_8Sgs}5&xtZ){0Sv{5V+h4j9M=JX!yE*dDxYp!^A@7vuF9l3KF~#LeU6N_4u44lY{?w=94wx z#pW&_5vCnOZSHjFnR02@u6nRwDgGf`X5t4Ma2+ZITYn3LKP~|a(leN15m8-&h$#S< z5ly$Wc7FD$-Z03QA(}%}D;6?$xm4}e&OVr-`C`=;H%zlaRZS(AEP-@ia0iS_J3jkp zTQY{YImq~!3M_D(v=euvo*+A)nmOL^0M zYqp5qlxK?wv5&z#>h5}&gP>YJ&kS9`#O^Es;Cc6(?k}!n+^$uS#EXI(Ujj;`ScpS+ zs}Gz}{TKkHsjA}K#I?mr!cYsS8-ySdB#X8b9NoxZeIz@RVrr%?4>=)_OJ;Naz&w*Z zp?i2_Rq%@+|KlHlew$x6a;n2xtf^nSwsYf}fm{*65MR%@!bq-*p>2hP@XqbSjuI)UrYtI-)z<1`np>7~OFI zBp{#6axJ;B21`3bD$ZXMmUu(A(cO)InCM1|RSjy?z?vA)`elTRk9lEUr_0RPu6uBc zHcR-9sRs;4L|yI6&nYXP$xcvhJgtkxOO`R8y6P3vy;(VtslL&HCreTe(}A}%rpHf zP|iPw&kG1tZEYFuE?s2&=IeKV==S8?WqQ(0mT6dOddktUFcAVUlK% zDj=mn?*fErA0otI7OOAuXvd_EQ=-~i%-|DMST--9Eedo81JRJujD%~>){MtT&vd_O zD=;cT2c502<+7C9&W6i=cdmb}pdtA{YExIZbEO6@osWnX^_;$Z&U^|Ly8MkVX zPQ@2np0c70-tI=PbDEl+-s#S+Mw#sE&JCOHDiJV5^e_>(c1d&G{lK(D*+BU6H7jc1 zYUVYa#3C^T?3aSi`w&>X#^)9aR^wHv-0t2UVAc*+t_EzvjaG!#99AdBloeD2|Nfo( zpm#r|>a0zSG^Dg|9_+#9H@vBRL$0N_nJgu;$>n;tW;a3?a5~PJ;#%en&K_~KlR$KI}YA`|Jake$9BCSfK5~Rq^0EIlzf60v$jA{cS1$TcST9HVczM7Ppz3-g!D3R{-KvtjV3w+kvoiu2#ZK_hl5Bq_qcdMU{g*;Nd0!%=*d7pK?gvPgiC{gy0Qev2S0tO@Gj$>JkKm4 zfLt`;XiPiW8bFuuaJ=U+E~)Y~78(FdjjYDdVl=St9XsvGw?*}`6w5n7b{N@Q#e|SC zFZ+dex*+(;_g)&BCeHmerUzF_JxACcYNloOZ_EyaOL9}W0}S&rE|4rHs!Rm zY~SEEt`jE%3wSEW?s;+a#T!z|vBz&1z3D-;ys=xKXlQQUSw5M>LGF!fPnI2CpFQPX zD@ELOFD@O1^k~h$F#^cW^f2SKM2fcd4+cN}`%iRFQf+UP8pn#+A&5sE97#M`aa>gTC6$$Dk^k@P#Izqz zn>n>=%Ay%(PCj>1%fuf|eD{Rg$NzHNSjCGKCzijbY**s!=^y<4W`Nky!u(qbuhzre zGiQDvOJI3bVZlIW+cxE^LzME1jCO75#Hx}$T$MtqR?kfT?-^L|o8$c|k(@LV-}z5( zn>kBK7?%~MLd-rbkuii6 z%h=0+)3z;Gx%anww@yz~%}NJTZKw#w@2G{7nj2u0*P8ugiaU*k-F%z&(IakObxmke zG@5Q3?nAzV9>#=X*>bT@89LXjnxE=_=bs5+;VNW_A0y;17Q|v`Sda;R6(H`@glO!< z6==UpQ&yJL4iQ}k7w`Sewn^)%|8iTE*CX?-T3$eLJu4eo$m()usW=JG}R?e?H z;ao!-aVnCTexEB3Nx^PZfZ_9hnhAulJ=sokI(II8{FQepUfXR6-~}X zP$ezg*=A|Gua~@;%@u3SmKhGniHg{7H?7|K596N^l7#m=*=uoTlkDP~8sU{{yG?AU zkYf2Z$CVhdyX5vLj##qj+TKXpVn+l?E!~NGZeS3Bi9B<@K#`7CsNDO*FT)8xZ&5=q zgPNLDRj9x#W`H6Rq1JbI6LpS`(F}w8!0=(4nb?`%zvEss)g$O>l}%OkBdR3JVC#fN z=8B}VKWW}fzJB+|ULJhMq&vcjA@1j>_*~p|dZd(b#Rq8JlJIFo{g{r(Y8|n=?u0O_ zg3>r(BEh1h4osOCVE_T^2Td1`in|sihfts3?R&rd-HbKq%Pmvx1#JjVecRnHSx2Ik zj9WW`g4a$j+Pc+KC1w}|#E3G<>y*XKw&k)3wM(LRUV5|1LyPJnwYgfzYt7?2#*(BX=zzC6jX<$?ihQl!{DDLCJ>m8YY}phUny)XKigqU-u5^ z^Ub0dN;YmRpvrKA;>)3N1gGx(!fz*3Doc0JVNvjv4SjtLjM5gzj>wL&p8=BT&3-dS z9F-&`ABnDU!S8mFL5@gW>UcN!NLf;O+UFvHp>;hwEd{}c40@()ndltm565M;O?i@C zIVB|W6xv?m@v~+=nD}-0qBSdWd;jvjOQt1`KYmVwDf*2_2<~5og^C9iK&OOc<)Dhy znYNnERF~NQwOQ9rP;7$r?V6LtY*#{>b-3UD(!GEF_wPg=oW1Tm>v_TDvIKQ^cb<`6 zzj{r5dW$zVT{76#lJ9lDnWLW)?oH~#w9e8Q43x_`N?B3TtdK(eZ!~vXh-8kO&8B>Kn$chnKt` zSUVFIx`TfS=>~R6(75-NpB`(Zj5+6|Yanr2Cu+zres2JYsK0)h2@e=snhd2T4mAr( zM_a)OjMP!W%elcvr1P0ue>LmX^3N7ucbfn?6b?c_T4Iq(uvA*%osFJf>AP|VeS1Iln`%Ur6P5=;skkeGOKTTxZF_s$s9hT;@?U#) zIBz^d)M)ET_EL?T);@J4*y~0BvroeoKD=72G^V`?%#1c%FCD+}o};+Q>L;$xj8>y` zsQ?jBWN>b^BBAymy7Wt?L770PNNf=xLY$S*-#A-(+f|U$xu`Ohe6B z)T|=Ag{YqBB{$xFPc>uU_(>rh#AlPbivQ9htVM|B3JegMX+lIdFm-JgN83D~Kd<=x z()CVB@w{5}@=kZ+N{`1|=~9LMzWO(7AiZes_Iu9-s54z$&Dc$#~%H% zVSbgXi2B@cZf*nl7%TyOwmceJ9gh|=ZX;jWS32WnI&|ZcdtvoN>uoa_gUP8xR|<=? zYr$aOm1;zE2K6Kb6&X(Hv_2Txgmfz&#Knomt-k-`z29zkRW;f4N=P`|F3^TUw5zR4 z#9gi+3x$joz&T~Av-a`67Tw7yK^_^w&kwP{dn%=oYLIl zoV$JRd4J&hp6?~!=X_gym;2_Hf1x~Go+w{Y9xD55*$>K|E4#C7N7?0NOUkB~jVXP< zbg1-O-YuoimEKv}UwV1z$4f_-{LXuO$&X6DQu2k8y(L$aw3VzbsVq6!yQ;)nJXri% z@x8^F;%IS0@$BLWIon44IrmHC9sKgB2Yn}xijBHx)W=7iQS^S%&x^iN^l;JMqI6NT zsIh2X(bS^+!uJZ_EPT1}VBuYbS9qHW+jAOoCg#j8TvPb5!s&%&1;6p0Q}FGAL*;K5 z+??|Z?_BTK3$_%53zilH3Qo@d_x!i>U(5eO{`L9YIbY8IRQ`qewfSe}=j8oMPBia3 zc@O7p%WKPpAl;uk=s!ou8ZK=!yro;DQv;6Yh(| z#2+p=xzcw@E>WKwqcGp_@tPtbri)-*Qm38ew8C&QSQl%HFg%X^(}dT{eB&ujH^uAD zd-|nkX8PK4H{zl*GNE&6Lo4c_)98cq`-2y9Qm{JS;yHToGoyWzd`#dV#PzpeMV~1= z;MD~9bC~mSTc^C^+INok)#k=pK2 zzM9-vPcvdwQ1iiu%*IJ)9o;_8SC!j=p(7e6p^I(OVG}6T+eDnYQ{H@b{7jY!sPh!Ln9$3i^6|C=v%yx0z zN|pcdi&XQMx5U5tG+#*0jkNKGBC12?(9`C5dC6VUFr5RfTUtHeee{oK_-3;Ya!0i` zg|e&O#ovEdJ_GFn=Qi?ZoCGLjJI2E7t1W-o6ji7WUoh=GUI%&=Cey0~f9l{5W zMra$>n)khy(@ybC@ofa@4FjuL>hJ0Drmnq8y2Zv#GEZ{aiZ?Dg z)i*tNV1rD_YC=dv3}x-Bp^nL>&OM!0I%YW1G!R!SYO-b&~kp(N8-!E(Nli2 z==qa;6LVv|ZLyI2`0jdk%~)T^2Nm&_WC95%3HVtYekU(|P0AYU!Gs;`5N~UwDH^iS zkwJQAPhYSRyAXt|5#KMyJ|*8IMuvrUB(-=blA2{ld3cNb-#o{+Ob&)qTYycp(9>*# zxyh#-+?Z(cKK<(k=?r=rTnSWLNOV9EK|5nXz z_xhVUjv?B_Vv^Z6+2uKW-P_ug6R|^4dYKxqELCKRJ z@lDB%fq9K=qO-}O{HLGnl$H=n&{x~pDT|L@^3oXJbm^M(l4wHuB`ag{muY1vx0&!R z%6^#7WHoo`55BA8gzf?TX&qzjtgRITXZq&xSWaGq`=>2p4w`k(>tDFZ6t*cs<{=L0 z@|=sSey6*kFYHf+7U5j4$H#Q`)B7HmE)|Q(c+QUu021JlyY07q(hXwSj9`lr2`Tr~ zi|Rh7<<1^a)l5@~P1<(%*L9?fZlOIej%lY>|e67M)$DkF5!yw+#XOScY6Y^~!VNA{*SY2vnALf{ioEH*S z#&f?kc9h?Dfm|oi_EOgx*&O-s9lNpIj!^4`i5eQ zWoXpYvo_80q=UbCS-LgScuy}k=GXIfe!YJ4Xj2>9r0ip{`u&6{SyQm9qephKDC#-T zsTWIZ3Nr$~Io}KYKwlCf*l>~=CzS}*)C6^C0U`k3Q{k)4FKJ%{))Wc#wkPx7z5lP; zei>I7T)9s?RrIOA-P#y2EB4uJS!XTttk7DIM1el{(`UsV+|eIBW&KB{Xp6)Q93dLA zKO`AQQlZZVv?xs*W8qCC&ghOJ+R<_PgDX^MO4hyp^VM2@5R8&@kKdf#znY+59O5fv zs_N>{#sqkccA&WIn-VZ_?&1PG*N%r!UeYNl}zDitXR?yAui%TNg( zBHEKejc)CqeCg7|`cCX751pKBR?b;=WWKf}*#j^K{(bX+^y}MWv$azYVC)yc}P!2 z;YxUaeyGE|fWkJk&(S>39{1+MAN6gP`*p@B2C%UN0Vt7{%Yf_yg@!X zGe=nL_o%V-0Xt#G_|CjZd$#6STI1{1((d6ALu2}Q1eESZJ-z{y4{%{P> z9A@L(kDh;}UXk6;ScUo;@!a*$&GPj+m?p$m9kN$EJI{qi zQGtXZ0-!IysgTkrWWNFdQaw#R?^oN$`D%O}36R*x`V8-sm$|jMqxG2wq=D#>*)<{X zBC!?=6jk3`S}1*tV;fTxD(rczzFJS_JFkUn0rPW6a2;z2(JlGX?o-e5)#r8~@pkf| z5qe$ZpV*z+QaN)senYx{M|%PZBh=9NiQuwzD{`JIWOA4+dpAl8l;zlar@yQ<5<%ig zggZh6eCdkk!7pE=l`AeQ9mR-{VXb8P3a{A@{Elp9%vDMpkpP$$VyN{|`eQ`$SF(RYySJ<>1M6HPE=xV<6;p#@(wV)yW)*>*!#(nye* zvn2>_!S`ZUM5TCIFz&WKWB`Oj%4Ig2eP`cvyS`*gyt=k3D|@DL-~PdJtx7@xTKaMA zJ59P(I6&KJ4Y`G{pRTQ!kto%Uo)=EaXewsxmQ>9ZOAcs_V#1+1LTDf#30V)2i3Vbbdg$&F+q@T;HxNM^E*aHpt7?I1cB5?g?L0sY#TD(DLR zNo1-KhwVU;AgF(6MY{4%uACL}eewgRNugjG6%a@qFH9mCY)2f5r*elLPixzeXdCB? z*#R@2_pi86N0jrkH=;=_?92JNxvO{=$08||ye?WiGMJrQD2c*=L;=lU9R zH*Q8;qD32J4f~v}q*%BM<|5x6rNN+Y1G2lVakNUFFy?m04k7xN@QGelztZ#OnKz-o z7K#q50u=#H(YQ#$G$P_AkcIJR~Bv4bxjJ9vP|kHK9x9eZ?_iNbj2&~Ag_IhMKhSZ1f$d3^7Y zqnYQAz3?c33nXe?z>53zf=7Y)k3&CL=B(LPk4Zc=Mno|HAQa-VH)Erss<*g4<&;|4~ef8 zYhyytnV?OxYs_kTY&eNI#2I!(tirHY$p^)RC(5CnifbLj89-+JOR$J`^uYQ6JW-xv zP~lh>t?bv~RFr5|lhzzJ8P_+%tS6Xj2c5vGq_PkJU)rD>TaTveuRd?abkHl*s|bm9 z+^?q7R!klGqwZuFs3i2&^Q~n0t{lnQVVYa5^@>jX9b5nrfd{L-eK@*T{a8fkiEiE< zMeYt*Hcy}~C?mvaD1HpglIE|lT#`!0M}TG+-!UC#R_hjqkS+ zt_J8@JMfwGov;1|q&S)8w=YxRP`a+ZQB0g2l8hc17lUO>*tHt64+~6M{)opr_R^KY zctj+b#OEd=hqd^KI`Oxqooa)m#OfbMVdwz!(oc z_vGN7#|HOYf9${m#Gf5~c)vLHcWxbeW+!LL%{5*R04E`4=FVo%;I-R_uK7ZJ2tPQF z62mNuRm#K8%Hjm!ZO0zi$|_-ML%R+hJ#ypF-P@Q$q@+*YB>P>_fou+5xBKYBj}G1a z45zV~eJ2ec1p9pK(H+j+1`ppj?3@>_KYHX&-6Ip11TH?mht0vAySS%Z_4t)XpTF69 z`~3%ouDRpreJ}EBsN0X%5Y>I80mcILiFOpwz%DCDc^E9|8Ju%sn=#K{Bg%Y`P12*vJ zI`bPEc0{4_g9mRNy7{@G-47hwbsfm2z4sIFo7fNyt^>3JdVJ{S#{{zAKf3cOJ~i=# z?>v9&;I%g&zj7CKE8q6cor72JBO*~EB{kZUfMD@@`)P&U{^|Y|{qyO_DgUZiPl`>s z?K%F1h7Q3GOX<jZ1oh>mBi%bVN^zrV;wNB8cd$4C`i{lMVWcl_pgZU3HKt!Hj8w;NFP zOfd;b=^0cYg^GfBqdKh-#o2d61Oy0VTBt$1 z6$6a~jL9%&{Qi+%X9a7D9H=9h9&v}L5{nOGI9^pqEM&Nsu$@FqM1mGnP-5`nDCaVC z;~q$$YNz<{^Q-5}sBJ|;^~7Pez_?+%PnE016*=kI5ihEm(wsWrayr&Dx4T;4FeACMukA4)B%Nw zXlFRh9B7cCL;h|5m@3~DcAFDEWD6G2%Cni>EU1w@I9->%YyU%22@$CXZPr4jCxT|} z>q#xpUnyyJ4a=t-TExWd%-dNGUTsVWH?$Nh)wCAYWK=XIyZVj%l|r*_rs=9$rg{Ym zgP!E0oBY)6-JLN_&1hlGtx8AD4v_lCB!yB^W{I*xPv?i!qxI=KhwdqXiF(q!^hBkM z?KJ4~ASyc!eK8tFI%!z0)sX89XgQFS5TOw(1)X3Hg(Uh8QO7)GUazO@t>UMP z9x1BxdJFms{yBeh-t^p6IluPymR?>`_cyh_|Lq48+tT;`HU$oL{4~Kj5rxqrEJjd# zVX*4%wiY3?6B$+vU&MDntZSfoDw2TPyQtlgyF&Mp1VpJ_K*IG&W;qR?xzBQx# zREbZWRtXXlUI6B$Vb>pf>T{49n0XojZ1Acphj!g#$nQm_J9K#1*+YA-d*{VFU@6BY zFm%iQqtD-EuD33;LM(xvu4d|Yu{*oL-x)(zYD2zkrM;eh*!7Mzmy>!2QzH|B^L_=6COM( zGMyR}V_n>Ee^>eest!Gsmgu>?4^S~jU%U_8>d)O$?L~kk%SM-bzFo{Gr6ZYblu3N1&>OKfxl( z3~KX5KLGfQXu!FIKcW8MXT*a7D+e_g14V`Ws1kJzcp;6SbKRXWJ=v-DO1mb~Yq#zC z)2oY3(D-DHl}H!JaTgFo(m>#Sg2UFfg%bI0|$5re$*dt8+fqi#<4F6^$~8|>^exc z54pm=k`M~v4bao1KE%)>(Uzb7bn%5gdk7&iURncR%jJZT#t!di7ZH!c%R;VNJOt(! z(*Z&k3YkU+zuMN5{7>}p8X-`ltQ2KWzwLJLIn-<)Nzfi<^N@m5TsF=nVjTz2U0fi7RDV$_}>lzd#F}jNKlClQFSKb;^?EOmi zU97>&+}tLYf$%;%+y`W)wi)oWEbcamki?WOqLRL-q(p~RAYd40#0(UWBwpbAh0T=w zB>J`*Y;U~m&b?c=Z9RI!LXD_xH1Kc+<^*=WZX4WZFCCAZj9bz{{q>xM%gxxc&c>Kx z*c5vDZCv&%8%|%e&UxvY%*<_DfB6+4rc)-@4J?C;4{1dzqPgGdrJb0)!pSX>%@o(Y z9vXI*2%iE##S~0Un?phvtEMg{7UoS2XbPga2?wb~DgrKli>hO232QW>TE$SL$nJhW zw)TxwlbqZs5cSTKph z`oz|gOFUyIg`yg~bwQKh_1X{vn7ah{=2`_P#*UJs2p52<>dhQ4U$&`XJPP^v%*j@Z!~MeN4Zo4rnMmmQlu&tFyctXC91hGPQK zHMgThJjK{RWN3DVlpgBhp>j=c_Ap@vxZxv1fZ^Cpq$AG{2y~O~Y*7+)V`0eZ<(cHR zJ@5Vyp|d7-H)R%AZe6Agk7r0pW6_67AhVg}IU+^T;wLyy=dHS-6qK^vj}HZEJb ze7PAyq&HANQHb$jt9`|m%k(qry=9!`#H`|Q(bCXGpTIW;)qtSV@v3NWu|4v>v+>kx zmzpZABEX-q)~XoySZk`!t~sq0`OyJZ6jh^(!gdiQm!nmsB6NuMT^r~SmXsK$Q8qm9 z(0&*#UW_%HxQ+6wGM5ia+nfx;{}HtfIX!TpKr#(hCDoH`<<=Shz+d0JeH7tJ)9Wk= zBkh$YiBBfNZ?vUFLQL9w!!4vA8nLN@sX4Kpfs28Xuv;acp>8{2c#=*dUMZ8O+RnlU zJ2-sc**s5-hwQ;`g5GW92;Nbz(;F}fTioChmm|`xTjv*JOrw?+6QIQWBJeuQG^;A} z(kq?bIm4k3MWi z^~;wAOc+{Mbjz0TmMs*N^?9-fzM#nY1$FKZi^^^inam`x=BdN%mP<18t946 zGZB(Uuld5!hp!pjbvtw!gIC>xtHnrG7q1$%Tw-BOqS$ojO;^K<05!e%-e@=;woeCAKbmqkUwHD z7<}#}jOl~VZxz4M*6qmrcVade+;ye7Qqr0o{@mMJ@A6y5gcn~Lyy`)04dgMyCBtum!(_FnF4yYRHSWT4C{+s5op8loFMq=o!__up&a z?=|rMdkv)53?6=yNX4r0b?Hg!Gye<>C(Z3*x)S$04)XeyC`ip6n|rK9=z}MW*dw_W zt>O8lBgY=S-v4Po+!FO>Bo}~^ozO#k1S76pS6&*x@?b_8W+7|%cU^?Np8|?(F9jr# z%dlet;6&ZZ#D*R`I7jVunNy7XzKl#IN+UdS8M1eX2D{3FBOnneL$^$Rj_`;L#kz;T z4OFH9YJ@ozyQHALT~{;D8>&Cs_oT{*I(Cc{Xs|+}sk&erE)$IR$^(^O^?l2(imGa{ z@@0`EVh3o?`pcO=69JeHMS&$?o0%KE00*h01(k9*4mr83ny|`V)nQH6Trq~)C=7cA zC!oEm&N{?sNr7U$K5Xu)0xJv$=n$ZlOwrJeJ5N>gMS5bn5V~i)Hpp6lRBJJf0K9gs zIv8x?=-rb0aOMSnW8;!gV{3O;NHt(uUyR8nw6?RSFVv`ZD0oJV1E{jT_okx!qVw!J zlN6JZTGHg_Xzp}zZ1Qq=361~AShE5to?&^Euy4}JDE_$jZ8+ z2MyE05VB9`b5@L+tJW3O0C2XY9;KK#PY-2nty>&s3@;V4{Gzht0~JUs*b_R z$c-2pf!?0|(vG>sfv4zW98%CvngqG(7Qo^i&C z%@?I&P2FOy6NQZuVfk}+3;|Z5aVZ803?u@x7LsC6023lvmz8&`vP9Av+wkde?a@kOMJubOmIZAed6HA2O&nS^0Xu?r|j2CD)f1kwsfDIcO` zcQu2c6rAcvZ#;hJjSxCw#q9KYb>pUsM1Wfa4OOha4kEaCn`~#-LfON=+uYO;|IU3=x=u3LV&^?KyOt?wLpVd$BskKS=J!Ya(;BypA#4KM}T?4g}okKeiV z*yqwiyRRMEBXHND{f7o$8nCfO8P9c#&+N^2K2Nm*3zP_SBll6v&v}83y`94`9Xil- zZVH&s9N2=&gh4ry%+wvf|9YT%V=AVngT??%1Wa@LTC5F_OJRD;u7%{l8sVBy>zvUR z99joymvRC_N}s`-Vr8O53&n?N6-wm+om z>37Pg%9QB1#H|%m3~43J%m;u+owZa@Z-#y6f1>)UO0Z3s$KV-@1Tn+%i`k4Xi(^72 zCFKHTWivT-R9hGPe~IsEPhoz+NArJ~cTet{Lq_HA|TLs?Qs)THDY_Tx2o~;f4jn1fE)^TIbH0DF?ps%bYULCF45)YH0Gu zi$(?$YmA2t0(Yi{)WtpYn{{z(A#`aXVmY}bKIAHwalM6956YEEPAM72vEoQNI1?ka1n87As88KY`GGr4xCbPRvZbVQ8w3x%Fw}iJn0Krx~ zy>S0~?_OT&shzm=5;L!lvmKpoh6X2^%2{!|obJ;`07@;RlY){B8?t{c&bzsuuN6%? zH^&*Ss^vw=i$@AXNskwg>g)wSv64ezxaO#nmO|VShBbnHU64EX$yJ;CI%Ao6``>=N z83#huhIF1s$R{(k28pPmrN#4-(p~?_p3n)dll07pa6Sd(6`|Lf09BQx5>>4~wT$E9m~o4Y#N9QsaY zC8Ya>`)|1b0tr4^Vem;hF^KYqr#rwwjrdtl1iXyutxheT`pDvsBW91kl znEuwtLMp>vZcL@S4m|%FDUT|~h0^7Gd${(=Qr6ua#ZI3{g`>$PQqJlxR224Z{5w&| z@~slc5Nt4~2nklglDbwZ!oRRpFh423F&!W}!8jyZPzn%VYj=Bl+3!PIpE4M`()=|g zT2QPZWW~QGkP1UysX6=rBoha~ABvQ9Gu={yke(4B+)afh97ggKqbBL0>^12%2cF$N zie@ryT8%Lk<%ypXnav23<~L;R6C;sv0Xgj=+oy6SZ10;^*5$coI^&zN9A zh+QAEy&pn>7TY`{(}*>HSd)coo=7oQXc&;SU?OoRxeNeVps3K9TSD-*k^|4Y+h67h z)vVusf%?*^TEF4Ptco_*htueX%MBq((h33+4+l!qlVH& z6BnC1nL4hlpLE&!DW4-d2kL=zh1!-Du>378PVBawS7BH+(}22heTuFSwue8NVNA^g zum&=W#?##g?s(5r=BXW9mtJOC#3}0$xk%)rnCG^42Y#mtiFgq!GP%=rgy`}LMG z&-743+At($CW@z9zLD@>RW`>Lu_+(1p3Qo+m+%L6ghpObU+>p5H4%#~M#wGI^eYn{ z04&t&o>)(3rGL2u@5=b74MUDHcF1O+ z891S<^fIy-QfmPrSPv9JNy)lwP_V;Jcghmq#u`- zjq{B5EcKL*E!mO(qvG14H;O`q_ZNP=d|lr5++N?WbN=G}H}CqgCpqPB|H~BKf7L*^ zRNxj7b-!mSx|2Pv@Ef)#+N?D@wrIp=D0|`}2DAVZUs7gU9b^=Ryak|d1fhp!!q|qW zf#U*V#O9j||Kh#V3;@;ejJpm>P!Erj(Bqc=IOe=&rZF4OEZ_$?ZzU7;u=Hg}72E=h zwF+nI%{P0P6zyzJ_KKav%y~zdOPqF>KK;I9Kl->P&0V20W!i^Q)YQMQhY`T~4e6db z$2g{AtEgiP%a%@t9G4ZPyrUm2Uq9+X`*o{pm^r8i!c^J?ut=>~rugiZ9qwV$rd?0rY~)xhvNsWvm27+!Bh1R19~7>RWvw_v%(e2d>YyDCzj9$qeKd+kw6W5(Tc zlv4N3tR;x6jTuGoMiASQD~*c@6kyR;>9@lDT>JdQ1P&fKEauWf4-IaAmf%1?H(}%W zfu|)h5*Pr!{=0!M++_9)9^MO>eqB$*v;UD9XY^<5B$8siR;keeZX&4j82N+rL-k8m zaZW^vRO?}^B#yl4+1T1nMS@)5!Ca@Nuz#NaF6|lRda?L<~UO*`n!59QBrSZ z5vrRTW{U26?fGuY1!e$5w0h_yMd-Jd@H)FzL-xAlFOqpt=EybHsUlj;^^Xv=NYJEc z8S)a;IcQxGi&n53E`@BzTJwYABe?GD1IBZq=q}HzbAl?HEz48;X|Y-VjK= zQXpja_B-mAkzVAp;=>8VyE;cGXfn!VR^9jN4NFK*Fs>^7Ni|nwF0suOcn>?2_R9P^ z_6hc9vDY$1T45X1U<)dSA(VDN*ZKvSfg|EZZf=ibYHuT?LIV2`Z7r;mRB5EMtDSgy z4Sh$3Ci1N-<+<|G!q57g`kCbxfdf`K?IrpZh8_w>T{Oeti0=k%RjfC*%Z+vS01LaR z9CiE}vcXu7Nh;M2LAz@xmhfQmGSa>S7K0DAAj90@7;WWhyV#k_?)}%VE~8e7(UW)$ z3@5mBIU}WEr6*nuxpuo9j7t-1oyZf$fCXSt3Hx!CPN@bESnQ}i2dc~ibs{s0x8;R0 z#S*XxWbVG74iAI1+WX{nkM;VviTyb}xyWD@Ud6z)DWIQ=sd8xB9w@Sq&B zJ)gQ|qd9d0CU+$T#vQl0vl@emf zCK8cQmreJ0?)_-JQ!%sCx%cgTsClSyqL|5KP}r*toTp$EK5gDGM1~m*Vcly`AEP7V!t@l><-L0 zR4F(1+_<`Ko9F6}Wh*SR;lAr%JxxI!>B%~^4NMU{gGBX?Tnd$!9*}Z2(pu3*8ifL@ zA?{zHIWZOiuS8!Hl@Mi6Tp3KDqM9ncb+hKcTe`qsZv^j1%iG)TS)Aw?lXtPq#X1Mh zm|fi(Rv?DRZkC{JO^a>TOy?+~fmn;=QsM%2aYGHm(*p@9X0;mlqAkeam?;5$3hx@0 z3#LK1dV_8{6t~Eh!d9G0NZxjLinQ(o9f<@}2-JIo@Qx>80M1-eii zgLtuH*#%+R2QH?G!2 z(<&F*-tB)|fp+t zkDNe60vyN_qCYs|P>Wrwv>}ewQ}1|Mv@bH18$%EYJ}4g_KD;>& zu!rUco)5jatBbiyJl--}u6`nC`?+>$t7=f)@VW)$8pBc{f`P_a5CfoxQoRUCy%K5B z-CjEC>*)WDo>6BMw-jXx#}(|z`!%uu*Oc$}eLm+2@7VltRhNgbno1Cg>F}+2O6&FkoWHuv*b#>xj_wT)#Nca;V@1@25;X5i&oUh#tlGft+Z@eImc|LsWxI z6~UNhcLGHsQ#bxPmth6?7Ee{(Mv8>fj{mB+4p#^T{KX$346UNxq2Lj7;(N z=rG5fDg1|-T+)d{$FIe7F|neyURB4;IB_`WoGmLdQ*TUV*VG-MN9%^o|0>8(=l=DP zCKsACURM5E&PQFf!$l({m*!NmHL!7Ww>l%_dAg#OKvm4~;&F^0eXJ2(904ZUVrAvV zSlAiuGpA^dCD$tVGGtxS6qu9JpE`8xKcWVx=F^Ot=?l8r;q^u-N};lSa4nSGS+_K3 zdj(d>nJJMloY!9vY39pf2-d($DuZTn?Nx-k?8Yrj`pnyB6(06Vcn;mlG#C3LTMmX$7hs`AmF7$wpyi!k?QTkW{2md=S~+n z*}O|__UaKZGS>mPj7+0V&pq_UD=9c%#@D2$=@_^{nmM#VFc_p-Hg~d`v=)F=og?q< z`}}_Ya!J}Ej0J$M>E!4Fwlb?@-YSc3R=)~83}>Q*y&B2ik@sP8sg#i}dTaivo|{~+ zp^=#CH4`IOO2~SolgTr)TJyk|XaD>gXgU{8Sf8G*YSh3&<{<= zQztseOwpDi#K&G@)c^347s)YbFj@?&liWxmHoKUnWYj>m;r3#+)i-SFAw1YN2&DW| zd%75Euv>^AOjO&fY2Z>l4_`HQo&enSTQRROR`Qok;$V21r104_vD!%8iB-k5*LgYD zzF{Q9I!Nn}>;BoRoHe1=u-_?bvpCT;ObCK_LCmrdFz%w$rQJnMmp=QacNdfFaC~Tm z$_s6pEQDC>i1=|jTDq)-Ng^Wq6=#P1!Gu<3$qGck6kSotQSSG?CiY^h1|32J9o)qP zZeOlB=UrGxuFo_-`^6oQT}+!?A2ljYonWT>j1VPUq*2EHFggMR8t(Pb@YCMD{Wf7S zTe*1Y%1;Ow8VStyg7L9w8A9KYAUlgh*lF~Xk-_2*7LFF*o|b(^ZF37VkL!uUHpGc< zygHUuz9k9#o}BVqupkYeirR$@Npz}e{lyZ(G)ZrfM5F;Nx%m|hlcM6>k(ai%M8}XR z5H`kOE0#P_IZz((B*-*odj^~pQu(MI3*}R+^klVhTvHLD5@?|@rBdmSJo{f3t*#OJ zH)ACyhE3TDK@=bT+!2W*fll%2J1nY17a^XI%N%%)u(#t^UWv`zuUqPd^O@->&)$+j=o>qJO4_Gwpbc~v-B^A$qNC^{q)SVDmHAYrUOzhx z3{1de$hFvAWru=-DtudbsxAcnm4s1Okap267CI_R8yJF>` z5@y)T(BC?Fu3V9t>;L7}t8Ko^;}73@^zi55ngnJ3%dOW)sN>=52Jd(g=1kgxL8I3% zZXkSdal(*NIy{+cR@IwGOAcbK>P|HEv<~gLfyhQ@Hv)nQg>?4N0tXKbz;R+T z1jlcEhCohoa!wsQc#UYfQzvGU@Q10q=I*M-0aDm5v5pc%2z$OT#X(OoBYC_y0E z)vU76l;@s$7!ElG%k)?!kz0m~9Gf38zDO}mSU0Ri$k{*m(!>5N)IA=K)n@6EO>09J)I+$1n`;z2ni^s zLb->{2-$r}PZLOyWKxDpduY!`OhWcN zRV30ATl?b?`2(}p$*hh&To?xy zCpsU@`%TU}PC;feH5kUdVIY`*JjkUjkmPp6FfDK;h6z+J>*49kpS%BOfilm;i8bl- z%^RGejYbrc53WeUz@8R4hA{geF1uJOl!;6=$QY=8F2lWwSTeiH#A(G#5X9eQ05EES z6sV46`jTKFac3p*K5x9W?3bRWv+ru!u>PXs8%vo}4C2c)$P9UzSr;Q(QuhR{CtD(Q z=b<0{>Qg12(Gw@H&~f5|);=H~i8vq*#tUmD)Y`Y!a_AcVA`!N5(w!rP6LOS968eq< z-XEWA%7)NhE85(!34fzdeiF(c`2TXxMIO&Zmv$u@+SZ_``+>=}MK69{JgY1dRfMz}Z%b^+t~oZo zVP`5uv)TyQtt@;76o)z7=%rirP>ljKbJ1-43&h|SATB{?eYJDffoGyotCt?e21B1?#bo*Y!hPP2 z;}QWMxKA|ZDktU}rBA~SNNzHo8tH8i1G?M{OL&h+to8?_LLhr!7|>WMapk#8BJ%q7 zJG=Z%m8&v<9{=B#EPE+MH_mlW87eiF^qYX{P~?%O}`^Y{O}0r7ZTut!z? zQnCl2`%#M3tSnz)ekG@X*$dLrU1j!XG7vgR!j+Q2Oao0BzyzWlbD01xI=A2}IlnMf zISWp~OxeKCU;X_uXqRdlGMA_zR7zJ9Uk)w@=-PD5L>BG{HrgJx6YP>aUfF9_SjmLc0cl`im0PEl;~9s^piIy8FAIh+FJ_8PH`$_ zW;!^!1r?rK%_;<~Ne8j|suuWe(=kwG-mDwh@~l*LLMxbEhI^eYc*u8>xAy=!S&%Tu5oj{ zWL-L*!%I%N!h3r5p#~NizzAm$6lOcqG4QiHcb4{KrV zbS2=H8J%+Kw_j_`?4oy@?`F`MLBJGhjy6bQ!wE&DnW>BRL;JIwSnkW9ApiC5WyK6F1`=)1Jn!Is<6-y&J3Z!`?;qYgf%-wnKIiDnn-t7T;OIlC5n9A!|+Q$h{s+wIG8U`Zs2FjZ_u$q@E`fk%S6Db(*dm_{l%N zyVjV0ylVc5DxqOV7^ruAek_sbs#|I;Larl=Bi;V084xT9t_u*72yylL3R>3z{q3^> zu{>1-5v?gv;V=7W(I)SZQ!jRSu0v{Np6-T8bca(Nwv3T39r(#Lw~a^qoIHEIN=*&Y zCMDmvy@XYu)4i^ZR!*oxk=yLAlU4sB*!W}wslx<}hn#ZJKtOjO1+9Ij;C#>ToB~!M zdQ)Zm_V>gwwggz30B>68u}>4mm%U@=5tSpf2DejRbZ zI#%!K`Z7wIh!U|n;koKDU@V3&tD#v;Rod2vjVK!6qM%Y44<(*L0Yy+d>G!du?s~&l zWsL@jbtV5n%qrYsGsJ(W&v3Y~%mZm<)|=QEe5)kpRcKR&0Bg8Sf_VYnNeo$-8bGk} z{WI}_AN}H*PpA(i)1y9=O!1nYbqIRNo?e=@`3;#}|F{+IeK|H4K=`64q6L{(R{<<1 z+=~=eGGpL&5b`}eERwny;^@T*k(XjY8S@QKCQg1Xlb~xOU4g|EBM%Nq_{3Z}L9|B2 zd?AKLXdSF{CMtpfLAyq0%+|URpeCLN zc?Bs8A!c1Q)xyruX&@=hJrYaR+|!LKMU${lU1(eoAGu16d0ysG{mB}niED@g`EHG3 z;xu?I-LUO8+w%r~cx14s%rkb}98X>2yD-; zN}s07SMqTrF^CSKhMTS1MDiO3M<|$~sM0A5`w(&orV|muQ2uPFJk_i^Wo8&*N{#qE#p>IH^{fWyx8@9Ys40Q(BN zQp^q^EdWu;*woF;Asa>{`oJ`jt=1Dz+zy{049loY2z`O}o;JE^8<-LBQOyoOOHh*- z_!yDI7e$ZOl&c28V(#x=mu$4i$vAoBbDLkaz}s|vk%1p(z7t2%m=Ih)Rvdq&$eBc* zKu$$-1$S>RL)L!i9ZmFofB*`O=R5~}MTwkgLoI%d7g1i2wHpdMy>B_+pens?;H@A3 z=|b!&W2a~4nTN@HXg~yl0!~HwA&rl<55zKJgEnfMjK-uVGdgp=X57+8NvpJCDj>I$ z9-@)=n-~S<`tTYUSh)AWH>(x$yltKmKBuui zG(}=dJKL5>eo4XHg=4N&a{QXZ%dMp-h;cX+a6qY)B41dKSpix!1j`L#tOs2P#8?op z#WPgWfq=)U3UB(a_FYB>Ec0u(V46qEG!Fo+CKSMA1rU`91XWW+`mgPkL?IutEgv+wY8+O@W;kI?)@^BF2pj}L}q7SQ$X5;zB7JlCP205itx$|i0q_Yj9(4>~5Z0u7Mg6Z=B;ZQAA4Uk7xGWH? zV1oPlL0SC1aBP83FlVM2*D$4}dSZ42Be<^?m%ZE_5#n-R#>i_6O%AP39AwA~;JawE zfQlp>!v*rjc_tF0rupjYmr!3ReU>IDeeT{{1~1U;HnVv{f}1Cb`P%X-oOl&(0q2bO z_T88fSdD*?*xKitpk7{b`)A8uo$S10m0S_DT@||VA*ONOg)feu0iU^OB1Wz^_IwHM zNnf~k@7rT((i5hG$Ccbioiq|!rTN_mr>1O?ug?6qW~AnTd+d-ptuhAhbuBr*wRfi{>nI^|DBAe=F-Rmp7K*~s5i>)5op zg#5;**qJ!#qKsrRgxEGuM>cb&1`OKtKvF9Tx~|{f15-%CUo)c*fr9=mGLO6i-;3B1 zff>_*Qj#`Gl)_|WnrP7a0HCYJ>O)Z~{8NWv`mC7%PvFI?d~-eDw(DocoNyOY8aKj4 zVGE6R%g?$5hmQoNxS|SD>Yz1?@N6ljIN)U|Vz@Lb*~Wqc?W)w2$K!qZ!>8%LANikO z0|T4){_q9DrKU`&Pj67T%yyq`K++iL%~7P_IE@e61#AYc4abSR}g8sV5!r2sOy$`b2z)LPe} zR3eScA=xwrG?^6z$Em)i+nFt;AI(=Hm$Pjcjnz=vmtU^C{rSkQ^S zwO`74QROAoD!_9llaa1tjbP9hV?2aDaDLm5On+wYHy-H3NHca)dcBdOmu=~ZMZ2;; zAzi&#b}}3ZEx8e-Owfuw^$w$MNB#_Vs`t)#7{oM)WX8^vh&!-&StOB6-)2O_G9GEM zWzgHtIlXX}{ducvO>BT<8pi&dAi|0KJ1ThxaC9|y1C_|>gtXB_{JiOTr1NFmYj(y@PqTIj>)R!p5jAQXj@_PAMUIlKVNJ zv-I4(N4`J7eAjaIv84+LJ|%+K`9UFK8~oGY{kI%XKRx*3LqkvRIrjOh25);nn8Ek% z8zSAR5`vrTtKc~NB8A}aIC^B8fIK8+t3sU&-e~iZGk*2)Y`JC5&}kZ{r?T`Y*jp0> z3LeD5@o6ElC~f=Pbk*L+A2^4|u!(chlaN8Y72FixST};Yzozv!K;Z&aOTgn?G zKFG+F=Q-!N+FYr3C;0!_p8R!rpB^>Am-5WcJz93GtfKUq(y1jIeaA+EB!e4?+!R>f$Ytn*r(C$ioNAn(F7##=fLD#Nqq229=1A+z074j{xMESYgfx(qZq^h@9_ki-hgm&SF8Aj8O()Iw+(>2BXcmGh zk#bO;F$791rX8zhzv2kJM4Csyep`3IwvqA-r@LhCRWP?WZUf{)p_AI=8;NA@rJfzb zs`$TUWBraFzpzx%N+IoorL{9=pw~uHVOWg>tz|Dm_5qe+Ttpm1NL-EsFx&o8H=O#z zxu!-XPeLYV#~W{rG4Z$(xPw$XMnlZ-E-{atcoq62W)E`)Cbt(QW?2ek19I@1BkM!1 z+fwOY4<%g+!?Q-QEtn6WBhu8kdO1Gyq=~?5G^iZQASu2HQC8KQp+0aSFBKs_`wA@& z=8g9JyWJ{ks>(e7hJ6kL-bd!$hPLbJ)_>f$o@W6wFr>bRd&ee35GSRjoXwH)o54tGqHwg zQ3aHl>{e{1wI^M_sfF^pNU#Aa9*x*k z7NHMbWJ6vTaZ8cZdjVQ$r*PfgueUcQ@4TPA=gpb2!g=p1J?3Kk8LnBXpkfmz(2`#H zU~BqcwHF zrctX#A>dip^wg0M({!M#rB%pc><(xLJh|*pj;f&^H?gZ-vg4(1jt6uL(z-$CsI6&PsbzzV^NY`{0DmO$aKTOghdg^J>Zq}vEFfi!wKrrpA1 zMFJ8Eu?jC~6M={n%MDdS&`J<2u%&v*b(V4&zkl?Pa*o<>TYa7?(#rOMd6x`(fu<<` z+n~y)Q4?4tvRDdr9uvNmoj4n%su7*gC^i$UIegS}wHG143lo~(*^jUy(28#1T$fdK zh)CoG#g+vl2Cmr@KB?B2P?8c8o$N5bLAk(wQ~iAD^LG5>t0knop18O{>4(xj=0HVx zrVwicy+O~FU9^H^b|rLzvm|3PiWgr(y!fumwT*Crdb&M32MVt8eATJCS#q){d6N1v z;JVVx)_^BP=zNQnwcwBuW^cSA=tescO06;kHVjys7EaVAJ#3CDE9 zniV+L4XrFvDBxu=(K0M!FqU@MBmf;Nq7c`xq2t9GfBW}Os>&(MmLqFwm1mHc!KAvc z+wskxZo=C!x%L7Lwn#7IZs-Nxus~- zX`XDe)*;-+LF?H8rUwZ&mPg6bghX0s4ztsJb5YT@e^bhqsaYbuld~vrm9g90^jKs%><}?8AT< zq`IK-O!fz{@R=7N@jVL~E)`eP8vIdQbA?t}Xae z!Mgmf<-J?>e@X+n?-xHd>Zjh9y(bk(fWZGl|I*VlPrUldYUQ|1=c!aq1_=fABQ<2x z8(RAyc-5S?}n|mE)=+a?Vp#VH!kdzqt(sTbT|!7A})oS0NY3wUck~!B6xo#w*(6sBM3&k zK#pfsuJ1RnUzz3@s&G_-?qOP@O$*(u;?@OuD#NARkozvDbi6v!iWO`j*|yn@Z#J*ASuro0T6BBPdZ!}YQzaum#-o6gx5o@GQWNG`n0YXx%eMHn zvKedOkYOGb&=)`+r7Un?4n!EwwX0S*NtpXwu*BiVrQ_PUIc1&C?5ZSFg_ajUL7>vb z1sCe7RSgcX{GR>HWaOt2gB$&G&;Ou4nx2$7_{hDdf?gXpO#r?*6{*fxbCCLx1=EuL zhOB)T*<{Sx&kC9=VU_$}zDtWx>Ew_?g<6?02-AQJ1PV0~cnVIQGj_nLvBCEjGNp zm&6iH5rPqhFEGyPUXjE#51aIMrG!$UUt5T@n^?3pqM&%H2cM~8=b|wdaxm)}v_?7Y z+v;@`zzf2=j5JK5o1Mmhk(&mIme_3o7v>7|Mp63Y8%W#G0DMg*u8(G>X+m^t8-_E_ zK3i(hA8AE%fIoZYrrliKk)Lz?A8ui?6zg^XNAmapAXx!?kceWl*qZAJr7_cBQ{V2JjX~%-rkmAm< zr*Lwleg!Hb07M1JDN|pjC-eB9zX|w(q$8Pms*q)t@&yuc{lO5La#ljRCQTI68L&j` zu5eXs5l_MvI5&b$wfQBpg8CB>ISg>!`u3P-M5V;AC(cRSdO#Bmv|eHYnBCwo62pxc z1r78^@P6WA5tUNO12aHc9$&#jrV3oARRFYv3rHZ|sx1zR@qLE2uNo6o`kQ7ZZ zL1>nk0}SR*QWd$-f5WS3U}5I*J01#B_v5DwT%jXoCWLFUH@RAVmaMiGW}l+(>BcP@ zIw7?v1r*^|y3pcCoLSWdWTEN`{|P)neqybF76>3-r3;EK$tnF@#+%ECU^40vj#24f zh+>+(E0=J;NFRn%d4hGNC1{&KzNkq6SwWUrj8ljpb7lUR8SpPoCN?2w%gl=S!m)K2 zvH0TrMdtcfEjF70Aal5>%^8C2Gpkr#6}`Hd$=;G$JhwhpcGp;^M`%h`bW`ce0`jo? zv`kXkWtrk&PXZ8uQY_<$;pq`-ABMk8hMmV{q7r%B7NK4CDVYG9Nv^FaA_ZRO0KZ({ z)MQo3cr%Y*``o2vo@tZn(lti%n$oW6p*+qC z&XQZ(cNUos<%6Nm6_sM9H5$I12~%H94WM9=MG7kQjw8**g`T4M1%sXo^M0E1vadOB ze(u%YKbAe6f4pd@w7ulN2=Y%aJ#zc;PGrmAob*yvLoa{=yeF~(6c*5--~w{h%M(X# zhx9Cq6dp<%QJOK14?ecX%93di9d`P4a><*N@I!ka9lACx8HnTxC`^W)ylL=;YYmmW zA?$ePg&W}{83y@iXe;==_q!NopzlC$m@Q+XP?Z~&cxpzhPNd!H%HQw#$^Rgo>i^rK zq!%6e?a)$8jpHWu8_(lxdf*oM+eo4t`3=e9?K^i#Kn}hkgbIdGY>A5KSCg2C8=PdA z3v{qDXrPMhynpGkyq%I;Rx7(|wvi{S;3qNI2_DvEwUeza*uJ_`P5q!`1D!2>mg$4R zkFa!X9y%i1E+m~1)8mia^Ik4feDM7ASZ#NusZBkJM5^7eI4D_4WH}S#$nB7Io|^Ox zgw&rBa6T_;`eqYWX-ohnR6xP^V)RIA3=?B5rDE#TltnIKC;Uo5ljpbgThExCsW|e* zpOH~z;*@#m3ruZ8O@a`Rq~UgiJ0bE6w-b$@iVWMp3vZnxY6?+PCUy#Iip@jcuc=Mk z%)z#GQf3=SRYd$1pNbIp%cNrY*reE!putt<<~MS8Cha0uRay2dUfl?NYMKwoB+3kG zFX5I;bm_!uO*Lq`J({Cc20q2ck9-+R^r z_edo1;F2D+oQYo@Tdw)tuX=ScPo{=+A3g|LY6kCbTcKc0x za*uefvI|3CuYIZwmU9f6*+=3wl1ITNsgJTYQ^lYBPczT_*M`P)@ALcLE`>_9q9*-W zECk+)&j|Chba#{^{)>2#T?^R<2lrfMF|IwfXYi&SgZp+fpV$Ze=ldH?4b2DJWeC~G zbHbc-R2kUF@I1J4*U^U`W#@<_l2EY?UG=PUR4&myM?ZgL@ahM+L|jEH|9Hm!O1mDb ztL?{vpaeN-RTz%nZq0HsJeQh1pH4AnN62 zqKQW3FLZ)q%jI-o)>5O1&_>!!-w0j@-e77>m3U_&lb4+Oho39+@Pkn7QqprXMZ zCV)MQMSTJ8d*;~{(O55x9{xu1kO;AQigE7JX}RC=?6#Xjbxl}g^;t84!*|MWZ(p_i z)K*monuC%YQ-36yo5@ChnafF?*;Q2)Xlrh^?7?Ur%7-OCuwv6_%#I*TZlY9zp(7+x zo|IkVY2doz>Sqg*>4w^b<1sR`I?yX=59S2=X3b<|y8pj6I{i1EmAU-+pC7!4wlR5j zdb0wTGj%bzcEz$5-V&>sGkeZ?wdd8&a<9{j%?Zm{s*$A8CO#4}{x!6XBpGR1{VVz< z!VN1Q)aqDJrT1Tm91xH6S576Rm$?Znfw1v`cR6#W3Sb}SAg8?Z z+%?CZeU!m>nv9GF!0mbK4`=^)l-(LkTtzm5BD)4DKn!EBgjJ+8Fu?&6iEY8~DUn{; z(B?`yRZ&cdMzjz|7-FOu2+x`VYuxA>yO0oxnzzBlV`o{hqy&s4k<8-fANhU^Mzje_ z({0AG*q>EsgW2M7az{InboMfI!n5!*+y(g3ekUD4)#95ia~3^A|729n)|we;u-4Km zbOuK0H?W*7p*64@VrcRas)) z>?^x!+EquHh0ovg&=thTO_(xJWIzd%$RCrCKNx6_eCs!l%0H_Q8Yq2y5iN`;R@kgYW^44qo-xJI|-z zImDq`kG^m{M+fhG;OOC(jBvmS`oQA@FB}_qVesk)hcXWwd*R5?T`wBGOu7A`t@5+Q z#(Q5c^AzqX{dLYCOW!Dcq4XhQ0XCIxD6P+VrZiCMEqS-(hb6C-94UFQ;)de6#s1=goYkZLWz;L)=SMv`{};Q=rk zr*K2zqQV*Fm*(^qo>7=vaJ1kn1^WuFF6hjeT(GntR4~TdRlXqq&-q95U&((ke{X)$ z+vuH}e@Xt5{HgiHdHH4xwCW6$oZb{SH4$#kClJKcZ)CM+vr>73+3Eg{v+R+<*$}MU4Aphurb|TZ=GQV z>(hLp+^#lbO$ym7dfYcVw=2@!5yl*b^P(5kr66@3^1=x`JN^G6+ zwh(b4h;z{`NpE<<*W{+aAbUcl%a{?#_t4DGp5mLFYYFNb3FD8B9`|vdp4=6#shJsC zo5YAN@b<{Wr`KHc5#N;Dc(fzhOJ4{HwYu-zmnWa&Tb3*04q@Gv`3+l_<#3c)68rDj z-~CacJjgmt$w`76|MA}@`4&=W2^eOuG(^iGvn~GbNYa-(Wl_#`oeWle2vFxm}x*sbC`6 z7FtFWG+uJF&V>d_nDTw;7jdb%I8ZRrXZQeS<>aMr^_9I08)o*Oyo{rK7Hp2*I^H+i zM?r)vjx0@5CTq3*;+^_w4XdAnG7ZIhc=2fY=>1QQ^#$dfgrJjYFw|9xOyt>mb-R2l zULV7QxB*!dfA%{&wQ=!zNGP3Tw^_;U|LYZ}`lfTXC}2)BbI$gvvHH%*a0h;qsyXM) zaiY7R2H~@%ytjP(V^zK|S8}0-YYV1rc zN+iMcVxauU#Kmuo@lE%s3lfbf5^81WNyR_?r>VY%+%AOY?nKC>byEo#9;o1E$(gKL ze=2YEmXq}@_-9%5RqGqqroH1hnac_;Hbc|Zk|1~X?`?yip z3fg`Dbh2+!E+I?Zv3|OFsy!!X{&jjbq;RPyvQo%_coyw>cbB%Cji4=Cq7lwdcHl1Z zp7DDADZVK_Shb?k4s_95v0q0CT32(pE7TorY0BC9V4c=AlvfFE3`|;<-nskE810+n zgU6gS5pWnKnl5pjZC^-FN$MtpF6A|1gj*(tYksY2tT(I+G-(+qVjWt7k zC+x<#U;dgt7}KF}g>$N`c=oKjtio5v@JLJ{{U8w+m!kxUh6qNGVdY1c6si>fB#EWdj5)HN`9VMHC5mz95*@z2;x7UIs38CX$_-Bz!cR)%ma2t8AjR6`^}ep zAMs7|v4v(I?h9>9ty-`s|EzyF+qam3Qgn5&%%o!JMw902YdlJS70 zcg<68N}~bp0rovaLt~md>pP{J^jO+ZUsrcXCN-8>hs&GI#EGyJU*DtKrEvNcAO4@$7w~)&;_3G7{ zdTW1OZ@&h5ZJ*gnq5Ao~Bqrn|aWEg6ffr8HE`L!lM@Cm`t62n%esV(H6Ft(qGWRv1 z%8NQb>5Y+(9PcZ$_qGgl_@W=a+a)E4)2y;^YEHRpo9K@H^R7MNr*;MLB%noBji2?I zzi11P3PBR>iJH36;*4*6K4Cvc5U3dM-_hZa_T(RU@uPP21jV);NrVu9Npa%);$`=% zaKm)Y6Sssz3VE`f?Wpv6`BU#W^0wAkY@i>K4RZ5zb|Rz<^iO;KTQkiL)O_OB=>m*P z6leYP+Jkyad>`a23@xWCOI%l^%lqfwy{I3M&dR(k0EUk6^mBX8IoTKV#oL=VgkU3M zEI5zJEHQQY@!EP?I|cxjhvH_Ly|G%Gc1vrxBSgMRZ{@@%bwhJJ9)gHA?hT&zx;|D@ zSH}Rs8=<{q!U~O&=PR%0 z$}|!OBpyl#Nw3b`iG_ds;3cv>HbCzV=`ZR-wB4Ipw%|BlnJ?DI$Q|oLPzlw3S2y&M zG_*Ao>+L95d!@)bF-H3!h{{l##1u%r*mBwQp*a)VWk#Tk)EDg*88q*r3$=guHpWP@ z8d*zGEKW@BKp+tA1 zx9YCT&+yfASU1XNbJ}3U8DqA1|L_l!^vMvuWr`LqEtUmOe(Z7SZ3ywbQG~hy>N}Fi z{y&$l)_TQB(FD#*e8#BC?H~T_x89oQtMZY4SCWP#&ZJ?6AgJJjob~1W5Bbh!k0g-d z>Y$u@XO)v33ZoNlAm99NnfGyad++}B6Vg2NP^1HM4h7GP*IX;jL&VzVMmEbL_wpNq zx)CLraV^FK6OVZw>ixk7e6xM13Vi?G){um{SDGYoQ$F4=(*oQLA~m(dLoI6T#9{ZQ zj3YqnK+CrX#gRDf;Z;d=`yxy4)Q3cq=r>tlvKC#ja;9%hwjh!ekYs6{A(EW+^ox0# z_SSDmRIA_aSfG{0u?p3baa@#(+dlG+-jeMIh2-`M$>k()q&4pY=T;%DuTD$s{~4tT zzKSdcYPo%si73+5Ec0CPw{)JQM@u-2img&8J&o;1_TgS2Vd!mmXkjxT<`UT2?v z?2vYc>~ETSXxG2Wbe%cYs&5}trGn7TYQ1Z}suEz%r1f-0xmmjGJ9}|RYT1=#H2af7 z6)Jz>ChLkYBQofm_|^F;I|#iLVrg@{FQ)ns&*E&zO?>?a0`D)*+VAmg%-WA`y&&iL z?1kgzmh3LNsi>sjPx-&}y49iojLLog8t>P@-(CaB#qa#&mADDt@mUr>qt98|U8a(^ z!~v!Wme+rQ2#GeHT%1vhk+04btP$P?7uBdhdF9V@b5r31g$M9M%?e6HMpc;XD}8Dd zR^=yMavYi>vFW}fAioFnG9iP{uvSx0KAuI~B%(E|@2dG4OqHs3k2?eW((-*x2KYpjeh z*cmaKli_CczzaujJ1~6P(_{b|y$_ROa_E|SMxT3bcVLu-(foQdAD!KDoi^-XciB*-21GQ_rD?cVt; z1R9T!ygqC&Y3hokEh+Lq^7}~5I}r<=5wvosKEYGK(kFgI<3^BqQG^o9X{!~39&cKU z=isvwt|siw^qb0AnFO3Ik~*XteksC6@hQe&CT!P*W*J9KA%?U|4=^*KbQ_^|#M5nd zq5dEkb!D#e1_CqD$2AM=-b5V;P8I5Yi~_>9D*Pw!Pwcvv3<~xkV|?EF2tWu( zTWab#8Ts>lyM+;SYDJqiqGZQF99zR|z6vj z1{&mB`Oop!tt8XmImGL9MF`tD$KP=73XFf&Q~r3#;?t))4|$dhl8|*~wiAh89~-$7 z;HY?`8Y&%?Fd!yor;(s!aGQYZK?Cxj`qNuw7PC{i$<0UK4r%K4)O2F}8L%cu@T&f8 zG`||fKk~xip*^?DaB0Hz$E@zZVd&9)47ZjCNRJu4CN+}!jC0EHo`XY=?sQf$b@o;g zoQcdF-w=*D2an!#*GLk-W^(kgXNDi$E^eelx3Ov-z+A+0$FEKvCt1g&W6c4!*a*2< z43um*wh>rmi!zC=-xz3F-xm<8I&761CkQl6{N|9V$t?qV|3EL(9?3*HW2wUA=BNME zKvvl)vr`jHOtyEiB5{Lt^%sHoX_J1zgku##>7A=d@WD5NAMCxpIX)W!e{ zLOX1c!*iwJa&wS<$VC+ul2Ss<$@Jbbf6cN`&0=lsT<;=(y@g1@k8H-8GV9`UN7|Lm z5m{a@N}}g$yNcF&cRO`+rZ~dX6fAH;K^2=tP&mihfQ@qz8kqhknl{lNl#Ts?{)&pE z=P!SGWGZCg(<*9J$XLP+qW$_4G>KQ-<&dsOY83(3%LpQLn9X!g8EyRL1O>W#9(uE+ zI_FEyGgh>P+h9G3*m*ZmXUx$A>~?I){mU<(oTzhZYOTgHZMk?OlsvI+_ixCoT%bD` z)XxKC7etX{5T}C+I=@2#UkEiwFknP1K(|UhV)WMG+n~CsZynIBcsrQ|7->utN!o}a z-u!C#Ppe^5baAfm>E&R?%${Z^vPaj-%>$Atlr$CCAuh^~c;B&mKCb#G$RoKgm}aYF zdoxXlw2bRPU=@VxDR*aUEYTN$0H06=`%J}b+AD3bSlUH=tnovFGo@iNl@W45b-~JK zpB#%2u$PX+uvV=H6ar{+T@ne(@k-neuv{PAEbQmbSYu9dp54BSaTY27LB0V2yaKWx zvp3@^gD3pu+dnMPTq%Q>(lna{T3IcfEGQJ>~ewhptZ@ zy=%|N;T`_hZn(Fc)S|;z-zPHU{`>rF*;77r-Qe(@j~EB~$jwhN)WKWAp8Lv2ZpY1j z&+s*yhwnT%y!oz?!%q$!{4A#;W138)qt`rs2Bs2g~KtT zFt_SM#`MBv)_nTNhl^3I6_yon00W}`mw3PG$+@lQ z(!!$!FM7Y4J-zf`-qeyr&PefvzV+FeynlbY=Jx&>|4lV8cv9-2-~ZsF0J$awQ$A(u z8C+6N1j`DX2)NdSMc!J3?9KMlQ1iH=%l&Js&pmxDREpJUjwaZ=yI@#qZ=XSu7mGn$ zV4eNSQ%ONklO}2jyQZ)^f&9cl?ni~dN@zevf;x(0`h>*kf;V$&SJ8BhM>lU}IM>*rp{@5H zz3qYFTW%+0MuyUzPYx#^9DV+IfjT{K@6g@X5(YE0?Y7Z_&vSqwnHsrz+xb1n|M-Sh z^5w+UbLovma|`SbQi*JCJU|jzlsqf-@XO!52(!i1xyguzOO@c9iZbv9&%6*MK3Se24MR8u4{dMB)F(gP5^om5OvYxYFWf6O=x6*&QWknNt&MMp&yb(y*9%8|ar<^qG?DrRsa)73jv1!Mi4MeD$;J_G3vWR9$yf!Y>p} zk~A;cxn@F1xI0Zv<(8qgW5iEp<4T- zZ85QD^#~n8nCV|08IWeDT2PIdk24Oyz+`p~Uw=Ps)qO4@TU_o;xeS-Eg1Y$Vh$f;H zN~kYILmEKAa0E!9E8gZO*Yxo9HxBK*?&w|j2$UWi-Luak1~~1W8(S0Q&Yu;YQf*fV zxM3?Akvj=4Sn>+mw{u4p%U!mfoO*Qgcgda%1=3YQ)QHZwgun|m&4b_)4s2R@3mz4x zj0aKoOJJW=kO>E%%}BEB9XFUv+3EL{9#XvwrLTGuCL>wevX_rQ%SFmnZ zBE_LcZ$q6Nef&y zxdTOzvU55k^Q<2bbC;9TnJLReToS^LY>y5rrd4z$zF4`x-js!6MW|_v?h0g%{K6QG zu`n`hiA@>TSF3-Gn>2RQcPFg#eaC5QGP5yxr5Ost@{!3_DGqvRL{2!Y)ezAmqlVT! z&4^)#ySmQOsW)|M>Y-oXL~O@NWiykwY-yaSx@O)~1|YPzXb;5Ub}1w~R#{BujM(}z zX$l9?{L~c%7l{Pnir1D-sLWeZzpi+(tnnXN{Y~F8`yH9Rxfj{iqS#qNF7ujXK9N9R`)(bnhf?3F zE*5sRbwQ~nM%1o0g5^=AqSk^Tw5yGnmTQa|l$wHn2eao=CvM+gE&gAInO#XugM3bXXnQc zkUtxI;Vvr>Lq2JWw9|`4;peSvZC5^=)2Aa zA!CH9La486>Mw+zE8K-wxO)b+V!aL3DJf4)9xaA3Y`hrDrLl=tsBei8%Y?u(%o@pE z7za#!Ua~X)t?_nis+8FNrbMs$#7x|TeL=HkhgHav2H%6PNcJg%B+U(m7HL_gLdG%} zrOr=1{LW)A*-n@mN?u|b*0L2V!WUMzhRM=Sn0b4&$5jmJzDrhO-dVP)emZPnb=Y|> zBXLinhgV#d&Km1)V1i(FI9<<}VUe|U$HhpQ5S9ewTq0H&4E<;uMkA0^WK<|o(F4Rn zS+(==rG;P07DIs1YvxMRa&sQ6cj%67rZMx32zsC%D}$A14o=*Xy1A7vUltscpmmYL6hzHDXo0HJ zB?GV4-A1qzIuo(Q4K`Ss~JPbqe+6zF_Rh+FV#N{qEI--y159SY%4#z+0s2+4?c z68f(0GbxBhqx;ZpF)k`8P6xeTvdkOr4UJ|%m>6`pGy8+8mvucOc=>ZAN z3`Q6IWgGG+~koJ8eB8NiGTl4y-Xna+=JUj+5(lvO2p_TXznWR$poX)$5zr=R%Gm#TMZ~408)@x3htVN&d(U&dro-QPK|L#%+hQ!WeSo zpyceqA8lWS`*YHaI`bM8ZBbIXhoK7+f?aC?#8Eb0jC_I|62mVq_E>x&?V6j_nS5Q5 z%V(5bIw#9{<~aiPH!%?80n$rbLPjPPv$>B{xN>Y_uLX!^o1ii#&_t?g@H=1dn@$%s z!vA;^4B-B?;seB{A-Sk#le=Bg_A>Aom(P5&fi`3(5q3hf#g|4?GlG|V9Kq8_bKolh ztXWU0M{RwTiy7#JY|N! z5hs+puKh|>)@�CXvL1YnT1F^mBF{RbYIxy_~ie(+T)a<_>=IogQpMlV(N0H6HQi6m+@14(oSV)zyLBiWU}Df!v4VoT zkQ1e7-GfkPm?cStT0Z!VS0R#tRiVYevb@ZEqIP!FTg&qdQcbrkSyy{hbIo-aGkdL{^NN!BVIJfn?RnjaL?7SES;NBQoCQDyLBgWuRU z^*B$`8%$0eoPaeJQDQb9xlucrbon4_wrePegcn={aacmc%UUA8;?0R5Up#moqX{Ac z{MU=g@!X94XH^vJ$k=sZ`}qHOY3zLdQ|){Et$XW0Y2Eu&!mWk&3kSa-l%!AdeVsEH zL*!{;pP;3tpF!5_*RymgnOZx^0xXw+H;z7xA{SHsM22zg@DBb2w=B&YxmUyE#Zx7Y zGjR>K%ly-cb;HK8ESTXZSrt>E%gh|l{m>shUrT?bo*q<;7h-mSnR2qy8LYI-V22`*@8n5l_tY?!nMg&WX65h zk=6d1sl2U8yX@LuGu1P4^THjTe@wqk69D;eSEtBFq*iqu+cEgk)gJ>}Fll;^TA)i9 z|4@49izsl`BBa}ne(HszS8X49=1Kk&K`-R7SfK!>`G@x0ee|jwqX+gJ{nXaehM(Rh zc_cKmnMQXfUYh(y_79v#sY;$X`1Rx&#geD^6WTckG%ish0^eQ0HbiC&*$1G(ws_09 zCh`yJNH@t03`C9=QtlU9Fo~5Ic#x1aPXj;)Xx*P~I`v-99nLdUI#*TVa~R&@U9k7L zr)3LJjqFAA4%`}DTqZer@M}Mrk7PmOiAm$d?LF5S9DoCa5vZ*NSRHH7SYfssai7la zPA}pbTGVo9N$NO3Au&XP0$agY%!u1xzUsVHo~ve;&lEN!l{G46Ik&*7ulEiRG)EfI zFZ}ydqW6#bA6f%R-a&NNgCD$whEW{qw-n>8&w{$Kh`1EY=2?j8Ky&rhYFP6`>k zPq1SE++{Qotg;H$BIInZ9atm>_+@P9z#S=azTFXYeh|r7{_38p-0u07xm9Jb;!K?{ zt;>jUrKIo^o?_2qo`T!*&&{2a^KkY*`|in_u|G0Lo7V>| zj5kYukdw{Aby{~iw|Tng+Rw;o(&QvkSM1xnca@SM4>loaf<0Q;KoSWep8!$CngyVv zi;%Uo8Xnay3-yQ4(4lFdWC~eQzv`^YIdildP_C-9h{W(M$io{EM6zs%0Il|Jl|w8G zBu}(v83_Rzs>w@@t{|X&F-==iqcKwovrw1jAnfYFR3O~6YC*Kf)vRxcLW(6uA=WCZ z!B210hT&7Xi>v^RXw#e;rf5d_+}8 zyPA8VvACMd&2t4JiO#A@`IT2E!;GnqKoqvFHejnA8@sWblOmUh|1M#bL*YS!BxMPp zWv%2uch5jLHGSWG-=5A#fY`i4+j}w+A-EEo^U`Q{J-$cWUFlND+6Tf|(rFRVW_;J! z4Zz?Lj4OjCPLK2u2RcC}2s2wxvCt(*PeEM1D=Jv`nRf?CBJ-$fc-z z4xC=}r`&YSU|*6TZPKmjL#H#~wre&M1IDx(tMyo*-wpagga3eTn*_ANAZXuM5Unvy zrpN7LlFCE`y5vRs?)ZIai6nT&QKF7r7!{-m+Tx8q7wjLULz-r7$=#<)a)0fdP zcD3^0t>n5^R^YW3>?PgV*EOG*<4ZZUF(*^gPhSr;6=nwWYp3BF`y|Pn`+xW51QJJ{ zH1#s|8eGyz9C$`0_qlr+gy}J-{Hr4Tjwu>k1 z8ZJ;;s@t&@kr-j7f0Y21#L6nYR-CNl&62=?cyNsXS2IsrVk{~33dr=67Lnoh=Ii{g z-T44y#iF-xZ)nkBJrD#f=|W?{jtDtKNaCoe9IX?LB}6}~Gb`ZZ6Xr_E3Tg~B7Q;R2 za74Jsnx6Ev`cAMb587|@2CBcPjc<&Z$2FN)hM)Ctrb+7xXcifbQLy*O!=Yy>%(NxK zK(8g|>G4R3g4S(q7b7Q@Nu+J_?Mwx}7fhA!FG{X;?z?fSN)Tl49f;wm!`dnJ$T!e> z#9*4~(prB1kH3c#dg7E|vd!3`W^afCl8OPXV3O#DyU;F#%#QPQH#QZqn>cYnLQ{%O zT$DYB;=Z%fj7A9PO_{zIY0WTWO4k>-GidaHe}xV!j$9^9C|XzVHb=eur3rhoa9x|R z8`5zj!UYiINY~t-9-;91_A?gAbRr%_c__E$V%|eNe+qa`Pr0sOomfPT4w*e$-UQOq zm_^5}rbr=UK#Wr}MYuQl@%wN3^yCuH_^C5fW$H*-%HX0{|DFND!{V-Dh&_NfhIZ^V zY?2^tK~^|mu+gWtjvm}Ma`;L?$0!-4L5sm-HTuFGM?dwnLv97A0_v-ycRVflvi;W* z;9!my>_u`HXbgZhBcFbL^r|P1eD+gE?-2Bvb-`!u7p~@Kw{eB|rKha>xLvuks$h08 zT;71Bo5#8o(4ju?-zq)+KXbFeiv8C z#UTQ{m9o&fHVXlnnv0xs-nGFw#Cc}>5NCs%Y0udRP+Mt>A`E&61B%iXNb{Ig8{wQj zNJF8S^j%jqnK2ziw^InJVq_z}j{yRA(fSD%o4|O?Pu43tcl_AC-#tN=WqdkB{Ts5jiFtXIS}QZmakk&q?D9_hzURvMA8vO()2!sl`+oRcfP6x;UWRFeo2(a7 zewj+dK8D7lQ^nYII7T=sGvke;_J|3`inFk%IvF@YJBkZQ#2<`&ctM$d&A6W^Xa4i- zdg{3EdC9+hx!`Dm=t5yIx?By8S+OrdnIN#5 zjMdPXqiaq6Ccjdo%45i-{MMz)v2$W+Y(!E>_*eFHtb#liw7o*XRMt}`D$|0bfHLk{ zxrDfv+KsC2v}sEP2{Zo-Pc~N6aFcpBjrxFT)5@i&xns$C=JYfLIP*f(Ild30ZyF=4 zzzLnZTGH0Q=!t#S%!BGTTOA()=R*R}vV)c2T*pOkqT{-NeKOxQ>FcHUflr|?LKA@O zq5PJ|0-S0MIOm}3@jVdW<$Dx*|H*TOx58T#Dk>`cRdH3}_X|H?_*mifg_jquE1XsM zfvg`EW)=LR;FaR`f@cc$cvlu&TM#c;QgB9bLqT!=>%~X&U(P?Azb(Hnzu9|rer^8D z{NlVf^M0K7)x6!_y(RrwH{|u_Ey?>}?w@mil>5b!xw(5w?#sO~cfebb`?1_*xgX9g z&iQT5D_{-o&PnFPivJmQzM7nA*?-C&$^LTof$Uqe`?J?(FDM?&J}vu1-ye$a%lfSE zhb5DJU-mugyQ3t>ccrh*x71heo9N5V`jxjM>yuf{S+lZoyhpvSjvF2Km2poMe|FqG zs{AtWoP zT(DP=-z1E>U>CE#zUYg>+psYb*@zesLW`EwQ3mMX-2!s!wA2)5-ShfvU(sSuk@fsS z5RsVG(OlIV>WarZPun*5Nuki_8VJYY2-Cu#(bbF(1NqUr_r)8914Z_AG|?~7@4G{g z*5T|BWP=WjG!`wRwvqm#MonbXRHk}um$T?sa+c$ z@cuCv5)PfNe$GI@4Z#hI+zQIFXs~0eJZD$m`pVF1iPY=%&ENivK8q+rxbTSdf(0D~ z%z9nd7Vi_iTm8O;*;E%Jl)7U&BFZr&c6R`rAE{qJ5>+_Pbcn@r);Z92?-Gwtz;yNW z(rH5QfVIaLto+T@LX2WiIRQz7IzOnn$>*enY>C;(~Y z#7o2a0@6U6WH$@%v~l@DSI8P9`eGw&A5emnj15@$iha*KtPjh)Dn48wc*>titD~3C z``I6t3AvL+yTZj)<`{p=L32m3&$J~%eUNebUjM=rVQC@Y84e?Q7DkoQ-5bn!LZ_qe zDEAcmb>+BA`#vYEd%DixuPk|HB`85$S@$0JsW8m2DcT=|T$`IyI-&mO+2(oEo256I zj^Yi?|FSmXh`8>p`$@6v%#~KHGw@)mVc>v!F%}vB!qh`TBxBa_t1GpFwbNepo%HLA z&HFMK!q20FVpj0-Zf){1Lh43+3|Y%sJ>hZl6ez4RC~$%nJiqR0mQJhdW@{D`tMqd@ zg298|#m1sk%V60O>GC|)|1EPZ1kWy*>WJ3VLRQN?^G*GQ+rfsmH%r)#?6y{YQzw>9 z_ATIE;dX0~Fbl&jcJi)^P5Y3iP8(|E{EQ9RUwXT=($~ZTn(dn13!jn#ft;f=wq_U1 z>`ag8l21a$uMX>AXT5OUo#WD@VPhOcv`g%H(!%9lt}Wj0T-U50K)4IouM6+E{YQkG zDl@aO=VkxP+HAsP3pVft$$6Z;?Dpqm_5w82*bQp378kcmOql-0_J%TFjgP4(+B`Re zFN2iB63CUd9H08mK671kTR88Od=aL!8-8+zj`HD-W@bzI_1u5U>&N+~W~sZ6O*s9^ z7HJiRTYK7@+ctopuqZs_2}J={q&j?`BJCiy#Ji3=Fg+l2OxWa+0fp$*lq%0(aX?zb z#y-+&0L2K!nDKzs_btBol?SCYycY?7-eo(lk#0kWB`cd_Wha%FtY__du1nhT#%K>6 zZBwM8vWnoOa2)q;{tvhPc82c)c7fDtjEAZ*6Y&1WXsHD+%^s35b0fyA%F4>n#gPOM z=Yd)b*i9!CeDp1;y)kNi_TKMKmhWmpsjZOjac_TbqBPu%ydhN~=9SRKMNYYD8hN^B zWN(CFn0jp5)E4e*3m@Nl!x?gYTDY(@gsi1+^qrTKy~^4Q51mNR3E!JLr^`jGeqm`W z**~24>vG>E)>b${@SO5RX^0SY>NK0NEo)`p59NKWU7c~s@yl!@i@b|_ey%K5X}e|i zbMtRgZqR`iF2ihLJ>xp8U$i%{P`MX;ZNN}8B@0wTp<#TrqG^ zDn1q=IH%+IgY|dm=q?$8gw1e06lYS--u|=KggP)0CZBp&s69UE@E?j)B8kBE#~>+< zlCGiU>>Gd7tv#6*nBotfP4mLvmK_zpPUEiRdnbsz_pvO_}U1 z`*_|o-!evgfkr3}En!-$drSW4savJ70jlWkZj5ZI2&tn7YfR(B&4|s|Axz0zTYA2HdofJ(Bl=p)la`OaN~E6z6c!2Rt57APMHHMi z|Ki!cC_6h^DrSckA+!mNr&y*F!LYe2q5Hzf&>CmfkjR2)caHb)zvy!|k&Ccp-Yj^l zkqK4{yGx>Vg{l%c*ERiEW>wgp&_g&x;^=JqLU8u1S!vr({<&8@EWE3tD#JTz7WpTB zOgl&uA|0YUuM}fXdZqY>kPT1c@(?YzZwldtYs!rrAC*thf)Z&g{S`e>+X=r zJAstlP0|B=I1>LPu{d4Gsh;njcdaz>L>LctOKTJs>1bl0Cwjtb*M6hKyVFzXxx|xy zFz?pfSF(4NB`%*UFOcXrle?gshqET_to_x;n+fTqh#O;#^a zG9nn*fiVe&ber-Bk#H)~z8+^NPcD8+4C07~!~{6dSt~9Bc}a|%AnEOpT+Zkw-HRf> z#h!xC07SFE_L+{A=nXn7z-fZaz<=ytv&}LePQ{se3{53FfeUWLzIBQ6rLx<$$`#3oWAyJ zl8jZqlAg{`>hbsr|DE;w{$cOe!2jYJNKH%L^YUNFPfoJ>WORz+PX?;Harf#E3|00r zqy;m8K%62jX0cwok|v7LS28kdZxm;U<2A&9BY{5ZDTxDdmi)*v?1(~UXp}79q+XJo z#+F4G_*GM@s3t2P&H54{HfF4tbr$Fx;H8@g8ViKwS~x zCInkgzFK)=iyy(@t2G2N;C>40A<~o;WP=PdkXgWoRDN>L9a~CGj>#H>)ww{R`s>L_ z)an$Vwu@%0;1sZqsm6bbjX}Wcq=&ZiKO%HnM1ScT0T?4lDLD)oO>aO|6L4tZ-Cd!Q z)O5VPyryAS)_3RIRa9|SW1?Bll9(Lo7#klqh04r&l7QJMg!8SYm$YcTWRE zs14nfoS)qN#!JA`Crp@HXBh43&;vWj$YTGHV}T-)fyhwAn`U|rlhlXwvmIn4am-CO_d)>&*yQz4;Uy1B4{ZUp^ zj;T$F%WB#HzaLMb`N&1OPpwYwO}@RN)DxUiojjfxcCe3a;xNN*x`-$+;?lYj1{Fxs zQKz6jHTNMEmBdy>L<*{$gM&&8aFl0P`mv2{+9cRH3?|f8lmA`rHxaw=2#-bddzZge z_vd``=-^qa^t;-=EDU~DWTEAXa~x6ca#f|hSPlWC3fMAOS|{7<7Fo81g5-VG1<+_$ zgV049@j3uqs7pN0Sg|gW3@7(Ieh_w;@iQt?CZE`{O_65)(|DWF3mNbdnQI|dYp~%0 z1yrZH5RYoYJLGP(({BH3n{V=eluclC!+wcw9K+2AO(k)jl8zoABG*B`!!l#q9s;Wg z93^&AMuW3-Z9J6O;FT z?kEgVlO_j~Wf~EXnvD6oyQeX-e04*VtZ#998M3zjYWMZ~{dF>>8Ui2KGo)ouCzxpX zE`qWz^H=xM`BY+;7J};CDiO4T`vWgXEEV%)*q)jpeFUr}CT)W^WK!t?WvN_`dKqG*d2;(=*2D=d zXpg-9)~@6|@9em^R5Imm+o-PV8#TZXpXZe4WSV?$e?K63b0a~%l!j^21R|3{wRE141x2!{*T=iZF_T-NNhQq*0! z)xFEw`1}E$nA6?6;Dv#{&%MQc@X(=~AzdNhWLTL>kERa5#KOIVFM|b&xmc|!HCQ%5 z_l3aI;x9kCRZdt%LWfnL3Mjg(1%?^MK|@6d)eH%6slK6YPY>UD-Ox4njy!x2A{jFA zkuaAs)n#a}Oh-S(x1@4R}^nx&%$ zpMk|@S$+MQC9-+vc1R6}AK5ed!u3L>c;IPvu3F8<9e(t_Av&m!NY8K0AX}BiCl)Q4-l=&AZ)CwC%e)5ZqJ}M_cot|51X%7DfJ#9&QC%J z`0j){2-+^%-iWLqahpQBypi1jZUs$=-UP}4MJLHarutsJc^?Kx=$Df{8nroCM5Xl( zgvASIku|!@P-(OGT!b`#uZyUWF8~}^!Wmt`By8dmKn5t(FRD}0Y7Q{r+H6~n6hK~L zjV@7%41i%ASxdBCb}PcypvJ^Z$QQhFmgf!6ADp+Za9s@dxkSjK>01ocPVDxWFB=Uv z1<+ZAsliG$93%;w+&<8jex9+flC}cam13nOcUQsyIpN#ITWSmz$VEcWDW4^Cy8tpo zaY!zB<=XZB6e6f9Mm~4 zK5&T$UM>FWbg%dP^z^*fvhA9iH7`@Ym-$J;_Bq8uO-s!R&y;S{*ncnt8tf~K6*l{u z$3xuO`~Tjrfp^!y;Hj^?`A)7{Q&M#%JmGQ%E6My48M_SG8>+1XK7W891;IR%2NAtY ziOw_otWI#s|ABbL$fNp>4wevYaXE4{7&nFJOVy;Uf=v*UjnMzdV8jq3z5@cyVrG7T z3W+Xd!f^bOLQeEw=|t#^9jKfPcQOXKI@EVYPdvwWiqoUcN=uXGB@rjV*8#w{mNLqO ziT}{xL3@zGRV!X!OmUV$PHyrz0}O|YT9Cb;IjhE>bgV7Vh8 z#VLfw95MXt)}iZE>V9NVQmq zvb%NM0`Xjrt4ZZ4)tf!~iPs_IwPnLCa0;EyxkVD-!6ot`Dpb)07f+Z<+PV(_i~=iD&lYrB@n7 zwO(x^>x{XhYFz?^PNb_310-}VF?(sUqK-MpoHDZQk||XW)Iv8xk*;P6##4)C#|2=M z=i19w9XWj8$WG|(w;z3I`{=>s$URBP=)Ub?w$u&~2rWwpT;q!uS@zLGH}M?i#nVr- zcNdx7Mcv%Hi{Kdw<^|kOOvH=dY$egs(7`K@ZoU%A=8=1zggKv^oL_Htn$2twYSd>3 zNe6GXv&A~#-p`EOae%Dx!`nW^?~&)P<%L!$%%=XAo9Ljn!VMqO{Gp+A5q(^##3O+-h`ZOEkf>HCd3Eg>CuC?3?GE7|9*ef z%nBZpnl)R=Acaxm=!1`=x109DUKHM;&G(uU9KD%r0Hd1+W$(cqLwg^j3Wp!PAFC>S z|G)ff(rg^Q?LjgF%x2TbA&6qH6}vU2m1hT?n@O&!M-PwOcZ=9Ro_Sm@x8>^L)IOtW zqr9_a&a7FpW*$x63D3m1Sk1wT0k_@K z_NXloStP+o$+!mi|3jYA3yVKe_*}sk3zp^Y%l%r;O*wwwr?O(+A#Z4K$Ka0d9GazZ z;K`+`6s9CF2xxC(=0b3>velm4APfl$Knz{bST~hy51R(@dK#jHnq*u5&9sL8$4Dsq z$kDdQ3PQG7d9|KhfqV@|a>_loV?u#z^{k+YrqKDTxiQe(80?60ZxwjW-Eq8NGa;8q znEf3Ix_qERIzBntOiD*#AaO~Z0&B)vEL=0`Yb8U2H~r{|KJ_dm`_zOwz9k+97b7kq z{R!zhVXNY3u&kyMj;`9UfEq+75XMyu$5@YB;t0-$K+;%(6FuV8(~O|cYgEf?B+Qt! z?oh#2Z=HQx9HF|;%6SH%Cn9~gB{g+$=QTe)Ljy%q#~TAfHK=ex8q6=~YNrq~rE|p{ zBPMYH1yWPEY+&RKcPC(Y!b2hF8Y{a4+SEzi(aEcq1r_0rF`fy+HVjw+K>Cn&JF|%A z(*0d>kTkWqn%qTu^D4aiotK2wFQ6$Ji@CVB{i3&Y;wN}?B{?LgYDjHKObX+N9U zQD=pJP`$QqIkRC_$On|H4OYkT+%T*7OWv7IUCmBazCYkPgEt0j#%2B_w_A6*quBF? z8R7c}q|*%65AOKV)i_9(OkSIcoA^V`ytA%aI)0>UUG=hcI%ZKg4_Nei0v)=7O%Pm~ zWk}NWyw}0z6h8x$+V_;Mgg6X;YY#&?QmvRlu=+XFBBg!Cwt1=0_pqK5Mj7Sl?^O>N?)GnPPh;c7(kY!zY& zxt`)z7zE>uCf$%cAhsfMeQ7BoB%{oZ44X!DrF-f#sNBvzU5e&u3-`$n4({0gb_rBX z!6nOe+Nc#DHBMC?d!Qo<055LJ62migooF`a*!hZQMY#~3uk{HUM#+!bpsDz6X(6Je|7n>KwYZ$ZYMX_4u1oI9Fhi*BjihfgF-A7-fZL3mQM;4i3aZ9fRy5| zY_&63eR|1BUzu#)Wp1!??t8UEsMjE?qAE&m&k@Gx7+c zi6VrUovn~%dOzcgq^yweWD?e4j}kf1vX5f2XJk;w7R^nf+GI)|L%!J3Slna{2XiH{ zvB^|}^rD3RR203R5JQz(RBDrc8cTOnGAh_O_{5g+gV+4()6l?7niASF&u9jOS#&}( z)IY$b6xZt%5Kwf_uXf+G57|=7MsQQ{OjuSFZ=_PO!gP%uK(%6+a(^HM7sf$LEeIe_ z6NCh8pjpO6kOr{a3uiH+i;R+etw`Z06d0=cI-Iy(ma1Whom~_L*}6iSmqR^n5Zr6GRnNbVA-GJBjMp-;=Ssv#?Aku%fNbFy#n>bLfN9B;zR1@$U6rN-k5 z!NSmDEd;?YCay87A!~gpWEJ|2SXbgL>7is^(N9Xx;G>(N(DS2jjr03HW>?p&c{~UG z0b>~+bL(6gkC;9)*JXOK4^Tf%zfr8w5KYeI&gS(2{tylIYMd`?kPvMEFlCq3FVWfA z-ERPDi9mmQq8|hFBkwjs|Igea`Txdy7I?<3DjhC)tGK!F+5A2Eb36<3cIB1io|p6c zoIv(ruP6I5-_yP+-mZ*yNG{y=_0e;fbS4E&=TFw7ld!RST)BX?D`PAaPCOKkcgrZ5lJtuLfN0 zGSJ&yF>98{^)2lIeX5Raqi>#uVq8`w6nBXC9YBBypV%atVYIz1+J}m4mzXupPNC5d ze4;gCF)jm9GQ;*-#!s53JV+VA|5w&zZSkhOkj%JO`m<4!f@+bVIGXPi_bdT*wX?fR8wolO2`}l3gQuoX6Y84+sqj{ zv9&QpC|R#R0SMC&=k<0>{6JK!Uc#%mSqpjy{K(vbOjJR{lu+HKsoq7ESy)Rw{W zyq?B#&-ea#(udGet*5NCgJP^DIx}QzJcXS-K-Mqb_S=VYp~IORTxvQOK}~J=*gH3% zqcle&_7ViMZm(geg`8iE1n5GNv_%&Nh+EUru=?S=XPqagXx}#U%>ugaq+p8zflCPJ zQ?{;lGCm^jS_`HWvw3l3eHhrKh)XVxNs$p&(CFn1vXR)?J`|DLa5#RB&%WWTGn0$A z{p>3XOb*0_lcn01R(E1ti3ckxP=lfgve|ca4Y-OSXMbH+6of9XW48FofbIC8YApAV z|McpmH3*EVKJl2ydGd4$2Q^T6{UGfnp1CC#363CAW#Yu5@T2=XgtEX}sqv z`Bk5EZdb`<@RRFWv5QWgAvR(#G<9g?&5g(e#9aYCu$Dw>vbA2}_WCwDdYoThw~`ny z;}cC=k=Bm-z5qtf8aXTN1JbIO$rLRbveHX8Hv`#qj&jWu+Jlt#Q+WB%A4n-C(y2Xw zSX+~M+kX6u2IX(6R`Wp|*C)ND8IvxICFVC|ZEIlmTB_Uq%R+Sy;aqiRrvdmid}G3E zE1ZX~a9pye!3-=--AKwJsaQrLio(z!aP&Z%0tAyMZ~Oj_0zAx=P*iL50(zG)JkcR# zv8>K1NXM2<@<=YLvuIxGMbKu%(Gu4xRHlf|nCsFbb!Y9k&s}03wJKOuVT(n^w!*cg zPs{bie6GS#%eH_1MhESuEO>#6{Ur&^32k_ok+xB=8Mf_GNcTt+Msud`DZdRllo0Nw z!>cMxN-CMBKYIL$lkd%$XFpBlnOlzA_Ro*R2yL0UaO(m(I%NH8G5SUz&xlID=)Yx)P zK3Em=tKxc>I_zFs-eAb|5{8Kr*szP}&FJZ0)Q&C8Z_4MQqf((4 zQ=^f7k}9(Xoa_hJkKd7ftzC<-QfiV&JW7p$bP)m2T-I#T*-kMX`k2ASN+UiwhXIi{ zl$PlP9_tY<)?-aH#Fi$TeHxx-hBzSh$Yah`tc{3aR1tQ2tH8%If|S8;n0gN+ssahQ zht#+T+VKAjj@x!UDQyxWz-Dlj%mH{W5Cp|VhBgdmp=qIX(!a!3^UsXA=|bh7(~c2 zt~ur{?6P!E=%mEJ%8f~if{e_-$Q5Pqj<$#yMvOiBEv)l0x>XpdxrL!m-B(U`lK2TR z7lkFMl^7AkNhw|#5T`IuNlD&LK_61(f*C!0>*=TDH9AeCO23lz#nU)A%jK2{Q3~3y zLZ@z%E!&#6n>)O-sx!j5wKD8W1~U>B!7lu#qx$u8=^Eax!h)^ywL&7G zFk^s(k@n6YJOET{WZT})t-yLbWp16xN(Y^TojtO>r!CS-a+H`;5V8k*ZyjGiK8Oso zw)dap#B&@`dE@hizZEP7!n=e%CC}Kl_mQ)UJ#)t|Tr*kZpp7zF(13#VoslLd2DPqw z8UI7a+$XcRwZorr63U(hNgb%QCgOo{Q@<{V0_*Ou^N^MdHAqHQ$t-IvL4&w%F6MT{ zTX955$qAm~tbLyB6UQBwwXd+YpswiZ{KxW7&D&G*V&1Zxdwi#4om#pzL+^io<^3A? zZ?1t<>+`=Dn!^k-ZQkGn6ArteUXmrYRJXVH^)M@JYVB$1X^*?Yq^&=B^gB^3N;S8^DjdsOdTg7#eUey_X%qsNdn#d=;x!wY-%jNV@{mzy; z0fAP^lU%opuqLzHK<|bR?F5Wnnt8)ovF%jW8FKPq?(;vsxxLg=Th^2cX>F&9#CvC}E#>j` z?(8Y`)J$HJ zT%$0ML4$$VU?Qu>Aiokl&1hg<8|uSXeCURHq-_8)g>-dvc>LlZV$i?*g0C zlQvu(!(fF~kbgBw^el*^F3Cwr+%WTr;*~`Gz_mrc#EKD*m6fH{I?tnboxZZzZo5?# z`q~Ox6DntwsW9lJw>lSfV`hF8+&&2G-PrF&w<5?M1(O)4|msuKJUM&sr;#A1b{6X=V`uPfeUY5a;Eg)4-K zTf22tMcY}}0G#lT%U}5IYhpy3KDS=QG<=|&22jX1fH&aAz)i>*nz0hHj)2rz-~oJ1 zz6ho#C~rgs;#~PiNQ})W9^UE83pe^6weMar4=;_{@$A!*`7eCqRY~bPd3u`y-j+iI zjVuQSnk(4LkQ5-1FhU;cs6y4{cIhUd5^dV*U_zL8e{aHuf+FYkv*kLjPiK<&VLgf% zJY#a=3t!wkg{PS^Bd*l}jc%{n`{K>Kkr@885R%RK8`P@FQ;-e@WP~(KT6rtR2#LH_ zhc(s?tjrKVU=~8i{^nQnp7DIec^-u9sQ8HlvS1cLJu+Y}$X3*9gey@{$snHWe_`L@ zY(n&B)+VP+77Fm>WR-w}Vp<5s`(nXl!xZ2<*Wm(j|VX3T4w>iP5|Udg~`=8<_BHe9%} zA`)t5-_7y&nz$s}`%5sL>HhdwuvO!)Xiuc(ym0fMi+lgc$s{Dv2;f2 zV`x~^W=CVXq_|t%D~I_F!k58jXy^3=`bu>4&5G9rc}!gNb+<7q80@YC?1yV%? ziXxx~F!P9C%DR@Qtf(|7CTL_5bB`mSu?xHd>h9_Fi-G3rkGIBQRp(RmbxBbV2Lb$uy=K43<{Y1G_?wsYXWS-kxZ+%CEB#m}Y& zP~*$yuF+PL3<(O@ILD7wa_z3R^AG5Z&onIKj08miRhF?)PM5zm2Xn6V#_aD?Np9MX zPOMg4V?Kh;-R3HR4H%7Wd2T#hsuCMatevUiw30UP5@&yrv#oohLZ;fxa~1V}s| z0Pj;kUV^||{j;`}lsxJw_CD?@?k@Va_wk~o1%D{GB0rpaW%f`q#&V=*L6da_msq^hof{D?KtkHf(7O+L_m*RUvD9QvH$|pbsA^kez+^BN zQyrn(b@k){S#zoH8m}1U<^*Q}Dyy&$G9lkQ@V?FAVoz}LLX#S5jWN?2HAdukAvY5D zlD&j9Q;ehO1L|?3tINg`FD<*Fplxl>4K`9+sCB{6y|+|FUoDV~8|KmG8sSQ$LYbTe zN=|BqxL|zJrrcz~-Ft>E!m%7&_z4|SNwAXWZlK@V56Fz6aMrQwN~b0Vt}qF0OZFlcE_(2kHPOrE?ViAf1TNOS(pf~D(lK)Yx%2+0 z!`1k$aA{hkUas{_qwEIm3anRjUDyi%X~_>#tc&{TKWAXTFOO7ii8&6C(;0oF76?bM zGD#!USUdzy#umH?wp-qc+EhuWvvmOSiFZ{fy+A*G7H|SCf4K`FsxO%&x;&^B(E5PBXq}#!AQ>@NW-c<{0zqyXaG%e;D-Lbc}ECxArdv2n|8X{18reIhM#P zb|vzNK?IH3EWU9*RHw%2S_2(};=4yV-IPe&eaqo#z{{pqM3s-FB+f|KCs~za#=ht- zhRjX?)VKSMIn%!u4_dug>%axTId?Ni1M3+WfLc`TZpCTux&EKW)p+i8>ZdAo{@pii zy%a%h^6ZpX#oW~K#uTQc?%+I>+1OrM@I&2Koraa1D=>ehw-Gr(V%0(E36rrq;zhlB z-7vwSZmwQZ-w@ImLh@FNS2bYe_)S}dl;{BLw#<&T)^G%{T2$opK`sC1q{R!)_RN@a_EJ2IS!EZ90;?aOT`a^-l$cY1XTp6zt!8ru@u_vBdb!#~^z4s*uW*Ow z0q1>HK~7)WD6^`)b`OA3lCPeeaMxQui4=Pl%)BJ30Kt;9K{413NhgqxW}Kq~Z3LA> z285unThaiHSq;6%S>iNd+zEHO%$SN(lgn!Gk$)v#dJRZVlzR?O`^c|D_T$f*o2t6& z%`cagdMYL^OtvY`D%l})gVFT^kcalgL`@PjTD*Jl3iGRMgqgb+TL&8RSG_r1akzp! zG~t~9wOc5W6Q#wVTf*&)Jy0ojndT*7JTij%Pp3_E_%Dw4G+=f0M+uS?*+8OI1cuWS zYo1{RQ>{Bc|Li{JMdsKK8#IhmnFDP{XO+cNCE#FvC}&vUf$0B8JrKGhO<)&_cQ+H} zrVG*Ew8#tF{DkHXP~6o2FHm9Le}(@!HBgfM3(s-h0#Eiz-~G8czDcE5mh3NXD%xLo zL&1iE6S8J_3vz#*zcueOp6dTO&-`EVB&iSVdgE?j`4Vp}cuOqzOIM<(_2My1M3xfO zGf+(ov{MMBch)N54}hvJCI$)35<)DA^cVRJ#ncX+UM&iz-<*e4Nak)JMXHi=C^jLi z!Wd$4(~z-^?CmzDlu&Jpe2r6F=AgPg1s&eY&2V&PumTb}Ly4L>4heftOkV_(kI4pa zZ&0>`_onm7h>K3P?|S*Fl_j2~lh!tAd}DI1RLtr~(*Vf=yDmfss%{Q#0x`lZJ9t}jl!fM+1MmVOT%1b-8nir_4a?+ zy$Epi^r{-e&6Xoi8QB2h0i+v}C&A4bE6|&>UsU{horI}VEBYQN9i~9nE^p2Y$8RBk zO4Jvg5%{KcWI(S0326-KCkeTCyX8cWH}ktw*@@nkP5^2-H?9aHODRH`fySwl&@ zgJ}5!s{DxQcmwFth_?KMs~CEZqvt0epuoE~-@l4(L9-Z8VS|J^DEWdark}}G6;7Of z>G*j?XC3dnSjGFCuXxF?FPgU8nq(OR5)d zASYd8k3{&SOCi}oFp_}-sD2<5j~E;MAK}R6bNqUB=BeJ(^KSlt{nVAS@G48zoV3p5 z9-`z-6EoS`-O)bVs%K>CV0XAh%&T^2Ln0Cnkoic%zX$+mCT&ILXqU{}^~-+*pGg#+ zX?}#+#iF8nr1g(H!u@2}r;B{b0 z5XQ>@u;=tuVUjmjt>5(p8TH3cnO>tgi%QnkuW(wAQwXuu4)v?YE2{*BYHXJYBoL86UYXP;II;y$VD&B=q-LA6+2MU;VEVS{=D`UENl&ikss!w{ zfH4E%F#wQ!leh)65zCUR(?-?2O-3}s9%$W>-MpH{M$$9{*Br_4rrS8Q)N#A+{0e^X zsZ$puXB!9HWsrMtO)k(AJ$4Zi1?PxrH{!%%a*^h)&{?qu+s8VMUIh;0bHFT2-G~uK z=$FK*!IaeDlDV!JpS#^Mm=mEoPXP9!1&-;)o$*dE=R^#fWkNWnAX}m**#)gFYoolG znUyudkHi4>66PXvUUJUue|$7l;yHd&S;88%+U8)kAQ($1wqy}%o8#bb)X`{t03BCt zI}0IQB3-5D$ffb#nw($zX4s#*vI6k4^o$mP_n0r+9VY~Y1RC9L0ibOTF;rnPz}f%< zvzq?uP70mWIk*4*UvXf}m{xOzYWu0EG25*sc`(+pM0Gd$Q(B|-;kIzpUAF9i_F?8E zu>WEYXJP?2p)Vu86>%Tw=jyVqiPmo=L{C_{`^K#wY!K|E&K{K&&VChhw8<*P30YsD zd{7Ild?WF(TAR{IVBK4LA*4b87NcRTo8-uWrr!R}fk32f{g^hP!;_s5tOJ{oHUV4^ zB9?3Tk!>E(cR)fkwG&7{7?8a9_BXfj{pl6U)QXy1*V)rZ(3PMT62~r*q0%`bJyH<3 zAQPfk;{@rWRyb3$xseMzpB|TOSqW*~v&5z(*H^op~Y?rma-Y$r_ zhVDuq)}$RUc@XPMXekycB8j2(=+q{nVy9HT17y(^=@)jFK?QT^Zn%n~#Gz{&N!uX3 zADejETloj=7F7Z1DhOnl_|s=f$_nh3P6nV1;Eb6%({eP`BK}BYaWo1W;ZFdo#C&YO zA|>0z7f7HjNZ7J9&ZaSP5L)5oXBYf_I@P+Kh?!ToH3D{xlz7*ANi|dQK#{2p`*;BG^=OfQ0be{oVL>;C!h9GRk z31l8{F64o<l5AqCHRKK@@>J;~}vUwAPG6hPV1T4Ssj$HzviMT&7AZ&f&Uel%PI z0ZO{0=~^=n3qzhBsU-P@NIv4hFm~_VJ>CSW0m4#OZ%o_kzI8(+K#_3qI7F>^yo!>#% z!xJTaFe8M9M@e-Yxc@tKSd6BhowQh`d(wAmaARFER$OGG)y^cPGudJ>M%O!D+Oa;! z^u^6M<|I2}Rlt1=V}x@uw%cx~3`V}K(@y@T=O3I`uX0U*_EQ|wVVU9Ucl)B7qManV zmU+*LQezJ68tCj6r!{YY$xWITY1-lb0tiL+dFhTcDv; zmJ}PLtv9vlbkr)S==B4L6}k)^`!MHtnGR*@=n&u*F9|4tz%d|L&@4gX!`Ohl3yvDr zHvu^qRv|OG>*Qe%!ulCaZe95r3eDx#AKh0Nw`(41Os3ThecgdL!bNK(PC|2&u8U51v>h9fq=Xw zm^*nPZmKUtz-+8vBs8wJ1*BB~m5n_uK zRi%SEVW*P+NO?2N1Nb{TfHR3YAU0E@4J=!{gk~XdM`J4Pk4y=B4%u&0S;fqG&zmy< z%ucTAQAsliZwT@C;U3_qHlWL13LTZTYe_hP)iI7^5EYTX45Eb$!A|VKh7UC@EPCEf zowi6Zv__7Ym2t)xO(9*0YWlH-Y9k+77snO&jsHpkeE6+tCcn9x{WZhq`pB^ zc^b%;ckl2$JIrO-WwsCB@#OGzdq#F07)|XZwVpr_A3m|PD*J8oigTGElimYAzULH; zia*%UDP0=WpbchF>g2^BOn1;P81nmT#m7Jdi83|kt zW*Ktrd+pAx{(6frO388UeS!>L8V~p(dz+?8Jpvp;D##=V7OdDb5=k&+=n)|ppv(>Z z7194zm@=i`nu-^pbzNsS^ZGs?7#O5@pEL~F97C?Np9HuvCNYCgQik5XFF`EZs z_`=9b>590rr=LLD4(Gu3f^M*$ur+|wYv<{u5af7QSq*s3Iu;CN){@k!P_T7|#U39k z^nT2<*?XRcnEze*1Nq_ng}!g(Psz{CJDT^6yhC}{=f(5teIt2Qc}2PZn!O_TtJ%AA z@6Fwo+m(As?zy?a+z;k@b6(GR)weO{g`E94JF-8Uvnl(^oM_JKocTGY=J>|FI_|M? zsc{>})s34s?(}gJ#$}cMqV(&f`%8D0wwGQ|x}bDQ_A8~uC2y7dq~voY`%A7a=_si$ zIjv+|@$1De7k{>RZ}E-A1I10nwchiJ7i9mUcxrJ`(VvTkvrCHpvFL%KokjgcjYV}u zmDz1Y#~1#o@cV^_3m@`LF1)pHb78FT(!#~QD&IwgGYX3eepfJ5@Rfr71=slQD(LfF zSI zy_=|{zw+-S-;8WnRb<5J$27(7**l`^aXG&5S5AC?_y61)kdMl+(G+Qy)D{?-yH8s^ z^_w5|EtRd2wrDs+WVPYI5d@iJU=i3@_Kc!SPw|E1pmyokBEpAc<;*MB#ZL6i@DWbY zu2jY$DP8fqRVVvqW{bs621K~mq~O{A$Cvc}QLfqD9fB)}{`z;P|M9mU^v%tNCJz-$ z9QhjWeqmJHM>S`E?W=Mnm}Wqqa3xbHi~RlmX})FI|F+lGH?MZyB;V|8*o*}N9!nsF z$9qDc)S^v=|ChZtfs?B$^S`UQvvrd0u!Rs~NHqaMOjTF9vj-z~IxE@Jodk#&tE(&B zm0qe-)zzH_mE7vEge4M?5D*0-0?ICqvWemhqoRZ3GNPi4KoY=ZL>*TK{C}V4oOA0| zC#dtj{^y-}hkVj?_j}Jh_bkuydw$P~_B&1~Zy18frolm4Kp6Dc(8U=0ht0Ywa&q~Z zJcNSeG)4q(zP~=?{Rh7Gw(=QNiA2icoyC$&mB_}3hrW_Ksl2wl2WLr&m+y>iMS2(D z&CoH8@~#p|OqRNiRNP-{X&yQ>{VlyJlkH02fk`z}m%2LS#(SmSFo!DfoFSYGpET^{ zapkcgd0|h?5xA92>00PBUtX!jE@oCHsi2=_IrP0#E|<&jX?5kq-I4Bz=exW6%f58{ zC+vO**SAVWhPX@&<=%6R`USFy)Yf!6x6)_dci`m;*cb-^t=sEc+JsEMx0a@mI z#p{K1OEPrso$uN)Ys!@JRW#p}@>h_#CKq2K^m(zxOxnJkXjpUd^-1Lmn$>9dFCSDm75joz(3##knqrH|UJ z@cHvUJFR>Jm%<^L?<^uKf))wtQivMM_rL!i^&1O87PAcm9PyUAN&mRyyIPMJs^qKb zLeR-&cip)|U)?u3a8QzsiuIxF;p@xHRc=pQ!mK)S<)i*osqcz`m(S9Pxl8hI$LYJe z5}tD4ht=ivLwZ2BB5L>r_6>`<@al77+Hz5@(^3#xfvk>txbwkC`B_|v=Z}G~nHe)7 zOz16J5*@fF5~uDw^l#cucsWVI)RSq+boM6Se(D4*X(E#o+-|&Skr@L=edgrda)5D& zlNnkRN)qdx9qt=_`?2NK_JH*I5z}serg*A@j&Nbwoo`<1h=e{PJ`q^7`61956LHH{E%r9R?B8;xjS>xU58)P znm7A%ec8%T^rgU_?)KL57n`5gDbAcDKtxJ1B=)7tk1s#X-LVbohiv)h?pUIAZO%-j zPftHMUfQO4tTZ7;G+fbe-)f}&!nMOLlNNU5@#ZX1@W2yy{9(0>T2LkSWcuS9by0Qi zSAT7;YHdxY;B9O7D*}5B*1mA|@dNsunp0g;d3ZB=Sv7u_V?O%mFZ8=&UZsi1Z`mLA zuF*Hd!JB|woxQA_{*E2bX`LlH`V$=qjT#iXpRzkbm3BWNlu$!(dIxJY?>szN1`f6u zOnTyrmCGWPT`R&yDm!lHw|h@6Ur;WG=@uE-U*tlwPK8!Iw$I$P1+ycTGP-X(-PJzy zBMV;}T|RC|@7#Fr+;Gn=H|j}s*#xaweib}bcG2!<%zm;U2lkKr=O3?=k6BC_Jo2dv z_sFXv1T&sya&i5ATljrVc5gF^S0=8%k@YvKfP`sH9K;EVgX^54hIU;wTyL8Nd6Z&& zPKWOK-bN`vzMCN;*T%G=L=1E0{^nz|q*Jm#$@s&ptjm*sUp-m+AOmj)l>OAWU7I%V ziMb-eJNgAN5%14*WO`?f`N}umkcOex*&~7V zNiYBU^I91x@Ea{4Nf=1U5SZv5ao0^Bkd`XgDPBK*L87~S{A)vH7h+Qm@j`w_f9M!3 zu`fvpi>_s22t7aiY5A`Hv9>%iNO<9!a> zNW;85V%)!Vs?fkW7~J(@mgT3_pLTlr3VBv4;GxpPf$d|SzOhOtIfFFQFYHf6r~Yc? z_x09&boBnV?Qv*$SUF|N=+{&Z>tp=D1!#j>w%?|rPv5*5@wJKeROCk+FY|Y-f{^~K z^-DJE+w+nHlOV{FKrIn^Xu};kA7&xt0TE)|ItYQf-Ii$`av=P?-Uy~~c6TR_dFVH9 z#LCY_z9g#_I_HJT(O(OVAzi`Hn?^l9eC^1KD;h_fQ<)ypKjzBN-9wHk zTN&O~^|h)=|26W+%2tq_ov!^@wd-C#e{dWE^CUvWxi9 zPFZXn!{YYCi6M6VmW=RNfv?1B2*g6bAuu*1wk38D4=S^SMum2yX%6NwCZY&08@_sa=vyiNq zV(ZM$jXoZFz%SHX;taZiu+(U42Y!2b9nIcE-t}zH1sLs=Aw{zx3hTJP0_PFMirNAX zyldazJ%3SEsA1A3&(P8jv^ojPmsN`Pt3?^IP!TS$&R_9o+oPO4KZCNEO!O$ISV&ie zJT-uGluxOiUs>_5FLIOBM3&$%Gngknx1zPQYO5pj$-qMDg)OSJ&X0u8y5rR6rWfld zVS@_%n-*UoYB4`YCs=dHWVbXN`L@sqQl(MW#=jOl+Ee^}eE2u&2vqov7K>`lDCkt}z^ZE9 zO*rU9NGx%^3GXiC5TcwD03gv# z`^DV-f?GM=kZ*=0BKlD9OOV+(iq@p~FAd%`2)8IU2-1IXOGUjg_X{z0;Jx6^BLAQ* zJu4swDZVZJK+SarukK{T7(cbJ9p-Z^XfNNprdA=S^SNWF9=)*1!mNJ=G2KwpS3<5WXEa^hCWufKOf*w+Thyd;5%{dx*#pQuia6et>c9jA z9vMSeYqVVmxDrVaN%~8Z=R*;FK(4vmj_7u7qCYV?j|qbJwF~`U6ve5XM0G!Ws#kQvg;0iZHCe+r!{D(01aS&A96fJOrYC>n%CcWad-XNRd9nB zQ}Sv4DvLOf&m}6S4Ec>(gVLqm_t^MX1%+(tY)+!P697Xuic)Ya_d^VT{A~rDYidb? z2qfh6$Pfc?V=l%3qb29HrD&WIh(L5XMiL=r_g@A0f?5T(D!`B@t6@qw3NA=AH>Y=a z`RjiBLbfV2W6B!u_?Y<5ykh}`U~^c4%^~I{;6y*IE1hgX2u73&Zp4prc5zpv$#Tvq z;KtoeyZ=H5i;A4#nrhRMNN+6>cfEB}2j@cz-BmU3)1en#UDno?0ze#sOq!Hfa*EIp zqg@A49F7LB5WVhaKWriQ)r4sS8OSVf9m>9mLexd<9(swTo%ED4V|K*TiPu$8eNi-Qm> zO~5&ZAwJuY*kKm}dpYW9MZhzIp@;_Jv6O=O;A5>OL0(CQKc|n`=r~MlYt?!Z`p1c5 zc1dm?*V6(7unP1oWm?J50lL9Lm!dn2k&4dg3c*T_5r_o0QYyIKgKyz;FLFWPz13eJ zsSgDq+!7Na7=@jSW!wb{xwi-gR(LJf9lp0r3F7*XS3=6hqV4Z!YXQ?B=(065S!Rbk z)*Tter4QgNwRO3~g=%SCE*2Xyp3Q8`*Eyy@4ye)&229nLio9LI_biuQnRezY|;b(q6G%8y^#~_Gi#@g8CUjBSLwAgN&}u+1ghy`pNI;ze5QlIM}df_Ofv1AzVG3S7mpTt zL4!%#!n~c|5z8frIz7r7io82ap%WcQ8V1Tv^cf^~Db3QNEU}2<3{&gnbWI#qloU5T zJ^VU|?;m1W0g+hmI)U9Qx?!p_Sdke|)sY{9m-M z&^hqaZ~eNS6ob^f7wY2{K$zr8~&hk!G|-{6L#P0%GuHskIzw?qd(dpJ+X-Wri; zP!$^ZUXkUO7{W>47s!Gtt=@7V4+>-1aB{^zg$|NG6GC}_Pj$oF zLZ|=b&zl{1@pE^EV5P3DU#>ZsD)E7ej~q#`6Tx3BsLXLfMttO+xIMQSTLYdNx|s-< zlC_1XjOFVa5F`MPHcFD69%&u>p zi#4onBjr?HqO1HB!DN-30&6fxtV6fmc=(1#=rspFe=Uh#fmShm|KJdzQ zpBzDsqKVVIu`1T=TBA;iTpz(m%}4-lX?z#CVr%5fBH#klp(3?CXtM8^UQJ^`ix6|6 z9+hDfR}AT1wMkpKW#{L%K=zqP=oMahCkK9#yn&m|fo|elun@V!`D>+9RvOOTHJWQAygL{hCm(){~ z{igoVY5Yfsv0a>?Id8|n%TNAfLS^W*)6QC|?W!;VNtcFcE?SM0xE`CX5Elm5MNMUFeX0?x9%&PH*oM9pC^;{an^oOGcs5_yMRCoim8 z5y`o1jjOP8NP+=lOkuDk3PT~_s?LZR!w!wT>0D6-xAC5#%lj%?NQh{7T--Ff0fu&4$=5XO=WHwH&?_PWQY-CAmWzN@=!h{s1~wALoF}$#V{v! z1DxX-L(!rk#MJFwZQRAyBR`r>Y4> zhbLcURz<$mPux&w8Ti>He>_1GH+V@+@=<6{CSxS_L#W$|c%A72!c<#ai~C10vLmWw*rO;UB|zKynV#VFIn)5BM|k0liO} z7&a$V29W@?eZg{~R5#Ek>KHCv#;9mCWBj_l!$603kc1r>49}dS{j^J@dk*)^XVwKHs0DNz8l_)-1!yN3vQMmDC!GB~UJW|l?1FUGP9 zWI&@jT(@$mFHi(kc7eJEoLU;Qz5$8IPSp`~AeF5yOX5q^gJ}9xcuAAAf^X%BTSLc` zRh5leF!J28su5qSI#{`X^sh#bEpH$5{^1`UUQ>3(uul&iN;vyl|Na-$K;OV^H#|-j ztFfoW^G3O*Gk|L?;@E@hifNY}^CKI0v@F_-=~F7*8&zn{vz5Oe z4oy>?F!+&n+BTy(qEPHA+oArtxKn&o!%I895c*&e=_{Np`V$8)Jo{Eba33g=Fu{9VtthP zBiSv=uO5_jagE_A(EVl(GM!J%tY{13wU7iI9abe6IDX)cpWpv3g8*r)E(?#S9!RfF zW!5r1u1f5PEl#EEB5wBrUCBlkgAYdXVQ>>S7b6lfnBdZM8pXj9S)P#0B$2huE}V3F zWcAvn#VhsXoRE|euPcsuggfpkw`kOuctWJQd2rF}+Wdv{T@^qjiCWr06{7vB@d-+= z=BT-XVN1M&q7UKElOZ*k>Fx)ZjI{>>03jC5XnsD$nYnN?i<^K4VVz`mCzGTmHaB** zRX85_T3a!>E#VvEVTK|LqGIk|gEO!>61QN2xJ$|FJpVZEi*sJ+>aSEazTY&Qy4dtu zrZ?3Rq=m{{*GtMMY|bs|)-}tuMC|zX-x{>Icd~V+{+A0st-0hAGgm(=dZ|9k9KBl z9pkWi9i{Os#k|}I=1EXg3nk&4G%6?CU989nS zNeo!>(N{qvD#19aThVZKk9PLGVlFVLCD8*x5Zw$0J%7MTM$vvnQ z@u6eRCLA_ptx(~$Q%?U;=y_9jGRu`ziJ3KA2?8cqw~Im<&5fQ(W-eIT?PTH|J8<`R z{zd${ljnKIo4GoNsn2|RlHrOMUgAgttMTFqkmy_=DQ?aeM*`|@GorMlrIJi={rbY@ ze#WyQ@j>n4Y7o8FzF+H#ic7-asibMp|7Y>3D)e)C=MUWavs*V+g{F+3~{t^0$SRtI>*=Kk#25&~R)%F&Vm8(pRgW%%>DQ7UzI1sjo z<;LakE{;}BzpHpS1e=o)IUs>^ifPd`FQsF)6Sxi{v?-~sFcqRlDhG<14LHGDH*o87 z&l4qHJ87{uUVG|hzOVE5b4IK!58oC_bpPxbwf>6q(LpCI??M&ortLTsiKa~uQz!`$ z>X#aB3mM2`Iu-a0u_V(%%@G~f+dVe@fx!D3$%cMwgc{?}FM?Va;-d6hZ{xrnkNvdK zkYOKddeTI4LA0lPgzV5CYy6dX@t_&f7-OuWk1&LgBRQi&9BJ1vUXBF_4!KitO+qE)*m@=Jmbr~SqRL{ilh}(&z6CC$M(Fj{L%q6c zv34a94vCr%IK^|tmKplciQ&7--P=x|Sy()9+dq8%WW3q&hP1){jS<6OXJ*I7#gxM4 zCqb40(T{t3(2lr&u3d?2TpTgP+%e<6Ce34myDG96cQq6C)JUUb3u&J|a^i7*QD92M znU;!D@1S`!{gC!U#6F8AfHbF>@H`(+Akty6X9@f~78u%OTLd_}M2haocSk#RB$@UN zV-9k&yr)=RWEtxfz_Gq3-Oq3(nSLnw@{ql>Ps+OVSxBL?#65MUqWNYb{qxb@PR7dH zKK1Jpa3fdG@WvT8^7>3BCT1s=vRK}gEUv`^2h}#EIV^K(=u1HTL<9g&vYXtet(_MNRWeu~8|n^KRaTB2 zy>{fEM%Gq*YQ!reW>h^keDtt$ht3Uk4|%fuJ7qV7`~JE~_%EGR2=Cv&CqjbU*cxxF zwrj5*yeO3UZRu{&m^x|Be_@+!Af*?G4*0@NcSewDg}@G5V>$`dZIDl1yf{L2F4>PWLsJ8qy&z3)R0iX#uLPhAyw8c96V;A5PGUt$Io$g_NZCUMxNb9sxS1w?I zCg*ZgF6K{iu5f~^F{Nj7g}3j2=7n>sLQ|(M_0FP2BH}fo;?mDxR8{WZKeq+SCU|NQ z8o$uUGQ}K|X5zNyFv;yoIUMi+5{6|il{U;9lWRt0wIvACm zcoNV72P=hSL%@c%gV4AZGy(HYb`co5b5Pe38d|)M){X>V6lU#z_)eyvsT1aTW3+|} z>ztr(q7rSa;?YAEU%h$HokBluBL$>QNLLl+DugI`$&HPsZ}ZLCk#gJ&YkZAC=DhB* zq3?U2d-v%_ryWn04GVv)34#w>m0uE3ig<)CRa=72TfnOBdRMeu5cIGJYV zweNpGD9$BLCZ)6Y260;xXPN}WjQQ2DA|fJXg}_6b%Bj9zDs_8fb>T zBr-2M0eEF)dJNnRqlC)2+RTuF1QTXJs`cj4ivOxHbX}oO*Vm*%&-&HvC$+G}j0J8? zhcj=|wg0|1DoNB(9p9qlxRp9sGB>!0=Hi--ZPpRf6ke)@tQ z72C&8)}Y1rk%Q1m2*|F=s6#cYs7dS!*UOya@eVWrcF9)1?ovfwKudA*({mEQ$h=nB!ff&v=HFj?ytlL zov{e^HL=VaxnD6AI+TGt7D8Y1ZCcliLA^3VcHP`khmpm~vHKMop#(@TDPd33f zOu^d-f-o|n3A>>#yXXx119dAUfZ#~Sbz2Ep@67#|Uf#x_IC+&HodBRKg}fOqdv$GivB7O5Tl*g1PI#ZjipoNs`bG3 zCyw9#F8^(3`?0EqZkvh2&(^6=_rlk|_RF@>p>g9++oXhKo0{_-or!H^timtB3`z0AU%*0RJBujRT%(ND-m>fe z{q@O}>PC-i;)!*rrEpX#C>H}C{aMs7x_CPXx4bCV+G5D(;o-DpKrm#j8oD;3M;Pv z_h)9Abl>M1DPyT%I#aoAIRR9ra%VTXW!xQDf+bsvszwS6P7;%;TGBA)mW=D*n$Ws| zD#@oW>c#4q5Utopp=dh+Wh~kQ2Z|k|W%aTDDrvvXKu`zG3P~b`N{r+Kq~@^cQfT`| z%IMPOOZ$gELZ3p<7GqZFhAop|kuA})f1JEe?mDXJXzPI{A_IQl=H6M?zw+981_wA{ zm+I)y*bm@A{$PBti?}RhTCAKFe>T@vvJp2Yh`=P$M2~48w3wty)B=|g`{5=4u?eubhaSK8;1_O% z6Y$_8j~x2Y=ML}r#Gwy;93DWk|L{$Z9J&FnKn`7gXz%9_K6=~Bw_a%uLGX9*q1}hB z@?_6d1o+#k4uor?eHuu#j%X_0l%~U~(F3VZ4*fSFKsJvL zq`{K!$gm=kg}Iwu=uHKG!uV-^wG0|;=C}C5yY^rGG@t3TX^YpXRzFbDoshgD=2m9i zH;oM1j0aC$E0+&9C|xRTU@3=h||FWAP;VFyPC<*r}r}5D0Bj@e{)&NhnWHNurpX|XHbx@zBLp$&_+!nPi>;C{*uZ-3ve=X1VWx;E#&a++ z$^f8IVd!llLA!0c`PT$O!T97%%tuB-nerOXSZ^$eP1@{X>Q#%R+w3+ei0wf)#oD7Y zykqt}^OYVv{S&90uYG4rH@QefpUU*~vB@y5dELQFV8R|zo;_qa`0(E z%WPnZlUB#*5OZgwvfju&PkrM;dhgVwl6;rw)J{S+lj2wC6w~IP+rrKtJXL~J49TC& z-q=E!$z5|78v%$A6f3f&VdlZ~X8O{m@0R_*Qcy@`PVe3GnUAa{-`4o*P3TNObK zp_lW285p+b z!7oly>90^eK^%qyW93v4mIp86Zih52(awcB2y0b|2DnfV?93LWaYfSV7I!wMTA&XD zo@+ULj3}r|E%E{(L%2yKd{T8=qcf;VF(1d=7G~#c&-{jt%r*=-q0jC+>HA~;(SOl8 z^a{iN6KzhDp39U0CB7Kj7%$6szNe(x?C$LSkWo%F`dy$+Vbrf}V z67B5Ikw4p?W84b+B)m!8Ka$4?YzXJYY;;`7O?REV=bqccRiPE*HVqhy=)fGCShdj_ z{bNghTdeV#(3eIu|8e!906=$XGW6^Abd&_QZfFF zdTTW!Gi%)DftxDH)fB-Z-4^{801aMASCKlMIRAw}VreG|JM`t5NV168u!b8MjcEGV zv-NUI7VO_Mz&5jPWYvsurb%GT0W7V?JtPb3Fk!Cu%a-VFk0$&0?x|jmXS(^wmW9|A zR?-_3ueGmIY!jWR_pMA#xn7JxT04Q0iEP>$?UWNTeTf_nMZxq~*5%@9=Bg2w2q|4Fro6$SLQg zN{R}G(GJBUFso&MS1~%|xhk%k6EMd`Q%NdAeN<6VI#Yz8b(N$`GtY2RoGTl^O!>B| zw-u{G!%$7mhr-l7x9lH<)nd}}9AK9b)rA#-`3s6T^-AYj+S`Ca(OCQy<4`xcsejsh zGPW?nAl+nOY{Y=Y!_lr+TI2W{X}^so)>|?T*F)GCdg8GYKj^>nZ2ABh!H`bOZ>*=+ zt{CY?tWkDHpLo5UsD==f%s_|-gvpv)qg!F4rq-jasIQt#Bi&6pNmD@5DA8n$a4Ue< zByC7c2iEu%PzwNR_w>T}p-;=?w(i;gKvIR8&M`W*U6AW(actUd8JC^sxle$?sm4{F zKw>9BNy!W*BEP8XzEP$jJ)vS>c%)wwGk1meT>oNuRcQI-#K0!z(QtL)Tpc)bV&;M{ zBW(mQAJjlTqpmJyR~ZZgtMNa7(ISRgOoMvMh!sX1EFm{PvItSJ$Zc>&(QX|TOeFI! z;Cjt(5?O%-LJ9Mea0T}mmwz;}BGKB)$BZvRB!p(TtWKPC>xjGM$DH>@^lGrcmN=XfhP=cM8n3PS8#z^BbR8q&C zGA)Yo?_ikQ-!W;kiTg&{ubYwRL#;};YV?$!q=kveRtAGts=J6>Ak{SGjpE6X_rdxk ziCK<-HzfG5`Zj^L=*oA(x?^9(3e7yDSV?Sf0%lC=rRYeABH2*C-qt+>-^GzfIuWnd zWbK(+Z?k$fY-Aq9$lWvDE&T+Z4IAf+eP#=7l3u$d-wmymC9`VSD3j63L{sV<-!`N= zsd|>N4{6Jli2>%YAmo4xww-grme5|;NMWtFDLa+@9w|dQ9QkJ?ci9f}%bb9{g6(KV zki4nHR>4Tfn2qHf=O#0lIh!kBGyl$!+TdGz{wLQ!<!^+ctFRs~sxtxl)_DeVGxnBF#!NwsNH-C9kPxRB&kG(<+$^LvVeQc=UypsP$~`_MZsR+ zE%sh}YX7OAdM3@d$Otf%tB}`|Vp8r*fZKo~jIeLJ68DcC{?Iif{y22yKCuzrd*h+Y z?E2yBZ#nev7Y^;coZ$6C7hm=AXC8j#<_8ZxeD&dL?m7J7$1x%nb=dQNHSFrpZ_NA6 zjn&nOLQKdA50@a5entk}(A_qATW8(UxwpqBuGn>g_s4I1dot44q}p9+eVIbT@~+-Y zJ5h6KT>=pGSK{uFS(}7sCy|q0vm|Olxa>4lALAm7h5Ha+2|2s z6V|XDL@E(H2=r90HC~K)swWpf52fQH|2_O^_x;4wgfG13P1Bk=pM%5;aT>T7+yQPU z76*vZ>-dYNVbDNUl~yh94aJ2x@wLVg!(gSCX%q=d=ug~<%s_maz==IOdh}*iZZFL7 z{`kNz@QzQ3E%&A%w;~+H4gZ_>em%}A>8}9}B0pfv zKwzNIs&r$+S&I=)0JaiV+?Yo{ZDXt=o~{WNFk(D#eo0r*&mgY2K+D}cdQ`H_Re0ST zLTHjO7h*fHW&`1L#IzMqZ1Bq`Km+QB1$&joiVP=Txn`7ZCB-s}Hz+Xkf{^zxIXjgYnqWv>w%U@Fs{m@#9f`Gpe2 z?LM1|r7eiYhiYeLNw5fX87h?c=H+YabC@w4rDsXiW$T)XfG*L&dey1-x1l^t;A(FV2~onP)>4+5WSE=L#xJ^ z6@=PRMezubepaNaBkRGAmjqhtC!8mC;@HDixlD7sl zj9bP7$0Ynr5O!ijs9#Pf(k64AnYQH(8{#-sBoWzT4~-oh=JVh;%kaXWhhh`<%Q467Gg(X=iNUbB{FQOURW7SD(w+-SM%$NOj3EO zEz+n@0!!dONm&e6D#J&k&MTWziFPyK85@UD!owF{Uh@=K6(cRy&Ms{B{`B#e7HEcc z2@S%O+!vr?Kl*AsuzN$1-92*dnw4uJ0%|qS3Vp9<>ZigH|6#R^puSrPw~D%%2C2jO zW;^BU$nLugnYL&SVxGvQT778Mk(#n~EEYWsXt0F*0jw2&9^b>ZH5MkXX@*7ycJ+ws z>|-!)ne;O9<%HL@H5zz#+&>-S1M0|_=muS_t<^={Y+dxu^|Ib;zxnRFL2*x>i3^tECuWZ9>WW<;)Nl+h_^UEVqu^BA z`rR@<=(vPjf@)oD;uSE%39po>e-bQQ{n%w z3ynBsc+aq>hn+F>v7t@n&z8MjRv$hqR5xZ~qOa|w!o`5 z_`f-gD6fbC$=HVF{_2`+Yj8d8ZxqilO5ld&YH~GUHAQELL98W|^T`3z#S4rzPSCpQ zaKs?2U&5*Q0e?v7%l94swjp)c!e+##&txWrM!$!FYY@N<2$cp5Ifz?#rkiM#oP8f~ zQ^d9;z(^6!MD7eBYXlvIqGqCia^CxQ-Txg7C^G37xRci)P#SV%m5@E#ef40@5YHDk z=o#kqU6=waJR&Dx=N3g+??DUKkw#3!mf^y>;GBzEH681DE~@(BvJ+NihCS)O`wUYS ztf*bUCWUuGJ#s!`z!dn+JblbC7vSK~B1dz*trppCOXAJmb=&uks|?MU(15i@RK3OM z(1UZU*t9EuCi}{ZJ1-Z_Ws?cpUz&Mw=M{!!@8$dU9=_>r#XSi*-{E^ce(13e9lH7| zE&xV-=;BYja{Zl$ue|ioRo8RL(oAUg^6DPsN%`zEV7`-N8e*nl-ho%%OkNHQ;hnSV zwkO77jhPUyH`W-GG(qwZ4nQ^Y@KKP1`GW2L?baw60uH6uB1l zTSS4;34FDX+jZMx=!+94#tU;ydiKTbr~yKhKX@5;J4A|=DwCjb&MTPs zu_Z79v19ml$lTJ*mVL)y0kTbddh4#6o?DN%Yy9MSs-84#OSJ5Oq8;~)`6WS2%Z^2o zqc@`-Q0^r|7wVq6HA9a!j!lJcbvMLaV5*(z-JQnj4sCy4WYq`JLL~-CwV$REFqxl_$@UFeT7>c!j z{1h){)ZjjX%F|t4!QZ&qXrP}6_7y{;m|ek?G8ia-o~c*j9~cJIlhvG(l+G~EgC}F0 zF;|H3jv>vkJT~`NuO}r*=nK~$v#$)wFI%_M^<0TZwFIfrtt9`^A8X$P)NG0aIKh$k z4MI+4`98P^KzK8`W6)scbxYusyoK8N5ij*5DmN4=U;Teo5_{|BZ`Hv6ni}ZZ_4vgL znIgwcHuTZ;3}-Eg;!j*x!MlNn17RpRV_?Z<8q>f8Y|@;)N>Wj-{tmgyuQX$WP*CV~ zYURilL&u_|Lri$%cRjWD3?dUIPVuJbusfw2@SFU_WLB`txU2}aU9DLdo{2W%>C76C z9U+*@6oGkNX96AH7|Z}}b;+nKRxX9{DF)s~W(9PT_dHqA|8`f_x;mL71R5KaM-Ei& z`uyEn)TvsSfm(&jq`w;zBUX3)-IjC9xLH}NyNwP(6kW84a8=`D-J*yWdn7;@fp(4FqVd&`MZVxRK6@wlOj$C-6pwt3=@~A zv{Cm^)1U%xZ{r4f=Zb>Ro{Nd`Uh!rcmzS%oyf!o{ye2gIu2J3i`pZYWclb%el0&{9 zUQ>P`{QdtmCA#kZSw}t^uFqm{a zMmNziTzJ0v3e?qNBh>>Qwewytzw%7l1P+tgvD(>^6{{yh;1ka99vd4z(D3Zfe|BtD z=(NcTyyX<7ta{f}G)ppOnMm?w;=OAm{4~)KScTiWc*;syk2ETGq#0)n3%xkq3|dUj z7RghE<}QF~s$lafu)k>8RhUa;ThbSx)`$<8_%^ZICjyx?(laYBE0+j+^E&g$k!x=h zEv_mGBd`rP%(8)tCN26(Fz7--b*^-3*kC|+OW>|0*fC?SEI#T@Ea&P$_wQ*g(oL~M zn&RoEtzg{gR3C@@6?0B!hX8J!g{jmIuW*q-LBu3psD1Y5pL{oNsIij@mL>Xpl6_^} z9{|Gc>>~c0ISCp-1}~Qt##o3P#aF<;9aQZZ&XvX2hAzw36rwW7=`x*om*opH6bm-M z03w1ubMu{La1kB6G&upVnwGOh92dT)_;G3KE(cBUmcc;dAVzNOi7Qoza~%5hY{IR5aS=tz=L5F_}J?YpD_`_rP)kz;B)wd=)WMK{}#z)1JX4$U2$ zc~OxhtCf0vs5_c8dC8qrLA{Hz6nq7Z^9k(m;?I8Sk|8GYdWM2QDvR?|Fc;EepcRu>kqt{~Fd}aR^Ub*y2R$jjN@t5ztkCm5h zc$^%rvhw(zLmxT7%AwmnbNHjTzI4xSQvDph>AqJ!df?^zu6yOc{+B*-FGmkPN(kxY zFWvLu!?#{0n-5)k=-$sBy7UoFkU-MQNMmyK;Ri0|iGEaQXz%%_K5?r5&eN0K`KUO` zf~*mU()L|Mm)1x`6YJr~nj~k=4lrVjEVKm^hj;;YJQ`>!J22@7fB0yf5uhfSNoYzl zO-a|CRGCSoZj6e?jDTC$`Q6BsC_G4s)-DV^>11HO?ASkI zURdl(`gP%O0Xx;*?MF77P3QANuF4W!#R>k3J7i4c964f#&E-m)&{3Yv9`R+G@o;hVvZNMH?i9ToFrB$gLIvbALhGI06( z46@0q|G}-h>xlZDFlEy?Wt&_hgJ~|cBh}Fi=EE$+0^Wl6=xw8~j_(0bIO!?2qd z8bJ;D4PEo|AwLjhhbo^HtDD!Xkca-LpY#X9cFe0$X>_!mb`$|jWUXu&yqVlgJTLYk zJ~^1=70*DrtHsgSb@~1%P+sH*H-CJtLM!VveU>+74RC@6$>{+m2%I_X*I$YI$Lb|3 z0yIz!%Iq6ig3V8kZOZnDES`A1>b0fCw-9f78z$o12l1BkeW1<%p z6iSJn&=KveBegd~WQ_5JS>J#CZt`MI8auTxRh_xsX5r5TLDthhZ#vb*U)#Sr_$)d0kYK9*xf`+!Jej|XbB~5EF(#ubi{))Sk zsdy`Zei{4GT(R_G14G-+cQymU=*UR~Ebt$`u4v&>0OJ=9-SwN{L&If$rOftAYr~%z z`?!-_ARrmh49$iq91@Q?-s2dQqg@Fhoe=smLa2MT77w78k+{QN;WdB%kH4Fvtf-n! zsjT{<^%AtvM9@ZTqfpz%#9+YVB-5=x8qfAu;%|1QS{QJyDKjpwI?GDqgD|54c2Z7E zgF}%@VcIhRBeqsve(KbJ2z{zpE2T0f-flu~1U8{~iC`Q77HoYyAUrL7C{)(@x6()6 zy3boR@MmkFGW>1QxIG*ytE#$v^y{N$4p}~OW<|@050&o@Jv{6eL#xY&|Jlp`zdKks z;lP)_H`ll&Dh*jvLwCL--Q+q0Kq|a@-S&Q} zD;ZXI)lr$MN|InV+Q?NsZ!P0FB2T=bX^9zb!_(H(KXiio9<_7D7yvREx)`^n^!Av@ za7aPhNX;Wq02ed}0Fy_&buiu_|lJ7LTf&)@gk*FT4O-m z!tfAtlsNUoG-SH^8+Zf?WMa^!a@%FQO0NJ(;%Y#2C5sG4CoQ&cQTP3@dQPTQ6L_$B z#J45!v2E{^0#U6SP@eqG)ZaPsTQ^;QXL#!m-2QJ7#IK398&uNwHi)RFybaw_UJ_!u zW!yhf5A;$ZI}O+eB#>B+2))x5A&z2dsp!dUN{u*F{#93~x>^2hR6j;ak+cn#vkKX$ zD-q4WY)?PR0wwLS#(@?S&;oa$SPvAH54Ic`OF=$7y98989$0qZ`CsSh5|fsD+f}aI z70$_9wZ;Gj^gzLA=mYtrnY^Uv^jDxMJ@Pc7Zn1z7B4V;pH|i-7fDb(iJau=g0huZn znyMa8fg#H-K@9-^`y>#0n2Oq!U;G-XZ=@w5^!!O+R%Fdf#x_9THtFAlv(B+4B zU*Rq(s7l=TvG^Yi1rlWTgf$0iwxAc9j_^b z_qabWxo8pXLTe+q?$=Do&Q2uD(j8W-3|5vE6h(q`=bBO0o}|{jZQTibP`*Q86`?at z7_vFnTnCm9?pJ~v2i|tzD=%)Y3XPjMwXhkqQdxE3LQJu#1dJ0UOEJ)<5=&``M%u>s z(=Gjnoz;K^(wg2BS+Qel%&-s#2?Ja#n8R*}J&JP)@sL1TkRKhWx_n%@z2hy$)W$>v zIt2RyQaA$#dpfWoYLqsjqfeaYc=&W+Y*-#|SOn_nU%om1vQgwKkp9XiuH{=b=lRMI z1E!NLbc$XL^EY4I-67UnC%7Z|Q`J$H9W(%0S}C2?R3ZRQ!f37D8@yq%m+}Rsu~Iv9 zX9AOiA|s+-ZNbjgn@@Rb4t(K@X(*k>#`6YSSldB%F`40F$$U>I@EG}v z2X`_nMRXP-{;RW}G`ZxMkWb~#p<}hAGQd4BwJB7KsxOWyp&PFn{=pDD<94K*F5e*; zr8V+>!YQ)00*8`6Yx-q{B}bz8L(CR&VOp17(W z_ZOEHd5I;YNr^QIpewR5Zv$^cR}UC6LO+R*eX9IcSK7MS$sGOGH^wo)ihZ9u44T>6 z`pGInjt$-|Dvz@kR6dFI-9>XbxU;#L=+yu@mD8;Vr^t6<-Gm+pU6zk@!GSv-SWnDz z^&D>lQo@L8uNqyRNN;)a(bo80?l9b8kHQ@$B_+c^zDxOU0MqIj5-4tM(0VV@B^wu@ zwsnolQ%i&PY6u$|Rh5+1A=Yfks-mbkBO-)eUwGWR{hFF3@#JPS5ZR2#Hpz!7)-8+M zmOdi%s(!#{vCIzbE(M_R3^W|L{lNtUVojb_s8k((*BBIh{)^R97`Xzgan{!ZYQ%?) zlNj5Ubye6<$C`*7q(qV-Pt(FA*g`Z7TIwx%hE@V>0LC@VtyU`Ilh8T~P|UXo$k2j$ zTAGeZZkAC$^zk3Q{pXdh6l+9>(W8gc7qg;?amECYfw5`B^TRAI?G3A7EQ3{8?BOcz z3_51>%yztKu2eD*Q2o|?D)|U>sG#geiAD%MM;_nNK^`9~1G6w82$)#1!MsyNa)2yG zW#crE+ZT23fyoEX!n4!w_=GQGk&U9m5sE^5kfCnWPnJ&7ZuPcW&lnvzs3L0V z-tIQC$ouC~;GmPH=s&r+GK2_L2mI&K-)y<|7p z2Npj8#*>i&sZzR!B1fcaIKVa7L!`bO!-%|UE90Z-M@^0Kvm4PBt!Ub$vZ0K~F}vEB z=}5Qu8w?sap7c<0R_S=Aa)0>!epj3Bo}dO~eUz?g3B))?Z<3}dNBGHT`f>tAGMEqp z3LsG978RUmzTr3-wpGEa8$C4al~Xkp1RqMMY7@^2+cVz}M%NRo0dt zJNm)0{-KkG#YWsU`W>OUqxOty7;@3bZ51s?Qzi$_+I8;(2bVAiPMa|hR=Id!6mdz$ zmnK7x;tK|Cl72V6G?Rg%BeKbz5|Qk?FYNrVV-LfH*CI5s0DoK#kwxNlByX$=kAN>b zn6)qsN8s!yNE9G4w9*irfHs6`qy_cexO_AWHL}Q18!=||TZ(TD9k9h%B#0EIwtk&U zkk4wc5r}+7!*Gs>h)MCO(Rt^W4f(`8d^j*!0&S^NC}PZJ0u?ucz!-zjL$aP|)<-zk_-Wo8HQ-csr{M`A|7s35W2(jc#@)^)Vtyr8 z8S1u9go{#=xKlbOoBe&n?WD9BS)Y;M#t4L#NPA%NBpXNk$I5TS2l@?B7HDd4#=URM zlyJHEq6A#dETncFc`&1ihx+GVTtDk;4b*q6yHQrJv#u z2Gn6a4Sfa<%UrJ9&_x50j!td_tid1!O%S2~a_J_%bFLoMJJN*uBDN_Sz0#;jW&;RcrAo$wSiKDwT()AvqDK9ZoY(&DQ-T}wC1sbqZ~e-Zq>S3y zqqJ*U@Yrho3a3P3F&SoJ%ks_heePg-APqbONR%#s*PGtIgZs4r2D z3fh3SQrtG#c-(nzWV0{meQ?(u&s=;SePhbJ!dM3VvTATWIFh)NTWad;l8w0AGJV%V zDy8KaB|tiC7p}Ctj<^VzIO6n(#)2`0QEM*35`6X`*@yGhWD8Nit4^ap(+*?$AEZqNJ zFh0KZh`}}B)$O|Tk*5g&CeMdAO+}7%s0~}82Zfg+cQ*DvjMQ!|ZVoMD0>BWfnw8i- zAs{$i?EE5%XCZSD{2ijO2puxuLblpMh9|-nbX0V)3K;NPWK>ZRpmExe=K$ZC9#=c7 zwKcz0bM&dcNbBkB*b&u%F3M0(EKZwu(XK}h-ggSN(bMJ?GGoij;WW_BCqtu#LCfS! zaf0iJN_0;-Lren-e-@H-(h5WRnF&|IIS>6jXRaCs^zyPU~Ff~ zaLc&ay@8=`sT_(R-7^(o8zp?CL1z`?w$Z_Aexz;&B&?v+7&gQkE@GpNJy9c;dU+PF zK}=NU7|b=2rkF`X^G`@30TB&Abh#aflQxmDoGUG3U43!(3;l6x_3y$9-HX-MF5Y$j zlQ%Sw#%p3Mq`>V;0(}I@hoQ8q74MK+#_j&Vy{>;@L|MJN)$}>u*j@MEaNi83!-;de ziE3J?l;IJBJQm}m(BzZ`CI=5XaTTz{zY09rgmF{6 za#Kh0M$B^oX~FI}<<*<7zD!@3!}GOoDA7VJ5Kt9a>!uuPu-=I;rqhsEdT!LX(4mq26>~?7E!)f~ z`p+%j{Q2Iom;CtJiIt(_$4>Oh)lRdjcdp`t1S1q@eaGC{k+ltQxEWxpHe2=qnj>U9 z*BLo`c~dcBm?=hDunj{w8ziW~c5Z4K_LWcp>&gsucFcBn!|0yvA?cbkNy;6g+})dY z>7~yP9Ua;{DU%+fgb&NW$MHxM2k6ma4c2NGW1Nz43Ey6$ItCjPo&9Ukr&9h({I9%{ z(`2@l2asFWJwW=Pc|gQGKxR>Vu?^=uS$XY7_d<2^%0C-=^37}cf5;JU;-&lUBo+d0 zgbS4zpjAe9JdMWhXnujZ>Appp(NLQMvzOt-L;A5;@lve1dgPd-5t;CO?cIFhWgAS@ zF+R^Tw;kl$#3aIhoX~Jm2d5ve?vfurIdZgQjLxY#vmG=us5OlTJ^E@qu)AKEo8S@@ z6D0#I0+#UH+U13bz@Bug#=G4QG)=7vYm}+do5ej zxwd)J1FH;#C1=$~u<3%!!3Q9=ExAy1zv9dZxmmI;(uo5uDcTN#UWZD6Aehv9D6Z+6 znwoX1RwiTRUo9K&#+$l2jO#wF=XR_d+r^PU>7wo-g^8Db_+RJau&$n#pQs>-b9*{k zvho)b=BiwAEiS92bn=Mz7IJ0$DdOCel*x?ri1?`Duikv^jWv!uCG^X0o^V{~r~Ux$ zlTbA0kSG0H1y6u_;dF!ul~%rJOG;^B`M@K!pq?;e5WESu+_1d37I&-Gizvz{F?(2c zU1pFd;R`0u1d=>pI5ZJlZRq;y+15F)VX{t;=hUxSS!48k0#-45V=$~)y|^i|VR^&k zzELCJH~b>MI*a*J1xDrr1roNXQMW?ifEBX{K{60UjR-4-A2=AJEw_a;O2h$K2ukZz z--9fny`4H+xjKsil@d6(EIYiDF1_r^2F*3L0-3C=+8d9PHs4$PiR)@<5Ei<8KN;G= zii^2$d3;SnTkn{$p>MjzJ!5+C1QX{cgk%{PKOj>oUZWs{_@2!HDrH#R9n zoPI%3b?FF-{z3Fp|0MA&U6C2nec<^SO#f+KucLozQREFzx&G~)WrBTEnYV6^xRT@n zpg$~dUJ8MN1@;5I9~2uu-f@=}cEWT9-E>mXPK{E1CS}hND{$`GV@Ro8Skm*veTh55 zf+ZqL0J9*sn)uFU7_Q9!q@vHn~kj1UDpj>Qjrt{6CusGuV%0El9zg4nE;2_ z_*!qOv8ubg%CYXY?hDdHP+VYseT|v@N68n~nYe&YD!s(g=dWG1)`A%&x*Q9b$$??w zhd_n_^EK~Z^TBVOFdj3Gt=^sLwXuq^&!M)YP4Uz9fe&S1P&#-#~WIb|qe* zP89O2nd53KV;e+bWEy*h6ouk4^bg|HARwS zePBgP6oq2%3GLoJ>6bY9Y;DX4+(9P@efXfM!UoP;Z>cSqq^0tfUh=|Sq?wD4omZ&9 z0E9JHEXFaDostN9%zpT5cgP=fql1p1Ge{&9Y3@ZoB<2Y!bNzgLHl4!#fIB~eF>;Fd z0d4k6UX%NaA2TAAE>Z$^J;{KHF2Wtv!RZ*v#3+Q(5pypZN-BHSDWN-FJh6GiiLNH5 z@FZRmvX-J#K|Mvffu(-CWj-!Saip?imXXULi5UoQLC@ZDoRFlJQMEmh+xKRs&q z$nRGk_rFP$`|tLyh1l*VetH6v()ik(;d8riE1jTOiMMm6A(-EIVE+i?HnEu$-T|MO}`M#Euvv67qtSN*I8db=ze+MvxMLzlB-tBqjb7mJGvtIHZnca{52Gjg$lja$O zX{92AVjzhq7Y2&-ju>VYefSxv`U4)eOI#*a>tN^B0$uHT>aY3y2ed8FG=POk`;m0~xbj7LpW< zCJa#yrTF*Z_y6Y9@A!{_yAl+GOKKNo+!qO*!BUJaQF(~|$OO&AtWkE}oZa_)xdSit zxT&=mA(>$|NF0C|QSkeL)0dyP+qE>^11vyYB-9l_`GH)o=UqAM4S1JLCzw9Z4yys& zj$>e91|s1j;vDJ-s}{zs912@OR&0f$0<^SPz%yb2gz*QUZZ@SK)tya}M5N=wqTRP% zK@xgWU=|i=FZbrng$ zGZrnJ*+}|21759(td;Vs1PYou)5naJi=1N5U1VW_?mnleN^;8J-GSSmQX}e&0E(IU zG_&gY@2D7G4vNaw>&%j&rId6*fOCRERJfe1IQGrr4pBkOo7;u0C~xlWTX&v>>@{|d zR{?QdxVlq%rP7306eN)Dy!@#ZGI-$CoA+D`%%3=0nWPQ1lA_jo5_y0=2wzrQQUk8N zs{?&jOW<(%NndRKkR;^O0d)FID_$~MA)5~_2RjsP3hX?aO+)&S@APVSf9%Qk(1@ok zSZ& z_ds+C^JE!4ReSY!py4xeWV*buyFdJ~5u=4I@Lbh6E)->iq?>_XMjzQ_;UEJ21fZ4j zrzFXxOt1y5QDjUkygQ#9_EgzaQ_h*Ox;f?qlsiJ4C5a&m7eyu0j=8(Rl+s-KDeTq+ zuJywB-B&*k+w0T`^9q}k$Z+$jwe^OG1MFb7XKh=$cbnO;3P1^U786}6CK2+nNOzhw*s^84Io}M5Ev6j_qAWqJQa$a40~p0c zBwGTpSvuNlOwEQ+cddx#2&x8d<%e<)Ubk>Sy^T%r6JKHUit2; zZy%1)XZ&QZR_DsfW?;r7s}W)${UvTI(|Y*Rw;p`tibI#&z<>C7U%KlS2|(I+)8V@h zkc8;v`#$>eeV==&@URHX95_7i;LGR?3CNy>>x>uxoN*8gvSszi_f=E5? zb}VNSrM0MWr4D0~8RK*iN0D>%lr`z$zkjDGE}*Mf;yYKwr@4x*>{1ImTZ~am!2r(_Tw0@O$Ky{Dsbu+@G7I? zA&J0$Dl=zFOSN=q6EQ=9)%ah1z2RAYY4>F&H%H{qz55UEdl-IYy<6m^$Mz9@_VRuA zynN?QD3+b!ZvFR<|DsDZRDz{2~tn%{U}KMz{m zu&UnDw#+t+&OGSw{`(F-1k=gghaTH`_>rq=TtRvlQyvrMCT;5CJ0V0l_~;ccJ$e=V z+Qr*N4)44F(1DL>)4b>KgV(*Z`{Bbo?|=iPs2w(b;}qe_HqCFk_qN?H@5N?9bW8qN z!Rjn+mmWayCu81G*5bvTYfaWBhQZiU`nEBi0I^`UZA7LdYEEP;>9ebR5e5Uw4?Y~a zK>vU{jF!+}nqWJDko3&2;xU(b#6^yN2 zVE*dL1=r(tLur2jTBqn1Y4Cj|>9+N%V!fw^4J`}*hk5N;vAVf>IH)T?HokTD(~pp> z`uK^bWyYwgo$G0qtkP6u&Ml!PY39F!xQHBP>?FxZ3ly1S00?nqsC2Ir@p{?RQOCQiB3=Ijmnz261)a!dKw7qkpjTTo1J{?U>Toqlw zvZw{JORag|u?Ne4LJt5NK(_fAUZeNT$Ik|YI4QP9l~%94U9#DBot^5TIM{Lh6*%$b zKocEog|49&`SWmOr8BkKgQ0(#dD5k!e=R;qWPGtxio}IT=aAN-(PQ!0=C;j4prV&& z`L+xbJ8Qw&fxhie|JJcWatiAv@@9D7eg#r-_#QW@!{P%=#K>H4%;HPP&UEBzJa~${ zr;Na)8gJaV6gpWhH|R&9ytQ%p#lbuLj)&tU^(GsuqY3OeGx(2BqGo`2xAUJwL?-2=ysCC(4d z%X$fs#~dnhPe@`Xj5T1R8`rFMH6fgs9pWkE)ip;rYm$*eBW(%NH(dYd3mSc9L8R9{ zJ!VX}!u1ueV;S5ItCA(#|@ z;>A@P+APDi#rP6)G>Aq}eKVy4`JWeDKDc-6kB{*yY@R+1sb%EpA)AKi&vX@UuJ`q) zC_SjIrG~Y6%Jzjox-wWoyLz@0!gb_oJa|U&!-~igP56Ur_W=eEmENx_rH`$ znZE-E{1O~>pFE#op?J*8cu)U!vUd5x7*#AUWVGxRVi^0wY{F`z{6rFI(!=taHW#xa zI>B@6_)-D^kw+O0f+3R9N=;BXqnr*D!Fjs$zw%N>u)G)a!no%(W(8-&xd~ z5%EI)+!0m{-KFewU2veeX|-r<`R%ruEtiT_^hqdCp>EE zdsF4&X+ca!j+$*xu@0TW2Jbsx7F+n#Gj~m7L?>fkV=B24u`Ne*FMPqBNMn%1z=<8) zBITSp$t~r($+SbbW*!+7BnZ7Al_j=FgmtdXP|eGG*cK8FNGAMvOdUn#8!OO31RJsA zOGYCGffjB?t@w8pkvx}^PeDhw_u(|^QHV6DQHe5_(%ZWkLZFn2CtaHT0Q)W+Yrq7# z!G`rK`4!qz5++~*P_c_BAPSB%5Tp@68J_NF)%Y;LAQZXAQE6U$Y?G3!25e0D*e!|7 zDA#dn=T(-yM*hEisQjNRUKw%wklTizKWujR%h&iT@xajrIf)OX4kcP0%}+4( zDRotm$QT%f8d7;o|BN;ExI^2{&U5;E@EI5VpTgck^Vs8 z+h6`!8QzTQ_(eKARN}G*b}rul>Lc(l#kIIO)R5_dPJraUi z&ls<0Fp-jkwuSzgv|(4F!lH^HVQhg?2#5x!7a9xC+%1wd`xMss&Lj{- z9YR}U-PuIA(@nc`wf>3^pAtsk;HfJ$r}|2~uq)*THaISF#>$n8rfRb>ZfJwB@#hkk z!)q(PRFUmfOE=Q}d7Do>{Vc!o9GnX&RsHOKgY|C}_1Ca(<7pOdtl}>9jLNJ{Xl0Ai zCI<_T3s`O6f<~h8M{U|!LZ9e#q@fLH%+ZX}?JH%6@RgbLk`ku3K^eGj)gvq*yU+Ln7Reew)w1z$G*r5eS1)cF^B&NyUgQuA$e~5;^M#G~}aq zDkYFUb6`~ATl>HN9#SAooKh%Pb!1>cYi4U!{%HBVZW)&qwICIvL6>I4QZvYiOgc(6QZ{18^<1q%; z0BF!gqnJf-zd&AE`H4x$idl-wXRuKLMbA;I;geJS5Kbn1#{ih|wyM}BJu0{nc#J4( z;=7e=;JOqCpX7Jv0$($f7sGmmWh5zGj9I04eIZi#)=$43QZrg1uOs6YT)cht%gyid zuB5*bH)~5A_E>8%5QW3X!Cqjp?#P2whAQ#_m{!Nf5OqdoYnkk1Qnk!@ViY$fH6{X~ z*K5#nILVaHKt=NT5F)eCepgM_`zwxxx&K2x9<6MrYf{z(iX;U5nKw#y0NDXks8|50BguEC%r>39VNo;(wJ?5qq zk)rs**`ly;&OK${4PEWm(hPHt!GULHFN2hm&fN)m`;rZNlG}%4m_}jKj)@h_F-_{a zDLC0rG^$%77X$$X1r#sR69ex~f0Z39%)Sp6zWKlx7mp52om{_#X#souB2qLorc&*( z2K0xl(3R(8D*-ctzZx$+$!%%)eRe*g12QZEwcH`HNzA&4&r(!l_d55xBOed#_p4~S zcTwS)AAR~F&1CMK8I%5ym+=(!l@Uzv;y~dcEXlMlV(Op+n9#FYBuO0qgM45z#pwhUCHJe*Qib5jC`_o_lfh2QRJ0b78WGW~=}L)N<>nSM zpN=#~Ci8afyz}HBDKjh)aZXSZI%^&g8#s;$s9kQO044pg-%dt1fJ*(n{V(_bWErs| zQ|1nMNI*{P!$SSOj2Utzm|;{8T%5o|^0!dHX#|{v$K3eDv-dwV`0zd1yRJQa-_D_% za%qqc->@OO`#Jpj+1n3fciqYV!`Ey%wD+;WjW-^?@@{wzkiGvl3N?6T#wi`gn#@7tuRpR@Q)g9v)V0H@)tTT?((On9*0SCQ+AkLL*R}YC ztQ4le>1OzdM`Lw}2}G6D8;OCkGUMUG=!*F+C&hi-ce(vj>z|<;b(wMfKX~N}Gd!-^ zW9DQgDgxbu#W{?E)j`0z1#xE>;(y@12j;_!WOU}-F&$VKa%ok_3)-k!X;ngJK)*O$ z;9!9jh+Yz0BAEzU(*fw@rsT$wf-x_j^3}CY1x#Pu|AY0rh)X?g{FFHcii66MqCJOu z*YNAC;5K7-7~6v2ycxY1@zG6X?2+0ZV;xyeC+p zFEDg{O91@ktp=>44RDQ^q{PU5%An!}#;B8cu$?T$?Z&hKiJo{-h~N=RgJ{vV!5rmU zA$?Z;$Sm7~`$Xb*XcNMPMIT&>JkqcTaUL~1E%q1YahpCWw+pd!Az0S9$-r(-l#*Z- z!COIe2tp0ykjj5Rxx#Cq))ZNmam3;jVgeAsCy6G&d|+NDXAG$!G;Q+(kiDOD%q4|D ze5_tR4Y}01oiG_dYK2x~hDd3dl!xddn1&)sUw~MUL?MlLW=`(^?zdjMh$^pd%#1bM zRtr&9;m5KTeqz5+U{F{Z7QN4|fVGcMK%?Wzw1p5uoS(dv*%|edJyK>Zq$mg%BpC2C zF{kN$MON40jas*)?)W}js$1)8JUof65nHU6Z?Zqy-YW%s>(!S!oK8f z*sr#X36}ronhPsWB^alU45ia>LL14E@q;$QoRBjbl5j{ZAZ97YbO$Sb%X{O@ECNDS zwK);@#YiT=pREv}$MvmE3L%}1qjw&~41!Th+*gRw7-2h9uGhoJ`Xb{yr{-_iPZw#z zE_E+GwWDR=u2&8&ta8m6cfM=9N)_wKM27@o5eNAN@djw>9Lx_0+);ZOc>mMp1tHB5 z>rLxNOTS+{)~>N>vomW3?%e*`bmq(xrewyb{E}Iv94^Zn7Ut2u*t;%W(6nSR=^E;0 zT;@H+-g$=CN=hzrprB7_P7GOLVj=wv-R+|EF^)>7Gxk!80_KW=(Z6>6vs0Ej`W+t- zi931V(2X6nj zZIDL8wqxYAeuik>oN?6F-oe#uzWePTYXuIV?m< ztqCJt*BD$w+YgJMa^0pcIBkx!4d>HHFMg)?GOE7Im5+_ z^Q}+rF`dAl3Uz8_@@(N*V&+Q&x{SWxzCiAh6$q*h8II|gUU&_4TxnvWj%utU_Y<6l z-Vhx@bYP=J8K-Q@T)66&RWBA~oGP21IdZmKN+*7g8nM;Mj+MbFld}>ScDxyGTt0`9t7QzP-Jx}LNs|>}!y>vA zlz?pJgc{IiSGc=fgLYAB=VVd?JMP}@^SJ8AFU(w^hWsmRbr>TkdA*X7S9w`b3>ls0 zH9ISg3Y>QqQRM5r^2zcX9G2l%94_HFL$}1Q>V0J*G{o$Z0ua$&Yz0P3ux!HmWEJEj zF+JfA$yC$)i(K1(RrQ)omjLMIkZILch3!&{7eM^<6x~|T!u=Knd|qKQXAErr!-E%S zNWDw-*GwNtpF;9y&d#j`WHncfiRI-h#TRQ8p;m=r1|LfjA<8KN$(&sX>*F{e0jnq| z4Jw@24tlZwFo@Bjh;_FTmYUGc9%w}oaLghX>C)deu>G5l5VAXI!rXzWC@m7V$xzv_ zXmx|M!qk#Ta()C!Jf6<2i2&oApcyJ<+)r_EE%$cf3jn~?=AatZET(O8#PdUnCFV7}6{IodTD zHHGh2YFcIiz8t~tvS!T>#>knn#YU$hGAjnQKXn(aWy+XYK@}V`=XV5G_eO&B*3|+i zx`@@Li1`HsaO5F!9SIc5yNL0TXQ}f~-cWZ!EI_N}Lz$s$#id0(VusZgK5dTNo#aC{ ziRO$gV8vEs%4#kAxdQ_W2X6WK*KlX}{YwYV)_3cl zfUl$pVPXYRGp3w$_&?hY9C?D7?(rYi&Ak&(!jF2t{c4h>4PuaIdt=m!Rv1TATJnnaqJNHFnG^>D9Nm3w_FDn zedvXKhjwq1fQOrQ4_=o6k}G&@HJ>aFEL9y4d%=Io|>iwayqyGka^C;HS2l>=*>PlS4`uzxWWDdUAEsvy0Q@zJiqCLvJ)Knjtr zrM)pn;xIwhQ#ldEpsf#&35j#zzqQ+5SBRj?P+Q%)4qIjfpq!Eu>`Eg)POkcV>$G_P z!y>&gL1V^A(VcZPAloeZ6>s2$Npmf8kff_n{$#;Uh$}reP%b~JpjDJM(~#?Oco;-2 z`mg!*g9x|QM6qDiN`R3m9k}_~+vhV)j`m-n0apQO!|OU2&Qo3Wb+cyAnmv3EDg@ul z0;9^PjQZvMgqN6wgU>!{S`o)3@F24(d;cSYckIpXc$i$QAc$JW9@kcAjp?(|!mO)( z`Xu>Gs9TZ$hN?Ak=|~Oy*J_}G(^eP%+BNDIWot+MXw>IQ_m!kaJz8?ksGCOhmHykP z)uYZGHK}CJsFLcp%37yN4Rx;qtG6K>}*px!Qw#ER|+?K##xxrkhtxDf(oQG5nw zQTN3~$aKhOV0rNchH_j;OW5+~B2V1=5|5Y%g0aJqz!PPq{KU~GjdG87_qN1Z1M+k9 zO-qh;Po%`1!8m2**Y(>^{nK&oDMk2UB=cAx(Ir(dcc|!&Q{4;MOAsLvGsqaSGpr#LC%Nm3)~#FD z-WQ4pBYdjEk3p(EJm%-WJl<4e7{@l%C=1UmyS9J8J&S521w}mA8C*k%RG=l;(uW0S z(#dnqJH|bU&xD7SHzylvq-N|T?@o3vb+6;LT%Sw@WDTyabg_0A;qtIAN-(ur5mMVZMdV` zN=cHBX2T!@<2t^p)WVZXCb%28Td=R2Mih{07ke~yWa@tW_$$lCyU&y}V3rJr1C7w; z#ljeY&qc8erz%eR=buW23s;Vs{y;umN#Zg8w_o_NISNQ! zXyhg8rmH1^nUaoBc-@NAw8;_+Vj-^s7FR?~`wxoD^ECV|k>rDEB;4rsuimUxMlZ)f zN?a`FIeB2{*u|?pcd~nK(YluKx;24w7pz=;epB=62C>BisjkYa+Q&&JSVxnQhSAm$ z?unf-V_ra;3wAhphIkgS?xzzA7dOp2@6o6AN!GDO>Wtm~ne$G){&$bblls8ihx?rV zYFlKA%YXWyT-ZmeN_8fB0?lISSb<~XsGs#-VJ-~fd}HvS5{=!@FHr+Cc76QQW8C%b zzH}(6#j*S${!nz(D|bub`!JtGy8>o$^s-LBG$_tm$Gxy$rHdEs*?F4#eA#81jWnHU z(`km`w2WT<7>VGLMcxG8+*ZB2SN2Ewk+_CO!Y!gsFt&SZBJ9hA`Pj_!CsEN^NMQvM`o zY;*5h`f;CoRqi||YT|uPE_wIgua;Wq?USgyfLT;NKd(u~lHTs#ZVUi{-rlI6bVY^V zyz1AwJ00%q>=Q^Z__}a-QsIGb$Y9&sNp;9DU?qvgC?}SwSfXUxljrMok#skoCmQl& zQV1stpXnPXO_ia8@h%~L`KW(E^YVoaOHMd`;-gaMGOEz)Y2_GK0!}_XoE!>>Dt%~% zPkOZ23Fv}}c(gCDphKuY!pYT*OI9~ubl%aMKNyvaFeVh$f;o@08`%_cLtcbHPiI@+aEF$gFhIv*&tO>9o`>O{Sx} zod#-6=?&3n>5qT)j1I~m8yJ7%0b`XsbJB_p(nZ7shVh)tsN(G~pMLa@_Wx8b;TR1o zN#0cXJ@)x`8c%Qs+{~P05{b(UXx5I79IZ1$B+C8y9HhreIo5hj1Xy^>U0G5tqvqvgMpA_Gxq#5p9^v3h@ z<=EYlNCk3|LH)F%s~$X8nmqFj1B66XaW8w-_3edMALpJblM&KLryP;x>{BYG2Ll!e zk>fa=49o)*8^)TQ5=ZKoqZbwE8wLrG3y0gBm*azoL(JbDDR}B@&uQt&&H*hd*3iHz zR*xUtccBa#%((6Aqyz(BZR zrTiiPugzW<9zqBUfe_~C(zic6UPe@=P@#}Clc}r~c}h1uu^Z7C%F&Hgr> zw#+()>n_~r%y2S*gB^$`d>TxI`vy>hddJm?ff+m1X|Ay#|IRx>m>-vXw6GWFUtmez z*m;SrMSjB!oR*3JL>a^Rg)0yr7=nk5k|75&i8LA9{A%sSX@Y8DEZ^3;@xl%}+($%k zNw#n#RmtC5H~G6NZ~yc!y}mUC=6+IrrxMf*RQ4#C$NZX^Cx1O)HX7a=jXf-E!HOs& z=ceEaHNJAirSQKjb$awHl^gIlC%Qv^^8Qgd+zN^5{O`nC_EqmaG z>{EAT?|3qB=!LC^wmy)(`Rc))4;Zq7cjeeim`qEEoF1;HCT&DGg0dj>Zh z$Zoks9&YdV>kdA98wDPE=t&NlLT0yKcksE5xy>z`hiz_sFuUtE=jh;52M!&0FneQWXrtWR zdQIJV`1WTG?t8}BdFX|^hW1$>FuUQJ?8Z-1fb7=wLl12>rIj%RDyX9H) zb--lfww9e+?mx6!Yfme4(Td!?pT2i!-xg<+_mgH}7l%*JopPJkU!C3ZY4-4~oSo*z z&Sv(yec2}-bT$t^unV}lvk50#_Ic>#hP|${$wwR7_=vMgf4jsPzYJK|$vQKOsy8YRM`}cEN_PXZ|U$uE? z*LDiS3E8b1I0scAedx36p`K$yI?5f7o6mjP;I>;2J(%I@>{Xx1?s*`4)gI0e-aoqD z!58is%_kH`mu=O~lfINDE3oinV}gF%gr@D9&nN z_(>q#?(o*v=d&K#*Bd;t4aEb7gofV@E?gj}H93X2pzu!Qb2@~SB)#H9pAf(eqg)#6gpYO^G?exXVwoRJTLK@b@0m@mC}{X%fOQ_jp7t|kxKh2$8S1E*p%(ksSJxG9qD z!?(o1V$OPN=M9f2t$>f(8>TbviIuPF4pHWt*D6&)THmZMA+o(=R`0IfQr%O1N%h?7 zYR_*xKlD83xzlrnr`>ao$LH}Bw^jYF>R+qAQ1wXh6;<1+)>W;ps;e4bkS?gLDlWdD z^3BTc72i|&Lgj;%H&zZ5e5tUca&=L6<++vhmE*w={HE}ZikB<)R@_ywz9La^NyWSh zUqwavPs_hn{^|0~<(-9}DPLN2X3^B5W8A-Y|BL&xg$ef#cb~i6y~I7k?R9$!e^a=u z@F#`;RQTn>y9(|o+)x-VTv>Ql!R~^qsH!QhDeeo3Xu%}=o;3?g5j0BYzlZ= zqWbv-YEy%048(zD<$>H zx~L_xPnNFx-d-y)i!?TJ&P!s2h(jPxOQ(lhO!sQeIidO!`M@ETjCJqvx zF$GT-J1xaZJc@-ge?DiddlqkFj^k;e^$^xn1NYCEUwg!L7{_Xh0p5b6Qk4W=MY~h0 z8HE_1%H~a6FLqF)thJFSo-+i(DyxNyFHM>c(Zvvo9Us0D2_+H#O=H~;q~#FniY6C} zi21RtyRNrz!j7}`9cIS~$PC11$1B%=XrA6=8W!@-aA27vrfK!VnU*U2&F6k057(9| z=vrZbFzvQGGWnc~rw81ZNpr)L8E6P$jF-qUEghyGV_s1sccX+r7ESxZD-+!dxEbyh z5Q)N1p0hAhbD>zc;Pbt2Nm*#nD4pFge4cR1X+;$5Tkx88FK~Jh+`xp1V)MD^lnd{B zU7D&Gv4oz3z(Q%n_Ikl?&*!z7;bx+Ziyw*SvwFhLNA)rZu{RFlfOayo?)rJjGtyvU z+^3bssSDTJ*Kg1gXjnc$009E(qOr>F+;Wq?R|LFl!cRtwbe}%RczJcZ_VqJ2i`h38 zZ598PEC6y(`lkzSk=7H#e8)W85eb;o0yZ7=iJ!RqT5%2LdOwkM=OmK;c_jReF{!y8DN@5aUoR$XH6<{6nz2x*M z_*mh~dQXxKY3x)KFCZ@~m`+YPb^YXz=uj-Y#-U_4qb!{)t`eP-qOPQ_?!Hsqi`=mm zx(>5IA`n?YiR=80e5Ryd7@%R;KjE1tw5bOBS|nuzHpx~Q)7hH($ICw;ZW&q{(}~1W z1e!2+u3p}_WJyWn%O$2u$*~wRjxZKB~=uE&96*0%pZM?u8d?+?VpMsT6o}0!ji+cK`5O4qi~&RGLsh zraV8t@++olFvnuHwnj`P?{t6h3$K`GBs(P)_vj=l8eG2X=J)G{?9L(uS6ga;EY6|3FfQTb8t6~PhNMbnf@d%T3DPt z67<08G1tu6q=oEL_9f}DVo1L*0XBZ>tUr#{y6I&}ebH&Z zH?uttf~8MpCPo5cP5G8$ODB#loSgOMU|8FBH=_Y^hJ;Os1L|Y5Z_@tUO{`*)kZnGB z!=h6^+u8j=cXLrJ9U`11AQ4u5j!2HYVpImds0=0LkG`$NBQ_(LT!#<2GosGI);_=S z8GiW0*9x?rP+Af#aLZE_%ok-}|C)aH%lj^8WNs5VXC9Wk;_8#Xeyn?nJJ!|X7bhBp zV)dgti*#pV9r?$D0pRA`VNCE1txjvFG7OM1uC>Fx{pr)SI?WNOP8~6%y-gYS0rhOh z0D?qt;1aV>W)*hH;<)`6>Ik0*C%dIcgvUi@bicFK+!KcPAg`Lx<)p9g`>>Ai z;&+sBk`{iv2Ds=o7>F3L_`^c8Kn;@n}dr_RK`FFb>jt4f|Xvk52?IyFB3ShRQQ z^!Aqpp@2*q!-U#PG3>R{U*4)^VS-Mum*0ZtKY5S%_GKd$?2RM?kR51B6LCr-gJ5>- zBb-P={ZfS;pLs)z!Up6ojNW)HgR+{cCMrg242>ow)oqq#*A~sy<`jwcbf-kni0N8Y z*RQ^)RX{99fGH3484kqO?sU=8S0ma+?6E_%Ciq>%yIJ`K|2yVpOlK*XcTG%F;ipFX z%(I>StzaCAj{e9a&eX1_tAc7r$<@z4exfrun!}944Bye>-+k*X!5`$NTU<*rBDDG$ zcH-gasdb+UISuXao z{+u-}!`o<6vw!~5=Ca1n8N)@mrp3bxf9#c4d}H{P=QS+9a8dK5;`PjCEAuL8K1vGn zs75>kldA52!gzVb3G2t}A9t&1O zAsCW!I^UC%+nHC=%%nGd{F4)auZ)?TaqF7}K_e1&kpF8%GKf-ah(m-@LhO@wbKRSngGNJxs=#SAvbZ-txyCX zfA#W1i3V1^@ss|!)vl>i&tKnqf~1|wOgsW_eFPl)h|MF>*$}oF2jbz|wvZ7od*EjB zn=J;x-H2}J&2HFp_}*IwZ`wC>^8;`0hlpj`;mnP=;K4Ppf9UpI=7hud?9J}oqPfnV znmKJP?oLf=_TbPybq7CmABPS-boJqTx8cks-ClO%ErWM$W#^1(2M_GX?!SBR$~%Yl zJ)PaXAMdfmo@=B!(a5Ze6O4sXpjla%uyz^)3?b{Vy5S8KH}u90c)~e+aNqTVPuy>u z=Ehfh@Y&7Tt2ZBdVb7uK9wV>cTRU%hK>oagcRnz5$F=;Is}9_KaQ|(9DTc0p5)b;J zhcbhihX;4urstDau^HHW)b-2yX(zjWo%?oznuT7l15P+d4WUL=Rt8O+D=x@9r=!pQ zP(HX`aMam7Kvqq5*vemQed;LM;kc=pDaxnp2}a>z0{$((Yw$CgB~V>iY49c`OeExk zDOR-JXdXqBv^vwQq=QK%n))LDnc)x-M;&d0Gz@59-CZ?a;euQDSaEZydAC~9o(%+l ze*1S})(DJiSU*KkJpIM>)BN*;eJTIaHp{5dAn4bS z0EqE6iiyTbBSzRjm!!v(rPr`vIBbYQ>i9l)kSG`F?DASXE5x&Eyh`kmyEnE>=ux@81RpQIYGv;T7T^v zDox5P#tPFIawLrqU#W_ud&H^ZBbznJe8~F{HC&xm0$;de6P?{cs9+gJeX*zj*2He) zi+9Jm;L`;g9<;L&Tg|S%zvb{7KWeNNhM@jp&Ct|8C#TXo)&pln{D77{V41UKC@ymx zWzqmqZoMmTS4k7(!FHYi#z$p7;hW=ZR;qhBL!*Eq-7%l{9Fv&XIEuq9WX803oo#9} zd^vm@CjoT9k~POoE^KjXQ!`&u4Lk^@Msetwh1@(`7sfNFW%4QI7>;TfDndf9s0-_u zog7&@G5kl8_9yq+;P_zIksrU92&_lhx3!ig4nnBQKLV9EAb`}_suF@LCoJ-Z4 zS(I=utuw9nBT>v=f_iF9+ijcUoz>=4HaTW68u4dP2tsONYJON5Z6fqMRJ4ga1n!)N zqQ;X7VUbu3>pboum#5(CuIeqG&sY7a%I}_5@of3wlA-c7WmgyfbHUe3zxTf+_>4T} zzgYwQAKP^EAC870@3_gCSt?%*OjwA~OSrnG6PYG|0ec7et#>KurcDTCk#y4e(u^rF zZ{7Sr{rrHH_CgH!-gYumSk$=Sj>M%4S)PnP>R3pC9L~*uE6*+V7m^I17A}^V{<=AJ zAqX@XAFL%qfMA7u^&$S7Bi7PBqG!vGa0}UNV?NzsT`TJx(|wX$g*KkH{Nx@?I01l3Fl_v1RpO0+hXUV$yQ|?xiKbdB*rN2}NOh7W5`$fCj;n zpM%xetJ5Rn!5Rcw8NQ){PKN2c)Sisi%q>wf1tOr!nC5k^H1x4TYTQE*kO;0a?@CIq z6bYQnbfl9Z_mNPfUfiHq*yS^lTgQJ?#SqAyO2 zv(I{}y|j1W_4MHq=!VvcaYy8>KAh{Vos!5vi)%a9eO(=0@;58fzVRLoS$d+`BJ+_k~3 zo7&mD9g;9wvY>>6N~ozh`Q%5iAGiZGNuRD$VUA|%oFF?>nlapBoF$}5+2O-hB5^%P z5Fp&4zD+w`UkbtX*oMpkm!?ay`3iG`o{%l}wYFLd2M3Tl;jNup?-fqvVXC-wAFmbL z?T17x?9Eqv5K@wW!Gw*E@}c=42AOdVBLkJkZ+oi7wV`|TOV#AHkex?3=okn7_DmLT@1y`)07No#}K88cSRZDzQii@QASY4o?T8xD(U|fh*94e)H2h zgrD*C7mU?WbP*P{G%WC`^fFnIaycsj=a^kw6&EENnV!Q{8hR$m_PMCq*nK*JMK>1o zJEg1_PIX)L0|LjE4Y@?f(<@BM)2?=3S39W{eD))qmArkz0Ej+mUPTfyWX|6B>wouD zyH1$k&lIaHml@p>gp|VkNEWi(8kjQ~`5ZYa^%|Z%b7g^}a7>#e zuoP)37;!j5&4bopY?92#=XbdZivJ!&y8mCxSW)<{tF7>?Vi5UXu714w*6Qx+Rn@iC zm7cdeU+_HTxz&^Me9W`NGs9B`kKgC2HdV!|<`>;kHL0qq@{P*xS3X;LN9Eee6_saJ zo>o~^@rQ~-6)#onEgUQi!QXdnMGxrv*%c=i#VU%+e^q!z;e+KbmG3U!P#!Knue`Q= zTzP5PVA?$%>LQOHM0(xA+&u-znZ-d{1$A@x{Er{Gw-zrxqVo^mfsA3pN*gq99z* zR50T|sk#po9$WC2g2M&hDcDhipt*$_(_tN2$D9SE2Fk1zDX?!`UZNfR;8~H9-;D$6H0`4CWX$Q7IrA? zc}GwD#oGesOLa*E1-#%mSD>R2=7QFqf|u|7q=2tfCIS8pu{5B(yen1XZQ|xNvT(`PpB55 z4pEzWf+1*pl(G!j4~qP=|JYO^U@IWz0(UhFKVoz|RlR=pPX*;IOmq@J#Uz~yh{fI< z?Vb3zpw^B!8k7tlsqV<=`#-KAE;%f7Y?xbHYt^S;@GAkWu>1Op58cGi7KAS2HveRb0JKcth7s?k*V$}8X4@YW{)||kzh-*B zyeN!pt*SY3nziP7^~$3KkDKa?wI$+x9MEOSpU*qdUCZ8R-L#f?(o`-%NU3;rSg@R_ z-oD_v2z*ZKB9m`eHbMBD&>K^z9KyC1SQdn2Ef^xIE{fKReYbi8-)Ws=mHz zj*cd{hXkwJf)8%ehaYJ$_ldXB?gFq}V|!41Y}pOh{Z_wBcL+wi1=no&O!05@b?I3wce58U{}KMPVj zmF^>dh$w8NeG?%p@SZ*HU%d4jp+o{>i>si6){hJb<gqe?&$~WhN)^=o{sdWA z^f|Gbrhhrfx)*M=WJvC%D+OOYDJ|Gl3Bv3Ppor0Eg}pnREco0Fmq>?8C0nSH<>3^( z=>Dc46%Xghy$-cs4g7qg6K8RGwS9Q9BZY?@>n(#+)h^RrE6K~j7 z!RZTzPIEW8E#flka%x?vIdcYv(brrs!QJ@RPLr^fj~)91eN}SK&tJ^-K;GN>pwwI& zH({&p1EC2z40N^C)(3hZ(?}Mac>ZO%U9AbsH?9}{b%OwLbz}~~wip&7s@&FzAKwtx z5!#rL9D_`*4-}kp`M33Z;1(4UFy`v^08LtkgGqP$Zqi04DS4%B$+GmD6MOUV=?lHsJAADgGcnNmjr2R9s&HIYQwz#Te}NT6Mh+(cK7yXwSwW;dT!L$EZRfa}G( z=Sa*Y8!Z7^K{UGb?QQzboeX#Bz?xvF*L6)`xwfrHALaGOLAL}_x<2}&f7z=yjU>B? z(|{fnNKJRj_3hsT&2FLv$W9j%zUlE_c@6h{6Tvt@N*cCI%RA#E@##z-F^0Hrd%--Mk4Z1BsMd&`PT97 zv)v|Dy_qa!zYIY-H@01Sqc-hGjE_R3499e(bi?5JC%ez&!gj#!(h!<;&EH`0pWWRh zA+bcqbjIuJ0|I;pam27q!Cn$r7RS-!su_%0riDWGvWU^rKOW;gIeV;|IzkHtHytyhI4YlK4HZyAvA*-cd-v4?1_UsB1 zmM1Mz#aSX2oM4|1o%!YS?MH;dgfq%5Wu@j+%)9xsoeKk`%5^~zKs&SgEG3^I?i z92Pdz!tB8N65NTD{o!w(eU*;69l=mnYPvrh>z2EOmNieGP*Zo&B=`Al*#s>>EzzfP z>wb}?*q{2tnE&W3%MTTM?(5^+bN|}C&bss>?I5|k2by#17x|Ygbl=@Ua5h>1yIbR; zG;~K!dG+pNw12jf@Xi)D+C?1kc_*GDp}%6+VT6$1`h63%f2h$)WIHR|Mpg?e*ZdsK zzw-aOF#NYs0Kxy4l{LAl?<;Gn_Ic)4Ew7wZaYp%wVdQV)^Nf^Yqy|Q6V59~{YG9-W z{`PAiQ=56=(7u%(*V&UV%~XOxE1%%Oj^uA3D;$v9#Yo8{1#O77qXz@oLeTDrkW&m~ z^RSJ;U%h6b#HzyXBirDG82Oyq&KVVK<56ES!QQ z##zc^?R~P2`P)W)Dk=`W$Uqr#E=N3G-I$K71@<&i+-$TAF~X2*aueVCBzeg5uKO%` z-(LD_r{z{_Wwkq!31wb<`}Imd8{^N+Oyy(adaN{{G&@SfVJLA{j-V<5=Ws5`#D%== zx?2Xf+yKR)1Rt2ahQo~mLpNV{_)bU}`@N@IhgRe=(Ro)S>;RT2;t4e0chzl0ug-Al ze3mH|6vKj~zzo$1e7iM>HP4q0#?aq@5p{wi(i|8Dg$!c?Q2Deu(0^Ly#b0g>gWaBd zX6ECNlJTmGa`uqi8lPy`MxN26S&>0n+IQ5j%icbTwOPwTr?=TDYd!R%{rp#If>gb zJz8`^K($+web^R2ZOhHEl_~0L7%Easr;x8Eh#jXM9b`tPGxOq)Up|Eprb+dg2@1`~ zG&TlP!KKZMTEnY|K?tt^(2>6$Fo(@ADH!dPxIRU`(8MJ>2^&$e%}df*YR{RWVg&=%XQm$v~|_O3P_?$$5#!2 zAlin5o$3xrywI9dCYbr$EuT&C#)Om<==hPLr{QA?!dt7{1NIV9DB0bJ(HA-*P_;=x z%s~s|@V9wSM~gNbZc_}j#11r_N!y7*~tEgkv^$z`G=zhP|v)LkSFe$4lPj9IVLoh`adG<<1+`%f)#Os{g{w z^S{{nfoj)rV4Cbo8N#e1gV$bIG83BlcTX>+BUT|i@xM19`ore>daS$C>KF&)hcnh>l61}V8;Di6ox+WxVl?r>di z-T(&lnab;L5hV-!3dSdE;D#E4n-51QN2Cb(AxsumVt(=ct&Bzt2KoE=Fub&{i!zGA z(s@J5}BJHzuJdJH{tBeQldvtb4rnL{apx5ekI#`zqP>dQ=pee zTU*fFH1$Fv>Ze|a*L&)MsDeuU%U~#-niKqn_TK_^H&N{5%pQFF`Rvx~vs<1V+I0^k z-!PF6?c4e0{tely_GfoJH}nv!+Yb*tw)@Tf8@cV0n(9xu5x1skSno_Nn#7q3auuB# zftHL>z)a(jAR3;50zsXcCsg|IP@0(k%|cT?|Kt3b%QbM$6AkwwutfzSA_xe3 ziK|ZcFE$HHk<01c1ljhcy%Np>uGT?V8)Q6AD}y2r_qKv`rl15!L?d=$3op?#c&9YP zGmV~y4j_I-3q77EYhH3k*@p{1VBQ^ShWdVr|CQ|*p_`7Y?~iG;N9F{SA)M_bp-L@G zBzqPn`l9wSkUK);^mf_8l;T?dYUSJGoj0$8dQVEErS@sMAT18sirpkvn@eiqHEI zf%fYM$TMsFsBG0lyWQ7M(+5Bo>9b2pWkje7Z8jq66Y!YE$xRX7SG@?Kyb4vGF98;T z$ZJtC(CMwETtyqy2qjZcAybe7+fZr{>TnE--&sN&P>e0tyRk_fQBOW=Ti=cieWX}i z;zV-xw`R8A{5y2Kz{G_aze|Rmfl_r|V>gW9@>S}FXm=;6UW2Lb){eZ@zk zmD!157tR5oRQ89UdL+Lr*!BqUR&y4C^LCWC(f*`Wgp>WB%52}XeOa{*cS|+aYM?R1 zH|*<+o4FdmH})3TRX4YPyEq)e_p$CKy+^5qqWBj9|IrVTlzx2D!#%Rg}sf< z%{gt`s`I{P1q8b8`j?VRW;yjdJExR8YffC8vxk^M26q1PU~{_ZjK6#meLFFvVhgn+ zEQW0nSqQQuO>A&TLr3JE2At^3AI^$jXOg^G1qxxy%*tBsv4&aL$HGLOVPA|8-zURQ zJX4st`RUtcR=NC>76y+IgQ91~Gz-^|nlVjCDln0UuxewwPW9!l2aY^(2E(}6h%^+) zzK0hYSq2`V8)0;!&QJ56*UZv&m+!PGOq}-AY5f1quCnIpe$P*<-l>XL{;A^WvN!%` zB#M!{jMTtL4UE*lNDYkCz(@`JgVaFgnAacP)m!D7F|jG7{7W9kAMTf|25q>&5=5Db zQ<&HVQr_fNpZ+qqL491-3*inM9pNL4K*6hjB>0#6`{j}hrt960I#W&+s1$bMB@ zKQJ%*>AkC~Mdli)G(>9`bH_wi3PSX7vX#(Rq%Me(0DudJLH@cFY}kq54B(&eo)?aH z@?7-dRk(|qmoLC)Bve?aU05N!2I9;<)n7Qv#0C2Z?hHM z3UKoUh;K_0x@cI38HOtxmvP@EJZ%94W#D>M0T&0G&wHV94qLiLjdy;yXK7aoT^*TI z0$98kbW6Ah-YZj}g0tT^@2U@K4b@J=m=Lsz7hH=@sgE{Isyt6g)B zYs|ExE0#~_KMq8IFa)F)qwF<0lob~(T3IK5f%iWQ3(^Kh*F#H)_Dw)-wDm>X2tw|p z6(*Os*S^gJZ@@dadrS8IM+R?ydhnJfAuq)2HFRLx(7+Riw?FKix57RZaz{yv#|gdv zRPyt@nkMq3&POW?)(IaOu_%&ERY?w7uuz!i`EslF(UkA<+)kf;p0%`35`E9`C0Mm$ z?;uP9{T_d}rbdrtpL}ypmFt*e#;#H~{Q^t@=D);Z8mProyKOfpy3oG(dB;+K>kck1zWw}-;R+|#$l zTx5H`Gpi5&VBli-vc^o!lu{TtVV08doj6v!-Qh5HAM*=_!|a@|J8i>NDhzFljy1Lt zo5%D9?FOHKN(!MHll>uOak|BZD*sS0+9}#hApa3^|2SlEKDZE$GzXYc_{chg8&~fp ztRB|w{pR+z9UxET?~x=Rsm!>8`|rNg<2vDliJ22Lo8D588Yz-w**^hYZGdP*2rB(T z#t`zdkMN7MQVLqY5?CsfzDmI;kRqyvuf!xaX{5oLeB)n>e_wQ?^NPy4ipeO62}2%W zOt9wGvYtx!_rCsOc0S;V@&3&D+71WKHfbNsFKHm06)PV^lV*i@e@+h{G<&%;#a@#F z+2NT`LWZ(FNE-F>Q6DW@V!uW0OvolNGUVNu1c0ed_R5lU7w~|9 z1_{Sn#OO$zibGS8+eJVPBwnzK^yQr=@EMpU#f0WyQsSw#KClX+|J>K3U&o=)5>s{4Do~2c;FL{ zm66vRsezFi7^#7g8W^d8ksA0LsDXi+{(t%A=T}v`#!sHxU#kpI1C#MFik66j%U%&y zjbR%o3f4J|_KNMC?-5VC;;!RfWGc|MtzPk!Axv4~yN$sYcPuV?ahrs?lS;QLx{i>> zLFtC{1^cqx3|j%I$52{rHeB?#VH9jC8aFKRHpt^r;=TQNw|lYccDye2CZ9r{(C9#} zt!IiU!rUIXdRMEVuF6H$3woc(0a52$!{iIidlBETElZ=$+ey%Y;z9)xYM2FtGQcO6 z10mwf=h$}kS!BD;HrgMRC@>4>x>TmH|HnW3dMT`36a3wyM3Jh6x+L9$%af4pKI${K zM#5f>3~ty4=ws-n&yuddYZgQ3(KkGC=)i6h0+8Lc@z8x&WjB8&yJh#m122$mzVV6| ztH--gEUY@54p(@OPjAOBtLdKp3;KUF)IS^7=J-G+fq|!RLVp9fv5E2Tuol6HvqBnH zJ+jg)2q6(=%qSH?s<(2HstKxSNt=+i09rdP>te#NG&{6ExBxF8(M5gD46HL^%<@jh zLW55ioP&TE@a58Ay~LhFVAN-+g04f$k9oRCViaf-wQwYB<5X90N-mR-a(^1&FkX0S z94xl})~jon+o2T%*{Q$vsv@8!NN|U=>!=!8f)U^4l#8#fkbqVLkI`^*xobX-U4Scs zvnM$&@C#o$ox)k^vuY?ayZ=W&{oQDI|0c}HjM91yFQo=FD_YFSc9~nt<-Jst2z{nU zl3>D$S}ZgL*9&*s*vg1vW348I19Sw!0z-2Zg|z1aKhsl!Oc&!YR7S5GjyMB-pyN zS-Zeuu_T+Z1`{MYInXaj#3>O#V0YvTtiT>)0-FjRC^-2B*Uy~ltkq&sTT(blu57L9 zTjj#Os5VJ64Tgb&X;hgN;U)V2XxA5A)jz2|*|XJCUNyF|vZAi+Po+@K=h^ziP)V&rPS#`RDd8GM2o{K!E|-GQZL-OeXpxw*zK}U|6uJ z#mJ3roA&_05h{}IFu_;SJvddSL9fk6D(N+36tu5k;No(Nn6j=018|=pY2OBLSDpP=Fj`K#T~H#v!B8$GiU7h+H+M{5GU0p zOlZ?s6+!gJZ?KN}>zL>eP@LW&BLV(_Zn8A>U^2FHrrKu499(i?BL2DJ(QfMnWlFSc z&46bZ(2odNGmph$W*g1)q2eK6c#!xOna<2f{jMEf+p`FnU_xzXfm%Q_i^wxVC}7vT z?qodOO!Q17w-&IDSqsEIFl{NyjtFQBY5*cbkfe)1YA2owa$0-1mr+{5JzlcRGXf(q z!d$FFtGcH!h%*|{zhww_z5Si?+gx{<@2RgVsXdH&-FoG`4UO^?i@Ldi1TuCptvf|? ztbtMtpH+6B8T&9Cws!Ov?s)EVQ9z=T=4Qqz#x=7lieQU_1qj|kO-a;G$y zAUVb+v02MY5to<)sDfF<3fhXtOFp;&^5{^8b;ji8V)77tQeKXT?z%~@7Z%tR0Jp5% z*Efu?29vAil@V4rgDGT1vOzd;uX#oAJ7~!Kop!mp)&?a%Or~tdr@z#rEZmI>_JpIZ zI|Mo}$*i9H3+P^S3CO8TNH4jW<(pdkT`JU@0r`TB#VZy&Z!r_eK(0ZKYmux`NmhoJ zSnrG-Uv8Hw?z(=#m}sFm9Z6(8w+;PgLPd5 zsw09W^HpIf5Lcmi;35{+InF|5BNvac^6kmO3gWeDBQFM^f7*^49y4z|G_e`gH}M|-g3YwjkeB4-bO+z}OJl@S23^+q{qP`kXj zJ$>J-IGrEUKu@r>&p~tBf&ad~?iN(tzD)Cu>pndJWpK)w{iPZP?7^%MrL0lt?5q_L zG4a{wZv~vqQ*<0N`UI7MQfvYMyf*>YBw8i;GDl%J2=6XZH{P?&X6P((d*>ck}2{^kz5j-UE4ar))DTV8D%7=X2AF9L?{tIatWEKIiU5BZ&~oRM-we#_Yg4M-%bJoa--QIy3Sc&*f6eWbh}embvj~5KQeFPv$wmt88ehc(AP%gF%mIe z*hMkpsXW0BJ+LKTD-r>DKx6_eEli-9f?MA>SU_<5)HA~-2q*;FB<)|5vZH@Qj^YtV zYzFMp8ob&HkFXgyDI4-agUl3S#D|M(Dr(((?KiERlPSA(=y$~!E5=TYX(L>ncPtUjaopzBHJL9;Ce1nV?u6ech} z6yF{yL0b}8Sgdqa}!FyLom}wm&ZOm|b6V;R!E_M_EFWiGrpCpn7 zH1O}0o_PfqcZUmKx*hDnE{swZpU%UOLH=o_&oxfn&@&%0zD;CUWM7oiFj7jBv zGH)Gubv?#qZ|-JYw0evv!BEY4-6&VcX-lB4k(jd3x9d)GN$L}w6l-S!^T?$rL~2AI z)g)oV00wK!O_ZKgZX)uV#Ig*J3kf6mL`8>GHP6=R-aj-%^#AhWRj!gP#cvd^^8B&t zxyo=wSJ@9s@BJ&v_Q>v$8W^d8ks27Of&ZumGNayk{d;lL=P?tZ%El3Osl*%0-@=p;bDzp5AJi&G5*T&B%{2Df3FuT+Jb+syF z7#k7Fxh*c*H}iGGk{a%u8TW_RuXRzVDfKJ0P}qNBy%^k~|4${_(vtjX_*Otp_)y?1 zroLDyE;Fmkt+(3o~4#w@2+y7qpeq^9r|yKuGj;0D79LKr0vr~LH#lrq^60p*9K z3cju1^M}v>Y&_`;PY5(y2yNlYp(@1wbIJ7hx^DepzIOIDEMDwgw8Xn~$pY`WW-pvz ziKzD+W@6caP-sl3uAf~ureeBNvRb6lNE~-j-o4cRl)v4YZqtedl^IH;{22JSqp4~+ z4dL24+mclrb;_KHTqT@ULM7eR(I?WTbqhoktQACj+uOa)YRV_;P72@tX@B1GDDmAi zrT#I-0=qUAgv&mWW{Nkzh~M3jnk5TXc$*q?8RT2-<(bfuZGwtQ++I0xiHM{^V$q2bJ%?HE2 zC8^_xI>uKd2+&l3=bQYAix)SMYrWaH>rxUgK9z*ACLOyCFD^KB+vG2}-p#A}$aFic zxkag-XvMN(y@s7^GPeWQG2U<;i+r7tuusnJ(mG+y-$Q=2P#b`2YB_q`ro-T*hB%oG?_#p@_TqPC-im1oZCyAo7yv~B4YW{?)ED{nG&Dj_S53D74^ z+l-B3hDxb2cHwpIOWfx;g`eK46qL@BDJ|NGVW~9WW@rP=XaR(ogLwuzyM6kiDJLDA z<@;6Ng#2h)I|3Q>RTOkU>GL}^5&tr7Vy5D8QN~c|FX>eOhyM7=)5X=UiBlWPcGNB0B^ zAY*~)Ec*Q!(6z&Z&Lz>r1TW+mc*jUBYrW~fr~df-4Y*Cm zO_|#-HvYm1>qjkAg4J}`UM^5Bli^zddo$nrDJ4sWNuVIUGO~w@Asz5Q8bbidj|9&l zwSs*asYgf3c8ki@uFy~f_ZA^0WPCCc3Vi^vNKM6j3=~@7Nu+z}&8(}9(YlQ%;MT?* zBE)%?Z;>~KZ&Ng3C1l~Kbb;U?3O zro}6-s&lcuVtE}c06)%|h%l+>T@9vR%cU+HCNhPIi?QR=BFBSDaP;a>=_T+ls&C{%v7J zLAOg2hTF^kNZ38{Sa9ImTi#k!<(e>Y&J13uZd5aaaX!AbVA5K&MFB%>=T+3 zD;kC~N33WzL)1rCR&6Lamv4tqLC2`id}!dM@2?NwnjSkj(@p~}m@ucUBis{P2bzTY zTZ@41JLkwkj%*)M0|TLf@4U5dB_h-K`hj*MGPPrsN@~9D7K~SfWxIi(8c8G{Uks`;ziKuZ)9FpBGzQOAM31Qu+Yyd- z$@DFuVYDAPU-SHFifJ8b$;K)PW>|E$Fz7L>n%S6VvFU{ZhqShR4B1iyQo^YNzpV{Q zT?(b)fQBx9{89lhxok+`mzp+E+2MipX9B`V0kp!6m> zUI6VRw>VU{koZ`^lT!cY1aW{Ft!rTUz)L^>(goG78RHfX9H&xP|Hn<7*J=h3vht-? zOLD7JFx4I=q;!qFw9?cKKWTxH2NAWbP%-NkMzU(8XD?i~0_x^vEBrHRKt`ICdZZCX zxZWu&HT8}6^rbMFdZ*XS#Iq<~g0xw;j>^fL3%*jQPEN0{sg*!mpSPYQuwb9K%i=|H zJelb9o<6;%&gY%Z=CW{-dAe7Eu1up4*Q68j32H<9MPM>r+ipFv{21qR0#cK&qv9~a zEfB0eriV0O1oAvN#6QSH-T0ZzLgtCEU^FQn3!~CAeQ+lMfX)E3fxVWe0XDN|U^{wG zKBbtqIhU_x7uZyz-;Rux(cM(A1PtmEGsTrwU)Mzq*_O&LQ70n4Lhs2>Mr`cF6_K7&4 z?LN;FU3lW8GroMnZ|y0he%e*b2mblRZyk$=U~KJ%Mm0EOVk;!8-0~o)=NNPNKh0VY zL~I{;?TM`XxvuT`A zTj3toEUKv*yo$TM*=?J$8y-9O!aakT-G^Sd=Fouy(4!wba5v~_F97pH8?GK4xN7M3 zZG+c8l-=~m;KpkYK6^bO$V2_l4Lx!D;VU<0_dJljY7fE3hcdTiw{FPZe_QrBAk|Nr zbG%c%@^J5=UH2Zo^(yW>c+>8|>xg9j%;B4M9ooHl=+n<d$51#g?)#1Z33Z5`Tm<>00V1lE21!xU`rj=fU!=XRQB3|>#c z9`H6bF7poF^~~U1>p6Jnv)6KSeXnaC9a{g)q1{g$dicuh?&l8ezSG?E^(!;H@8J5K zgHPX;-My2SJ@m}A*-bATzIXlLXRjT4Vk4zA_qPJ?z4B5wKRLL0*WewGXYY72aB%N7 zNCXdE^C<7h$%k*hdT_(m?DM;*;=tki?mGC~gMq_0Y~+7n1=bnbek^dfe;1haKs-%? zG07l_jwU^Yrwu-P_u15~nHmx5D{ zhY7jYAsq~;FGdb+{%lL09+U#uqk0njx3)4`ImRdkr4%}n2vIC#iuqp>!3Gio-`erz zG1ad6NlpF7sm!o`tey5PZ(L-W7ZE<%M}D{bbtf}k-nsY=kI>w0;x>~<&c{TY5TZc7 z1DIEF95bu4t-U=jwj+_0op}Z<)61ZT#N%wV;zl#`G%|z;yMBW`#9JTn`e!RY4tt{aC!o=f+3at52SnkpXB1}*$fwRJ)+_tor!Jj+ z8q05=ARFA#%D9Ipq4bIpv;3Yp%9PQF9%z&rudFY_+13{N3G)F#9!KubHkxT2_~s8b ze9WWi5N7BgeTA9$D1M|}!GvUQl$mhYMj&_65h5d_hWUCb8)6b8@2pCHit!NfZ=sp@ z4f4`}2@(cfX8VHk#`%J4w$)uywbA)-v&?jEzoZZp*z>d$Qc)4o-3*L7ujjd3g|8hU zCXZ~6)WH8kH6Z@~O7~8e=ccMVDu3wSS@GTKx#i7e8%uv*w72-r1*f=Xc}hxNsd${z z`uj3_-?@7%2BXPy{vWD=kyjb1fxp=r=sz~I=ZSk-Jg!-@TKh*UMs;CRFxX&E&<#nl zPT^Nd{nvK}-uKkkuiP|v>-B@%9~ivlxkDRvzJBGk*(bLcodOveaw869pM4ghZghjg zH@|Rb^G4K)PvGamfhrRxBC%R{1yL=|N2OE3h9X84=5MEr z$K{6$*D-CA+GZAq3r!4Ua_4NNg_mqhy5r^2rO-ZKOPt`8Iwv8Dw(V%rv2 z?A?;)9!WTFvs;Obojn#VPJE=2&UYZ4IdI*}^YAT?owRn|w#@JF35KT}@yNUg0U+rUmnwlk7S&Ha?iSlR0=$BU#m)*40o{<&q|k`? zw3&*`{(%R3Jj#1ssZraW1y;tfWu{;B5Eg?K&RPVHIJyFwXo*wQ$c|tnrY6#G91AOG zXZ`G`q0*PaaZ;9n7#3?Q-Ds6*dW~0vibogBwrgl* z(Py~Dyht+C5^MpZlC&0qydx4sOMo3Mf#hf!W{mf4hSg5BR91y^LK5z@q`=BeoHNZO z=UTFKzO56FO%%N5luJ~H;m}MBs}g8WpE9A+5^6Rqaynm5%B)l@@xW*sv(P~p3F;?i zbzW@PM!-Jh+-~$63C3uMM*Weve<`oF%&O?7Tqw&j+)4`RI6W`hl-Q{kb9({7p9dEr zrc|+32qIp6P~Ik8PRW6ZS{^Pv#y!?~-#SZS#w{&geyM(&Fk9NP;QDPq2rjai{5jwf zUq_M%^p0*CUe25EBVLIV66~XoNF%Uz=-u&TuA!9vyf={MEp00Qip#mJHER&s7mVc} z(`~!z7}z=^nNMb({oz+Wh-;XZbf&hX%mT34QU8MGg5lZxNEjOt-_ z|mzP)#Z>GO)dP{d4Pmu|m5;rp&`O#BHgi+a{RQT9a zM3CpaNM~jC?%x(8XTy}H{zVEP7^s}z+~A)_z*H-YjDD%&&Tb}QzE`SaZLs5rt-yQE z)>$P7y$}?E%%{ddhb%#JbJ!l4+364MIjE)lWC2t@?->PsEj==fh9uu~)>dyMl6kct&6>@MmcT*uXoYqKZanXqN zm>y1uwxoQ>6#=?GoOq1vGrJl`2T+Olm9Sf^QqTU`1dDVhyXS|aj9un8V0M}5)Cof&$SLeSJw*n~NGRGj1r`kf z(3q1x@~XS4pny;aWPZfx8Qj1pK`6`)vXCld$b$;D@E+G!{$^FR|9ZhPC+~jx_shX8 zOblceXs(kJWbjCtuYBZcKzAIz`Ht+smN)m`_J7!W^T0T&E8oAhc#$pHfMXoTF>c`) z)>*Tet2#%lCYb6NCbN zWitY49|3Vk6s2vxltE%>)a(J9ZedyT1+VethEHz9;|mru}o3%xrq@-7jBU;;Ww- z$;{J8qmp4#V^F++b`~2qdMDM~)S4g_RN7fvf5LB^I3gM9Bm~GvATr!}$3_{ry2Hn=Q zKntb1_O55s@|36tAIGExhMF|s;@t!pCIfZVa{o#$fX7APzGxD*fNQE1-#x9N@TYDq zDr7E|q0ohAH7VX#7Ko1a6u_X#t_zzsyAosE!bGb!1EB*6peQK*!oXV1(j!sf>ki3< zGHuU2`Ms?rz7^B!Gu4Wa=mL?N>~jZpq%8=Bz=;Ig`5-#dVKPRWrS%^b#JeT0TuHCx zC0#7Jb7Vl2v{#ZPHpYA7dj=#Zx8p-HxO_(z!(3r*OSi30{k(Kh*L(T~X?OMq%N_}s(4Cz&@Rdv=yu0y5WyD`UZJt+EV7 z&C)W5Fcy)G4X^INGfxh_^!&iFC)qgl@~5yGF+!33ZD8+RCVThkXOExWelvbsJiDh) z?md0*PPXCnI(_8u=_AK*ms0HX9nTNmcgxUix1N6V=|$!Es81bzYGD6~fg>;C=B_T6 zBZIeXA9(x_H-QN3)Un-z_ddwB>uk-N|F-qyC*0?+l1`82jc|=vErPSxay0A{6Uh1S z_&MOI8a?MiG?>UWF&mSR^U2w>QMUKlA3Y9s;M}P*ve&Dcm#vNW#*;lU`Lp7^4Say> z6Tnee;nn!31zubOr-Acg1)Ml|fN+r-uE+}4J9%wOLcm54-eP%$$;h%oJOoF)EZ!rr`}J1vPp%MPg4 zdj?FHiNT=QJ0eJ%P5P_}i@=^2XzEjCWdz1w3#85mZ<}WLl(^u~^c8QWDZ}g3wW~Bc<3b^Y-2{`=7=h&9z+< zW*gf%?GU;cY7jxjDq}Q*QYzRJiBSsVP$CfyHWL$$t{@uoh_-X;@0jkKHHx9Rs%_E> z<_e}_nRU;;_PbPxuWHt+Oo1vA9q5ec){PN5MwH-$U|8tZaHOZOIJq0vSxPN)exfbq zWGkdyK%N?MJObrS-aGY08FX7G{wmR)Y8T~16eo9wa}>Cq%H%Zi!O+13y_2_ET@ zV19`=irbMOwzcZUZ+zg~F>9T^4m~9#fhG%ts-ZE&F1e;X7djGDX2Wx@e)@a}1LiEL zF;c`eJ?Unny`@4V-)x68KSg2!&$i{3kaVFsv^IYjcao6>FXKae;CZv{+gm-M9R#8I z{wfzcflLNS*MiR8N}yp*H_j>0olykVSPXcqfyJ6tPNDYXD@g&_@08EF2V;4Fx@E3< z?la#eCXuYTH<>YK8kTkO$sl=(Jeb?dZCSZZDe}&NyAG3;^7tFucfE69-*I+7?b-Ej zPKhX-hBQ>JX(&ZQA`^R}K7IEb^w06V;68n|$$Fc+kKl>Ivgi&pCS>dpt0?wGOm2;d z5KKE5Upbf*W1|^hyE>vJu*yyJeZx25NYV4-|7qN3#}4?uG3K$tB?TYJzbWtVpYvn* zPu@Fo&7p5*;C`Jpw<Pq-!y24nB zY`2?#4H~T*0G12=LB`^%$=TUFf>iw%C;V>Am(9C^t#5%|F^FL}08I(fj3g^=O5+v@ z$T3&gG~6%JVTeaVvdW1&nJW(O{8dA-@4D&H2Z8;dIleNSSdP!`o1ibSN;W#On~!@=YZv>mSx=Kp586h3SQY7R^GW(kYHYB_>= zY%a21k!8IBU9Arg-0FL+B5cO`?n$BJp5$sRz1RoU>N*H6wPgkbji%$Df| z1#m4dvEk|N$cS>0ge)kID^e(CJ)3qQL2eM0qH3W)&hC-tF&81kRRHs<>)7E1_>b$* zsPt9x0Xsnlc6oR3FZ27?#G7K$+6=Ajyn2C)5=<9%VmQWB@AAEVAb*nkmBPUy!>@!l z>d3AkN#uR8_ie?;;}pqFHMuRmL%Z?Zs(2=svoxrESw1ik{)Rc&*vB!D{fk52eP|UJ z^_f+fQk_(@TSUV%=9~~#v(`vc+*w<>Q#Db?=7UylV9*9fJs&joz=Ma4(mrtHlY_TD zFn{RS$-(W1P91-Yg7fS88pOvrf5W7BPtvT+YJVQ9%)D&Vo7c_EcJ1!U8_OdTbRF&%&J2#e{Km@tmze*8&s|; zHN9CBu86URnx%Y<7@GCCd1(JiikNA^PUEXRZX9J=@|}Ja5cbe4laK8k%2XL$q9W?x9D2);!5~$&9s`Cbb@C zu4sU4o!P*F|8`V$1^HVX?O0A0x2V0c{y-^1y*_ws@6i6EVE<2leh1rw&)+h*{kSMW z$5`xp*1x8%;Yw^b>#H`4Z(7{b19v<=___PB0tqbt*3S*>d2Hy!rvVEMJ@Lrkj+2;d z1|Pod)Ujhj557A1?7mY+cb$6m^IMOUEc<}-P!)^q`UaHBT^2zEIED-4G~H8#jBd%( zm}3Yvee$q@cI@7|0Q17gk}}%{-5sF3{4LI`JGB4vSEDb^oS(T=;Zrv-+F@oh%hs*V z+AwH5;UP^Okl3yqjDiS;H3p6E~Vd{fx4mSuVlJ@jT?iLYkv z^;^|0nYnDlc%||4>V+pmRRy0hE&G8(KhpWrJo+esunpA@8Vj{OXx_nZslFbifB`{4gQfI zVVcAYIdu4rfn$3H?s{P0$jgHd+&!@8t}_RSk9qL)v(I9l9N2X)figK0!L`w`m-=pZ zTcY~CcVarR=@*RFGOV5gDnYY~xA=U8(f_0XJX(CT1x8z7v<3ciEuj2=^ll=ax+w`qaDB+mV8XuF&1&f z-K5@li+Xf$b0*i<#BRSrW zKRmGWX4FB^|7?PVJ5L=wHt_OEKq{sHfEGck-1_{$6DI(`4DQ)`>ec;ReCW_F0<2EG zdQxC8C!ZeN_0s8E_nq0Ecrh=o&qG3HL1*fpS|dPy^<;e^jvkYvNH3L zA0F9H8%>!#KRW|5$h^`T?BXj)8&#hmU3bj8X>q2;wunQT10+;A4WVg~#4!;9Gn$fp zU`z0UYvk6&0?b;Ku(|DkBx!3xNv#`rJic2|-KZo{g|FyTtNu{wpgtaSKdy65AqxrO9B~r_aeuHE~3Jp~g@Y z41agD-7GCVQsbwCV%wl!N%WchUc8xW-27>cAaFEF2h*wXXh=8Zx3!78^1qySo>QOl z1=-cF|K{)|N?Mg^(hifoMC0=s5`8Hb{^M>~?z#1v^#vOfsZNMjlj64X&&AD2O8}Fh z2MbV3aa$T@fsvBYNP?f-I4pg=CZNrxdho_N&Cbs&yx_Ug>29@&6zrBjsW5Q@FvNlu zA@3XM8ropgtg7Ba!--do3{17<3TyJBe#Q0$B57u6NOUdsG#AL=02d!H*&@@uu2{I2V zPM6e#HkXi~+%ck#gvWytQ6hQly=>t1{yT_goIWdGdHp`>~eqdd2d}wPpU= z5h)J@76iUR=zmrv#Ko&+S7P|bDZr5UkxgS)h8uD?U;@X+mSsw(-UPyQlsCyRV;VJMqKEmX-J_X4ZBoMPX*6-2+zi3J5e( z1Mg~-txykKk@nhpZ;|Wl;{KYty87$=%R=OZf%#1Pl5)4=gVQ)SEXy4IhW z^y7=@V~geEs;F}M7;Z&;^us$WgjZ?MB1zzYbS3*r&RYDEDR;;I_Dmn6H{)uzUAdWs zC-(i@wI#lp)8?gY6h?QKB(q4RSVMj;3(Gl#d+8BL849vnd^$YRmBb3_z{Quz5F>{K zDByV563Lvc{hE^Vs`81!HO~g{0Oz9ZED#%e3JMx!VFPWEijd|;uZB|tKX>j7H3>Mi z$*wwa^S3_^q;y6wyGrAkGF$5T*l`xbs9z(~n!U1|!#SE~nw~k$7CDXh6Bn_7@mCWy zibE)B%R>QnOg3qzHyJ?0`Na1rlcT&vGhLa?K|&;JBdU+xHL&}@&^-s>s~UWG*TC%u zks8lD{Q%M`u;{_hXJ7$4^T1~ZZ-0E~_zozm=1Egsd&#Wt6+oJ4H_`(80Rm0YWD{Jh zLDIny-2|bmKwg~0tO8TCrxa6(VLPC51uGDP)kv7(z!K#i%zduDn=d`l|Hgbm%S-3q z64K;$*=i9b8N-u_Z4kw_Yj~;=K!JHNHvBL)k9=Ts@`JQK|p{S*)mT43B zX6~-JS#I}a|CxKXySswb-}{8SYwJ7Oyj+Ucar($X=-o~qJS8(-T zpB=gg4u-k1V=Fz7q)?33P5}N3|aboi`^vaND+Yp$Uqn5AV7y>sBuLH`N~mN5=` zf{8dGIk0ocJm2B(%vqn;=61JA(M#ozi2~ACL@EIJjr;^05A%dT)PSlqGqVLc5ih#~ zLb%6YEBVH3b`8RnF-1thbQG9ddjN2&2zIDtq+oUB)@WlQ&@0nhs4o;Cej`m1ufVp} zXlqxd@zu|LtsB51?8TSxkYoxk1r`z|I~9wzl)bWaUBiqHXN5r42|e~l1o|afryceo z6sOeQL+$Qp@j9OPB7`inSvArAtsN2C-hAX|-?Ti3Yj_p{&P4qS zilmPHLI4KLtb!XrAX)<(2`A+NVdZqBht@txt>6^K4xrI92E-_HE&`3iWMGKzegF9J zqUZ7i8K+}XrINssFOPx{F)E3MMDmJ~>VeWarv&Ar)1jD}h{Avs!v0Zn*S2kBt}|C? zi6rbTQ$Odp_B*JHO{rn4U?>%&?m>pD+0pc4Ru)Z9d_uG8} zd*3c>_EMP-y!z0uYcMa(m|bJ?z0G34#SlSKhfaG131*rowov-wig|*Rv`<$)6ba zx>Li7g*x=xsR``sjHsX!i7Zza1C4#<%;hJJ{~Uvym_JtNP}Lp|^|gsePvpBCA1avY0NcWrd&|!!HYdtrMr{#y*&uIEskxKD}k!9Nmx%E`^>4M4_ko7h$^)B^{L|^ZyvD* zp1J?@?b*{Wy+Gih<-g(zKlD@vnq#f577h=fuN!o9lxq(hyNCOUaO6foOP$NadSZE| zg?+v$)5pH#G*5f}r8 zuual==wg!8LM#`7o?Zr+^ofUm5-9drbJx}?sH?<j7AU7w71VErPbmKBH1Lnz|nzORm6a zmAJ(|$(D#=Wu)H@NAgzJe=O=pl!C34B+iT72tnfcT;zUc%)8&(RP38I!&Fr1KX2-JHBT4Z;8tLfG?ViK z%wg!>0;AUzK~Rc2hhCYO?E+gtC9 zE%wcsxu{Qrh)N(GCBF*BR~{tU6N=%CgvZ3JE$_)Xj4dWcLmcE+5kD@CTMG&V7mnzW z{x!~}JWm@Am%OnwSK8BMmN_Xm@kfy% zLPmnso_a#t6YB84@zB9sT{hPh$MPE8C&lHaA3>1O2onY94(EVAgAzrcmC|fYNjxz4 zqzx|xaUwz+C*i_0ui4R`>tW-YR;^w4^;+g9ZXt91NH6Rdqz7cFL~yoxtEFV2%#x-8 zG!on(ay|1?v#%wwl{+nnt}BL8wA-L=WyTB*mK6Ku&RBE>G%R_gwS=`YRz|l)%9>KC zIB|Q}^zD^3>|Bjw!5CrhkbIjml%-Q_`M&!)uFb8e5CZ{j>I;?U2Vpx^^lmMMBnd_uS5z;W=1{Z zJMrN8H=VcFy+y@vlUQqKQ$R0emTuN4v#U;8hOZIyMdYVV3X7DOsGPgze(8i@>00?5 zN=jON6Y^I3ijNjoOguMlbcPv2(k)%I zV+Ap$d8OIBjijWy5=Pp1OLrn>6aVJ6tla6`mP$mBjq}T($>E|tWszLu!xZB)vzvtO ziG#c|(j=rDNG5$c^}_m5gKs;Xx;Q1dXhZO3O7g5&s0T7@&rPsQD*2QQXSt!o4lx(o zHN0emg=7Dc57AB~qm!eF5QnY|JdS-YrdaACscgE)4o*5TV2DmsHqq!IGJAI5vT*$1 ztw8hCVnwxc&&GtUd}lK3DrswkhU%u}?0DEs=ZyJUH>{foT6~Yyd_s(vx{Nky9%3@< z=q{rJj|;07BP-FjnBkpva}Rg|3IkGEJD^y&we6>NT1}}4l}uAOiuBpD*X?-Zx4&zc z}9TCJ=m;w8&lc+CIc%(`u@&Ig9A&GbX}pQ9!5ulKB2cw}l-hQevbht^(n#7Sc4PU8=_ci%Nlm2NHMzSRVN!(M zvK$kEx_RK`OPZe=x+>nD5OWHEgiZMZpnJ!^7m8WpHQ{4T!vZ0%obSaU^>bJZdcONU zFl6Sts92!YcW4~!XK{D!S2S{;v{6y%uOeo!m8*)UDG^$7&1%MgVR;Wdz23RJ^r2qK zP{4O5Th6ac1A;)NZ#f4?b_+wxuRpdo?O(S;C9m1hbRP@~c?gnyT|4%_`7axgL1tgJ zS#dihrX3pL9x#+DBK`2@Q+%h+rof}2@v+M+Geup(JoH9hs57W)QO#i({rj7S$HerBBJzSsh5T~qrOJW|SVd2MUgI^KRH?rz`~B5Hg@F_IXZ z`SkNRIl%V#Ypm7Q(5t&oXAhbrbB4VElnQ(f!is1z+Z_{H#lilYNxvst4)?z}c%Pk_ zkBi}p8G7cvQ^%hIsxh?x0V%@|P2xIDStlr%{V$RQPcq~kd5E7Io<@!j9eZ}@!4m^d z-&ZEN6#&#$q~Y;x0ZA%ryk0Bg(iMv5!JI{F=uK+ibz!r2o zS4@IfBk&q4QxoZ1Ww>?aACE|)KM={z$uH`iv^^i2r(23<+VJIU?dlcvo+;XQ?7Ne< zm>iT9urk0w3suGc)Duc`fbc)#0=Rh6}6pH@LNbpe;q_Gz(?fE#l?=vNX!C`nXSyf85FqYzfh$g z$=Z|`Q_Tt<4SC3>i~872lP~lAGFLUNzvOHuUkiX6BbJg$@PQF3!|3j43;fwzpk&NT zzH@wQefif*8}#>|+or|`j|SBqwhzjfSC|AdO}DBhF3dxF~Z zc9g13FT2G+G7S0aAKJFOCorX{NipJ}In;!g8~XikJbc(MGpvl~Jvt%K!F`*p(i?5XY4*ts{Y2mg<&*~s3PSTjW$bE!; zaXv_pjwGl@-iK62uzVhDUeldpzfw?xnTV{}Rp`HH2pT448xjLe!u_SJf+G<94u)TF z`fc*uvak(+?TIu{tUz2PE&Eu)jml}V!7erk5fUwZ@RT;JF1w<`@GhE`joX;~Y^ZZ$ zAk1F1`;CXN@>*7X_FU}|nd`A53ITDBpz@C&z&A4DH}4#{=Lu}-3KT;%LgEmuPB`*R znk=L3dVE0Ii^ST|C|$=@HzckRdzlcK>a>gT0&*T^rnvIy*}z6Ur9X7I-ApPF)2&mR zrakVkxMIUernHOpWs)DnWq42wY`rlPM#Q+kZF z5)#!YooPYlnmyTf2)vs*b8ZV*ILr_NI>p%9h??8f?yN02xEe{s`$U)v+@K2y`4mTk z>m6B@_=LZx_^MoglPeHmq=7}1c6gC<`JkR8Pz}vS!%0o3K+erDEOdaKF#OCS+XYgo zm^_R8c}lYZAQ&2hcJ64Mc5A_RoCd8d&y?)>QU>blsncg?E-^No7My{@xEbYuaiH=xCFaZy zwY4yad-vTBUtIc$IIWu2@81>(RHaP#FR$OY!Hn#TmNGnSnRM=|yn3ff6-zQR_k8iS z_rqm9t12^DH65!hg@z9R7k5%={WE=yhdyq^J1#Y=xPVi(BF-P8YT z%%@YQ&Po}2i<0hmXP@(D$;N<2O&aoBVK@Qj5BbS0dx(rYb?kAWzx%QWN&aZBlH)Oj*8Qw#%1yjJv}af*7WKz>zVNpfYqG2U>$GR}&#gzoiCpbnTHYPI zDl-2`vx_0w@y~~Hfn;W?R982{zC_z;0%8<&sl24B79Q&)oeUft_Y2tqQxvjxdMrjs zeH=O`^AE$CuQ$;y8R=e7aeqMGGfQ!ja0wxok zjfr0MpJN*5eoUBhyP&C8pcy8LmT0s5F@DKIBiw3DgHSvt46Km@EPZGLC_&Hm$vdB!LtR*0a-WskjQ`LbG(JV8B) ze&FwvaS1CEK&Z6#%1v7)ekEV%kg9^FrQNc4*~t6wo=b|<5@-uIHf2As^Ux2*D$FEP zFhSXXZtJxJHUXmVR5D>NtTZ6lJxY$V~BV2Gh&h>)v`WEMgn1+K=-7Szyo zn5h>OHC}zg+isuKGhP%39cC&Qy1)~r3+FZg2#vKrLp%~*0Q+>1rAEUczc}h5&4jB;s zsv0fnZ;)RJcMjt_EzU)qJ|-EIC6^uIvi3X``EwJQ@AiE&e(8?smF4nN`3Vv%NiVeP zcod~Tn+fOZU{s9s00fOmB1kPySwI!ikC@WsbG0$kIplO8MRtV%R?p5zX4B5>z2w83 zIn&yz^L*w~NnJ;043Aucbu=f|C3?g!Jlr-VeM`HPqdn zFd{)QE5IC;vvheMR0_!5G^L)@OP!wRt2t|^7->l@;=Mv<+s=KjmlF0luPQ_0CbD^C z770&TE0LkZ9}BXV*?agN;FM%!DLF7xku5T9ZD}F7MkKz}c-L(3n*78Is!*Angp4UN z13LgP+FxI>2w|ZnkZh6#&&GY_ZTyMvt`ASjcbcGrq-%Y^kFiFgvEj z(yg3fgm&Tc_frPsr32QNm#cOpLUVn)7JwIVt6xPH8Y0T0c#=K8ps$JT$Xsj$MNMYh z&O86{TBvbHM(jD!Pk-nv^3&(g@JnpP5=fu)hh!#>9 z)t7pRe*9NPJIIJB1YQtnDb1D~sc*tik@np&ecVG0ZpF&EQuf@Ne}7^;s=&0_h7~Zo zwJ8DgC)L?pQKbtyBl|VtVnK2j!CFUeJ_JY)v2Y8RNBe}>iTTD6>Yu7C{$O6zxlehO zLX~2Gq{zjtS2IfwS&$xeRWp=Axs@n~vgWI!2JRjsitxJWDc#Ol`;5_VSf&^IkfX;j zrbppckR_MxfT)14FGvO(x^b$j(Y|ad`-z)>c5Ck>-~7^=>R|`%5^e0f!+`a0_8={0tC#=n>kHQB_n23+7%k02z zp{H`}6`egH+wvnJ3r*1&-=3}|mfOv6ALtTboUQuf+ajnNeNO&zM?Ze^PoE|u#jKeX zhVQB*t^+gA&L9TYQP(zSZMkPw(w4;KbQo`ifLt1xZ;`hkEkInnCehU(G$H==p#Qr% zkUi2(xrwi3(b(VmNI)-AhO|v~$<04G*a(lv^f}p?Dkx+iDnUjUe`YLZmX;nF=?3y| z;QL?#HyVW0#z}RnR$LiNP!R;BPi{)lAb6$V`u&&JtyqP*a03foF2r~>SFTy9ud`vz z%9XZ0qTW?89;&X^O%@c#F6&XuZv5DoWS{da6-&T(Hj5FflYj-bqD@UkWv~?oPX#@% zxJ?8}z8txQlStMpj(0M(n~JMqz)aqaZIA3(@*+D?Yxn(e)%L%#MC;9Oym580@7#(- zn@wa6j5o-vHg>;5UP?0c-XriCiZNcHE%gf7Hjyy(LKK64bG`XtCxwFt|ZP>p#uEjjhhfyeLn*R5FDK-yMR(umP}(cSTV*MS_z>_pHFZU(~} zM^;17sSO~n-3Y5I?jQe3nOk{D?%2ssu;4W}lhY2BT4~_Y`jdbF_Av^Rp3%Lw%Sjh> z)J(|XfZ>^}g|d>Al2Dl2b}EFJ{#n_(zWS9VCJcCs+EcQt43G!MHL~a*+P1tW+@Lq` zyX$M~gzle1SJ@FYegOoFFAl-_jqQ*6FaE@W@^UGs4j?hmi)OhsgzzrOWD^phjYlIh7~0*ecUlr;{fqe;l=6geWBCaQ`pB;m#JpeL{v$yn_|K^)F@dj!BvYSfXtaki7G z?8Vu;9)9o!+$AJYoUUqrc2&BE*6K6M)~(LkG8@YT9Z%1~9$|kqo{ESbQ4h7b7%Rp7 z=J55|(bb~cYQ&RQAAD-X`7dAXHdlqDPUO!HnFtu(b^0o3k+2Er2u4#Wv^4-iovmB3 zrw_9gd2|O8szOyIQ~+~UXM)c)l|~Encz9gM4Og;lwPLZQ5T;KYFCK*vjUt=K?tSg6 zwPYKeS-n-Ato_slg~Ya?BpMT9#Y@_2%X983(5L`;BF1=>gt$(@4WeuUfJ*w!6;vgM z7MOAAVcYY1_xR#}>TI~y1}P;RhCb|;$N zGg>Gd$N0v9KXr{3NAIxekoAeRsdi2N?O1iV9T;MO#}K@ zG^`t8Ff)FLVLI_y%>Dt1jPiG485SFl?H<@vv&yz!`hJxid;JCOi&+G9A}$P1)kTd9 zx=sWW<)6Eno*hwI+ai)Cq(2?=j0G4sJ=bXuHMhgyJmR%59JKHP(ybjN2QV$+w72_c zc47y0!gwp1bIS-zpzWeCcf>f{UzOec?r&CNgqSryyHPDs{c|K^C@6^b$nXtIcLl-g z{Hf0BcvCNrGOPI4(VC$o77(INK|ZK$p%%K%_RXERLHQPp1*!}qA~!M4S!aSa4ls}J zAFqRrL}TbNQ?4IpJd|XAyw1uK39nc}jEzu^=C5vH$a(f75Kpg9ZKCZZO#B9bm!&{etSX(48 zKne3;XFDWKstJ_s$<`UAgcjM1PS{oXaIi7UnD4oS>i^?6`6kbplvn(TiI-0JV$pr< zj{X~MfzcKiZGq7i7;S;k78q@T|3nL9FU{=KG+iR9ST>tCvjb$AAsO03>Z-#jmz-;V%Su5ha*&dFi83HhCf%^$q~B>o02A=(Ok{B zg`J%%`=QL9f6aUVm{;}MOqBteV3tBjQrQ!82A7bq7I9A+b@^<^tlUplxtcw^X6rG`2?&vrzLRlc$?zYY=sLXraeU1eHZCWvV zv1kPVBXmfHre4zC1-7MHvr993zWg*w=j_rY*?Ag-o>_&I9kh*Iv96|QSn?*Ma$5i+ z-1BSj>_=RES|FKMY8)kv?i$HZNM(~XY9dS-Z0x{SRVUe%WEj~gncZ(aMA-K1S&K4)X(N!-q8unB za)9t9CYm~;$t3t)XTy8eL;;ZD^z^GQlhcTSnbZzsNg2HD?o&s1an?ODaQmmuY?mxf z6fkKch8^Ksma+BJ(K}9`IN_cV1VuYAhNM_M%+Xv+Huxs2yg1j8o;nA@@l=qIN_U(W zB!lFfu-B5tg&@j+0}=~B$i#p}KC2E|mDHQM%zQMnd+^s=lw#s0GX`HG##8C#1iVJX z7Tg?du~(M&)SBfh>^!L6qiSi^kq2~xiC2+foWV+{Cv-H*nTQ+9ruO2n(i zA8XAmEZtm#D33o)DKnPigA&C%Hz1UYlbdc{p7z|z**^4j`HUsDGPbJAT~!_Jb_ z43$U$zle8&7ci`BDz;r2?J$qu+%@6bLi*1$lTde|{zM&kxWQ0>hFyS&65LJ12L#QP zsCq~LIhlWby@^*e+sa<5Dr1)Ls8l*tpO$zKG7B`ddp0dSho@{G&~~^&pdq47#kCH2 zns`5e&$6ebG~5q7gS@O17lSVlW*4&2K@TBu47R$Din9^=xddV-+My&JHy09+he1O? zv=PTBkPxkp2&gcD+dBOtQIlpRK@;GB_d=Yn>Oh)C#;PucDkqB18s47H3) zh@p>whRP#2S0kPhXI&~_vkt1+BbBMny!+B+ns72>ssFM*2I@A|>Rl2|D~DVtP)-%G z)5Md1!Vr=YTB&@-@@f=4O`IVaN4?Rh0YrY@A2AFbIJEDbbu+&2yTxhB@TEYXRNC#MpP2ktS2%aBSh56vG~ggQwaseKPLPb=Ish_W*r@cn z_IIS$jb;X(UX9&nwk1pvL|mt!Q%yl%$s@~a>ho+`dJbzsO!Mrmzp^&R^rxS<4TR6V z(QB&4?aW{AR&a6Vip-mPq1T)~rz$f|J4mKTBf^q#ng`7>wE%_MyKR}%>-n}F6pT&n zkWL6WwzRFy^e2;x}&YQkpyA@l2iY^gYF}^2E z9+;?XMh0_v6SZcR$$;M2EQ2!Gq*MJjWd6rjNX`kd#g5DLv9c?84#B4lUs?8U4M8BV zme}z%x$ddsL$jbe!i+C<{9dE)CEJJgR|&q62;}BSk9`F!jVa;yC-{$EN{$9XNzBNG zRS$AeflfIQ&Y;YQ7{k6JvyvxLPNF5kGRB6>u-X^`@(blhbixZExw7xw6pGXRH8v;_ zzZ^e*&HCj5|BCvBP6<08WsMz3Ez~eV@k~rrgz!M|o%`3VkdT$KvNC;A)#45A2&+<8 zbF=3o&db~Pp(lL87NDP1MR}+r3|Ih2l)|&bN(R^nvy2!Lh?q_WA`n|vRI^&hU&pY? z(Isrtc}Yv>8a?O?2KFP{g9GfO`ig8=p;Csaso5J9VZTq&|0j-L;hVf}(icm9SMte{ z*~L>Q-aX-8C(IcC3WrDkjkdsO3yikFXbX(Cz-SAMw!qn2Ai3}NKm1U!upcEfLcPS$ z_DS&Qa00(X0#+M2yh^tkB)T+6c(xq&%bw^aqS>yuskc`;V0VdFzwhFCXY$VzQ3(#8 zVCCZMHTzEgU=v&))92eVdH*~`QzKRiC2niPEDgk(gEe|kGJ1)Uk~Yr4vzR}m26}i! zFUAMsz!s#b(i!ILA9){`vg~7pqWGE$LCi}|1YrX~d6C=OtDsYv_$nEJ*bk{>1Z_Ya@p7iW6Jk{=j{^DN; zbE>(<5>VmW&6BC$_fOwF2maHU!Rw4)@VYgy7k9Vgj;)5kE1GiFmVNw6nicDW#0nEb zEnzAu#fw$Mwjs%;M^xVY-TeC`RhqV6#o}fnxg~&G?g1vY1+P75ScT@NXi(ubq<|pv z<@&*9!wEIa6>5rje4~WxJJ)e4m6^Wp^}Se$rcNvEP}Q+Rz!?0srF3D*?mGwW?|=K~ z9)B%2!;nJQbJ#@I*n|b*Lp7EmlEk%nmH7D3oDZov#5~o)oV>0C58y|5qog(A0*3E} z>l3N1;pKAY8~6SFTg$24%(oJUy(4) zw0o=8@aotX3*7du7MG|L)&>0x1x0_69l!5eKeS1PiVOkkWKEHnPV_+D&r1N)kW6~E ztlVkX1WX*cwvM8V?~7@_x;GNWR-IT z4P?Na6z@XcC8R(NEPcc=2=SzYydLfb6%2_J1tDJWrid^MjXc_dFBZMoR3XS`tCp+W z19k6v?b(WA-|QJx8}&1ot>|i@R_W^11^E(X8*WO^nT>V|9~)o*`H&duj9R-%is#0W z>;9%>l1)Rrh{!R$i1FE9yYEX!ENnZa^DZ+sj!+`i5fy+yDjsdmZCMbfR1mtJLTX|NE&g~!kf7AO6u9kR|+ zjBplZ4edZ57sL(fe-8mU48FRDRz5ckr2ye&b|!_b>!y8Ts`KW{7ikhNAUv|pMLpT( zKBtPAG5cORTxVEqry1hdj_@+<$O6^T%y;gFWlq>!Lt<+-wo);Qg%dqcrZ9BT2iFM? z!>TCvr9>l%U=@BN-zy&&rtwW&S^nxzj`Qa~ovUxAY~S-QCYc9I7fA+|{L&jtlme;N z?0ALYTUPEgup}Xt>r*Cfp(UcNY~oF?y(8CWe7BDo|CKzU#nDlwyxJ~3j8(+NKVWAy z;5LA5LQ914!A6>i{y(;8vTt(vq*=vtCvKl`J3FKQMq6OC1x8z7v;{_6V6+8BTj2kl z7RYve@vG0`4+Bk}Jy(g|vj}m-1E2vml6}`+A-Tyh5CX6f3coiZtUugG79Y3(B^)1O zX+Oc2O@dv;?~Bd@Qd1n@0xJ;nf>S7`3+AF!#q;G-t-&#%+~PJLv{xE5nw@X^`BmTd zy^AEcI9R?gSWz9S456IZT8)wh2(5s7WLRCix+wKU{X^a*JbPH7K)9>15eiexGYL;p z3YUK%_Y86@~&FI;Vc zXjcP=;nx!gKXj*}o(P~L7z!skH8Hz~TW>^cyW6?o_oF|GQx$ihC!oXkottL&7W}pQ z_?7+1FZ}xMRfaM921Tv(7j?u0mF-)9e=%k zox00oC}Qi^$sPEy&>---U@=rLFwaR$#5Tif9gYx)E&RNp7#SQfG}+9$He>~)3+a=U zd{r|HLo4mj7E&*dFByKqRxw?PeZpphWgB3Qey`fc)jHWE9C;Eyfb%sHGiSxvW`kiG zFPwVMxF0wzXJ@Jw3x)lpn)x)?A_Ie=#5bnGAO@~vd%%7QMu`Ajy#-Al${5I2aM$bO z>4{i}T#qoN5u^*TfN6hLT0rXO{lFoD=3@+zutC5mUV;nRrefyf40>KxcH$S_`D_0q z-z78FW~M4JY9$%M;x-L<-m|>kpLT%e28r@`=c|nMyRL z4fcUyQ4@m;utz>vVgeKhVQ%DiZdB%bF<$)8G`EQtcz+bI@9V5F{|ULV3^3|J2R1|a z(TJ)Oj@0Zv2Zl#p`mnTBVmq?4pl2@s!aw~(2TY_(R%GVzy%v>jRC|rs7R@Iu7NiiA z*AcJ{XFqDpM76nMYAMU5B6C>%?Pv{*s(uoTstt{R2&ntbtuzn<{ygnRPdnh=|?VM5u2 z=@asb{;}u_MV~I(lRu{DhRHjNwiK-`3KYG+$T$A&@qa!3vGKddca3itf64d{jr*5z z-y3&)+^5FfHZC#l>T!QL?t}S<#^sHDXY7y09v^#nY<6s7@;{E>F$>3hXw2Bc!O6=Dj~6~&xVP}*g-wNP3+ES3D)?=|PYS+S@N&U{ z{9}{9QP5w|UQkzXS-}VL|2_Z5dHeHv^ETvNmN$pYIGyJd%qom_HlIbh`Qs(d@*@t- zE4ZStEdgMsvz=+sq+TaEEXGb|{_e;YGX~84=_NnCu%MQb@o*yTeksOyXlz1xzx>io zrbuhNlM#Zn<}F>Z5DBx>eVFDXgehA3jh}RoE8oPDI@sYNFx78`l1Vv~n9jv8bg> zP>GSW?{xi2tpuY|UntQ+#w&|}Af~K%xZ-%<_UQ!`g>9{&L`C@mi_y8aZ;RvyC`^BS z+mwPig>5au(GT2{V+*Ej10nBK5YW4?~W!pee%n_%3(y*qYR!KG4u#7V9N&~xI4 zcVwp(SOsm7IQT+*P+U^C&iVeYv^yeF^+6!UVY5;6zz?gY6wKuZ?R~Krhh^pa_fy(_ zkw{YuVpZC@T(*|6RrK*2#!oJoS=bitrF~M73xE6kKYqBNzOb#am9*h4OU>YZfkPKUqcvaR%X&glN1^ zNePflU-acV?Tw*$>sIO+zj*uBztbi{DM3U6ewNantnNL0NpX^<+W@qJ%AVsF7i{E_ z+9TcJPODCV*2@uUZ1ULE^cj(J3kJT}rQJ0|s+=T#t0wv_Ht6bqesDZQbFwQE zwl71-wF&4PcirFrO26Z7lA>DV+lllPeDB{Pa|+fJB9A1x+T)2H%hm|w?UU#{B$0Nh z!mIm!p+6J_#8Ne+u>E_N%1dQHXzJ{=YCAh?Y0ybZF<+-w!T*Oq_DT#Jt?DpInQ47JF5{vGFiPCjFl0_LGs9nX-S zt#}K0WPG=LCsk1pDvWk8rlF9*njeZ=t5IGW62W>wO-F(?!FA#-U{_e>Cd}G3RjVf7 z6anzUvyKjR!H)H>N#DgY!aPT7nzcAWzZREXKmyb*)k1;{FW#NJl_xgHh`=!%S>mpc z(`?ST@GsxdSJpv)H$RFb#}0h)ri%*JQhIr4TUXR-=oaZL=zgG8UBQm3uEm9?Uu>4~ zIvVT3Zx>3^CA&i-1=PIjzIe61egZD7D2u{-(is`FaM%l9w)m#4HS6j-``)+g1{q#a z;={ot)n`5N`A* zP54UNBkNNwb($?HIq_SY|JKL^(bldsor_UZKFe_n9-1Mq0$Cv%k6UKt+tG8gd~#j^ zLLtgBG%=zT{nfUETALh6rG@wbXFZx}s`}m!XXh5IANFgeVu?EsnlN#rPEDBn%tg}Y zy?0SfC0ViX-KzgN@)YPuFkuK>yX)474*aZ+|;pgw# zDYIj1It}flMO^K|sjc$yw*sQ>>Xad_A*rdBYV=uxwHyAT^yibcE}cywf^C~xO@{e; zz#_rAMHk3xZ*@=A!cUjNf>g6+`uye($(SrjGZ?-x4p^*Owz0p(B+`&U5JN{xWL(|s z>H0_ssPmf=pyLSzaW;!D-2N#U5ebJ$@Tth^1Eou@Hsw3WU?h2rIP7fZ9sJ1~T3ylG zgc}e&A}-%@GUEHn(+BlgVQau(rPHaEPArQwBsxW{h$M>^y;`mh6lIWZ30do6p^gya zBrmV;qFc7UD&tUV6x=?c9m$Zjxg#3fSi9+i>wE9Gpy1L1NoAyYFr}W(Hsi#3{qwX& zBm|B&_i;+sG9FxX-TNOpw_tWbYou2nj@7lHJ=%F9c-qCt(HiZ@P4_X9u}auZII`qx zP16GfS0NIouoSdLqV272G3i7w5%h?ut_k2lYWcD6TvS$Y9fgxg$xzV>+}kb9%%6}H zpB`y%PxlKlZc#izS^$Pu%WO`)v2=$FD_D&>I27y1-#_m`Z4;)VUK>;+C!)QRfB*g2 z<~ev{Eqs{n{ZP4S{nns~fVyB!-$ogj&=8YpD$$s*7;g}J3SYcXKQVFz^JPXf4np|! z6qq#aqkCs)4+y1Du`QeX($?n3S7-l9hY;itO`d0R=H>l3{t3O4=P*Bvr^j7eH~G_f z;vF9SCoP~KfM;YAiw^^CYSNjXKUrR|1wk|tsajOApb{y-9!0lY(ItqsM0^V)bCT;r3lPbY%lVFZ6t3l#-uF(N6)?8c9AXa?j>8I5HKYpWcaz)8a z#hWI!7Dd<@{WsbIqb)Gn0;4T3+5)33FxmotdJAOFIrz?R$KgLO4Q8%TNNFa5fg{}q zixWCYqAwK7ZCQF|O|zmw1NYFFIBYxSZv6U~1#r|=;39_xaA6ZpAO06Fo4pzaozxo#bcyxH zu8!ysEpRg%9s>8Uvk{DRV53JZ0#L0#X-)*X)0n^dZ#+2kFXiNOm_ECIs(Md%lo0qG z+Qi@LE6fu6O)3PX`)B2h~%F?HQ!JV5`I8%fyCb zSQtpaw_)98n65EYCR(~8G^q|oIRRX4iX7#qkNqUI|sbk4!qA|C(Xpd z18_dkDZX!_X>-pCS)I}F-*YrN@`&EwZsZ&v{>TFu{4xAUbTt9lTMrH;(bJ^0se=1=lnH={i> zOB0x7HovDi`I4~~7{{;cZ%$p?wJnjjGH$P|Kk-)r_ArcW5coxf(t_8bPbW!{`?T%} z;=d^+uxk6Nxevx zgU{VHaP#v6`|cdrf8zAPI|uJMIdJE(fm`>z{mMf_4`+ss9eVqfTiG~u{KVU@-1g@7 zjDO(a=giT81G@)~W}TzEPufTM_3o4Q(NixUG)LJubMGyu58i(!bGLnN@R7UBIW`6k z-E-=dhX$U%*=$h44=141asLT!&TXg^7og~=V|Vg1IAB>0?B6fj$L?WyI&;s!?T4w{ zz$4oS?|p7?*O523XT?-iuc7*NuYPc~U3t!!B_tFx)R^K>5DQIAjI-YTa=#$yP6@d2t|{P zkoxNq#t{7W?x{_%Bk+CIG&4Q;$;HEnD`P13PcajuY={><7*I})xtb2?nqTuOGEeFr zLj)uB2JId4*uFa^P5p~S?h`Kz$>oEjpW?-nps6aBv!{=doFCPhoW zTr##eKJoB`A5Mr&m{1fS_pR~2{L`oY(UPMrFxmp6Eil>wqb)Gn0)L(s$j<)4TLDef zmaSLj{mi+w;_9u0vkeDVvc*|j=CDI$7Vr~aA2iUj0Fpk)OgG?CC27gXh8`@yNPolf z&0Qn5%+3UV{)zgUh}_<@`VLPhj=H2m($vRpKtrMO zDhG(H6{~Pd%;V(7+TvY#Paxo+w^xw#x-m@BbxG3R*~v}aiXyO~5Tc@o@$|t?7f4f1 z>g-WEWtaM`t*&}M15M@PcRaUnK$#r$P%0h$aA zL?W2;AODxU+HuKTWhoPI%VnmWeA!>(E1g-DnTHYxX&L5>G`YTmVTc35F@dT$Yb$rE z*5ijX(C<)(XlW9(D%4L7SGh9(;o_0(;X+HUZ^DrFDVsTbW_pt zU{%Fcul~<8p~f?2udqXnn<63LPSnU=k#sRqLtMkfH5@MExW86lwM+af!cmMK^Kv41 z^cU}YNK=$+zJ%hx=oh}fbsnc;QRebvzdj%C?%9`G`aGF>5Y$4Rm`LJ%cDILZ;uFmk zsV5kMo^2mOKF>YhN4`e{93TYcka2KK3kn!-+rD^auOY0@fB=6{)tA8Ul2gCBdNPi>NFZk{P;VOr+!^6$0 z+3T?grAL1PbtM#jHVeunSbmz}SsA{v?A><`?CSUDTEbxo(6?&10~s%c$B%BH zh#;FL7Hd?BoRha45BQLjW~UbJ|8wY3_eUR?jbTOAlYB&C9 z>XU9eRb=vBI>u`gs@`#G6P^R_ZD(I@Vd-WAgI_b_B^i7rzz1WVrViHErCcv+q*4=@ z#x|HIGGn}L`M%+8>$Rc}J9n=_*~hO%b(&TY!HAGo+CM|0MMQKZoH?0F#S(qG zfP*Y|7-DFo#%5a(LHAeuDbRXy@|mBCHe~?U==9fJKmNr+L=xDGDuNY@s=|S)aM1Zl zpb6hTBwwd|W$;c#m7t%e&k*7rJG(p&oyFRMnq=C+8ujmN)Wid?< z5}YKeQyz(W<+f~Rr00N>jynKio6-cWJATXD&z%6E%#>Zfe=A(rlU`%(xk^#cCtk%wZlTe8iS^O=nvEt>Uoh`M z(VU5rz~n`IdVQ6?HvD(Q<@ghglGhLtahfaO4Vw@9HB!vKYQtuK&8oU}-BqY+(yo!R zRSDnEzWl+Xb-BkACq0qvsVWodY?ih?PlKQ3`n)0my|gb*4P$ZWf)w9SFp%!j-43>6 zL`q7^%T&PB(YXXSu{SeiyMFb;e3ZJJ+Ew`DmehfW?J zc=W;G(4o5qo_X@r(c8)Jxi*m|aLadi$_2%F%X7~~1`;4B;n21Sx!cT@d4%RzUk_Q~ zHLQRDFj9tSY-FG_744J^A@Zd{A+KjrDR}QS$?P?;)n3(GlF10SJZkBbq6)|c!WXkwx|u1Z|}^`gcCx2{!Q{)|)~gG>iWE?kU} zUS(udd1q60{H|aAV1=dw$;_oo<(Fn|YQksTrwe0x@oZYUy^QpTor2SfVs#!RXSI7->dixF_siX4dZd=bvsi zX*gCX@nj!JuUMbNNz)b0(lUocG2Wyn{bd_gH0U=)y^>-e6>De8UpreH=}I-0U@#(~ zs~>2HB6eVV5G#&A#rP%sgn*%xWz}C(w%QFz6C*Yv&z`A?osmW` zSJ5VCN3bW+L}tdWx9(`f_BpdEp>Vj(V$}lL!A#PjoE=?+yNPPw)u9G2bIPdiYU8pk z*2By+Qb=Y~#fV_Dc5J$v=1$qW#I16<-pUxf107J}Y9dY-y=JZ<`v1f+FZw2}EB;<_ z<;43Yv=!Z2ls_&z_B&&*;PB|b(H0nOfzcKiZGr!zS|Bs^wI}ZyQ|y~Rf9?FB2n{QY z-0YH${Kvbk;^n_ANa0_?kcy#J+f^O#r`tG{ajR6!%LnauBo z?qu87zxMX|uvgEm=uxY7NhsEmAgqlbS9G@W#g&!Ab}Tt;OGotxN?pSsSR(952^axLasE_;K?7#QT?7=tt z54@Ru;m!UR&OCaU5^}KrGjI04@@D_1-t2!?HnPvwbE4tpv7g+StJ2~|T)^B@{yeT| zSPI|;OQo{gzW%ANjzQhF)?BYG)=v~Z$_YY&sGHtx_0n2-28S}lrPE-4dw2Cid zlXEcz8T#vuWNKQk;o9P#`kr#CTCqgPss0e^6%QDsWjKb=VTMCs?F@cGtZg9U9@XJJ z{RE}l(BR?uG)em$i3CJ|$_gRutYrHnLO1`gKgn&A2LRK-05ogjn#}AYY|X>7lky z2I~Cp1CQd3(Gk~%EKqtak^H7bMhYI+!BZ^n!eCv)4Z62;Eg~Ec0_vr$NdA3KQwe` z*U&5X4IRGY^s~>LKD^zG6=31bm4G$Bx&8Km+wK~?^Ae!zq2mWmA9?lkk%Om>?l#R)cIwzZP}Hd111~)x z4|??0(GxKZjJb$5%@SC-2 zHwYIx+E|VI#KU$hIqk2(!lALu}|7@*ern+9KCYK4X%(_rxs*hetCiif}-LOWKICXS8 zGtS_PpC8(P-@wh!$n10U?!iy(0WNP!`sHH&%Cf8JF!m7z#EjVGv6VWM!^?$7lQnHT zaI}Bml|6$m-a43h_RIqZ25!CId;HX~I|gn)$gFqj_}v4??grmJaP-c>hxecEzi(jA z9Rttp8NBTd>Nj}%eSqf&_Pl@%<+Qx+C#a=lsi0TiIk@A%VCK{Qit@?@@)kEF-v7Ze z_bpZ$r+&v)o7=NJ)J)2aWT=t3ii0F@XphK7$`EuS1@J+8$U2AZFc|9-_LqI_ySMi! z5}{oU$2U)D3<(cuV)zP0SC6g)Pp=xf44e*GBqV2&4Z|P7ya%lacq7NAQMbCT`RR#2 zUg$nV`TtsyQIamCmH?{L0bdacTsi?Pq7i~vSV(m+ToyRg4BbnHx|Roc0t4`}LtwoP zu8TmnK-~wUQ8tZ1<&dn0i9l!Vp#yBdTU&ucft8@~ZQ!8*$b!JN65t^?;Bu7|U}+C` qA4C{KG%u|Lc)(*3Fs^{^2RaTEkkAWO!E8v)fn-cUMu83xBSZko>%7qb literal 0 HcmV?d00001 diff --git a/Example/SwiftDBAIDemo/project.yml b/Example/SwiftDBAIDemo/project.yml new file mode 100644 index 0000000..125fb64 --- /dev/null +++ b/Example/SwiftDBAIDemo/project.yml @@ -0,0 +1,26 @@ +name: SwiftDBAIDemo +options: + bundleIdPrefix: com.swiftdbai.demo + deploymentTarget: + iOS: "17.0" + xcodeVersion: "16.0" + createIntermediateGroups: true +packages: + SwiftDBAI: + path: ../.. +targets: + SwiftDBAIDemo: + type: application + platform: iOS + sources: + - SwiftDBAIDemo + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.swiftdbai.demo + MARKETING_VERSION: "1.0" + CURRENT_PROJECT_VERSION: "1" + SWIFT_VERSION: "6.0" + INFOPLIST_GENERATION: true + GENERATE_INFOPLIST_FILE: true + dependencies: + - package: SwiftDBAI diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..e5d0f4d --- /dev/null +++ b/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version: 6.1 + +import PackageDescription + +let package = Package( + name: "SwiftDBAI", + platforms: [ + .iOS(.v17), + .macOS(.v14), + .visionOS(.v1), + ], + products: [ + .library( + name: "SwiftDBAI", + targets: ["SwiftDBAI"] + ), + ], + dependencies: [ + .package(url: "https://github.com/groue/GRDB.swift.git", from: "7.0.0"), + .package(url: "https://github.com/huggingface/AnyLanguageModel.git", from: "0.8.0"), + .package(url: "https://github.com/nalexn/ViewInspector.git", from: "0.10.0"), + ], + targets: [ + .target( + name: "SwiftDBAI", + dependencies: [ + .product(name: "GRDB", package: "GRDB.swift"), + .product(name: "AnyLanguageModel", package: "AnyLanguageModel"), + ], + swiftSettings: [ + .swiftLanguageMode(.v6), + ] + ), + .testTarget( + name: "SwiftDBAITests", + dependencies: ["SwiftDBAI", "ViewInspector"], + swiftSettings: [ + .swiftLanguageMode(.v6), + ] + ), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..9561798 --- /dev/null +++ b/README.md @@ -0,0 +1,346 @@ +# SwiftDBAI + +A Swift package that adds a natural language query interface to any SQLite database in your iOS, macOS, or visionOS app. Drop in one SwiftUI view and your users can ask questions about their data in plain English. + + +![Swift 6.1+](https://img.shields.io/badge/Swift-6.1+-orange.svg) +![Platforms](https://img.shields.io/badge/Platforms-iOS%2017%20|%20macOS%2014%20|%20visionOS%201-blue.svg) +![License](https://img.shields.io/badge/License-MIT-green.svg) + +## Demo + +| iPhone | iPad | +|---|---| +| ![iPhone](screenshots/iphone-results.png) | ![iPad](screenshots/results-chart.png) | + +| Custom theme | Sheet presentation | +|---|---| +| ![Custom theme](screenshots/custom-theme.png) | ![Sheet presentation](screenshots/sheet-presentation.png) | + +The demo app is at `Example/SwiftDBAIDemo/`. It points SwiftDBAI at a real database of ~2,000 top GitHub repos with live star counts. Generate the Xcode project with [xcodegen](https://github.com/yonaskolb/XcodeGen): + +``` +cd Example/SwiftDBAIDemo && xcodegen generate +``` + +For a real-world integration, see [SwiftDBAI added to NetNewsWire](https://github.com/krishkumar/NetNewsWire) -- natural language queries against an RSS reader's article database. + +## Features + +- Drop-in SwiftUI chat view (`DataChatView`) -- one line to add a database chat UI +- Headless `ChatEngine` for programmatic / non-UI use +- LLM-agnostic via [AnyLanguageModel](https://github.com/huggingface/AnyLanguageModel) -- works with OpenAI, Anthropic, Gemini, Ollama, llama.cpp, or any OpenAI-compatible endpoint +- Automatic schema introspection -- no manual annotations required +- Safety-first: read-only by default, operation allowlists, table-level mutation policies, destructive operation confirmation delegate +- Configurable query timeouts, context windows, and custom validators + +## Installation + +Add SwiftDBAI via Swift Package Manager: + +```swift +dependencies: [ + .package(url: "https://github.com/krishkumar/SwiftDBAI.git", from: "1.0.0"), +] +``` + +Then add the dependency to your target: + +```swift +.target( + name: "MyApp", + dependencies: ["SwiftDBAI"] +) +``` + +## Quick Start + +Drop a full chat UI into any SwiftUI view with `DataChatView`: + +```swift +import SwiftDBAI +import AnyLanguageModel + +struct ContentView: View { + var body: some View { + DataChatView( + databasePath: "/path/to/mydata.sqlite", + model: OllamaLanguageModel(model: "llama3") + ) + } +} +``` + +That's it. `DataChatView` opens the database, introspects the schema, and renders a chat interface. The default mode is **read-only** (SELECT only). + +To pass an existing GRDB connection and customize behavior: + +```swift +DataChatView( + database: myDatabasePool, + model: OpenAILanguageModel(apiKey: "sk-...", model: "gpt-4o"), + allowlist: .standard, + additionalContext: "This database stores a recipe app's data.", + maxSummaryRows: 100 +) +``` + +## Presentation + +`DataChatSheet` wraps `DataChatView` in a `NavigationStack` with a title and Done button, ready for any presentation context. + +**SwiftUI sheet:** + +```swift +.sheet(isPresented: $showChat) { + DataChatSheet( + databasePath: "/path/to/mydata.sqlite", + model: OllamaLanguageModel(model: "llama3") + ) +} + +// Or use the convenience modifier: +.dataChatSheet(isPresented: $showChat, databasePath: path, model: myLLM) +``` + +**SwiftUI full-screen cover:** + +```swift +.fullScreenCover(isPresented: $showChat) { + DataChatSheet(databasePath: path, model: myLLM) +} + +// Or use the convenience modifier: +.dataChatFullScreen(isPresented: $showChat, databasePath: path, model: myLLM) +``` + +**UIKit modal:** + +```swift +let vc = DataChatViewController(databasePath: path, model: myLLM) +present(vc, animated: true) +``` + +**UIKit navigation push:** + +```swift +let vc = DataChatViewController(databasePath: path, model: myLLM) +navigationController?.pushViewController(vc, animated: true) +``` + +All presentation wrappers accept the same parameters as `DataChatView` (`allowlist`, `additionalContext`, etc.) plus a `title` for the navigation bar. + +## Tool Calling + +If your app already has an LLM integration, use `DatabaseTool` to register SwiftDBAI as a tool the LLM can call. No extra LLM needed -- your existing one generates SQL, SwiftDBAI validates and executes it. + +```swift +import SwiftDBAI + +// 1. Create the tool +let tool = try await DatabaseTool(databasePath: "/path/to/mydata.sqlite") + +// 2. Add schema context to your LLM's system prompt +let systemPrompt = "You are a helpful assistant.\n\n" + tool.systemPromptSnippet + +// 3. Register with your LLM (OpenAI function calling example) +let functionDef = tool.openAIFunctionDefinition +// Pass to OpenAI's tools parameter... + +// 4. When the LLM calls the tool +let result = try tool.execute(sql: "SELECT * FROM users WHERE active = 1") +result.jsonString // return to LLM as tool response +result.markdownTable // display to user +result.rowCount // 42 +result.executionTime // 0.003 +``` + +SQL is validated against a read-only allowlist before execution. INSERT, UPDATE, DELETE, and DROP are rejected. + +![DatabaseTool API](screenshots/tool-api.png) + +## Headless / Programmatic Use + +Use `ChatEngine` directly when you don't need a UI: + +```swift +import SwiftDBAI +import AnyLanguageModel +import GRDB + +let pool = try DatabasePool(path: "/path/to/mydata.sqlite") +let engine = ChatEngine( + database: pool, + model: OpenAILanguageModel(apiKey: "sk-...", model: "gpt-4o") +) + +let response = try await engine.send("How many users signed up this week?") +print(response.summary) // "There were 42 new signups this week." +print(response.sql) // Optional("SELECT COUNT(*) FROM users WHERE ...") +``` + +`ChatEngine` also accepts a `ProviderConfiguration` for convenience: + +```swift +let engine = ChatEngine( + database: pool, + provider: .anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514") +) +``` + +For fine-grained control, pass a `ChatEngineConfiguration`: + +```swift +var config = ChatEngineConfiguration( + queryTimeout: 10, + contextWindowSize: 20, + maxSummaryRows: 100, + additionalContext: "The 'status' column uses: 'active', 'inactive', 'suspended'." +) + +let engine = ChatEngine( + database: pool, + model: model, + allowlist: .standard, + configuration: config +) +``` + +## Choosing a Provider + +SwiftDBAI works with any provider supported by AnyLanguageModel. Use `ProviderConfiguration` factory methods or construct model instances directly. + +```swift +// OpenAI +let config = ProviderConfiguration.openAI(apiKey: "sk-...", model: "gpt-4o") + +// Anthropic +let config = ProviderConfiguration.anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514") + +// Gemini +let config = ProviderConfiguration.gemini(apiKey: "AIza...", model: "gemini-2.0-flash") + +// Ollama (local, no API key needed) +let config = ProviderConfiguration.ollama(model: "llama3.2") + +// llama.cpp (local) +let config = ProviderConfiguration.llamaCpp(model: "default") + +// Any OpenAI-compatible endpoint +let config = ProviderConfiguration.openAICompatible( + apiKey: "your-key", + model: "llama-3.1-70b", + baseURL: URL(string: "https://api.together.xyz/v1/")! +) +``` + +Use with ChatEngine: + +```swift +let engine = ChatEngine(database: pool, provider: config) +// or +let engine = ChatEngine(database: pool, model: config.makeModel()) +``` + +API keys can also come from environment variables: + +```swift +let config = ProviderConfiguration.fromEnvironment( + provider: .openAI, + environmentVariable: "OPENAI_API_KEY", + model: "gpt-4o" +) +``` + +## Safety and Mutation Control + +### Operation Allowlist + +By default, only SELECT queries are allowed. Opt in to writes explicitly: + +| Preset | Allowed Operations | +|---|---| +| `.readOnly` (default) | SELECT | +| `.standard` | SELECT, INSERT, UPDATE | +| `.unrestricted` | SELECT, INSERT, UPDATE, DELETE | + +```swift +// Custom allowlist +let allowlist = OperationAllowlist([.select, .insert]) +``` + +### Mutation Policy + +For table-level control, use `MutationPolicy`: + +```swift +// Allow INSERT and UPDATE only on specific tables +let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders", "order_items"] +) + +let engine = ChatEngine( + database: pool, + model: model, + mutationPolicy: policy +) +``` + +Presets: `.readOnly`, `.readWrite`, `.unrestricted`. + +### Confirmation Delegate + +Destructive operations (DELETE, DROP, ALTER, TRUNCATE) require confirmation through a `ToolExecutionDelegate`: + +```swift +struct MyDelegate: ToolExecutionDelegate { + func confirmDestructiveOperation( + _ context: DestructiveOperationContext + ) async -> Bool { + // Present confirmation UI, return true to proceed + return await showConfirmationDialog(context.description) + } +} + +let engine = ChatEngine( + database: pool, + model: model, + allowlist: .unrestricted, + delegate: MyDelegate() +) +``` + +Without a delegate, destructive operations throw `SwiftDBAIError.confirmationRequired` so you can handle confirmation in your own flow. + +Built-in delegates: `AutoApproveDelegate` (testing only), `RejectAllDelegate` (safest). + +## Architecture + +``` +User Question + | + v +ChatEngine + |-- SchemaIntrospector (auto-discovers tables, columns, keys, indexes) + |-- PromptBuilder (builds LLM system prompt with schema context) + |-- LanguageModel (generates SQL via AnyLanguageModel) + |-- SQLQueryParser (parses and validates against allowlist/policy) + |-- QueryValidator (optional custom validators) + |-- GRDB (executes SQL against SQLite) + |-- TextSummaryRenderer (summarizes results via LLM) + v +ChatResponse { summary, sql, queryResult } +``` + +`DataChatView` wraps this pipeline in a SwiftUI view with `ChatViewModel` managing state. + +## Requirements + +- iOS 17.0+ / macOS 14.0+ / visionOS 1.0+ +- Swift 6.1+ +- Xcode 16+ + +## License + +MIT. See [LICENSE](LICENSE) for details. diff --git a/Sources/SwiftDBAI/Config/ChatEngineConfiguration.swift b/Sources/SwiftDBAI/Config/ChatEngineConfiguration.swift new file mode 100644 index 0000000..fad6f62 --- /dev/null +++ b/Sources/SwiftDBAI/Config/ChatEngineConfiguration.swift @@ -0,0 +1,113 @@ +// ChatEngineConfiguration.swift +// SwiftDBAI +// +// Configurable settings for ChatEngine behavior — timeouts, context window, +// summary limits, and custom query validation. + +import Foundation + +/// Configuration for ``ChatEngine`` behavior. +/// +/// Use this to tune timeouts, conversation context windows, and attach +/// custom query validators. +/// +/// ```swift +/// var config = ChatEngineConfiguration() +/// config.queryTimeout = 10 // 10-second SQL timeout +/// config.contextWindowSize = 20 // Keep last 20 messages for LLM context +/// config.maxSummaryRows = 100 // Summarize up to 100 rows +/// +/// let engine = ChatEngine( +/// database: db, +/// model: model, +/// configuration: config +/// ) +/// ``` +public struct ChatEngineConfiguration: Sendable { + + // MARK: - Query Execution + + /// Maximum time (in seconds) to wait for a SQL query to execute. + /// + /// If the query exceeds this duration, a ``ChatEngineError/queryTimedOut`` + /// error is thrown. Set to `nil` to disable the timeout (not recommended + /// for user-facing apps). Defaults to 30 seconds. + public var queryTimeout: TimeInterval? + + // MARK: - Conversation Context + + /// Maximum number of conversation messages to include when building + /// LLM context for follow-up queries. + /// + /// Only the most recent `contextWindowSize` messages are sent to the LLM. + /// Older messages are still retained in ``ChatEngine/messages`` for UI + /// display but do not consume LLM tokens. + /// + /// Set to `nil` for unlimited context (all history is always sent). + /// Defaults to 50 messages. + public var contextWindowSize: Int? + + // MARK: - Rendering + + /// Maximum number of rows to include when generating text summaries. + /// Defaults to 50. + public var maxSummaryRows: Int + + // MARK: - LLM Context + + /// Optional extra instructions appended to the LLM system prompt. + /// + /// Use this to provide business-specific terminology, query hints, + /// or domain constraints. For example: + /// ```swift + /// config.additionalContext = "The 'status' column uses: 'active', 'inactive', 'suspended'." + /// ``` + public var additionalContext: String? + + // MARK: - Validation + + /// Custom query validators that run after the built-in allowlist check. + /// + /// Use ``addValidator(_:)`` to add validators. They are executed in order; + /// the first validator to throw stops execution. + public private(set) var validators: [any QueryValidator] = [] + + // MARK: - Initialization + + /// Creates a configuration with the given settings. + /// + /// - Parameters: + /// - queryTimeout: SQL execution timeout in seconds. Defaults to 30. + /// - contextWindowSize: Max messages for LLM context. Defaults to 50. + /// - maxSummaryRows: Max rows for text summaries. Defaults to 50. + /// - additionalContext: Extra LLM system prompt instructions. + public init( + queryTimeout: TimeInterval? = 30, + contextWindowSize: Int? = 50, + maxSummaryRows: Int = 50, + additionalContext: String? = nil + ) { + self.queryTimeout = queryTimeout + self.contextWindowSize = contextWindowSize + self.maxSummaryRows = maxSummaryRows + self.additionalContext = additionalContext + } + + /// The default configuration: 30s timeout, 50-message context window, + /// 50-row summaries, no additional context, no custom validators. + public static let `default` = ChatEngineConfiguration() + + // MARK: - Mutating Helpers + + /// Appends a custom query validator. + /// + /// Validators run after the built-in allowlist and dangerous-keyword checks. + /// They receive the parsed SQL and can throw to reject a query. + /// + /// ```swift + /// config.addValidator(TableAllowlistValidator(allowedTables: ["users", "orders"])) + /// ``` + public mutating func addValidator(_ validator: any QueryValidator) { + validators.append(validator) + } +} diff --git a/Sources/SwiftDBAI/Config/LocalProviderConfiguration.swift b/Sources/SwiftDBAI/Config/LocalProviderConfiguration.swift new file mode 100644 index 0000000..c19f3cf --- /dev/null +++ b/Sources/SwiftDBAI/Config/LocalProviderConfiguration.swift @@ -0,0 +1,336 @@ +// LocalProviderConfiguration.swift +// SwiftDBAI +// +// Configuration and endpoint discovery for local/self-hosted LLM providers +// (Ollama, llama.cpp). Wraps AnyLanguageModel's OllamaLanguageModel and +// OpenAILanguageModel with convenient factory methods and health checking. + +import AnyLanguageModel +import Foundation + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +// MARK: - Local Provider Endpoint + +/// Represents a discovered local LLM endpoint with its connection status. +public struct LocalProviderEndpoint: Sendable, Equatable { + /// The base URL of the local provider. + public let baseURL: URL + + /// The provider type (Ollama or llama.cpp). + public let providerType: LocalProviderType + + /// Whether the endpoint was reachable at discovery time. + public let isReachable: Bool + + /// The list of available models, if the endpoint supports model listing. + public let availableModels: [String] + + /// Human-readable description of the endpoint. + public var description: String { + let status = isReachable ? "reachable" : "unreachable" + return "\(providerType.rawValue) at \(baseURL.absoluteString) (\(status), \(availableModels.count) models)" + } +} + +/// The type of local LLM provider. +public enum LocalProviderType: String, Sendable, Hashable, CaseIterable { + /// Ollama — runs models locally via `ollama serve`. + /// Default endpoint: http://localhost:11434 + case ollama + + /// llama.cpp server — runs GGUF models via `llama-server`. + /// Default endpoint: http://localhost:8080 + /// Exposes an OpenAI-compatible API. + case llamaCpp = "llama.cpp" +} + +// MARK: - Local Provider Discovery + +/// Discovers and validates local LLM provider endpoints. +/// +/// Use `LocalProviderDiscovery` to automatically find running Ollama or llama.cpp +/// instances on the local machine, check their health, and list available models. +/// +/// ```swift +/// // Check if Ollama is running +/// let isRunning = await LocalProviderDiscovery.isOllamaRunning() +/// +/// // Discover all local providers +/// let endpoints = await LocalProviderDiscovery.discoverAll() +/// for endpoint in endpoints where endpoint.isReachable { +/// print("Found \(endpoint.description)") +/// } +/// +/// // List models available on Ollama +/// let models = await LocalProviderDiscovery.listOllamaModels() +/// ``` +public enum LocalProviderDiscovery { + + /// Default Ollama endpoint. + public static let defaultOllamaURL = URL(string: "http://localhost:11434")! + + /// Default llama.cpp server endpoint. + public static let defaultLlamaCppURL = URL(string: "http://localhost:8080")! + + /// Well-known ports to probe for local providers. + /// Ollama: 11434, llama.cpp: 8080 + private static let wellKnownEndpoints: [(URL, LocalProviderType)] = [ + (defaultOllamaURL, .ollama), + (defaultLlamaCppURL, .llamaCpp), + ] + + // MARK: - Health Checks + + /// Checks if an Ollama instance is reachable at the given URL. + /// + /// Sends a GET request to the Ollama root endpoint and checks for a 200 response. + /// + /// - Parameter baseURL: The Ollama base URL. Defaults to `http://localhost:11434`. + /// - Parameter timeout: Connection timeout in seconds. Defaults to 3. + /// - Returns: `true` if the Ollama server responded successfully. + public static func isOllamaRunning( + at baseURL: URL = defaultOllamaURL, + timeout: TimeInterval = 3 + ) async -> Bool { + await checkEndpointHealth(baseURL, timeout: timeout) + } + + /// Checks if a llama.cpp server is reachable at the given URL. + /// + /// Sends a GET request to the `/health` endpoint and checks for a 200 response. + /// + /// - Parameter baseURL: The llama.cpp base URL. Defaults to `http://localhost:8080`. + /// - Parameter timeout: Connection timeout in seconds. Defaults to 3. + /// - Returns: `true` if the llama.cpp server responded successfully. + public static func isLlamaCppRunning( + at baseURL: URL = defaultLlamaCppURL, + timeout: TimeInterval = 3 + ) async -> Bool { + let healthURL = baseURL.appendingPathComponent("health") + return await checkEndpointHealth(healthURL, timeout: timeout) + } + + /// Checks if any endpoint at the given URL responds to HTTP requests. + /// + /// - Parameters: + /// - url: The URL to probe. + /// - timeout: Connection timeout in seconds. + /// - Returns: `true` if the endpoint returned an HTTP response with status 200. + private static func checkEndpointHealth( + _ url: URL, + timeout: TimeInterval + ) async -> Bool { + let config = URLSessionConfiguration.ephemeral + config.timeoutIntervalForRequest = timeout + config.timeoutIntervalForResource = timeout + let session = URLSession(configuration: config) + + do { + let (_, response) = try await session.data(from: url) + if let httpResponse = response as? HTTPURLResponse { + return httpResponse.statusCode == 200 + } + return false + } catch { + return false + } + } + + // MARK: - Model Listing + + /// Lists models available on an Ollama instance. + /// + /// Calls the Ollama `/api/tags` endpoint to retrieve the list of + /// locally installed models. + /// + /// - Parameter baseURL: The Ollama base URL. Defaults to `http://localhost:11434`. + /// - Parameter timeout: Request timeout in seconds. Defaults to 5. + /// - Returns: An array of model name strings, or an empty array if unreachable. + public static func listOllamaModels( + at baseURL: URL = defaultOllamaURL, + timeout: TimeInterval = 5 + ) async -> [String] { + let tagsURL = baseURL.appendingPathComponent("api/tags") + let config = URLSessionConfiguration.ephemeral + config.timeoutIntervalForRequest = timeout + config.timeoutIntervalForResource = timeout + let session = URLSession(configuration: config) + + do { + let (data, response) = try await session.data(from: tagsURL) + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 + else { + return [] + } + + let decoded = try JSONDecoder().decode(OllamaTagsResponse.self, from: data) + return decoded.models.map(\.name) + } catch { + return [] + } + } + + /// Lists models available on a llama.cpp server via its OpenAI-compatible endpoint. + /// + /// Calls `/v1/models` which llama.cpp exposes when running with + /// `--api-key` or in default mode. + /// + /// - Parameter baseURL: The llama.cpp base URL. Defaults to `http://localhost:8080`. + /// - Parameter timeout: Request timeout in seconds. Defaults to 5. + /// - Returns: An array of model ID strings, or an empty array if unreachable. + public static func listLlamaCppModels( + at baseURL: URL = defaultLlamaCppURL, + timeout: TimeInterval = 5 + ) async -> [String] { + let modelsURL = baseURL.appendingPathComponent("v1/models") + let config = URLSessionConfiguration.ephemeral + config.timeoutIntervalForRequest = timeout + config.timeoutIntervalForResource = timeout + let session = URLSession(configuration: config) + + do { + let (data, response) = try await session.data(from: modelsURL) + guard let httpResponse = response as? HTTPURLResponse, + httpResponse.statusCode == 200 + else { + return [] + } + + let decoded = try JSONDecoder().decode(OpenAIModelsResponse.self, from: data) + return decoded.data.map(\.id) + } catch { + return [] + } + } + + // MARK: - Full Discovery + + /// Discovers all running local LLM providers by probing well-known endpoints. + /// + /// Probes Ollama (port 11434) and llama.cpp (port 8080) concurrently, + /// returning their status and available models. + /// + /// ```swift + /// let endpoints = await LocalProviderDiscovery.discoverAll() + /// for endpoint in endpoints where endpoint.isReachable { + /// print("Found: \(endpoint.description)") + /// } + /// ``` + /// + /// - Parameter timeout: Connection timeout per endpoint in seconds. Defaults to 3. + /// - Returns: An array of `LocalProviderEndpoint` for each probed location. + public static func discoverAll( + timeout: TimeInterval = 3 + ) async -> [LocalProviderEndpoint] { + await withTaskGroup(of: LocalProviderEndpoint.self, returning: [LocalProviderEndpoint].self) { group in + for (url, providerType) in wellKnownEndpoints { + group.addTask { + await discover(providerType: providerType, at: url, timeout: timeout) + } + } + + var results: [LocalProviderEndpoint] = [] + for await endpoint in group { + results.append(endpoint) + } + return results + } + } + + /// Discovers a specific local provider at the given URL. + /// + /// - Parameters: + /// - providerType: The type of provider to probe. + /// - baseURL: The base URL to check. + /// - timeout: Connection timeout in seconds. + /// - Returns: A `LocalProviderEndpoint` with reachability and model info. + public static func discover( + providerType: LocalProviderType, + at baseURL: URL, + timeout: TimeInterval = 3 + ) async -> LocalProviderEndpoint { + switch providerType { + case .ollama: + let reachable = await isOllamaRunning(at: baseURL, timeout: timeout) + let models = reachable ? await listOllamaModels(at: baseURL) : [] + return LocalProviderEndpoint( + baseURL: baseURL, + providerType: .ollama, + isReachable: reachable, + availableModels: models + ) + + case .llamaCpp: + let reachable = await isLlamaCppRunning(at: baseURL, timeout: timeout) + let models = reachable ? await listLlamaCppModels(at: baseURL) : [] + return LocalProviderEndpoint( + baseURL: baseURL, + providerType: .llamaCpp, + isReachable: reachable, + availableModels: models + ) + } + } + + /// Discovers a specific local provider at a custom URL and port. + /// + /// Use this for non-standard configurations where Ollama or llama.cpp + /// is running on a custom host or port. + /// + /// ```swift + /// let endpoint = await LocalProviderDiscovery.discover( + /// providerType: .ollama, + /// host: "192.168.1.100", + /// port: 11434 + /// ) + /// ``` + /// + /// - Parameters: + /// - providerType: The provider type. + /// - host: The hostname or IP address. + /// - port: The port number. + /// - timeout: Connection timeout in seconds. Defaults to 3. + /// - Returns: A `LocalProviderEndpoint` with reachability and model info. + public static func discover( + providerType: LocalProviderType, + host: String, + port: Int, + timeout: TimeInterval = 3 + ) async -> LocalProviderEndpoint { + guard let url = URL(string: "http://\(host):\(port)") else { + return LocalProviderEndpoint( + baseURL: URL(string: "http://\(host):\(port)")!, + providerType: providerType, + isReachable: false, + availableModels: [] + ) + } + return await discover(providerType: providerType, at: url, timeout: timeout) + } +} + +// MARK: - JSON Response Types + +/// Response from Ollama's `/api/tags` endpoint. +private struct OllamaTagsResponse: Decodable, Sendable { + let models: [OllamaModelInfo] +} + +/// Individual model info from Ollama's tags endpoint. +private struct OllamaModelInfo: Decodable, Sendable { + let name: String +} + +/// Response from the OpenAI-compatible `/v1/models` endpoint. +private struct OpenAIModelsResponse: Decodable, Sendable { + let data: [OpenAIModelInfo] +} + +/// Individual model info from the OpenAI models endpoint. +private struct OpenAIModelInfo: Decodable, Sendable { + let id: String +} diff --git a/Sources/SwiftDBAI/Config/MutationPolicy.swift b/Sources/SwiftDBAI/Config/MutationPolicy.swift new file mode 100644 index 0000000..93fac7d --- /dev/null +++ b/Sources/SwiftDBAI/Config/MutationPolicy.swift @@ -0,0 +1,148 @@ +// MutationPolicy.swift +// SwiftDBAI +// +// Defines which mutation operations are permitted and optionally restricts +// them to specific tables. Wraps OperationAllowlist with table-level granularity. + +import Foundation + +/// Controls which SQL mutation operations the LLM may generate and, +/// optionally, which tables those mutations may target. +/// +/// `MutationPolicy` builds on ``OperationAllowlist`` by adding per-table +/// restrictions. The default policy is **read-only** — no mutations are +/// allowed on any table. Write operations require explicit opt-in. +/// +/// ```swift +/// // Read-only (default) — only SELECT is allowed +/// let readOnly = MutationPolicy.readOnly +/// +/// // Allow INSERT and UPDATE on specific tables only +/// let restricted = MutationPolicy( +/// allowedOperations: [.insert, .update], +/// allowedTables: ["orders", "order_items"] +/// ) +/// +/// // Allow INSERT and UPDATE on all tables +/// let broad = MutationPolicy(allowedOperations: [.insert, .update]) +/// +/// // Full access including DELETE (requires confirmation) +/// let full = MutationPolicy.unrestricted +/// ``` +public struct MutationPolicy: Sendable, Equatable { + + // MARK: - Properties + + /// The underlying operation allowlist (always includes SELECT). + public let operationAllowlist: OperationAllowlist + + /// Optional set of table names that mutations may target. + /// + /// When `nil`, mutations are allowed on all tables (subject to + /// ``operationAllowlist``). When non-nil, mutation operations + /// (INSERT, UPDATE, DELETE) are only permitted on the listed tables. + /// SELECT queries are never restricted by this property. + public let allowedMutationTables: Set? + + /// When `true`, destructive operations (DELETE) require explicit user + /// confirmation before execution, even when the operation is allowed. + /// Defaults to `true`. + public let requiresDestructiveConfirmation: Bool + + // MARK: - Initialization + + /// Creates a mutation policy with the given operations and optional table restrictions. + /// + /// SELECT is always implicitly included — you cannot create a policy + /// that disallows reads. + /// + /// - Parameters: + /// - allowedOperations: The mutation operations to permit (INSERT, UPDATE, DELETE). + /// SELECT is always allowed regardless of this parameter. + /// - allowedTables: Optional set of table names mutations may target. + /// Pass `nil` to allow mutations on all tables. Defaults to `nil`. + /// - requiresDestructiveConfirmation: Whether DELETE requires user confirmation. + /// Defaults to `true`. + public init( + allowedOperations: Set = [], + allowedTables: Set? = nil, + requiresDestructiveConfirmation: Bool = true + ) { + // Always include SELECT + var ops = allowedOperations + ops.insert(.select) + self.operationAllowlist = OperationAllowlist(ops) + self.allowedMutationTables = allowedTables + self.requiresDestructiveConfirmation = requiresDestructiveConfirmation + } + + // MARK: - Presets + + /// Read-only policy: only SELECT queries are allowed. This is the default. + public static let readOnly = MutationPolicy() + + /// Standard read-write: SELECT, INSERT, and UPDATE on all tables. + public static let readWrite = MutationPolicy( + allowedOperations: [.insert, .update] + ) + + /// Unrestricted: all operations including DELETE on all tables. + /// DELETE still requires confirmation by default. + public static let unrestricted = MutationPolicy( + allowedOperations: [.insert, .update, .delete] + ) + + // MARK: - Validation + + /// Returns `true` if the given operation is permitted by this policy. + public func isOperationAllowed(_ operation: SQLOperation) -> Bool { + operationAllowlist.isAllowed(operation) + } + + /// Returns `true` if the given mutation operation is permitted on the + /// specified table. + /// + /// SELECT operations always return `true` regardless of table restrictions. + /// For mutation operations, this checks both the operation allowlist and + /// the table restrictions (if any). + /// + /// - Parameters: + /// - operation: The SQL operation type. + /// - table: The target table name (case-insensitive comparison). + /// - Returns: Whether the operation is allowed on the given table. + public func isAllowed(operation: SQLOperation, on table: String) -> Bool { + // SELECT is always allowed + guard operation != .select else { return true } + + // Check operation allowlist first + guard operationAllowlist.isAllowed(operation) else { return false } + + // If no table restrictions, the operation is allowed + guard let allowedTables = allowedMutationTables else { return true } + + // Case-insensitive table name check + let lowerTable = table.lowercased() + return allowedTables.contains { $0.lowercased() == lowerTable } + } + + /// Returns `true` if the given operation requires user confirmation. + public func requiresConfirmation(for operation: SQLOperation) -> Bool { + operation == .delete && requiresDestructiveConfirmation + } + + /// Returns a human-readable description for inclusion in the LLM system prompt. + func describeForLLM() -> String { + var desc = operationAllowlist.describeForLLM() + + if let tables = allowedMutationTables, !tables.isEmpty { + let sorted = tables.sorted() + desc += " Mutations (INSERT/UPDATE/DELETE) are restricted to these tables only: \(sorted.joined(separator: ", "))." + } + + if requiresDestructiveConfirmation && operationAllowlist.isAllowed(.delete) { + desc += " DELETE operations require user confirmation before execution." + } + + return desc + } +} diff --git a/Sources/SwiftDBAI/Config/OnDeviceProviderConfiguration.swift b/Sources/SwiftDBAI/Config/OnDeviceProviderConfiguration.swift new file mode 100644 index 0000000..e4632c0 --- /dev/null +++ b/Sources/SwiftDBAI/Config/OnDeviceProviderConfiguration.swift @@ -0,0 +1,866 @@ +// OnDeviceProviderConfiguration.swift +// SwiftDBAI +// +// Configuration for on-device LLM providers (CoreML, MLX) that run models +// locally on Apple silicon. These providers enable fully offline, +// privacy-sensitive deployments where no data leaves the device. +// +// Both CoreML and MLX models are provided by AnyLanguageModel behind +// conditional compilation flags (#if CoreML, #if MLX). This configuration +// layer wraps their setup with convenient factory methods and integrates +// them into the SwiftDBAI ChatEngine pipeline. + +import AnyLanguageModel +import Foundation +import GRDB + +// MARK: - On-Device Provider Type + +/// The type of on-device LLM provider. +public enum OnDeviceProviderType: String, Sendable, Hashable, CaseIterable { + /// CoreML — runs compiled .mlmodelc models on-device using Apple's CoreML framework. + /// Requires pre-compiled models and supports CPU, GPU, and Neural Engine compute units. + case coreML + + /// MLX — runs HuggingFace models on Apple silicon using the MLX framework. + /// Models are automatically downloaded and cached. Supports quantized models + /// (e.g., 4-bit) for efficient memory usage. + case mlx +} + +// MARK: - CoreML Configuration + +/// Configuration for loading and running a CoreML language model on-device. +/// +/// CoreML models must be pre-compiled to `.mlmodelc` format before use. +/// The model runs entirely on-device using CPU, GPU, and/or Neural Engine +/// depending on the `computeUnits` setting. +/// +/// ```swift +/// let config = CoreMLProviderConfiguration( +/// modelURL: Bundle.main.url(forResource: "MyModel", withExtension: "mlmodelc")!, +/// computeUnits: .all +/// ) +/// ``` +/// +/// - Note: CoreML models are available behind the `#if CoreML` flag in AnyLanguageModel. +/// Ensure your project enables the CoreML build condition. +public struct CoreMLProviderConfiguration: Sendable, Equatable { + + /// The URL to the compiled CoreML model (`.mlmodelc`). + public let modelURL: URL + + /// The compute units to use for inference. + /// + /// - `.all`: Uses the best available hardware (Neural Engine, GPU, CPU). + /// - `.cpuOnly`: Forces CPU-only inference. Useful for debugging. + /// - `.cpuAndGPU`: Uses CPU and GPU but not the Neural Engine. + /// - `.cpuAndNeuralEngine`: Uses CPU and Neural Engine. + public let computeUnits: ComputeUnitPreference + + /// Maximum number of tokens the model can generate per response. + /// Defaults to 2048. + public let maxResponseTokens: Int + + /// Whether to use sampling (true) or greedy decoding (false). + /// Defaults to false (greedy) for more deterministic SQL generation. + public let useSampling: Bool + + /// Temperature for sampling. Only used when `useSampling` is true. + /// Lower values produce more focused output. Defaults to 0.1. + public let temperature: Double + + /// Creates a CoreML provider configuration. + /// + /// - Parameters: + /// - modelURL: The URL to a compiled CoreML model (`.mlmodelc`). + /// - computeUnits: The compute units to use. Defaults to `.all`. + /// - maxResponseTokens: Maximum tokens per response. Defaults to 2048. + /// - useSampling: Whether to use sampling vs greedy decoding. Defaults to false. + /// - temperature: Sampling temperature. Defaults to 0.1. + public init( + modelURL: URL, + computeUnits: ComputeUnitPreference = .all, + maxResponseTokens: Int = 2048, + useSampling: Bool = false, + temperature: Double = 0.1 + ) { + self.modelURL = modelURL + self.computeUnits = computeUnits + self.maxResponseTokens = maxResponseTokens + self.useSampling = useSampling + self.temperature = temperature + } + + /// Validates that the model URL points to a compiled CoreML model. + /// + /// - Throws: ``OnDeviceProviderError`` if the URL is invalid. + public func validate() throws { + guard modelURL.pathExtension == "mlmodelc" else { + throw OnDeviceProviderError.invalidModelFormat( + expected: ".mlmodelc", + actual: modelURL.pathExtension + ) + } + + guard FileManager.default.fileExists(atPath: modelURL.path) else { + throw OnDeviceProviderError.modelNotFound(modelURL) + } + } +} + +/// Compute unit preference for CoreML inference. +/// +/// Maps to `MLComputeUnits` in the CoreML framework. +public enum ComputeUnitPreference: String, Sendable, Hashable, CaseIterable { + /// Use all available compute units (Neural Engine, GPU, CPU). + /// This is the recommended setting for production use. + case all + + /// Force CPU-only execution. Useful for debugging or testing. + case cpuOnly + + /// Use CPU and GPU, but not the Neural Engine. + case cpuAndGPU + + /// Use CPU and Neural Engine, but not the GPU. + case cpuAndNeuralEngine +} + +// MARK: - MLX Configuration + +/// Configuration for loading and running an MLX language model on Apple silicon. +/// +/// MLX models are loaded from HuggingFace Hub or a local directory. The MLX +/// framework provides efficient inference on Apple silicon with support for +/// quantized models (4-bit, 8-bit) for reduced memory usage. +/// +/// ```swift +/// // From HuggingFace Hub (auto-downloaded) +/// let config = MLXProviderConfiguration( +/// modelId: "mlx-community/Llama-3.2-3B-Instruct-4bit" +/// ) +/// +/// // From a local directory +/// let config = MLXProviderConfiguration( +/// modelId: "my-local-model", +/// localDirectory: URL(fileURLWithPath: "/path/to/model") +/// ) +/// ``` +/// +/// - Note: MLX models are available behind the `#if MLX` flag in AnyLanguageModel. +/// Ensure your project enables the MLX build condition. +public struct MLXProviderConfiguration: Sendable, Equatable { + + /// The HuggingFace model identifier (e.g., "mlx-community/Llama-3.2-3B-Instruct-4bit"). + public let modelId: String + + /// Optional local directory containing the model files. + /// When set, the model is loaded from this directory instead of downloading from Hub. + public let localDirectory: URL? + + /// GPU memory management configuration. + public let gpuMemory: MLXGPUMemoryConfig + + /// Maximum number of tokens the model can generate per response. + /// Defaults to 2048. + public let maxResponseTokens: Int + + /// Temperature for text generation. Lower values produce more deterministic output. + /// Defaults to 0.1 for SQL generation accuracy. + public let temperature: Double + + /// Top-P (nucleus) sampling threshold. Only tokens with cumulative probability + /// below this threshold are considered. Defaults to 0.95. + public let topP: Double + + /// Repetition penalty to reduce repetitive output. Defaults to 1.1. + public let repetitionPenalty: Double + + /// Creates an MLX provider configuration. + /// + /// - Parameters: + /// - modelId: The HuggingFace model ID or local identifier. + /// - localDirectory: Optional path to a local model directory. + /// - gpuMemory: GPU memory configuration. Defaults to `.automatic`. + /// - maxResponseTokens: Maximum tokens per response. Defaults to 2048. + /// - temperature: Generation temperature. Defaults to 0.1. + /// - topP: Top-P sampling threshold. Defaults to 0.95. + /// - repetitionPenalty: Repetition penalty. Defaults to 1.1. + public init( + modelId: String, + localDirectory: URL? = nil, + gpuMemory: MLXGPUMemoryConfig = .automatic, + maxResponseTokens: Int = 2048, + temperature: Double = 0.1, + topP: Double = 0.95, + repetitionPenalty: Double = 1.1 + ) { + self.modelId = modelId + self.localDirectory = localDirectory + self.gpuMemory = gpuMemory + self.maxResponseTokens = maxResponseTokens + self.temperature = temperature + self.topP = topP + self.repetitionPenalty = repetitionPenalty + } + + /// Validates the configuration parameters. + /// + /// - Throws: ``OnDeviceProviderError`` if the configuration is invalid. + public func validate() throws { + guard !modelId.isEmpty else { + throw OnDeviceProviderError.emptyModelId + } + + if let dir = localDirectory { + guard FileManager.default.fileExists(atPath: dir.path) else { + throw OnDeviceProviderError.modelNotFound(dir) + } + } + + guard temperature >= 0 else { + throw OnDeviceProviderError.invalidParameter( + name: "temperature", + value: "\(temperature)", + reason: "Must be non-negative" + ) + } + + guard topP > 0, topP <= 1.0 else { + throw OnDeviceProviderError.invalidParameter( + name: "topP", + value: "\(topP)", + reason: "Must be between 0 (exclusive) and 1.0 (inclusive)" + ) + } + + guard repetitionPenalty > 0 else { + throw OnDeviceProviderError.invalidParameter( + name: "repetitionPenalty", + value: "\(repetitionPenalty)", + reason: "Must be positive" + ) + } + } + + // MARK: - Well-Known Models + + /// Pre-configured for Llama 3.2 3B Instruct (4-bit quantized). + /// Good balance of quality and memory usage (~2GB RAM). + public static func llama3_2_3B( + localDirectory: URL? = nil, + gpuMemory: MLXGPUMemoryConfig = .automatic + ) -> MLXProviderConfiguration { + MLXProviderConfiguration( + modelId: "mlx-community/Llama-3.2-3B-Instruct-4bit", + localDirectory: localDirectory, + gpuMemory: gpuMemory, + maxResponseTokens: 2048, + temperature: 0.1 + ) + } + + /// Pre-configured for Qwen 2.5 Coder 3B Instruct (4-bit quantized). + /// Optimized for code and SQL generation. + public static func qwen2_5_coder_3B( + localDirectory: URL? = nil, + gpuMemory: MLXGPUMemoryConfig = .automatic + ) -> MLXProviderConfiguration { + MLXProviderConfiguration( + modelId: "mlx-community/Qwen2.5-Coder-3B-Instruct-4bit", + localDirectory: localDirectory, + gpuMemory: gpuMemory, + maxResponseTokens: 2048, + temperature: 0.05 + ) + } + + /// Pre-configured for Phi-3.5 Mini Instruct (4-bit quantized). + /// Compact model suitable for devices with limited memory (~1.5GB RAM). + public static func phi3_5_mini( + localDirectory: URL? = nil, + gpuMemory: MLXGPUMemoryConfig = .automatic + ) -> MLXProviderConfiguration { + MLXProviderConfiguration( + modelId: "mlx-community/Phi-3.5-mini-instruct-4bit", + localDirectory: localDirectory, + gpuMemory: gpuMemory, + maxResponseTokens: 2048, + temperature: 0.1 + ) + } +} + +/// GPU memory management configuration for MLX models. +/// +/// Controls how aggressively the MLX runtime manages GPU buffer caches +/// during active generation and idle phases. +public struct MLXGPUMemoryConfig: Sendable, Equatable { + /// GPU cache limit (in bytes) during active generation. + public let activeCacheLimit: Int + + /// GPU cache limit (in bytes) when idle. + public let idleCacheLimit: Int + + /// Whether to clear cached GPU buffers when eviction is safe. + public let clearCacheOnEviction: Bool + + /// Creates a GPU memory configuration. + /// + /// - Parameters: + /// - activeCacheLimit: Cache limit during active generation (bytes). + /// - idleCacheLimit: Cache limit when idle (bytes). + /// - clearCacheOnEviction: Whether to clear cache on eviction. + public init( + activeCacheLimit: Int, + idleCacheLimit: Int, + clearCacheOnEviction: Bool = true + ) { + self.activeCacheLimit = activeCacheLimit + self.idleCacheLimit = idleCacheLimit + self.clearCacheOnEviction = clearCacheOnEviction + } + + /// Automatically determined based on device physical memory. + /// + /// - Devices with <4GB RAM: 128MB active cache + /// - Devices with <6GB RAM: 256MB active cache + /// - Devices with <8GB RAM: 512MB active cache + /// - Devices with 8GB+ RAM: 768MB active cache + /// - Idle cache: 50MB for all devices + public static var automatic: MLXGPUMemoryConfig { + let ramBytes = ProcessInfo.processInfo.physicalMemory + let ramGB = ramBytes / (1024 * 1024 * 1024) + let active: Int + switch ramGB { + case ..<4: + active = 128_000_000 + case ..<6: + active = 256_000_000 + case ..<8: + active = 512_000_000 + default: + active = 768_000_000 + } + + return .init( + activeCacheLimit: active, + idleCacheLimit: 50_000_000, + clearCacheOnEviction: true + ) + } + + /// Minimal memory configuration for constrained devices. + /// Uses 64MB active cache and 16MB idle cache. + public static var minimal: MLXGPUMemoryConfig { + .init( + activeCacheLimit: 64_000_000, + idleCacheLimit: 16_000_000, + clearCacheOnEviction: true + ) + } + + /// Unconstrained configuration for maximum performance. + /// Leaves GPU cache effectively unlimited. Use when your app + /// can afford maximum memory usage. + public static var unconstrained: MLXGPUMemoryConfig { + .init( + activeCacheLimit: Int.max, + idleCacheLimit: Int.max, + clearCacheOnEviction: false + ) + } +} + +// MARK: - On-Device Provider Errors + +/// Errors specific to on-device provider configuration and model loading. +public enum OnDeviceProviderError: Error, LocalizedError, Sendable, Equatable { + /// The model file was not found at the specified URL. + case modelNotFound(URL) + + /// The model file format is not what was expected. + case invalidModelFormat(expected: String, actual: String) + + /// The model ID is empty. + case emptyModelId + + /// A configuration parameter is invalid. + case invalidParameter(name: String, value: String, reason: String) + + /// The on-device provider is not available on this platform. + /// CoreML requires macOS 15+ / iOS 18+. MLX requires the MLX build flag. + case providerUnavailable(OnDeviceProviderType, reason: String) + + /// Model loading failed with an underlying error. + case modelLoadFailed(reason: String) + + /// Model inference failed. + case inferenceFailed(reason: String) + + public var errorDescription: String? { + switch self { + case .modelNotFound(let url): + return "On-device model not found at: \(url.path)" + case .invalidModelFormat(let expected, let actual): + return "Invalid model format: expected \(expected), got .\(actual)" + case .emptyModelId: + return "Model ID must not be empty" + case .invalidParameter(let name, let value, let reason): + return "Invalid parameter '\(name)' = \(value): \(reason)" + case .providerUnavailable(let type, let reason): + return "\(type.rawValue) provider unavailable: \(reason)" + case .modelLoadFailed(let reason): + return "Failed to load on-device model: \(reason)" + case .inferenceFailed(let reason): + return "On-device inference failed: \(reason)" + } + } +} + +// MARK: - On-Device Inference Pipeline + +/// Manages the on-device model inference pipeline. +/// +/// `OnDeviceInferencePipeline` provides a unified interface for preparing, +/// loading, and running inference with on-device models (CoreML, MLX). +/// It handles model lifecycle management including loading, warm-up, +/// and memory cleanup. +/// +/// ```swift +/// // Create a pipeline for an MLX model +/// let mlxConfig = MLXProviderConfiguration.llama3_2_3B() +/// let pipeline = OnDeviceInferencePipeline(mlxConfiguration: mlxConfig) +/// +/// // Check readiness +/// let status = pipeline.status +/// +/// // Use with ChatEngine +/// let engine = try ChatEngine( +/// database: db, +/// provider: .onDevice(mlx: mlxConfig) +/// ) +/// ``` +public final class OnDeviceInferencePipeline: @unchecked Sendable { + + /// The current status of the on-device inference pipeline. + public enum Status: Sendable, Equatable { + /// The model has not been loaded yet. + case notLoaded + + /// The model is currently being loaded/downloaded. + case loading + + /// The model is loaded and ready for inference. + case ready + + /// The model failed to load. + case failed(String) + } + + /// The type of on-device provider this pipeline uses. + public let providerType: OnDeviceProviderType + + /// The MLX configuration, if this is an MLX pipeline. + public let mlxConfiguration: MLXProviderConfiguration? + + /// The CoreML configuration, if this is a CoreML pipeline. + public let coreMLConfiguration: CoreMLProviderConfiguration? + + /// The current status of the pipeline. + private let _statusLock = NSLock() + private var _status: Status = .notLoaded + + /// The current pipeline status. + public var status: Status { + _statusLock.lock() + defer { _statusLock.unlock() } + return _status + } + + /// Creates an MLX inference pipeline. + /// + /// - Parameter configuration: The MLX model configuration. + public init(mlxConfiguration: MLXProviderConfiguration) { + self.providerType = .mlx + self.mlxConfiguration = mlxConfiguration + self.coreMLConfiguration = nil + } + + /// Creates a CoreML inference pipeline. + /// + /// - Parameter configuration: The CoreML model configuration. + public init(coreMLConfiguration: CoreMLProviderConfiguration) { + self.providerType = .coreML + self.coreMLConfiguration = coreMLConfiguration + self.mlxConfiguration = nil + } + + /// Validates the configuration before attempting to load. + /// + /// Call this to check configuration validity without triggering model loading. + /// + /// - Throws: ``OnDeviceProviderError`` if the configuration is invalid. + public func validateConfiguration() throws { + switch providerType { + case .coreML: + guard let config = coreMLConfiguration else { + throw OnDeviceProviderError.providerUnavailable( + .coreML, + reason: "No CoreML configuration provided" + ) + } + try config.validate() + + case .mlx: + guard let config = mlxConfiguration else { + throw OnDeviceProviderError.providerUnavailable( + .mlx, + reason: "No MLX configuration provided" + ) + } + try config.validate() + } + } + + /// Updates the pipeline status. + internal func setStatus(_ newStatus: Status) { + _statusLock.lock() + _status = newStatus + _statusLock.unlock() + } + + /// Provides recommended generation options optimized for SQL generation + /// based on the pipeline's configuration. + /// + /// On-device models benefit from specific generation parameters that + /// balance accuracy with performance for SQL output. + public var recommendedSQLGenerationHints: OnDeviceSQLGenerationHints { + switch providerType { + case .coreML: + let config = coreMLConfiguration ?? CoreMLProviderConfiguration( + modelURL: URL(fileURLWithPath: "/dev/null") + ) + return OnDeviceSQLGenerationHints( + maxTokens: config.maxResponseTokens, + temperature: config.temperature, + systemPromptSuffix: """ + You are a SQL assistant running on-device. Generate only valid SQLite SQL. + Be concise — output ONLY the SQL query with no explanation. + """, + useSampling: config.useSampling + ) + + case .mlx: + let config = mlxConfiguration ?? .llama3_2_3B() + return OnDeviceSQLGenerationHints( + maxTokens: config.maxResponseTokens, + temperature: config.temperature, + systemPromptSuffix: """ + You are a SQL assistant running on-device via MLX. Generate only valid SQLite SQL. + Be concise — output ONLY the SQL query with no explanation. + """, + useSampling: true + ) + } + } +} + +/// Hints for optimizing SQL generation with on-device models. +/// +/// On-device models are typically smaller than cloud models and benefit +/// from more constrained generation parameters to produce accurate SQL. +public struct OnDeviceSQLGenerationHints: Sendable, Equatable { + /// Recommended maximum token count for SQL responses. + public let maxTokens: Int + + /// Recommended temperature for SQL generation. + public let temperature: Double + + /// Additional system prompt text optimized for on-device SQL generation. + public let systemPromptSuffix: String + + /// Whether to use sampling or greedy decoding. + public let useSampling: Bool +} + +// MARK: - ProviderConfiguration Extension + +extension ProviderConfiguration { + + /// Creates a configuration for an on-device MLX model. + /// + /// MLX models run entirely on Apple silicon using the MLX framework. + /// Models are automatically downloaded from HuggingFace Hub on first use. + /// + /// ```swift + /// // Using a pre-configured model + /// let config = ProviderConfiguration.onDeviceMLX(.llama3_2_3B()) + /// + /// // Using a custom model + /// let config = ProviderConfiguration.onDeviceMLX( + /// MLXProviderConfiguration( + /// modelId: "mlx-community/Qwen2.5-7B-Instruct-4bit", + /// temperature: 0.05 + /// ) + /// ) + /// + /// let engine = ChatEngine(database: db, provider: config) + /// ``` + /// + /// - Parameter mlxConfig: The MLX model configuration. + /// - Returns: A configured `ProviderConfiguration` that wraps the MLX model. + /// + /// - Note: The returned configuration uses `.openAICompatible` as the provider + /// type internally. The actual model is created via MLX APIs when `#if MLX` is + /// available. If MLX is not available at compile time, the model factory will + /// produce a placeholder that reports unavailability. + public static func onDeviceMLX( + _ mlxConfig: MLXProviderConfiguration + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAICompatible, + model: mlxConfig.modelId, + apiKeyProvider: { "" }, + baseURL: nil, + apiVersion: nil, + betas: nil, + openAIVariant: nil + ) + } + + /// Creates a configuration for an on-device CoreML model. + /// + /// CoreML models must be pre-compiled to `.mlmodelc` format. + /// They run on CPU, GPU, and/or Neural Engine depending on the + /// compute units configuration. + /// + /// ```swift + /// let modelURL = Bundle.main.url(forResource: "SQLModel", withExtension: "mlmodelc")! + /// let config = ProviderConfiguration.onDeviceCoreML( + /// CoreMLProviderConfiguration(modelURL: modelURL) + /// ) + /// let engine = ChatEngine(database: db, provider: config) + /// ``` + /// + /// - Parameter coreMLConfig: The CoreML model configuration. + /// - Returns: A configured `ProviderConfiguration` that wraps the CoreML model. + /// + /// - Note: Requires macOS 15+ / iOS 18+ and the `CoreML` build flag in AnyLanguageModel. + public static func onDeviceCoreML( + _ coreMLConfig: CoreMLProviderConfiguration + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAICompatible, + model: coreMLConfig.modelURL.lastPathComponent, + apiKeyProvider: { "" }, + baseURL: nil, + apiVersion: nil, + betas: nil, + openAIVariant: nil + ) + } +} + +// MARK: - ChatEngine On-Device Convenience + +extension ChatEngine { + + /// Creates a ChatEngine with an on-device MLX model. + /// + /// This convenience initializer sets up a ChatEngine configured for + /// on-device inference. It validates the MLX configuration and creates + /// an inference pipeline. + /// + /// ```swift + /// let engine = try ChatEngine.onDevice( + /// database: db, + /// mlx: .llama3_2_3B() + /// ) + /// let response = try await engine.send("How many users are there?") + /// ``` + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - mlx: The MLX model configuration. + /// - allowlist: SQL operations allowed. Defaults to read-only. + /// - configuration: Engine configuration. + /// - Returns: A configured `ChatEngine` instance. + /// - Throws: ``OnDeviceProviderError`` if the configuration is invalid. + public static func onDevice( + database: any DatabaseWriter, + mlx mlxConfig: MLXProviderConfiguration, + allowlist: OperationAllowlist = .readOnly, + configuration: ChatEngineConfiguration = .default + ) throws -> ChatEngine { + // Validate configuration + try mlxConfig.validate() + + let pipeline = OnDeviceInferencePipeline(mlxConfiguration: mlxConfig) + + // Build a ChatEngineConfiguration that includes on-device hints + var engineConfig = configuration + let hints = pipeline.recommendedSQLGenerationHints + if engineConfig.additionalContext == nil { + engineConfig.additionalContext = hints.systemPromptSuffix + } else { + engineConfig.additionalContext! += "\n\n" + hints.systemPromptSuffix + } + + let providerConfig = ProviderConfiguration.onDeviceMLX(mlxConfig) + + return ChatEngine( + database: database, + provider: providerConfig, + allowlist: allowlist, + configuration: engineConfig + ) + } + + /// Creates a ChatEngine with an on-device CoreML model. + /// + /// ```swift + /// let modelURL = Bundle.main.url(forResource: "SQLModel", withExtension: "mlmodelc")! + /// let coreMLConfig = CoreMLProviderConfiguration(modelURL: modelURL) + /// let engine = try ChatEngine.onDevice( + /// database: db, + /// coreML: coreMLConfig + /// ) + /// ``` + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - coreML: The CoreML model configuration. + /// - allowlist: SQL operations allowed. Defaults to read-only. + /// - configuration: Engine configuration. + /// - Returns: A configured `ChatEngine` instance. + /// - Throws: ``OnDeviceProviderError`` if the configuration is invalid. + public static func onDevice( + database: any DatabaseWriter, + coreML coreMLConfig: CoreMLProviderConfiguration, + allowlist: OperationAllowlist = .readOnly, + configuration: ChatEngineConfiguration = .default + ) throws -> ChatEngine { + // Validate configuration + try coreMLConfig.validate() + + let pipeline = OnDeviceInferencePipeline(coreMLConfiguration: coreMLConfig) + + var engineConfig = configuration + let hints = pipeline.recommendedSQLGenerationHints + if engineConfig.additionalContext == nil { + engineConfig.additionalContext = hints.systemPromptSuffix + } else { + engineConfig.additionalContext! += "\n\n" + hints.systemPromptSuffix + } + + let providerConfig = ProviderConfiguration.onDeviceCoreML(coreMLConfig) + + return ChatEngine( + database: database, + provider: providerConfig, + allowlist: allowlist, + configuration: engineConfig + ) + } +} + +// MARK: - Model Readiness Checker + +/// Utility for checking on-device model availability and system capability. +public enum OnDeviceModelReadiness { + + /// System capability information for on-device inference. + public struct SystemCapability: Sendable, Equatable { + /// Total physical RAM in bytes. + public let totalRAM: UInt64 + + /// Whether the device has sufficient RAM for typical on-device models. + /// Generally requires at least 4GB for 3B parameter models. + public let hasSufficientRAM: Bool + + /// Whether Apple Neural Engine is likely available. + /// True on devices with Apple silicon. + public let hasNeuralEngine: Bool + + /// Recommended model size category based on available RAM. + public let recommendedModelSize: RecommendedModelSize + } + + /// Recommended model size based on device capabilities. + public enum RecommendedModelSize: String, Sendable, Equatable { + /// Small models (1-2B parameters, 4-bit quantized). + /// Suitable for devices with 4GB RAM. + case small + + /// Medium models (3-4B parameters, 4-bit quantized). + /// Suitable for devices with 6-8GB RAM. + case medium + + /// Large models (7-8B parameters, 4-bit quantized). + /// Suitable for devices with 16GB+ RAM. + case large + } + + /// Checks the current device's capability for on-device inference. + /// + /// ```swift + /// let capability = OnDeviceModelReadiness.checkSystemCapability() + /// if capability.hasSufficientRAM { + /// print("Recommended size: \(capability.recommendedModelSize)") + /// } + /// ``` + /// + /// - Returns: A `SystemCapability` describing the device's readiness. + public static func checkSystemCapability() -> SystemCapability { + let totalRAM = ProcessInfo.processInfo.physicalMemory + let ramGB = totalRAM / (1024 * 1024 * 1024) + + let recommendedSize: RecommendedModelSize + switch ramGB { + case ..<4: + recommendedSize = .small + case ..<8: + recommendedSize = .medium + default: + recommendedSize = .large + } + + return SystemCapability( + totalRAM: totalRAM, + hasSufficientRAM: ramGB >= 4, + hasNeuralEngine: hasAppleSilicon(), + recommendedModelSize: recommendedSize + ) + } + + /// Suggests an MLX model configuration based on system capabilities. + /// + /// ```swift + /// let config = OnDeviceModelReadiness.suggestedMLXModel() + /// let engine = try ChatEngine.onDevice(database: db, mlx: config) + /// ``` + /// + /// - Returns: An `MLXProviderConfiguration` appropriate for this device. + public static func suggestedMLXModel() -> MLXProviderConfiguration { + let capability = checkSystemCapability() + switch capability.recommendedModelSize { + case .small: + return .phi3_5_mini() + case .medium: + return .llama3_2_3B() + case .large: + return .qwen2_5_coder_3B() + } + } + + /// Checks if the current device uses Apple silicon. + private static func hasAppleSilicon() -> Bool { + #if arch(arm64) + return true + #else + return false + #endif + } +} diff --git a/Sources/SwiftDBAI/Config/OperationAllowlist.swift b/Sources/SwiftDBAI/Config/OperationAllowlist.swift new file mode 100644 index 0000000..562ab1b --- /dev/null +++ b/Sources/SwiftDBAI/Config/OperationAllowlist.swift @@ -0,0 +1,54 @@ +/// Defines which SQL operations the LLM is permitted to generate. +/// +/// The default is ``readOnly`` (SELECT only). Write operations require +/// explicit opt-in. This is the safety-by-default principle. +public struct OperationAllowlist: Sendable, Equatable { + /// The set of permitted SQL operation types. + public let allowedOperations: Set + + /// Creates an allowlist from the given set of operations. + public init(_ operations: Set) { + self.allowedOperations = operations + } + + /// Read-only: only SELECT queries are permitted. This is the default. + public static let readOnly = OperationAllowlist([.select]) + + /// Standard read-write: SELECT, INSERT, and UPDATE are permitted. + public static let standard = OperationAllowlist([.select, .insert, .update]) + + /// Unrestricted: all operations including DELETE are permitted. + /// DELETE still requires confirmation via `ToolExecutionDelegate`. + public static let unrestricted = OperationAllowlist([.select, .insert, .update, .delete]) + + /// Returns true if the given operation is allowed. + public func isAllowed(_ operation: SQLOperation) -> Bool { + allowedOperations.contains(operation) + } + + /// Returns a human-readable description of what's allowed, for inclusion + /// in the LLM system prompt. + func describeForLLM() -> String { + if allowedOperations == [.select] { + return "You may ONLY generate SELECT queries. No data modifications are allowed." + } + + let sorted = allowedOperations.sorted { $0.rawValue < $1.rawValue } + let names = sorted.map { $0.rawValue.uppercased() } + var desc = "Allowed SQL operations: \(names.joined(separator: ", "))." + + if allowedOperations.contains(.delete) { + desc += " DELETE operations are destructive and require user confirmation before execution." + } + + return desc + } +} + +/// The types of SQL operations that can be controlled via the allowlist. +public enum SQLOperation: String, Sendable, Hashable, CaseIterable { + case select + case insert + case update + case delete +} diff --git a/Sources/SwiftDBAI/Config/ProviderConfiguration.swift b/Sources/SwiftDBAI/Config/ProviderConfiguration.swift new file mode 100644 index 0000000..95a2ef6 --- /dev/null +++ b/Sources/SwiftDBAI/Config/ProviderConfiguration.swift @@ -0,0 +1,609 @@ +// ProviderConfiguration.swift +// SwiftDBAI +// +// Unified provider configuration for cloud-based LLM providers. +// Wraps AnyLanguageModel provider types with convenient factory methods. + +import AnyLanguageModel +import Foundation +import GRDB + +/// Configuration for connecting to a cloud-based LLM provider. +/// +/// `ProviderConfiguration` provides a unified way to configure any supported +/// LLM provider (OpenAI, Anthropic, Gemini, or OpenAI-compatible services). +/// Each configuration produces a properly configured `LanguageModel` instance +/// that works with ``ChatEngine`` and ``TextSummaryRenderer``. +/// +/// ## Quick Start +/// +/// ```swift +/// // OpenAI +/// let config = ProviderConfiguration.openAI(apiKey: "sk-...", model: "gpt-4o") +/// +/// // Anthropic +/// let config = ProviderConfiguration.anthropic(apiKey: "sk-ant-...", model: "claude-sonnet-4-20250514") +/// +/// // Gemini +/// let config = ProviderConfiguration.gemini(apiKey: "AIza...", model: "gemini-2.0-flash") +/// +/// // Use with ChatEngine +/// let engine = ChatEngine(database: db, model: config.makeModel()) +/// ``` +/// +/// ## API Key Handling +/// +/// API keys are stored as closures to support both static strings and +/// dynamic retrieval from keychains, environment variables, or secure storage: +/// +/// ```swift +/// // Static key +/// let config = ProviderConfiguration.openAI(apiKey: "sk-...", model: "gpt-4o") +/// +/// // Dynamic key from environment +/// let config = ProviderConfiguration.openAI( +/// apiKeyProvider: { ProcessInfo.processInfo.environment["OPENAI_API_KEY"] ?? "" }, +/// model: "gpt-4o" +/// ) +/// ``` +public struct ProviderConfiguration: Sendable { + + /// The supported LLM provider types. + public enum Provider: String, Sendable, Hashable, CaseIterable { + /// OpenAI's GPT models via the Chat Completions or Responses API. + case openAI + + /// Anthropic's Claude models. + case anthropic + + /// Google's Gemini models. + case gemini + + /// Any OpenAI-compatible API (e.g., local servers, third-party providers). + case openAICompatible + + /// Ollama — local models via `ollama serve`. + /// Default endpoint: http://localhost:11434 + case ollama + + /// llama.cpp server — local GGUF models via `llama-server`. + /// Default endpoint: http://localhost:8080 + /// Uses the OpenAI-compatible API. + case llamaCpp + } + + /// The provider type for this configuration. + public let provider: Provider + + /// The model identifier (e.g., "gpt-4o", "claude-sonnet-4-20250514", "gemini-2.0-flash"). + public let model: String + + /// A closure that provides the API key on demand. + /// + /// Using a closure allows lazy evaluation and integration with secure + /// storage systems (Keychain, environment variables, etc.). + private let apiKeyProvider: @Sendable () -> String + + /// Optional custom base URL for OpenAI-compatible providers. + public let baseURL: URL? + + /// Optional API version override (used by Anthropic and Gemini). + public let apiVersion: String? + + /// Optional beta headers (used by Anthropic). + public let betas: [String]? + + /// The OpenAI API variant to use (Chat Completions or Responses). + public let openAIVariant: OpenAILanguageModel.APIVariant? + + // MARK: - Internal Init + + /// Internal memberwise initializer used by factory methods. + internal init( + provider: Provider, + model: String, + apiKeyProvider: @escaping @Sendable () -> String, + baseURL: URL?, + apiVersion: String?, + betas: [String]?, + openAIVariant: OpenAILanguageModel.APIVariant? + ) { + self.provider = provider + self.model = model + self.apiKeyProvider = apiKeyProvider + self.baseURL = baseURL + self.apiVersion = apiVersion + self.betas = betas + self.openAIVariant = openAIVariant + } + + // MARK: - Factory Methods + + /// Creates a configuration for OpenAI's API. + /// + /// - Parameters: + /// - apiKey: Your OpenAI API key (e.g., "sk-..."). + /// - model: The model identifier (e.g., "gpt-4o", "gpt-4o-mini"). + /// - variant: The API variant to use. Defaults to `.chatCompletions`. + /// - baseURL: Optional custom base URL. Defaults to OpenAI's API. + /// - Returns: A configured `ProviderConfiguration`. + public static func openAI( + apiKey: String, + model: String, + variant: OpenAILanguageModel.APIVariant = .chatCompletions, + baseURL: URL? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAI, + model: model, + apiKeyProvider: { apiKey }, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: variant + ) + } + + /// Creates a configuration for OpenAI's API with a dynamic key provider. + /// + /// Use this when the API key comes from a keychain, environment variable, + /// or other dynamic source. + /// + /// - Parameters: + /// - apiKeyProvider: A closure that returns the API key. + /// - model: The model identifier. + /// - variant: The API variant to use. Defaults to `.chatCompletions`. + /// - baseURL: Optional custom base URL. + /// - Returns: A configured `ProviderConfiguration`. + public static func openAI( + apiKeyProvider: @escaping @Sendable () -> String, + model: String, + variant: OpenAILanguageModel.APIVariant = .chatCompletions, + baseURL: URL? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAI, + model: model, + apiKeyProvider: apiKeyProvider, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: variant + ) + } + + /// Creates a configuration for Anthropic's Claude API. + /// + /// - Parameters: + /// - apiKey: Your Anthropic API key (e.g., "sk-ant-..."). + /// - model: The model identifier (e.g., "claude-sonnet-4-20250514"). + /// - apiVersion: Optional API version override. + /// - betas: Optional beta feature headers. + /// - Returns: A configured `ProviderConfiguration`. + public static func anthropic( + apiKey: String, + model: String, + apiVersion: String? = nil, + betas: [String]? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .anthropic, + model: model, + apiKeyProvider: { apiKey }, + baseURL: nil, + apiVersion: apiVersion, + betas: betas, + openAIVariant: nil + ) + } + + /// Creates a configuration for Anthropic's Claude API with a dynamic key provider. + /// + /// - Parameters: + /// - apiKeyProvider: A closure that returns the API key. + /// - model: The model identifier. + /// - apiVersion: Optional API version override. + /// - betas: Optional beta feature headers. + /// - Returns: A configured `ProviderConfiguration`. + public static func anthropic( + apiKeyProvider: @escaping @Sendable () -> String, + model: String, + apiVersion: String? = nil, + betas: [String]? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .anthropic, + model: model, + apiKeyProvider: apiKeyProvider, + baseURL: nil, + apiVersion: apiVersion, + betas: betas, + openAIVariant: nil + ) + } + + /// Creates a configuration for Google's Gemini API. + /// + /// - Parameters: + /// - apiKey: Your Gemini API key (e.g., "AIza..."). + /// - model: The model identifier (e.g., "gemini-2.0-flash"). + /// - apiVersion: Optional API version override (defaults to "v1beta"). + /// - Returns: A configured `ProviderConfiguration`. + public static func gemini( + apiKey: String, + model: String, + apiVersion: String? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .gemini, + model: model, + apiKeyProvider: { apiKey }, + baseURL: nil, + apiVersion: apiVersion, + betas: nil, + openAIVariant: nil + ) + } + + /// Creates a configuration for Google's Gemini API with a dynamic key provider. + /// + /// - Parameters: + /// - apiKeyProvider: A closure that returns the API key. + /// - model: The model identifier. + /// - apiVersion: Optional API version override. + /// - Returns: A configured `ProviderConfiguration`. + public static func gemini( + apiKeyProvider: @escaping @Sendable () -> String, + model: String, + apiVersion: String? = nil + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .gemini, + model: model, + apiKeyProvider: apiKeyProvider, + baseURL: nil, + apiVersion: apiVersion, + betas: nil, + openAIVariant: nil + ) + } + + /// Creates a configuration for any OpenAI-compatible API. + /// + /// Use this for third-party services that implement the OpenAI Chat Completions + /// API (e.g., local LLM servers, Groq, Together AI, etc.). + /// + /// ```swift + /// let config = ProviderConfiguration.openAICompatible( + /// apiKey: "your-key", + /// model: "llama-3.1-70b", + /// baseURL: URL(string: "https://api.together.xyz/v1/")! + /// ) + /// ``` + /// + /// - Parameters: + /// - apiKey: The API key for the service. + /// - model: The model identifier. + /// - baseURL: The base URL of the compatible API. + /// - variant: The API variant. Defaults to `.chatCompletions`. + /// - Returns: A configured `ProviderConfiguration`. + public static func openAICompatible( + apiKey: String, + model: String, + baseURL: URL, + variant: OpenAILanguageModel.APIVariant = .chatCompletions + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAICompatible, + model: model, + apiKeyProvider: { apiKey }, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: variant + ) + } + + /// Creates a configuration for any OpenAI-compatible API with a dynamic key provider. + /// + /// - Parameters: + /// - apiKeyProvider: A closure that returns the API key. + /// - model: The model identifier. + /// - baseURL: The base URL of the compatible API. + /// - variant: The API variant. Defaults to `.chatCompletions`. + /// - Returns: A configured `ProviderConfiguration`. + public static func openAICompatible( + apiKeyProvider: @escaping @Sendable () -> String, + model: String, + baseURL: URL, + variant: OpenAILanguageModel.APIVariant = .chatCompletions + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .openAICompatible, + model: model, + apiKeyProvider: apiKeyProvider, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: variant + ) + } + + // MARK: - Local Provider Factory Methods + + /// Creates a configuration for a local Ollama instance. + /// + /// Ollama runs models locally and exposes a native API on port 11434. + /// No API key is required by default. + /// + /// ```swift + /// // Default local Ollama + /// let config = ProviderConfiguration.ollama(model: "llama3.2") + /// + /// // Ollama on a remote machine + /// let config = ProviderConfiguration.ollama( + /// model: "qwen2.5", + /// baseURL: URL(string: "http://192.168.1.100:11434")! + /// ) + /// + /// // Use with ChatEngine + /// let engine = ChatEngine(database: db, provider: config) + /// ``` + /// + /// - Parameters: + /// - model: The Ollama model name (e.g., "llama3.2", "qwen2.5", "mistral"). + /// - baseURL: The Ollama server URL. Defaults to `http://localhost:11434`. + /// - Returns: A configured `ProviderConfiguration`. + public static func ollama( + model: String, + baseURL: URL = OllamaLanguageModel.defaultBaseURL + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .ollama, + model: model, + apiKeyProvider: { "" }, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: nil + ) + } + + /// Creates a configuration for a local llama.cpp server. + /// + /// llama.cpp's `llama-server` exposes an OpenAI-compatible Chat Completions + /// API, typically on port 8080. No API key is required by default. + /// + /// ```swift + /// // Default local llama.cpp + /// let config = ProviderConfiguration.llamaCpp(model: "default") + /// + /// // llama.cpp on a custom port with API key + /// let config = ProviderConfiguration.llamaCpp( + /// model: "my-model", + /// baseURL: URL(string: "http://localhost:9090")!, + /// apiKey: "my-secret-key" + /// ) + /// ``` + /// + /// - Parameters: + /// - model: The model identifier. Use "default" if llama-server loads a single model. + /// - baseURL: The llama.cpp server URL. Defaults to `http://localhost:8080`. + /// - apiKey: Optional API key if the server requires authentication. + /// - Returns: A configured `ProviderConfiguration`. + public static func llamaCpp( + model: String = "default", + baseURL: URL = LocalProviderDiscovery.defaultLlamaCppURL, + apiKey: String = "" + ) -> ProviderConfiguration { + ProviderConfiguration( + provider: .llamaCpp, + model: model, + apiKeyProvider: { apiKey }, + baseURL: baseURL, + apiVersion: nil, + betas: nil, + openAIVariant: .chatCompletions + ) + } + + // MARK: - Model Construction + + /// Creates a configured `LanguageModel` instance for this provider. + /// + /// This is the primary way to get a model from a configuration. + /// The returned model is ready to use with ``ChatEngine`` or + /// ``TextSummaryRenderer``. + /// + /// ```swift + /// let config = ProviderConfiguration.openAI(apiKey: "sk-...", model: "gpt-4o") + /// let engine = ChatEngine(database: db, model: config.makeModel()) + /// ``` + /// + /// - Returns: A configured `LanguageModel` instance. + public func makeModel() -> any LanguageModel { + let key = apiKeyProvider + + switch provider { + case .openAI: + if let baseURL { + return OpenAILanguageModel( + baseURL: baseURL, + apiKey: key(), + model: model, + apiVariant: openAIVariant ?? .chatCompletions + ) + } + return OpenAILanguageModel( + apiKey: key(), + model: model, + apiVariant: openAIVariant ?? .chatCompletions + ) + + case .anthropic: + if let apiVersion { + return AnthropicLanguageModel( + apiKey: key(), + apiVersion: apiVersion, + betas: betas, + model: model + ) + } + if let betas { + return AnthropicLanguageModel( + apiKey: key(), + betas: betas, + model: model + ) + } + return AnthropicLanguageModel( + apiKey: key(), + model: model + ) + + case .gemini: + if let apiVersion { + return GeminiLanguageModel( + apiKey: key(), + apiVersion: apiVersion, + model: model + ) + } + return GeminiLanguageModel( + apiKey: key(), + model: model + ) + + case .openAICompatible: + return OpenAILanguageModel( + baseURL: baseURL ?? OpenAILanguageModel.defaultBaseURL, + apiKey: key(), + model: model, + apiVariant: openAIVariant ?? .chatCompletions + ) + + case .ollama: + return OllamaLanguageModel( + baseURL: baseURL ?? OllamaLanguageModel.defaultBaseURL, + model: model + ) + + case .llamaCpp: + // llama.cpp exposes an OpenAI-compatible API + return OpenAILanguageModel( + baseURL: baseURL ?? LocalProviderDiscovery.defaultLlamaCppURL, + apiKey: key(), + model: model, + apiVariant: openAIVariant ?? .chatCompletions + ) + } + } + + // MARK: - API Key Access + + /// Returns the current API key. + /// + /// Useful for validation or debugging. In production, prefer using + /// ``makeModel()`` which handles key injection automatically. + public var apiKey: String { + apiKeyProvider() + } + + /// Returns `true` if the API key is non-empty. + /// + /// Use this to check configuration validity before creating an engine: + /// ```swift + /// guard config.hasValidAPIKey else { + /// // Show API key setup UI + /// return + /// } + /// ``` + public var hasValidAPIKey: Bool { + !apiKeyProvider().trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + + // MARK: - Environment Variable Helpers + + /// Creates a configuration using an API key from an environment variable. + /// + /// Falls back to an empty string if the environment variable is not set, + /// which will cause API calls to fail with an authentication error. + /// + /// ```swift + /// let config = ProviderConfiguration.fromEnvironment( + /// provider: .openAI, + /// environmentVariable: "OPENAI_API_KEY", + /// model: "gpt-4o" + /// ) + /// ``` + /// + /// - Parameters: + /// - provider: The LLM provider. + /// - environmentVariable: The name of the environment variable holding the API key. + /// - model: The model identifier. + /// - Returns: A configured `ProviderConfiguration`. + public static func fromEnvironment( + provider: Provider, + environmentVariable: String, + model: String + ) -> ProviderConfiguration { + let keyProvider: @Sendable () -> String = { + ProcessInfo.processInfo.environment[environmentVariable] ?? "" + } + + switch provider { + case .openAI: + return .openAI(apiKeyProvider: keyProvider, model: model) + case .anthropic: + return .anthropic(apiKeyProvider: keyProvider, model: model) + case .gemini: + return .gemini(apiKeyProvider: keyProvider, model: model) + case .openAICompatible: + return .openAICompatible( + apiKeyProvider: keyProvider, + model: model, + baseURL: OpenAILanguageModel.defaultBaseURL + ) + case .ollama: + return .ollama(model: model) + case .llamaCpp: + return .llamaCpp(model: model) + } + } +} + +// MARK: - ChatEngine Convenience Init + +extension ChatEngine { + + /// Creates a ChatEngine using a ``ProviderConfiguration``. + /// + /// This is the most convenient way to set up a ChatEngine with a + /// cloud provider: + /// + /// ```swift + /// let engine = ChatEngine( + /// database: myDB, + /// provider: .openAI(apiKey: "sk-...", model: "gpt-4o") + /// ) + /// ``` + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - provider: The provider configuration. + /// - allowlist: SQL operations the LLM may generate. Defaults to read-only. + /// - configuration: Engine configuration for timeouts, context window, validators, etc. + public convenience init( + database: any DatabaseWriter, + provider: ProviderConfiguration, + allowlist: OperationAllowlist = .readOnly, + configuration: ChatEngineConfiguration = .default + ) { + self.init( + database: database, + model: provider.makeModel(), + allowlist: allowlist, + configuration: configuration + ) + } +} diff --git a/Sources/SwiftDBAI/Config/QueryValidator.swift b/Sources/SwiftDBAI/Config/QueryValidator.swift new file mode 100644 index 0000000..6b87eb2 --- /dev/null +++ b/Sources/SwiftDBAI/Config/QueryValidator.swift @@ -0,0 +1,114 @@ +// QueryValidator.swift +// SwiftDBAI +// +// Extensible query validation protocol for custom pre-execution checks. + +import Foundation + +/// A protocol for custom SQL query validation. +/// +/// Implement this protocol to add domain-specific validation rules that run +/// after the built-in allowlist and safety checks. Validators receive the +/// parsed SQL string and its detected operation type. +/// +/// Example — restrict queries to specific tables: +/// ```swift +/// struct TableAllowlistValidator: QueryValidator { +/// let allowedTables: Set +/// +/// func validate(sql: String, operation: SQLOperation) throws { +/// let upper = sql.uppercased() +/// for table in allowedTables { +/// // Simple check — real implementation might parse FROM/JOIN clauses +/// if upper.contains(table.uppercased()) { return } +/// } +/// throw QueryValidationError.rejected("Query references tables outside the allowlist.") +/// } +/// } +/// ``` +public protocol QueryValidator: Sendable { + /// Validates a SQL query before execution. + /// + /// - Parameters: + /// - sql: The cleaned SQL statement about to be executed. + /// - operation: The detected operation type (SELECT, INSERT, etc.). + /// - Throws: ``QueryValidationError`` or any `Error` to reject the query. + func validate(sql: String, operation: SQLOperation) throws +} + +/// Errors thrown by custom ``QueryValidator`` implementations. +public enum QueryValidationError: Error, LocalizedError, Sendable, Equatable { + /// The query was rejected by a custom validator with the given reason. + case rejected(String) + + public var errorDescription: String? { + switch self { + case .rejected(let reason): + return "Query rejected: \(reason)" + } + } +} + +// MARK: - Built-in Validators + +/// A validator that restricts queries to a specific set of table names. +/// +/// This performs a simple keyword check — it verifies that the SQL references +/// at least one of the allowed tables. This is a best-effort check, not a +/// full SQL parser. +public struct TableAllowlistValidator: QueryValidator { + /// The set of table names queries are allowed to reference. + public let allowedTables: Set + + /// Creates a validator with the given allowed table names. + public init(allowedTables: Set) { + self.allowedTables = allowedTables + } + + public func validate(sql: String, operation: SQLOperation) throws { + let upper = sql.uppercased() + let found = allowedTables.contains { table in + let pattern = table.uppercased() + return upper.contains(pattern) + } + guard found else { + throw QueryValidationError.rejected( + "Query does not reference any allowed tables: \(allowedTables.sorted().joined(separator: ", "))" + ) + } + } +} + +/// A validator that enforces a maximum row limit on SELECT queries +/// by checking for a LIMIT clause. +public struct MaxRowLimitValidator: QueryValidator { + /// The maximum number of rows allowed. + public let maxRows: Int + + /// Creates a validator that requires SELECT queries to include a LIMIT clause + /// not exceeding `maxRows`. + public init(maxRows: Int) { + self.maxRows = maxRows + } + + public func validate(sql: String, operation: SQLOperation) throws { + guard operation == .select else { return } + + let upper = sql.uppercased() + // Check if LIMIT is present + guard let limitRange = upper.range(of: #"LIMIT\s+(\d+)"#, options: .regularExpression) else { + throw QueryValidationError.rejected( + "SELECT queries must include a LIMIT clause (max \(maxRows) rows)." + ) + } + + // Extract the limit value + let limitSubstring = upper[limitRange] + let digits = limitSubstring.components(separatedBy: .decimalDigits.inverted).joined() + if let value = Int(digits), value > maxRows { + throw QueryValidationError.rejected( + "LIMIT \(value) exceeds the maximum allowed (\(maxRows))." + ) + } + } +} diff --git a/Sources/SwiftDBAI/Engine/ChatEngine.swift b/Sources/SwiftDBAI/Engine/ChatEngine.swift new file mode 100644 index 0000000..c3b9d74 --- /dev/null +++ b/Sources/SwiftDBAI/Engine/ChatEngine.swift @@ -0,0 +1,677 @@ +// ChatEngine.swift +// SwiftDBAI +// +// Orchestrates the conversation loop: user message → SQL generation → query +// execution → result summarization → response. + +import AnyLanguageModel +import Foundation +import GRDB + +/// A message in the chat conversation. +public struct ChatMessage: Sendable, Identifiable, Equatable { + public let id: UUID + public let role: Role + public let content: String + public let queryResult: QueryResult? + public let sql: String? + public let timestamp: Date + /// The typed error, if this is an error message. + public let error: SwiftDBAIError? + + public enum Role: String, Sendable, Equatable { + case user + case assistant + case error + } + + public init( + id: UUID = UUID(), + role: Role, + content: String, + queryResult: QueryResult? = nil, + sql: String? = nil, + timestamp: Date = Date(), + error: SwiftDBAIError? = nil + ) { + self.id = id + self.role = role + self.content = content + self.queryResult = queryResult + self.sql = sql + self.timestamp = timestamp + self.error = error + } +} + +/// The response returned by `ChatEngine.send(_:)`. +public struct ChatResponse: Sendable { + /// The natural language summary of the result. + public let summary: String + + /// The SQL that was generated and executed, if any. + public let sql: String? + + /// The raw query result, if a query was executed. + public let queryResult: QueryResult? +} + +/// Headless engine that orchestrates the full chat-with-database pipeline. +/// +/// The engine: +/// 1. Introspects the database schema (once, lazily) +/// 2. Builds a system prompt with schema context +/// 3. Sends the user's question to the LLM to generate SQL +/// 4. Validates the SQL against the operation allowlist +/// 5. Executes the SQL via GRDB +/// 6. Summarizes results using `TextSummaryRenderer` +/// 7. Returns the summary (and raw data) to the caller +/// +/// Usage: +/// ```swift +/// let engine = ChatEngine( +/// database: myDatabasePool, +/// model: myLanguageModel +/// ) +/// let response = try await engine.send("How many users signed up this week?") +/// print(response.summary) // "There were 42 new signups this week." +/// ``` +public final class ChatEngine: @unchecked Sendable { + + // MARK: - Dependencies + + private let database: any DatabaseWriter + private let model: any LanguageModel + private let allowlist: OperationAllowlist + private let mutationPolicy: MutationPolicy? + private let configuration: ChatEngineConfiguration + private let summaryRenderer: TextSummaryRenderer + private let sqlParser: SQLQueryParser + + /// Optional delegate for intercepting destructive operations and observing SQL execution. + private let delegate: (any ToolExecutionDelegate)? + + // MARK: - State + + private var schema: DatabaseSchema? + private var conversationHistory: [ChatMessage] = [] + private let lock = NSLock() + + // MARK: - Initialization + + /// Creates a new ChatEngine with a full configuration object. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - model: Any `AnyLanguageModel`-compatible language model. + /// - allowlist: SQL operations the LLM may generate. Defaults to read-only (SELECT only). + /// - configuration: Engine configuration for timeouts, context window, validators, etc. + /// - delegate: Optional delegate for confirming destructive operations and observing SQL execution. + public init( + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + configuration: ChatEngineConfiguration = .default, + delegate: (any ToolExecutionDelegate)? = nil + ) { + self.database = database + self.model = model + self.allowlist = allowlist + self.mutationPolicy = nil + self.configuration = configuration + self.delegate = delegate + self.summaryRenderer = TextSummaryRenderer( + model: model, + maxRowsInPrompt: configuration.maxSummaryRows + ) + self.sqlParser = SQLQueryParser(allowlist: allowlist) + } + + /// Creates a new ChatEngine with a `MutationPolicy` for table-level control. + /// + /// This initializer provides fine-grained control over which mutations are + /// allowed on which tables. The policy's operation allowlist is used for + /// SQL validation, and table-level restrictions are enforced during parsing. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - model: Any `AnyLanguageModel`-compatible language model. + /// - mutationPolicy: Controls which operations are allowed on which tables. + /// - configuration: Engine configuration for timeouts, context window, validators, etc. + /// - delegate: Optional delegate for confirming destructive operations and observing SQL execution. + public init( + database: any DatabaseWriter, + model: any LanguageModel, + mutationPolicy: MutationPolicy, + configuration: ChatEngineConfiguration = .default, + delegate: (any ToolExecutionDelegate)? = nil + ) { + self.database = database + self.model = model + self.allowlist = mutationPolicy.operationAllowlist + self.mutationPolicy = mutationPolicy + self.configuration = configuration + self.delegate = delegate + self.summaryRenderer = TextSummaryRenderer( + model: model, + maxRowsInPrompt: configuration.maxSummaryRows + ) + self.sqlParser = SQLQueryParser(mutationPolicy: mutationPolicy) + } + + /// Creates a new ChatEngine with individual parameters (convenience). + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabasePool or DatabaseQueue). + /// - model: Any `AnyLanguageModel`-compatible language model. + /// - allowlist: SQL operations the LLM may generate. Defaults to read-only (SELECT only). + /// - additionalContext: Optional extra instructions for the LLM system prompt. + /// - maxSummaryRows: Maximum rows to include when summarizing results (default: 50). + public convenience init( + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist, + additionalContext: String?, + maxSummaryRows: Int = 50 + ) { + let config = ChatEngineConfiguration( + maxSummaryRows: maxSummaryRows, + additionalContext: additionalContext + ) + self.init( + database: database, + model: model, + allowlist: allowlist, + configuration: config + ) + } + + // MARK: - Public API + + /// Sends a natural language message and returns a summarized response. + /// + /// This is the primary entry point. The engine will: + /// 1. Introspect the schema if not yet cached + /// 2. Ask the LLM to generate SQL + /// 3. Validate the SQL against the allowlist and custom validators + /// 4. Execute the SQL (with timeout if configured) + /// 5. Summarize the results using `TextSummaryRenderer` + /// + /// All errors are caught and mapped to a distinct ``SwiftDBAIError`` case + /// so callers always receive a typed, user-friendly error with a localized + /// description suitable for display in a chat UI. + /// + /// - Parameter message: The user's natural language question or command. + /// - Returns: A `ChatResponse` containing the summary, SQL, and raw result. + /// - Throws: ``SwiftDBAIError`` for every failure mode. + public func send(_ message: String) async throws -> ChatResponse { + // 1. Ensure schema is introspected + let schema: DatabaseSchema + do { + schema = try await ensureSchema() + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.schemaIntrospectionFailed(reason: error.localizedDescription) + } + + // Check for empty schema + if schema.tableNames.isEmpty { + throw SwiftDBAIError.emptySchema + } + + // 2. Build prompt and get raw LLM response + let promptBuilder = PromptBuilder( + schema: schema, + allowlist: allowlist, + additionalContext: configuration.additionalContext + ) + + let rawLLMResponse: String + do { + rawLLMResponse = try await generateRawResponse( + question: message, + promptBuilder: promptBuilder + ) + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.llmFailure(reason: error.localizedDescription) + } + + // 3. Parse and validate SQL through SQLQueryParser + let parsed: ParsedSQL + do { + parsed = try sqlParser.parse(rawLLMResponse) + } catch let error as SQLParsingError { + throw error.toSwiftDBAIError(rawResponse: rawLLMResponse) + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.invalidSQL(sql: rawLLMResponse, reason: error.localizedDescription) + } + + // 4. Run custom validators + do { + try runCustomValidators(parsed: parsed) + } catch let error as QueryValidationError { + throw error + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.queryRejected(reason: error.localizedDescription) + } + + // 5. Handle confirmation-required operations (DELETE, DROP, etc.) + if parsed.requiresConfirmation { + if let delegate = self.delegate { + // Build context for the delegate + let classification = classifySQL(parsed.sql) + let context = DestructiveOperationContext( + sql: parsed.sql, + statementKind: detectStatementKind(parsed.sql) ?? .delete, + classification: classification, + description: "Execute \(parsed.operation.rawValue.uppercased()) operation: \(parsed.sql)", + targetTable: extractTargetTableForDelegate(from: parsed.sql, operation: parsed.operation) + ) + // Ask the delegate for approval + let approved = await delegate.confirmDestructiveOperation(context) + if !approved { + throw SwiftDBAIError.confirmationRequired( + sql: parsed.sql, + operation: parsed.operation.rawValue + ) + } + // Delegate approved — fall through to execution + } else { + // No delegate — throw confirmation required so caller can handle it + throw SwiftDBAIError.confirmationRequired( + sql: parsed.sql, + operation: parsed.operation.rawValue + ) + } + } + + // 6. Execute the SQL (with timeout if configured) + let result: QueryResult + do { + let classification = classifySQL(parsed.sql) + await delegate?.willExecuteSQL(parsed.sql, classification: classification) + result = try await executeSQLWithTimeout(parsed.sql) + await delegate?.didExecuteSQL(parsed.sql, success: true) + } catch let error as SwiftDBAIError { + await delegate?.didExecuteSQL(parsed.sql, success: false) + throw error + } catch let error as ChatEngineError { + await delegate?.didExecuteSQL(parsed.sql, success: false) + // Map internal ChatEngineError (e.g. from timeout) to SwiftDBAIError + throw error.toSwiftDBAIError() + } catch { + await delegate?.didExecuteSQL(parsed.sql, success: false) + throw SwiftDBAIError.databaseError(reason: error.localizedDescription) + } + + // 7. Summarize the result using TextSummaryRenderer + let summary: String + do { + summary = try await summaryRenderer.summarize( + result: result, + userQuestion: message + ) + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.llmFailure(reason: "Summarization failed: \(error.localizedDescription)") + } + + // 8. Record conversation history + let userMessage = ChatMessage(role: .user, content: message) + let assistantMessage = ChatMessage( + role: .assistant, + content: summary, + queryResult: result, + sql: parsed.sql + ) + lock.withLock { + conversationHistory.append(userMessage) + conversationHistory.append(assistantMessage) + } + + return ChatResponse( + summary: summary, + sql: parsed.sql, + queryResult: result + ) + } + + /// Sends a natural language message, executing a previously confirmed destructive operation. + /// + /// Call this after receiving a `confirmationRequired` error and the user has confirmed. + /// + /// - Parameters: + /// - message: The original user message (for history recording). + /// - confirmedSQL: The SQL that was confirmed by the user. + /// - Returns: A `ChatResponse` with the result. + public func sendConfirmed(_ message: String, confirmedSQL: String) async throws -> ChatResponse { + let result: QueryResult + do { + let classification = classifySQL(confirmedSQL) + await delegate?.willExecuteSQL(confirmedSQL, classification: classification) + result = try await executeSQLWithTimeout(confirmedSQL) + await delegate?.didExecuteSQL(confirmedSQL, success: true) + } catch let error as SwiftDBAIError { + await delegate?.didExecuteSQL(confirmedSQL, success: false) + throw error + } catch let error as ChatEngineError { + await delegate?.didExecuteSQL(confirmedSQL, success: false) + throw error.toSwiftDBAIError() + } catch { + await delegate?.didExecuteSQL(confirmedSQL, success: false) + throw SwiftDBAIError.databaseError(reason: error.localizedDescription) + } + + let summary: String + do { + summary = try await summaryRenderer.summarize( + result: result, + userQuestion: message + ) + } catch let error as SwiftDBAIError { + throw error + } catch { + throw SwiftDBAIError.llmFailure(reason: "Summarization failed: \(error.localizedDescription)") + } + + let userMessage = ChatMessage(role: .user, content: message) + let assistantMessage = ChatMessage( + role: .assistant, + content: summary, + queryResult: result, + sql: confirmedSQL + ) + lock.withLock { + conversationHistory.append(userMessage) + conversationHistory.append(assistantMessage) + } + + return ChatResponse( + summary: summary, + sql: confirmedSQL, + queryResult: result + ) + } + + /// Returns the current conversation history. + public var messages: [ChatMessage] { + lock.lock() + defer { lock.unlock() } + return conversationHistory + } + + /// Eagerly introspects the database schema so it's ready before the first query. + /// + /// Call this at view-appear time to pre-warm the schema cache. If the schema + /// is already cached, this returns immediately. The returned `DatabaseSchema` + /// can be used to display table/column info in the UI. + /// + /// - Returns: The introspected `DatabaseSchema`. + @discardableResult + public func prepareSchema() async throws -> DatabaseSchema { + try await ensureSchema() + } + + /// The number of tables discovered during schema introspection. + /// Returns `nil` if the schema has not been introspected yet. + public var tableCount: Int? { + lock.withLock { schema?.tableNames.count } + } + + /// The cached schema, if introspection has completed. + public var cachedSchema: DatabaseSchema? { + lock.withLock { schema } + } + + /// Clears the conversation history and cached schema. + /// + /// After calling this, the next `send(_:)` call will re-introspect the + /// schema. Use ``clearHistory()`` if you only want to reset the conversation + /// while keeping the cached schema. + public func reset() { + lock.withLock { + conversationHistory.removeAll() + schema = nil + } + } + + /// Clears only the conversation history, keeping the cached schema. + /// + /// This is useful when you want to start a fresh conversation thread + /// without re-introspecting the database. The schema cache remains valid + /// as long as the database structure hasn't changed. + public func clearHistory() { + lock.withLock { + conversationHistory.removeAll() + } + } + + /// The current engine configuration. + public var currentConfiguration: ChatEngineConfiguration { + configuration + } + + // MARK: - Internal Helpers (visible for testing) + + /// Ensures the database schema is introspected and cached. + func ensureSchema() async throws -> DatabaseSchema { + if let cached = lock.withLock({ schema }) { + return cached + } + + let introspected = try await SchemaIntrospector.introspect(database: database) + + lock.withLock { schema = introspected } + + return introspected + } + + /// Asks the LLM to generate SQL from a natural language question. + /// Returns the raw LLM response text (before parsing). + /// + /// Uses the configured ``ChatEngineConfiguration/contextWindowSize`` to limit + /// how many conversation messages are included as context for the LLM. + private func generateRawResponse( + question: String, + promptBuilder: PromptBuilder + ) async throws -> String { + let instructions = promptBuilder.buildSystemInstructions() + + // Build user prompt — include full conversation history for follow-ups + // Respect context window: only use recent messages for context + let userPrompt: String + let historySlice = lock.withLock { () -> [ChatMessage] in + Array(contextWindowSlice()) + } + + if historySlice.isEmpty { + userPrompt = promptBuilder.buildUserPrompt(question) + } else { + userPrompt = promptBuilder.buildConversationPrompt( + question, + history: historySlice + ) + } + + let session = LanguageModelSession( + model: model, + instructions: instructions + "\n\nCRITICAL: Respond with ONLY the raw SQL query. Do NOT wrap in markdown code fences or backticks. Do NOT include any explanation, comments, or formatting. The output must be directly executable SQL and nothing else." + ) + + let response = try await session.respond(to: userPrompt) + return response.content.trimmingCharacters(in: .whitespacesAndNewlines) + } + + /// Returns the conversation history slice within the configured context window. + /// Must be called within a `lock.withLock` closure. + private func contextWindowSlice() -> ArraySlice { + guard let windowSize = configuration.contextWindowSize else { + return conversationHistory[...] + } + let count = conversationHistory.count + let start = max(0, count - windowSize) + return conversationHistory[start...] + } + + /// Runs all custom validators from the configuration against the parsed SQL. + private func runCustomValidators(parsed: ParsedSQL) throws { + for validator in configuration.validators { + try validator.validate(sql: parsed.sql, operation: parsed.operation) + } + } + + /// Extracts the target table name from a SQL statement for delegate context. + private func extractTargetTableForDelegate(from sql: String, operation: SQLOperation) -> String? { + let pattern: String + switch operation { + case .insert: + pattern = #"INSERT\s+INTO\s+[`"\[]?(\w+)[`"\]]?"# + case .update: + pattern = #"UPDATE\s+[`"\[]?(\w+)[`"\]]?"# + case .delete: + pattern = #"DELETE\s+FROM\s+[`"\[]?(\w+)[`"\]]?"# + case .select: + return nil + } + guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) else { + return nil + } + let range = NSRange(sql.startIndex..., in: sql) + guard let match = regex.firstMatch(in: sql, range: range), + match.numberOfRanges > 1, + let groupRange = Range(match.range(at: 1), in: sql) else { + return nil + } + return String(sql[groupRange]) + } + + /// Executes SQL with the configured timeout, if any. + private func executeSQLWithTimeout(_ sql: String) async throws -> QueryResult { + guard let timeout = configuration.queryTimeout else { + return try await executeSQL(sql) + } + + return try await withThrowingTaskGroup(of: QueryResult.self) { group in + group.addTask { + try await self.executeSQL(sql) + } + + group.addTask { + try await Task.sleep(for: .seconds(timeout)) + throw ChatEngineError.queryTimedOut(seconds: timeout) + } + + // Return whichever finishes first + let result = try await group.next()! + group.cancelAll() + return result + } + } + + /// Executes SQL against the database and returns a `QueryResult`. + private func executeSQL(_ sql: String) async throws -> QueryResult { + let trimmed = sql.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + let isSelect = trimmed.hasPrefix("SELECT") || trimmed.hasPrefix("WITH") + + let startTime = CFAbsoluteTimeGetCurrent() + + if isSelect { + let result = try await database.read { db -> (columns: [String], rows: [[String: QueryResult.Value]]) in + let statement = try db.makeStatement(sql: sql) + let columnNames = statement.columnNames + + var rows: [[String: QueryResult.Value]] = [] + let cursor = try Row.fetchCursor(statement) + while let row = try cursor.next() { + var dict: [String: QueryResult.Value] = [:] + for col in columnNames { + dict[col] = Self.extractValue(row: row, column: col) + } + rows.append(dict) + } + return (columns: columnNames, rows: rows) + } + + let elapsed = CFAbsoluteTimeGetCurrent() - startTime + + return QueryResult( + columns: result.columns, + rows: result.rows, + sql: sql, + executionTime: elapsed + ) + } else { + // Mutation query + let affected = try await database.write { db -> Int in + try db.execute(sql: sql) + return db.changesCount + } + + let elapsed = CFAbsoluteTimeGetCurrent() - startTime + + return QueryResult( + columns: [], + rows: [], + sql: sql, + executionTime: elapsed, + rowsAffected: affected + ) + } + } + + /// Extracts a `QueryResult.Value` from a GRDB `Row` for the given column. + private static func extractValue(row: Row, column: String) -> QueryResult.Value { + let dbValue: DatabaseValue = row[column] + switch dbValue.storage { + case .null: + return .null + case .int64(let i): + return .integer(i) + case .double(let d): + return .real(d) + case .string(let s): + return .text(s) + case .blob(let data): + return .blob(data) + } + } +} + +// MARK: - Errors + +/// Errors that can occur during ChatEngine operations. +public enum ChatEngineError: Error, LocalizedError, Sendable { + /// SQL parsing/extraction from LLM response failed. + case sqlParsingFailed(SQLParsingError) + /// A destructive operation requires user confirmation before execution. + case confirmationRequired(sql: String, operation: SQLOperation) + /// Schema introspection failed. + case schemaIntrospectionFailed(String) + /// The SQL query exceeded the configured timeout. + case queryTimedOut(seconds: TimeInterval) + /// A custom query validator rejected the query. + case validationFailed(String) + + public var errorDescription: String? { + switch self { + case .sqlParsingFailed(let parsingError): + return "SQL parsing failed: \(parsingError.description)" + case .confirmationRequired(let sql, let op): + return "The \(op.rawValue.uppercased()) operation requires confirmation: \(sql)" + case .schemaIntrospectionFailed(let reason): + return "Failed to introspect database schema: \(reason)" + case .queryTimedOut(let seconds): + return "Query timed out after \(Int(seconds)) seconds." + case .validationFailed(let reason): + return "Query validation failed: \(reason)" + } + } +} diff --git a/Sources/SwiftDBAI/Engine/ToolExecutionDelegate.swift b/Sources/SwiftDBAI/Engine/ToolExecutionDelegate.swift new file mode 100644 index 0000000..1297075 --- /dev/null +++ b/Sources/SwiftDBAI/Engine/ToolExecutionDelegate.swift @@ -0,0 +1,288 @@ +// ToolExecutionDelegate.swift +// SwiftDBAI +// +// Delegate protocol for controlling SQL tool execution, including +// confirmation of destructive operations before they reach the database. + +import Foundation + +// MARK: - Destructive SQL Classification + +/// Classifies SQL statements by their destructive potential. +/// +/// A statement is considered **destructive** if it modifies or removes data +/// or schema objects. The classification drives the confirmation flow: +/// destructive statements require explicit user approval via +/// ``ToolExecutionDelegate/confirmDestructiveOperation(_:)``. +public enum DestructiveClassification: Sendable, Equatable { + /// The statement is read-only (e.g. SELECT). No confirmation needed. + case safe + + /// The statement modifies existing data (INSERT, UPDATE). + case mutation(SQLStatementKind) + + /// The statement deletes data or alters/drops schema objects. + /// These always require confirmation, even when the operation is allowed. + case destructive(SQLStatementKind) + + /// Returns `true` when the statement requires user confirmation. + public var requiresConfirmation: Bool { + switch self { + case .safe: + return false + case .mutation: + return false + case .destructive: + return true + } + } + + /// Returns `true` when the statement modifies data or schema in any way. + public var isMutating: Bool { + switch self { + case .safe: + return false + case .mutation, .destructive: + return true + } + } +} + +/// The kind of SQL statement, used for classification and display. +public enum SQLStatementKind: String, Sendable, Hashable, CaseIterable { + case select = "SELECT" + case insert = "INSERT" + case update = "UPDATE" + case delete = "DELETE" + case drop = "DROP" + case alter = "ALTER" + case truncate = "TRUNCATE" + + /// All kinds that are classified as destructive. + public static let destructiveKinds: Set = [ + .delete, .drop, .alter, .truncate + ] + + /// All kinds that are classified as mutations (data-modifying but not destructive). + public static let mutationKinds: Set = [ + .insert, .update + ] + + /// Whether this kind of statement is destructive. + public var isDestructive: Bool { + Self.destructiveKinds.contains(self) + } + + /// Whether this kind of statement is a mutation (INSERT/UPDATE). + public var isMutation: Bool { + Self.mutationKinds.contains(self) + } +} + +// MARK: - Classification Function + +/// Classifies a SQL statement string by its destructive potential. +/// +/// The classifier inspects the first keyword token of the statement +/// (case-insensitive) to determine the statement kind, then maps it +/// to a ``DestructiveClassification``. +/// +/// - Parameter sql: The SQL statement to classify. +/// - Returns: The classification for the statement. +public func classifySQL(_ sql: String) -> DestructiveClassification { + guard let kind = detectStatementKind(sql) else { + return .safe + } + + if kind.isDestructive { + return .destructive(kind) + } else if kind.isMutation { + return .mutation(kind) + } else { + return .safe + } +} + +/// Detects the ``SQLStatementKind`` from the leading keyword of a SQL string. +/// +/// - Parameter sql: The SQL statement to inspect. +/// - Returns: The detected kind, or `nil` if unrecognized. +public func detectStatementKind(_ sql: String) -> SQLStatementKind? { + let trimmed = sql.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + // Check each known statement kind against the first token + if trimmed.hasPrefix("SELECT") || trimmed.hasPrefix("WITH") { + return .select + } else if trimmed.hasPrefix("INSERT") { + return .insert + } else if trimmed.hasPrefix("UPDATE") { + return .update + } else if trimmed.hasPrefix("DELETE") { + return .delete + } else if trimmed.hasPrefix("DROP") { + return .drop + } else if trimmed.hasPrefix("ALTER") { + return .alter + } else if trimmed.hasPrefix("TRUNCATE") { + return .truncate + } + + return nil +} + +// MARK: - Destructive Operation Context + +/// Context provided to the delegate when a destructive operation needs confirmation. +/// +/// Contains all the information a UI or programmatic handler needs to +/// decide whether to allow the operation. +public struct DestructiveOperationContext: Sendable { + /// The SQL statement that would be executed. + public let sql: String + + /// The detected kind of statement (DELETE, DROP, ALTER, TRUNCATE). + public let statementKind: SQLStatementKind + + /// The classification result. + public let classification: DestructiveClassification + + /// A human-readable description of what the operation will do. + public let description: String + + /// The target table name, if detected. + public let targetTable: String? + + public init( + sql: String, + statementKind: SQLStatementKind, + classification: DestructiveClassification, + description: String, + targetTable: String? = nil + ) { + self.sql = sql + self.statementKind = statementKind + self.classification = classification + self.description = description + self.targetTable = targetTable + } +} + +// MARK: - ToolExecutionDelegate Protocol + +/// A delegate that controls execution of SQL operations, providing +/// confirmation gates for destructive statements. +/// +/// Implement this protocol to intercept destructive SQL operations +/// (DELETE, DROP, ALTER, TRUNCATE) before they are executed. The +/// ``ChatEngine`` consults the delegate whenever it encounters a +/// statement classified as ``DestructiveClassification/destructive(_:)``. +/// +/// ## Example +/// +/// ```swift +/// struct MyDelegate: ToolExecutionDelegate { +/// func confirmDestructiveOperation( +/// _ context: DestructiveOperationContext +/// ) async -> Bool { +/// // Show a confirmation dialog to the user +/// return await showAlert( +/// "Confirm \(context.statementKind.rawValue)", +/// message: context.description +/// ) +/// } +/// } +/// +/// let engine = ChatEngine( +/// database: pool, +/// model: model, +/// delegate: MyDelegate() +/// ) +/// ``` +public protocol ToolExecutionDelegate: Sendable { + + /// Called when a destructive SQL operation is about to be executed. + /// + /// The delegate should present the operation details to the user and + /// return `true` to proceed or `false` to cancel. + /// + /// - Parameter context: Details about the destructive operation. + /// - Returns: `true` to allow execution, `false` to reject it. + func confirmDestructiveOperation( + _ context: DestructiveOperationContext + ) async -> Bool + + /// Called before any SQL statement is executed. + /// + /// This is an observation hook — the engine does not wait for a + /// decision. Override to log, audit, or instrument queries. + /// + /// - Parameters: + /// - sql: The SQL about to be executed. + /// - classification: The destructive classification of the statement. + func willExecuteSQL( + _ sql: String, + classification: DestructiveClassification + ) async + + /// Called after a SQL statement completes execution. + /// + /// - Parameters: + /// - sql: The SQL that was executed. + /// - success: Whether execution succeeded. + func didExecuteSQL( + _ sql: String, + success: Bool + ) async +} + +// MARK: - Default Implementations + +extension ToolExecutionDelegate { + /// Default: rejects all destructive operations. + public func confirmDestructiveOperation( + _ context: DestructiveOperationContext + ) async -> Bool { + false + } + + /// Default: no-op. + public func willExecuteSQL( + _ sql: String, + classification: DestructiveClassification + ) async {} + + /// Default: no-op. + public func didExecuteSQL( + _ sql: String, + success: Bool + ) async {} +} + +// MARK: - Built-in Delegates + +/// A delegate that automatically approves all destructive operations. +/// +/// Use this only in testing or trusted environments where confirmation +/// is not needed. +public struct AutoApproveDelegate: ToolExecutionDelegate { + public init() {} + + public func confirmDestructiveOperation( + _ context: DestructiveOperationContext + ) async -> Bool { + true + } +} + +/// A delegate that always rejects destructive operations. +/// +/// This is the safest option and matches the default behavior. +public struct RejectAllDelegate: ToolExecutionDelegate { + public init() {} + + public func confirmDestructiveOperation( + _ context: DestructiveOperationContext + ) async -> Bool { + false + } +} diff --git a/Sources/SwiftDBAI/Models/ConversationHistory.swift b/Sources/SwiftDBAI/Models/ConversationHistory.swift new file mode 100644 index 0000000..5c74cc9 --- /dev/null +++ b/Sources/SwiftDBAI/Models/ConversationHistory.swift @@ -0,0 +1,143 @@ +// ConversationHistory.swift +// SwiftDBAI +// +// Ordered chat message history with configurable context window. + +import Foundation + +/// Stores an ordered sequence of ``ChatMessage`` instances with a configurable +/// context window limit. +/// +/// When the number of messages exceeds ``maxMessages``, the oldest messages are +/// trimmed to keep the history within budget. This prevents unbounded token +/// growth when building LLM prompts from conversation history. +/// +/// Usage: +/// ```swift +/// var history = ConversationHistory(maxMessages: 20) +/// history.append(ChatMessage(role: .user, content: "How many users?")) +/// history.append(ChatMessage(role: .assistant, content: "42", sql: "SELECT COUNT(*) FROM users")) +/// print(history.promptText) // formatted for LLM context +/// ``` +public struct ConversationHistory: Sendable { + + /// The maximum number of messages to retain. `nil` means unlimited. + public let maxMessages: Int? + + /// All messages in chronological order. + public private(set) var messages: [ChatMessage] = [] + + /// Creates a new conversation history. + /// + /// - Parameter maxMessages: Maximum number of messages to keep in the + /// context window. Pass `nil` for unlimited history. Defaults to 50. + public init(maxMessages: Int? = 50) { + precondition(maxMessages == nil || maxMessages! > 0, + "maxMessages must be positive or nil") + self.maxMessages = maxMessages + } + + /// The number of messages currently stored. + public var count: Int { messages.count } + + /// Whether the history is empty. + public var isEmpty: Bool { messages.isEmpty } + + // MARK: - Mutating Operations + + /// Appends a message and trims the history if it exceeds the context window. + public mutating func append(_ message: ChatMessage) { + messages.append(message) + trimIfNeeded() + } + + /// Appends multiple messages and trims once afterward. + public mutating func append(contentsOf newMessages: [ChatMessage]) { + messages.append(contentsOf: newMessages) + trimIfNeeded() + } + + /// Removes all messages from the history. + public mutating func clear() { + messages.removeAll() + } + + // MARK: - Context Window + + /// Returns the most recent messages formatted for inclusion in an LLM prompt. + /// + /// Each message is formatted as `[role] content`, with SQL and query results + /// included inline for assistant messages. + /// + /// - Parameter limit: Optional override to further restrict the number of + /// messages returned. When `nil`, uses the full retained history. + /// - Returns: An array of prompt-formatted strings, one per message. + public func promptMessages(limit: Int? = nil) -> [String] { + let slice: ArraySlice + if let limit { + slice = messages.suffix(limit) + } else { + slice = messages[...] + } + return slice.map { message in + Self.formatForPrompt(message) + } + } + + /// Returns the combined prompt text for all retained messages, separated by + /// double newlines. + public var promptText: String { + promptMessages().joined(separator: "\n\n") + } + + // MARK: - Queries + + /// Returns only user messages. + public var userMessages: [ChatMessage] { + messages.filter { $0.role == .user } + } + + /// Returns only assistant messages. + public var assistantMessages: [ChatMessage] { + messages.filter { $0.role == .assistant } + } + + /// Returns the last message, if any. + public var lastMessage: ChatMessage? { + messages.last + } + + /// Returns the most recent user query text, if any. + public var lastUserQuery: String? { + messages.last(where: { $0.role == .user })?.content + } + + /// Returns the most recent assistant message, if any. + public var lastAssistantMessage: ChatMessage? { + messages.last(where: { $0.role == .assistant }) + } + + // MARK: - Private + + /// Formats a ``ChatMessage`` into a string suitable for LLM prompt context. + private static func formatForPrompt(_ message: ChatMessage) -> String { + var parts: [String] = ["[\(message.role.rawValue)] \(message.content)"] + + if let sql = message.sql { + parts.append("SQL: \(sql)") + } + + if let result = message.queryResult { + parts.append("Result:\n\(result.tabularDescription)") + } + + return parts.joined(separator: "\n") + } + + /// Trims the oldest messages to stay within the context window. + private mutating func trimIfNeeded() { + guard let max = maxMessages, messages.count > max else { return } + let overflow = messages.count - max + messages.removeFirst(overflow) + } +} diff --git a/Sources/SwiftDBAI/Models/QueryResult.swift b/Sources/SwiftDBAI/Models/QueryResult.swift new file mode 100644 index 0000000..91d72ac --- /dev/null +++ b/Sources/SwiftDBAI/Models/QueryResult.swift @@ -0,0 +1,136 @@ +// QueryResult.swift +// SwiftDBAI +// +// Structured result from SQL query execution. + +import Foundation + +/// Represents the result of executing a SQL query against the database. +/// +/// Contains raw row data as dictionaries, column metadata, row count, +/// the original SQL string, and execution timing. +public struct QueryResult: Sendable, Equatable { + + /// A single cell value from a query result. + /// + /// Wraps SQLite's dynamic value types into a type-safe, Sendable enum. + public enum Value: Sendable, Equatable, CustomStringConvertible { + case text(String) + case integer(Int64) + case real(Double) + case blob(Data) + case null + + public var description: String { + switch self { + case .text(let s): return s + case .integer(let i): return String(i) + case .real(let d): + if d == d.rounded() && abs(d) < 1e15 { + return String(format: "%.0f", d) + } + return String(d) + case .blob(let data): return "<\(data.count) bytes>" + case .null: return "NULL" + } + } + + /// Returns the value as a `Double` if it is numeric, nil otherwise. + public var doubleValue: Double? { + switch self { + case .integer(let i): return Double(i) + case .real(let d): return d + case .text(let s): return Double(s) + default: return nil + } + } + + /// Returns the value as a `String` (non-nil for all cases). + public var stringValue: String { description } + + /// Returns `true` if this value is `.null`. + public var isNull: Bool { + if case .null = self { return true } + return false + } + } + + /// Column names in the order they appear in the result set. + public let columns: [String] + + /// Row data as an array of dictionaries mapping column name to value. + public let rows: [[String: Value]] + + /// Total number of rows returned. + public var rowCount: Int { rows.count } + + /// The SQL statement that was executed. + public let sql: String + + /// Time taken to execute the query, in seconds. + public let executionTime: TimeInterval + + /// Number of rows affected (for INSERT/UPDATE/DELETE). Nil for SELECT. + public let rowsAffected: Int? + + public init( + columns: [String], + rows: [[String: Value]], + sql: String, + executionTime: TimeInterval, + rowsAffected: Int? = nil + ) { + self.columns = columns + self.rows = rows + self.sql = sql + self.executionTime = executionTime + self.rowsAffected = rowsAffected + } + + // MARK: - Convenience Accessors + + /// Returns all values for a given column, in row order. + public func values(forColumn column: String) -> [Value] { + rows.compactMap { $0[column] } + } + + /// Returns a compact tabular string representation of the results. + /// + /// Useful for embedding query results into LLM prompts. + public var tabularDescription: String { + guard !rows.isEmpty else { + return "(empty result set)" + } + + var lines: [String] = [] + + // Header + lines.append(columns.joined(separator: " | ")) + lines.append(String(repeating: "-", count: lines[0].count)) + + // Rows (cap at 50 for prompt size) + let displayRows = rows.prefix(50) + for row in displayRows { + let vals = columns.map { col in + row[col]?.description ?? "NULL" + } + lines.append(vals.joined(separator: " | ")) + } + + if rows.count > 50 { + lines.append("... and \(rows.count - 50) more rows") + } + + return lines.joined(separator: "\n") + } + + /// Returns true if the result looks like a single aggregate value + /// (1 row, 1-3 columns, all numeric). + public var isAggregate: Bool { + guard rowCount == 1, columns.count <= 3 else { return false } + let firstRow = rows[0] + return columns.allSatisfy { col in + firstRow[col]?.doubleValue != nil + } + } +} diff --git a/Sources/SwiftDBAI/Parsing/SQLQueryParser.swift b/Sources/SwiftDBAI/Parsing/SQLQueryParser.swift new file mode 100644 index 0000000..50cbda8 --- /dev/null +++ b/Sources/SwiftDBAI/Parsing/SQLQueryParser.swift @@ -0,0 +1,423 @@ +// SQLQueryParser.swift +// SwiftDBAI +// +// Extracts and validates SQL statements from raw LLM response text. + +import Foundation + +/// Errors that can occur during SQL parsing and validation. +public enum SQLParsingError: Error, Sendable, Equatable, CustomStringConvertible { + /// No SQL statement could be found in the LLM response. + case noSQLFound + + /// The SQL statement uses an operation not in the allowlist. + case operationNotAllowed(SQLOperation) + + /// A destructive operation (DELETE) requires user confirmation. + case confirmationRequired(sql: String, operation: SQLOperation) + + /// The mutation targets a table not in the allowed mutation tables. + case tableNotAllowed(table: String, operation: SQLOperation) + + /// The SQL contains a disallowed keyword (e.g., DROP, ALTER, TRUNCATE). + case dangerousOperation(String) + + /// Multiple SQL statements were found but only single-statement execution is supported. + case multipleStatements + + public var description: String { + switch self { + case .noSQLFound: + return "No SQL statement found in the response." + case .operationNotAllowed(let op): + return "Operation '\(op.rawValue.uppercased())' is not allowed by the current configuration." + case .confirmationRequired(let sql, let op): + return "The \(op.rawValue.uppercased()) operation requires confirmation: \(sql)" + case .tableNotAllowed(let table, let op): + return "The \(op.rawValue.uppercased()) operation is not allowed on table '\(table)'." + case .dangerousOperation(let keyword): + return "Dangerous SQL operation '\(keyword)' is never allowed." + case .multipleStatements: + return "Only single SQL statements are supported." + } + } +} + +/// Result of successfully parsing SQL from an LLM response. +public struct ParsedSQL: Sendable, Equatable { + /// The cleaned SQL statement ready for execution. + public let sql: String + + /// The detected operation type. + public let operation: SQLOperation + + /// Whether this operation requires user confirmation before execution. + public let requiresConfirmation: Bool + + public init(sql: String, operation: SQLOperation, requiresConfirmation: Bool = false) { + self.sql = sql + self.operation = operation + self.requiresConfirmation = requiresConfirmation + } +} + +/// Extracts SQL statements from raw LLM response text and validates them +/// against the configured ``OperationAllowlist``. +/// +/// The parser handles common LLM output patterns: +/// - SQL in markdown code blocks (```sql ... ```) +/// - SQL in generic code blocks (``` ... ```) +/// - Raw SQL statements in plain text +/// - SQL prefixed with labels like "SQL:" or "Query:" +public struct SQLQueryParser: Sendable { + + /// Keywords that are never allowed regardless of allowlist configuration. + private static let dangerousKeywords: Set = [ + "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT", "REVOKE", + "ATTACH", "DETACH", "PRAGMA", "VACUUM", "REINDEX" + ] + + /// The operation allowlist to validate against. + private let allowlist: OperationAllowlist + + /// The mutation policy for table-level restrictions. + private let mutationPolicy: MutationPolicy? + + /// Creates a parser with the given operation allowlist. + /// - Parameter allowlist: The set of permitted operations. Defaults to read-only. + public init(allowlist: OperationAllowlist = .readOnly) { + self.allowlist = allowlist + self.mutationPolicy = nil + } + + /// Creates a parser with a mutation policy (preferred initializer). + /// - Parameter mutationPolicy: The mutation policy controlling operations and table access. + public init(mutationPolicy: MutationPolicy) { + self.allowlist = mutationPolicy.operationAllowlist + self.mutationPolicy = mutationPolicy + } + + /// Extracts and validates a SQL statement from raw LLM response text. + /// + /// - Parameter text: The raw text from the LLM response. + /// - Returns: A ``ParsedSQL`` containing the validated statement. + /// - Throws: ``SQLParsingError`` if extraction or validation fails. + public func parse(_ text: String) throws -> ParsedSQL { + let sql = try extractSQL(from: text) + return try validate(sql) + } + + // MARK: - Extraction + + /// Attempts to extract a SQL statement from the LLM response text. + /// Tries multiple strategies in order of confidence. + func extractSQL(from text: String) throws -> String { + // Pre-processing: strip ... tags (Qwen-style models) + let preprocessed = stripThinkTags(text) + + // Strategy 1: SQL in markdown fenced code block with sql language tag + if let sql = extractFromSQLCodeBlock(preprocessed) { + return sql + } + + // Strategy 2: SQL in generic fenced code block + if let sql = extractFromGenericCodeBlock(preprocessed) { + return sql + } + + // Strategy 3: SQL after a label like "SQL:" or "Query:" + if let sql = extractFromLabel(preprocessed) { + return sql + } + + // Strategy 4: Direct SQL detection in plain text (includes WITH) + if let sql = extractDirectSQL(preprocessed) { + return sql + } + + // Strategy 5: Strip markdown fence markers (3+ backticks with optional + // language tag) and retry. Only removes fences, not single backticks + // used for SQLite identifier quoting like `column name`. + let defenced = stripMarkdownFences(preprocessed) + if defenced != preprocessed, let sql = extractDirectSQL(defenced) { + return sql + } + + throw SQLParsingError.noSQLFound + } + + /// Strips `...` tags produced by Qwen-style reasoning models. + private func stripThinkTags(_ text: String) -> String { + text.replacingOccurrences( + of: #"[\s\S]*?"#, + with: "", + options: .regularExpression + ).trimmingCharacters(in: .whitespacesAndNewlines) + } + + /// Removes markdown fence markers (3+ backticks with optional language tag) + /// while preserving single backtick identifier quoting like `column name`. + private func stripMarkdownFences(_ text: String) -> String { + text.replacingOccurrences( + of: #"`{3,}\s*(?:sql|SQL)?\s*"#, + with: " ", + options: .regularExpression + ).trimmingCharacters(in: .whitespacesAndNewlines) + } + + /// Extracts SQL from a ```sql ... ``` code block. + /// Handles 3+ backticks, optional newline before closing fence, + /// and single-line code blocks like ```sql SELECT ... ```. + private func extractFromSQLCodeBlock(_ text: String) -> String? { + // Match 3+ backticks with sql tag, content, then 3+ closing backticks + let pattern = #"`{3,}sql\s*\n?([\s\S]*?)`{3,}"# + return firstMatch(pattern: pattern, in: text, group: 1, options: .caseInsensitive)? + .trimmingCharacters(in: .whitespacesAndNewlines) + .nonEmptyOrNil + } + + /// Extracts SQL from a generic ``` ... ``` code block (no language tag). + /// Handles 3+ backticks and flexible whitespace. + private func extractFromGenericCodeBlock(_ text: String) -> String? { + let pattern = #"`{3,}\s*\n([\s\S]*?)`{3,}"# + guard let content = firstMatch(pattern: pattern, in: text, group: 1)? + .trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + // Only accept if it looks like SQL + guard looksLikeSQL(content) else { return nil } + return content.nonEmptyOrNil + } + + /// Extracts SQL after labels like "SQL:", "Query:", "Here's the query:", "The SQL query is:" + private func extractFromLabel(_ text: String) -> String? { + // Match common label patterns followed by a SQL statement. + // The SQL ends at a double newline, a single newline followed by non-SQL text, or end-of-string. + let pattern = #"(?:SQL|Query|Statement|query is|SQL query is)\s*:\s*\n?\s*((?:SELECT|INSERT|UPDATE|DELETE|WITH)\b(?:[^;'\n]|'[^']*'|\n(?=\s*(?:SELECT|INSERT|UPDATE|DELETE|WITH|FROM|WHERE|JOIN|INNER|LEFT|RIGHT|OUTER|CROSS|ON|AND|OR|ORDER|GROUP|HAVING|LIMIT|OFFSET|UNION|EXCEPT|INTERSECT|AS|SET|INTO|VALUES)\b)|\n(?=\s))*;?)"# + guard let content = firstMatch(pattern: pattern, in: text, group: 1, options: [.caseInsensitive])? + .trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + guard looksLikeSQL(content) else { return nil } + return content.nonEmptyOrNil + } + + /// Detects SQL directly in the text by matching known statement patterns. + /// Handles SELECT, INSERT, UPDATE, DELETE, and WITH (CTE) statements. + private func extractDirectSQL(_ text: String) -> String? { + // Match SQL statement starting with a keyword, allowing semicolons inside string literals. + // The WITH clause is included to support CTE queries. + let pattern = #"(?:^|\n)\s*((?:SELECT|INSERT|UPDATE|DELETE|WITH)\b(?:[^;']|'[^']*')*;?)"# + guard var content = firstMatch(pattern: pattern, in: text, group: 1, options: .caseInsensitive)? + .trimmingCharacters(in: .whitespacesAndNewlines) else { + return nil + } + // Strip any trailing markdown fence markers that got captured + content = content.replacingOccurrences( + of: #"\s*`{3,}\s*$"#, + with: "", + options: .regularExpression + ).trimmingCharacters(in: .whitespacesAndNewlines) + return content.nonEmptyOrNil + } + + // MARK: - Validation + + /// Validates a SQL string against the allowlist and safety rules. + func validate(_ sql: String) throws -> ParsedSQL { + let cleaned = cleanSQL(sql) + + guard !cleaned.isEmpty else { + throw SQLParsingError.noSQLFound + } + + // Check for multiple statements (semicolons in non-trivial positions) + if containsMultipleStatements(cleaned) { + throw SQLParsingError.multipleStatements + } + + // Check for dangerous operations first (before allowlist) + try checkDangerousKeywords(cleaned) + + // Detect the operation type + let operation = detectOperation(cleaned) + + // Check against the allowlist + guard allowlist.isAllowed(operation) else { + throw SQLParsingError.operationNotAllowed(operation) + } + + // Check table-level restrictions for mutation operations + if let policy = mutationPolicy, operation != .select, + let targetTable = extractTargetTable(from: cleaned, operation: operation) { + guard policy.isAllowed(operation: operation, on: targetTable) else { + throw SQLParsingError.tableNotAllowed(table: targetTable, operation: operation) + } + } + + // DELETE requires confirmation when policy says so, or always by default + let requiresConfirmation: Bool + if let policy = mutationPolicy { + requiresConfirmation = policy.requiresConfirmation(for: operation) + } else { + requiresConfirmation = operation == .delete + } + + return ParsedSQL( + sql: cleaned, + operation: operation, + requiresConfirmation: requiresConfirmation + ) + } + + // MARK: - Helpers + + /// Cleans a SQL string by removing trailing semicolons (outside string literals) and excess whitespace. + private func cleanSQL(_ sql: String) -> String { + var cleaned = sql.trimmingCharacters(in: .whitespacesAndNewlines) + // Remove trailing semicolons only if they're outside string literals + while cleaned.hasSuffix(";") && !isInsideStringLiteral(sql: cleaned, position: cleaned.index(before: cleaned.endIndex)) { + cleaned = String(cleaned.dropLast()).trimmingCharacters(in: .whitespacesAndNewlines) + } + // Collapse internal whitespace outside string literals + cleaned = collapseWhitespace(cleaned) + return cleaned + } + + /// Collapses whitespace while preserving string literal contents. + private func collapseWhitespace(_ sql: String) -> String { + var result = "" + var inString = false + var prevWasSpace = false + for ch in sql { + if ch == "'" { + inString.toggle() + prevWasSpace = false + result.append(ch) + } else if inString { + result.append(ch) + } else if ch.isWhitespace { + if !prevWasSpace { + result.append(" ") + prevWasSpace = true + } + } else { + prevWasSpace = false + result.append(ch) + } + } + return result + } + + /// Returns true if the character at the given position is inside a single-quoted string literal. + private func isInsideStringLiteral(sql: String, position: String.Index) -> Bool { + var inString = false + for idx in sql.indices { + if idx == position { return inString } + if sql[idx] == "'" { inString.toggle() } + } + return false + } + + /// Checks whether cleaned SQL contains multiple statements. + private func containsMultipleStatements(_ sql: String) -> Bool { + // Remove string literals before checking for semicolons + var inString = false + for ch in sql { + if ch == "'" { + inString.toggle() + } else if ch == ";" && !inString { + return true + } + } + return false + } + + /// Checks for dangerous SQL keywords that are never allowed. + private func checkDangerousKeywords(_ sql: String) throws { + let upper = sql.uppercased() + // Tokenize to avoid partial matches (e.g., "DROPDOWN" matching "DROP") + let tokens = upper.components(separatedBy: .alphanumerics.inverted) + .filter { !$0.isEmpty } + + for keyword in Self.dangerousKeywords { + if tokens.contains(keyword) { + throw SQLParsingError.dangerousOperation(keyword) + } + } + } + + /// Detects the SQL operation type from the first keyword. + private func detectOperation(_ sql: String) -> SQLOperation { + let upper = sql.uppercased().trimmingCharacters(in: .whitespaces) + + if upper.hasPrefix("SELECT") || upper.hasPrefix("WITH") { + return .select + } else if upper.hasPrefix("INSERT") { + return .insert + } else if upper.hasPrefix("UPDATE") { + return .update + } else if upper.hasPrefix("DELETE") { + return .delete + } + + // Default to select for unrecognized patterns (e.g. EXPLAIN) + return .select + } + + /// Extracts the target table name from a mutation SQL statement. + /// + /// Handles common patterns: + /// - `INSERT INTO table_name ...` + /// - `UPDATE table_name SET ...` + /// - `DELETE FROM table_name ...` + private func extractTargetTable(from sql: String, operation: SQLOperation) -> String? { + let pattern: String + switch operation { + case .insert: + pattern = #"INSERT\s+INTO\s+[`"\[]?(\w+)[`"\]]?"# + case .update: + pattern = #"UPDATE\s+[`"\[]?(\w+)[`"\]]?"# + case .delete: + pattern = #"DELETE\s+FROM\s+[`"\[]?(\w+)[`"\]]?"# + case .select: + return nil + } + return firstMatch(pattern: pattern, in: sql, group: 1, options: .caseInsensitive) + } + + /// Returns true if the text looks like a SQL statement. + private func looksLikeSQL(_ text: String) -> Bool { + let upper = text.uppercased().trimmingCharacters(in: .whitespaces) + let sqlPrefixes = ["SELECT", "INSERT", "UPDATE", "DELETE", "WITH"] + return sqlPrefixes.contains { upper.hasPrefix($0) } + } + + /// Extracts the first regex match group from the text. + private func firstMatch( + pattern: String, + in text: String, + group: Int, + options: NSRegularExpression.Options = [] + ) -> String? { + guard let regex = try? NSRegularExpression(pattern: pattern, options: options) else { + return nil + } + let range = NSRange(text.startIndex..., in: text) + guard let match = regex.firstMatch(in: text, range: range), + match.numberOfRanges > group, + let groupRange = Range(match.range(at: group), in: text) else { + return nil + } + return String(text[groupRange]) + } +} + +// MARK: - String Extension + +private extension String { + /// Returns nil if the string is empty, otherwise returns self. + var nonEmptyOrNil: String? { + isEmpty ? nil : self + } +} diff --git a/Sources/SwiftDBAI/Prompt/PromptBuilder.swift b/Sources/SwiftDBAI/Prompt/PromptBuilder.swift new file mode 100644 index 0000000..b7890b8 --- /dev/null +++ b/Sources/SwiftDBAI/Prompt/PromptBuilder.swift @@ -0,0 +1,238 @@ +/// Builds structured LLM prompts for SQL generation from a database schema +/// and natural language input. +/// +/// `PromptBuilder` is the bridge between the introspected database schema and +/// the LLM. It produces two things: +/// 1. A **system instructions** string containing schema context and behavioral rules +/// 2. A **user prompt** string wrapping the natural language question +/// +/// Usage: +/// ```swift +/// let builder = PromptBuilder(schema: mySchema, allowlist: .readOnly) +/// let instructions = builder.buildSystemInstructions() +/// let prompt = builder.buildUserPrompt("How many users signed up this week?") +/// ``` +public struct PromptBuilder: Sendable { + /// The database schema to include as context. + public let schema: DatabaseSchema + + /// Which SQL operations the LLM may generate. + public let allowlist: OperationAllowlist + + /// Optional additional context to append to the system instructions + /// (e.g., business-specific terminology or query hints). + public let additionalContext: String? + + /// Creates a prompt builder for the given schema and allowlist. + /// + /// - Parameters: + /// - schema: The introspected database schema. + /// - allowlist: Permitted SQL operations. Defaults to ``OperationAllowlist/readOnly``. + /// - additionalContext: Extra instructions appended to the system prompt. + public init( + schema: DatabaseSchema, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil + ) { + self.schema = schema + self.allowlist = allowlist + self.additionalContext = additionalContext + } + + // MARK: - System Instructions + + /// Builds the system instructions string that should be passed as the + /// `instructions` parameter when creating a `LanguageModelSession`. + /// + /// The instructions include: + /// - Role definition + /// - The full database schema + /// - SQL generation rules and constraints + /// - The operation allowlist + /// - Output format requirements + public func buildSystemInstructions() -> String { + var sections: [String] = [] + + // 1. Role + sections.append(Self.roleSection) + + // 2. Schema + sections.append(buildSchemaSection()) + + // 3. Operation permissions + sections.append(buildPermissionsSection()) + + // 4. SQL generation rules + sections.append(Self.sqlRulesSection) + + // 5. Output format + sections.append(Self.outputFormatSection) + + // 6. Additional context + if let additionalContext, !additionalContext.isEmpty { + sections.append("ADDITIONAL CONTEXT\n=================\n\(additionalContext)") + } + + return sections.joined(separator: "\n\n") + } + + // MARK: - User Prompt + + /// Wraps a natural language question into a user prompt string. + /// + /// - Parameter question: The user's natural language question. + /// - Returns: A formatted prompt string for the LLM. + public func buildUserPrompt(_ question: String) -> String { + question + } + + /// Builds a follow-up prompt that includes prior SQL context for + /// multi-turn conversations. + /// + /// - Parameters: + /// - question: The user's follow-up question. + /// - previousSQL: The SQL from the previous turn, for context. + /// - previousResultSummary: A brief summary of what the previous query returned. + /// - Returns: A formatted prompt string. + public func buildFollowUpPrompt( + _ question: String, + previousSQL: String, + previousResultSummary: String + ) -> String { + """ + Previous query: \(previousSQL) + Previous result: \(previousResultSummary) + + Follow-up question: \(question) + """ + } + + /// Builds a prompt that includes the full conversation history within the + /// configured context window, enabling the LLM to resolve follow-up + /// references (pronouns, implicit table/column references, etc.). + /// + /// - Parameters: + /// - question: The user's current question. + /// - history: The conversation history messages within the context window. + /// - Returns: A formatted prompt string with conversation context. + public func buildConversationPrompt( + _ question: String, + history: [ChatMessage] + ) -> String { + guard !history.isEmpty else { + return buildUserPrompt(question) + } + + var lines: [String] = [] + lines.append("CONVERSATION HISTORY") + lines.append("====================") + + for message in history { + switch message.role { + case .user: + lines.append("User: \(message.content)") + case .assistant: + if let sql = message.sql { + lines.append("Assistant SQL: \(sql)") + } + lines.append("Assistant: \(message.content)") + case .error: + lines.append("Error: \(message.content)") + } + } + + lines.append("") + lines.append("CURRENT QUESTION") + lines.append("================") + lines.append(question) + + return lines.joined(separator: "\n") + } + + // MARK: - Private Sections + + private func buildSchemaSection() -> String { + var lines: [String] = [] + lines.append("DATABASE SCHEMA") + lines.append("===============") + lines.append("") + lines.append(schema.schemaDescription) + return lines.joined(separator: "\n") + } + + private func buildPermissionsSection() -> String { + var lines: [String] = [] + lines.append("PERMISSIONS") + lines.append("===========") + lines.append(allowlist.describeForLLM()) + return lines.joined(separator: "\n") + } + + // MARK: - Static Content + + static let roleSection = """ + ROLE + ==== + You are a SQL assistant for a SQLite database. Your job is to translate \ + natural language questions into valid SQLite SQL queries based on the \ + database schema provided below. + + IMPORTANT: + - ONLY reference tables and columns that exist in the schema. Never \ + fabricate table or column names. + - Interpret questions by INTENT, not literally. If a user asks \ + "articles starting with the", they mean articles whose title begins \ + with the word "the", NOT articles containing that exact phrase. + - If the schema does not have a column for what the user asks about, \ + use the closest available column or return a query that explains \ + what data is available. + """ + + static let sqlRulesSection = """ + SQL GENERATION RULES + ==================== + 1. Use ONLY the tables and columns listed in the schema above. + 2. Use SQLite-compatible syntax (e.g., || for string concatenation, \ + IFNULL instead of COALESCE where needed). + 3. Use appropriate JOINs when queries span multiple tables — reference \ + the foreign key relationships in the schema. + 4. For date/time operations, use SQLite date functions \ + (date(), time(), datetime(), strftime()). + 5. Use parameterized-style values where possible. For literal values \ + from the user's question, embed them directly in the SQL. + 6. Always include an ORDER BY clause when the user implies ordering. + 7. Use LIMIT when the user asks for "top N" or "first N" results. \ + Default to LIMIT 20 when no limit is specified and the result could be large. + 8. For aggregate queries (count, sum, average, min, max), use the \ + appropriate SQL aggregate functions. + 9. When the user's question is ambiguous, prefer the simplest valid \ + interpretation that returns useful results. + 10. Never generate DDL statements (CREATE, ALTER, DROP TABLE). + 11. If a column the user asks about does not exist, use the closest \ + available column. Do NOT reference columns not in the schema. + + EXAMPLES + -------- + User: "articles starting with the" + SQL: SELECT title, url FROM articles WHERE title LIKE 'The %' ORDER BY datePublished DESC LIMIT 20 + + User: "most popular items" + SQL: SELECT name, COUNT(*) AS count FROM items GROUP BY name ORDER BY count DESC LIMIT 10 + + User: "anything from last week" + SQL: SELECT * FROM articles WHERE datePublished >= date('now', '-7 days') ORDER BY datePublished DESC + + User: "how many per category" + SQL: SELECT category, COUNT(*) AS count FROM items GROUP BY category ORDER BY count DESC + """ + + static let outputFormatSection = """ + OUTPUT FORMAT + ============= + Output ONLY the raw SQL query. \ + Do NOT wrap the SQL in markdown code fences or backticks. \ + Do NOT include any explanation, comments, or formatting before or after the SQL. \ + Do NOT prefix with labels like "SQL:" or "Query:". \ + The output should be directly executable SQL — nothing else. + """ +} diff --git a/Sources/SwiftDBAI/Rendering/ChartDataDetector.swift b/Sources/SwiftDBAI/Rendering/ChartDataDetector.swift new file mode 100644 index 0000000..3d921a8 --- /dev/null +++ b/Sources/SwiftDBAI/Rendering/ChartDataDetector.swift @@ -0,0 +1,423 @@ +// ChartDataDetector.swift +// SwiftDBAI +// +// Analyzes query results to determine chart eligibility and +// recommends appropriate chart types based on data shape. + +import Foundation + +/// Detects whether a `DataTable` is suitable for charting and +/// recommends the best chart type based on data shape heuristics. +/// +/// The detector examines column types, row counts, and value distributions +/// to produce a `ChartRecommendation` that the rendering layer can use +/// to auto-select an appropriate Swift Charts visualization. +/// +/// Usage: +/// ```swift +/// let detector = ChartDataDetector() +/// if let recommendation = detector.detect(table) { +/// switch recommendation.chartType { +/// case .bar: // render bar chart +/// case .line: // render line chart +/// case .pie: // render pie chart +/// } +/// } +/// ``` +public struct ChartDataDetector: Sendable { + + // MARK: - Chart Types + + /// The type of chart recommended for the data. + public enum ChartType: String, Sendable, Equatable, CaseIterable { + /// Vertical bar chart — best for categorical comparisons. + case bar + /// Line chart — best for time series or ordered sequences. + case line + /// Pie/donut chart — best for proportional breakdowns with few categories. + case pie + } + + /// A recommendation for how to chart a `DataTable`. + public struct ChartRecommendation: Sendable, Equatable { + /// The recommended chart type. + public let chartType: ChartType + + /// The column to use for the category axis (x-axis / labels). + public let categoryColumn: String + + /// The column to use for the value axis (y-axis / sizes). + public let valueColumn: String + + /// Confidence score from 0.0 (guess) to 1.0 (strong match). + public let confidence: Double + + /// Human-readable reason for this recommendation. + public let reason: String + + public init( + chartType: ChartType, + categoryColumn: String, + valueColumn: String, + confidence: Double, + reason: String + ) { + self.chartType = chartType + self.categoryColumn = categoryColumn + self.valueColumn = valueColumn + self.confidence = confidence + self.reason = reason + } + } + + // MARK: - Configuration + + /// Minimum rows required to consider chart-eligible. + public let minimumRows: Int + + /// Maximum rows for a pie chart (too many slices becomes unreadable). + public let maxPieSlices: Int + + /// Maximum rows for any chart before it becomes cluttered. + public let maximumRows: Int + + // MARK: - Initialization + + /// Creates a detector with configurable thresholds. + /// + /// - Parameters: + /// - minimumRows: Minimum rows for chart eligibility (default: 2). + /// - maxPieSlices: Maximum categories for pie charts (default: 8). + /// - maximumRows: Maximum rows for any chart (default: 100). + public init( + minimumRows: Int = 2, + maxPieSlices: Int = 8, + maximumRows: Int = 100 + ) { + self.minimumRows = minimumRows + self.maxPieSlices = maxPieSlices + self.maximumRows = maximumRows + } + + // MARK: - Detection + + /// Analyzes a `DataTable` and returns a chart recommendation, or `nil` + /// if the data is not suitable for charting. + /// + /// - Parameter table: The data table to analyze. + /// - Returns: A recommendation, or `nil` if no chart type fits. + public func detect(_ table: DataTable) -> ChartRecommendation? { + // Must have at least 2 columns (category + value) and enough rows + guard table.columnCount >= 2, + table.rowCount >= minimumRows, + table.rowCount <= maximumRows else { + return nil + } + + // Find candidate category and value columns + guard let (categoryCol, valueCol) = findCategoryValuePair(in: table) else { + return nil + } + + let chartType = recommendChartType( + table: table, + categoryColumn: categoryCol, + valueColumn: valueCol + ) + + let confidence = computeConfidence( + table: table, + categoryColumn: categoryCol, + valueColumn: valueCol, + chartType: chartType + ) + + let reason = describeReason( + chartType: chartType, + categoryColumn: categoryCol, + valueColumn: valueCol, + table: table + ) + + return ChartRecommendation( + chartType: chartType, + categoryColumn: categoryCol.name, + valueColumn: valueCol.name, + confidence: confidence, + reason: reason + ) + } + + /// Returns all viable chart recommendations, ranked by confidence. + /// + /// - Parameter table: The data table to analyze. + /// - Returns: An array of recommendations sorted by confidence (highest first). + public func allRecommendations(for table: DataTable) -> [ChartRecommendation] { + guard table.columnCount >= 2, + table.rowCount >= minimumRows, + table.rowCount <= maximumRows else { + return [] + } + + guard let (categoryCol, valueCol) = findCategoryValuePair(in: table) else { + return [] + } + + return ChartType.allCases.compactMap { chartType in + guard isViable(chartType, table: table, categoryColumn: categoryCol) else { + return nil + } + + let confidence = computeConfidence( + table: table, + categoryColumn: categoryCol, + valueColumn: valueCol, + chartType: chartType + ) + + let reason = describeReason( + chartType: chartType, + categoryColumn: categoryCol, + valueColumn: valueCol, + table: table + ) + + return ChartRecommendation( + chartType: chartType, + categoryColumn: categoryCol.name, + valueColumn: valueCol.name, + confidence: confidence, + reason: reason + ) + } + .sorted { $0.confidence > $1.confidence } + } + + // MARK: - Private Helpers + + /// Finds the best (category, value) column pair from the table. + private func findCategoryValuePair( + in table: DataTable + ) -> (category: DataTable.Column, value: DataTable.Column)? { + let numericColumns = table.columns.filter { isNumeric($0) } + let categoryColumns = table.columns.filter { isCategory($0) } + + // Prefer: first text/category column + first numeric column + if let cat = categoryColumns.first, let val = numericColumns.first { + return (cat, val) + } + + // Fallback: if all columns are numeric, use first as category, second as value + if numericColumns.count >= 2 { + return (numericColumns[0], numericColumns[1]) + } + + return nil + } + + /// Recommends the single best chart type for the data shape. + private func recommendChartType( + table: DataTable, + categoryColumn: DataTable.Column, + valueColumn: DataTable.Column + ) -> ChartType { + // Line: time series or sequential numeric categories (check first — strongest signal) + if isTimeSeries(categoryColumn, in: table) || isSequential(categoryColumn, in: table) { + return .line + } + + // Pie: small number of categories with all-positive values + // Only when clearly categorical (text labels) and few rows + if table.rowCount <= maxPieSlices, + isCategory(categoryColumn), + isPieCandidate(table: table, valueColumn: valueColumn), + looksProportional(table: table, valueColumn: valueColumn) { + return .pie + } + + // Default: bar chart for categorical comparisons + return .bar + } + + /// Checks if a chart type is viable for the given data. + private func isViable( + _ chartType: ChartType, + table: DataTable, + categoryColumn: DataTable.Column + ) -> Bool { + switch chartType { + case .pie: + return table.rowCount <= maxPieSlices + case .line: + return table.rowCount >= minimumRows + case .bar: + return true + } + } + + /// Determines if a column holds numeric data. + private func isNumeric(_ column: DataTable.Column) -> Bool { + switch column.inferredType { + case .integer, .real: + return true + default: + return false + } + } + + /// Determines if a column holds categorical (label) data. + private func isCategory(_ column: DataTable.Column) -> Bool { + switch column.inferredType { + case .text, .mixed: + return true + default: + return false + } + } + + /// Checks if the value column contains all non-negative values, + /// making it a candidate for pie charts. + private func isPieCandidate( + table: DataTable, + valueColumn: DataTable.Column + ) -> Bool { + let values = table.numericValues(forColumn: valueColumn.name) + guard !values.isEmpty else { return false } + // All values must be positive for a meaningful pie chart + return values.allSatisfy { $0 > 0 } + } + + /// Heuristic: do values look like they represent parts of a whole? + /// + /// Checks for aggregate-like column names (count, total, sum, amount, pct, etc.) + /// or if values sum to a round number suggesting percentages/proportions. + private func looksProportional( + table: DataTable, + valueColumn: DataTable.Column + ) -> Bool { + let proportionalNames: Set = ["count", "total", "sum", "amount", "pct", + "percent", "percentage", "share", "proportion", + "quantity", "qty", "num", "number"] + // Split on common separators and check for exact word matches + let lowerName = valueColumn.name.lowercased() + let words = Set(lowerName.split { $0 == "_" || $0 == "-" || $0 == " " }.map(String.init)) + if !words.isDisjoint(with: proportionalNames) { + return true + } + + // Check if values sum to ~100 (percentages) + let values = table.numericValues(forColumn: valueColumn.name) + let sum = values.reduce(0, +) + if abs(sum - 100.0) < 1.0 { + return true + } + + return false + } + + /// Heuristic: does the category column look like time-series data? + /// + /// Checks for date-like patterns (YYYY, YYYY-MM, YYYY-MM-DD) + /// or common time-related column names. + private func isTimeSeries(_ column: DataTable.Column, in table: DataTable) -> Bool { + let timeNames = ["date", "time", "timestamp", "year", "month", "day", + "week", "quarter", "period", "created_at", "updated_at"] + let lowerName = column.name.lowercased() + if timeNames.contains(where: { lowerName.contains($0) }) { + return true + } + + // Check if text values look like dates + if column.inferredType == .text { + let values = table.stringValues(forColumn: column.name) + let datePattern = #/^\d{4}(-\d{2}){0,2}$/# + let matchCount = values.prefix(5).filter { (try? datePattern.wholeMatch(in: $0)) != nil }.count + if matchCount >= 3 { + return true + } + } + + return false + } + + /// Heuristic: does the category column contain sequential numeric values? + private func isSequential(_ column: DataTable.Column, in table: DataTable) -> Bool { + guard isNumeric(column) else { return false } + let values = table.numericValues(forColumn: column.name) + guard values.count >= 3 else { return false } + + // Check if values are monotonically increasing + for i in 1.. Double { + var score = 0.5 // baseline + + // Bonus: clear category/value split (text + numeric) + if isCategory(categoryColumn) && isNumeric(valueColumn) { + score += 0.2 + } + + // Bonus: reasonable row count for the chart type + switch chartType { + case .bar: + if table.rowCount >= 2 && table.rowCount <= 20 { + score += 0.15 + } + case .line: + if isTimeSeries(categoryColumn, in: table) { + score += 0.2 + } else if isSequential(categoryColumn, in: table) { + score += 0.1 + } + case .pie: + if table.rowCount <= maxPieSlices && isPieCandidate(table: table, valueColumn: valueColumn) { + score += 0.2 + } + // Penalty: too many slices + if table.rowCount > 5 { + score -= 0.1 + } + } + + // Bonus: no null values in key columns + let categoryNulls = table.columnValues(named: categoryColumn.name).filter(\.isNull).count + let valueNulls = table.columnValues(named: valueColumn.name).filter(\.isNull).count + if categoryNulls == 0 && valueNulls == 0 { + score += 0.1 + } + + return min(max(score, 0.0), 1.0) + } + + /// Generates a human-readable reason for the recommendation. + private func describeReason( + chartType: ChartType, + categoryColumn: DataTable.Column, + valueColumn: DataTable.Column, + table: DataTable + ) -> String { + switch chartType { + case .bar: + return "\(table.rowCount) categories comparing \(valueColumn.name) by \(categoryColumn.name)" + case .line: + if isTimeSeries(categoryColumn, in: table) { + return "\(valueColumn.name) over time (\(categoryColumn.name))" + } + return "\(valueColumn.name) trend across \(table.rowCount) points" + case .pie: + return "Proportional breakdown of \(valueColumn.name) by \(categoryColumn.name)" + } + } +} diff --git a/Sources/SwiftDBAI/Rendering/DataTable.swift b/Sources/SwiftDBAI/Rendering/DataTable.swift new file mode 100644 index 0000000..44fcda8 --- /dev/null +++ b/Sources/SwiftDBAI/Rendering/DataTable.swift @@ -0,0 +1,255 @@ +// DataTable.swift +// SwiftDBAI +// +// Structured table representation for rendering query results +// in SwiftUI table views and charts. + +import Foundation + +/// A structured, row-column table built from a `QueryResult`. +/// +/// `DataTable` provides indexed access to rows and columns, typed column +/// metadata, and convenience methods for extracting data suitable for +/// SwiftUI `Table` views and Swift Charts. +/// +/// Usage: +/// ```swift +/// let table = DataTable(queryResult) +/// print(table.columnCount) // 3 +/// print(table[row: 0, column: 1]) // .text("Alice") +/// ``` +public struct DataTable: Sendable, Equatable { + + // MARK: - Column Metadata + + /// Metadata for a single column in the data table. + public struct Column: Sendable, Equatable, Identifiable { + /// Stable identifier for the column (same as `name`). + public var id: String { name } + + /// Column name from the query result set. + public let name: String + + /// Index of this column in the table (0-based). + public let index: Int + + /// Inferred data type based on the values in this column. + public let inferredType: InferredType + + public init(name: String, index: Int, inferredType: InferredType) { + self.name = name + self.index = index + self.inferredType = inferredType + } + } + + /// The inferred data type for a column, determined by inspecting its values. + public enum InferredType: Sendable, Equatable { + /// All non-null values are integers. + case integer + /// All non-null values are numeric (mix of integer and real). + case real + /// All non-null values are text. + case text + /// Values contain blob data. + case blob + /// Column contains only null values or is empty. + case null + /// Values are a mix of incompatible types. + case mixed + } + + // MARK: - Row Type + + /// A single row in the data table, providing indexed and named access. + public struct Row: Sendable, Equatable, Identifiable { + /// Row index (0-based), used as stable identity. + public let id: Int + + /// Values in column order. + public let values: [QueryResult.Value] + + /// Column names for named access. + private let columnNames: [String] + + public init(id: Int, values: [QueryResult.Value], columnNames: [String]) { + self.id = id + self.values = values + self.columnNames = columnNames + } + + /// Access a value by column index. + public subscript(columnIndex: Int) -> QueryResult.Value { + values[columnIndex] + } + + /// Access a value by column name. Returns `.null` if the column doesn't exist. + public subscript(columnName: String) -> QueryResult.Value { + guard let idx = columnNames.firstIndex(of: columnName) else { + return .null + } + return values[idx] + } + } + + // MARK: - Properties + + /// Column metadata in order. + public let columns: [Column] + + /// All rows in order. + public let rows: [Row] + + /// The SQL that produced this table. + public let sql: String + + /// Execution time of the underlying query. + public let executionTime: TimeInterval + + /// Number of columns. + public var columnCount: Int { columns.count } + + /// Number of rows. + public var rowCount: Int { rows.count } + + /// Whether the table has no rows. + public var isEmpty: Bool { rows.isEmpty } + + /// Column names in order. + public var columnNames: [String] { columns.map(\.name) } + + // MARK: - Initialization + + /// Creates a `DataTable` from a `QueryResult`. + /// + /// Converts the dictionary-based row representation into an indexed + /// array representation and infers column types from the data. + /// + /// - Parameter queryResult: The raw query result to convert. + public init(_ queryResult: QueryResult) { + let colNames = queryResult.columns + + // Build indexed rows + let indexedRows: [Row] = queryResult.rows.enumerated().map { idx, rowDict in + let values = colNames.map { col in + rowDict[col] ?? .null + } + return Row(id: idx, values: values, columnNames: colNames) + } + + // Infer column types + let inferredColumns: [Column] = colNames.enumerated().map { colIdx, name in + let type = Self.inferType( + from: indexedRows.map { $0.values[colIdx] } + ) + return Column(name: name, index: colIdx, inferredType: type) + } + + self.columns = inferredColumns + self.rows = indexedRows + self.sql = queryResult.sql + self.executionTime = queryResult.executionTime + } + + /// Creates a `DataTable` directly from components (useful for testing). + public init( + columns: [Column], + rows: [Row], + sql: String = "", + executionTime: TimeInterval = 0 + ) { + self.columns = columns + self.rows = rows + self.sql = sql + self.executionTime = executionTime + } + + // MARK: - Subscript Access + + /// Access a cell by row and column index. + public subscript(row rowIndex: Int, column columnIndex: Int) -> QueryResult.Value { + rows[rowIndex].values[columnIndex] + } + + /// Access a cell by row index and column name. + public subscript(row rowIndex: Int, column columnName: String) -> QueryResult.Value { + rows[rowIndex][columnName] + } + + // MARK: - Column Data Extraction + + /// Returns all values for a column by index, in row order. + public func columnValues(at index: Int) -> [QueryResult.Value] { + rows.map { $0.values[index] } + } + + /// Returns all values for a column by name, in row order. + public func columnValues(named name: String) -> [QueryResult.Value] { + guard let col = columns.first(where: { $0.name == name }) else { + return [] + } + return columnValues(at: col.index) + } + + /// Returns all non-null `Double` values for a column (useful for charting). + public func numericValues(forColumn name: String) -> [Double] { + columnValues(named: name).compactMap(\.doubleValue) + } + + /// Returns all non-null `String` values for a column (useful for labels). + public func stringValues(forColumn name: String) -> [String] { + columnValues(named: name).compactMap { value in + if case .null = value { return nil } + return value.stringValue + } + } + + // MARK: - Type Inference + + /// Infers the predominant type from an array of values. + static func inferType(from values: [QueryResult.Value]) -> InferredType { + var hasInteger = false + var hasReal = false + var hasText = false + var hasBlob = false + var hasNonNull = false + + for value in values { + switch value { + case .integer: + hasInteger = true + hasNonNull = true + case .real: + hasReal = true + hasNonNull = true + case .text: + hasText = true + hasNonNull = true + case .blob: + hasBlob = true + hasNonNull = true + case .null: + break + } + } + + guard hasNonNull else { return .null } + + // Count how many distinct types are present + let typeCount = [hasInteger, hasReal, hasText, hasBlob].filter { $0 }.count + + if typeCount == 1 { + if hasInteger { return .integer } + if hasReal { return .real } + if hasText { return .text } + if hasBlob { return .blob } + } + + // Integer + real → treat as real (numeric promotion) + if typeCount == 2, hasInteger, hasReal { + return .real + } + + return .mixed + } +} diff --git a/Sources/SwiftDBAI/Rendering/TextSummaryRenderer.swift b/Sources/SwiftDBAI/Rendering/TextSummaryRenderer.swift new file mode 100644 index 0000000..db6b176 --- /dev/null +++ b/Sources/SwiftDBAI/Rendering/TextSummaryRenderer.swift @@ -0,0 +1,301 @@ +// TextSummaryRenderer.swift +// SwiftDBAI +// +// Converts raw SQL query results into natural language text summaries +// using the LLM via AnyLanguageModel. + +import AnyLanguageModel +import Foundation + +/// Renders SQL query results as natural language text summaries. +/// +/// The renderer takes a `QueryResult` and the user's original question, +/// sends them to the LLM for summarization, and returns a concise, +/// human-readable response. +/// +/// Usage: +/// ```swift +/// let renderer = TextSummaryRenderer(model: myModel) +/// let summary = try await renderer.summarize( +/// result: queryResult, +/// userQuestion: "How many orders were placed last month?" +/// ) +/// print(summary) // "There were 42 orders placed last month." +/// ``` +public struct TextSummaryRenderer: Sendable { + + /// The language model used to generate summaries. + private let model: any LanguageModel + + /// Maximum number of rows to include in the LLM prompt. + /// + /// Results larger than this are truncated with a note about total count. + public let maxRowsInPrompt: Int + + /// Creates a new text summary renderer. + /// + /// - Parameters: + /// - model: Any `AnyLanguageModel`-compatible language model. + /// - maxRowsInPrompt: Maximum rows to send to the LLM for summarization (default: 50). + public init(model: any LanguageModel, maxRowsInPrompt: Int = 50) { + self.model = model + self.maxRowsInPrompt = maxRowsInPrompt + } + + /// Generates a natural language summary of query results. + /// + /// - Parameters: + /// - result: The raw `QueryResult` from SQL execution. + /// - userQuestion: The original natural language question from the user. + /// - context: Optional additional context (e.g., table descriptions) to help the LLM. + /// - Returns: A natural language text summary of the results. + public func summarize( + result: QueryResult, + userQuestion: String, + context: String? = nil + ) async throws -> String { + // For mutation results (INSERT/UPDATE/DELETE), use a simple template + if let affected = result.rowsAffected { + return summarizeMutation(result: result, affected: affected) + } + + // For empty results, no need to call the LLM + if result.rows.isEmpty { + return "No results found for your query." + } + + // For simple aggregates, produce a direct answer without LLM + if let directAnswer = tryDirectAggregateSummary(result: result, userQuestion: userQuestion) { + return directAnswer + } + + // Build the prompt and ask the LLM to summarize + let prompt = buildSummarizationPrompt( + result: result, + userQuestion: userQuestion, + context: context + ) + + let session = LanguageModelSession( + model: model, + instructions: summaryInstructions + ) + + let response = try await session.respond(to: prompt) + return response.content.trimmingCharacters(in: .whitespacesAndNewlines) + } + + /// Generates a summary without calling the LLM, using simple templates. + /// + /// Useful when LLM access is unavailable, or for fast local rendering. + /// + /// - Parameters: + /// - result: The raw `QueryResult` from SQL execution. + /// - userQuestion: The original natural language question. + /// - Returns: A template-based text summary. + public func localSummary(result: QueryResult, userQuestion: String) -> String { + if let affected = result.rowsAffected { + return summarizeMutation(result: result, affected: affected) + } + + if result.rows.isEmpty { + return "No results found for your query." + } + + if let directAnswer = tryDirectAggregateSummary(result: result, userQuestion: userQuestion) { + return directAnswer + } + + return buildTemplateSummary(result: result) + } + + // MARK: - Private Helpers + + /// System instructions for the summarization session. + private var summaryInstructions: String { + """ + You are a data assistant that summarizes SQL query results in natural language. + + Rules: + - Be concise and direct. Answer the user's question first, then add detail if helpful. + - Use natural language, not SQL or code. + - For numeric results, include the exact numbers. + - For lists of records, summarize the count and highlight notable items. + - If the data contains dates, format them in a readable way. + - Do not mention SQL, databases, tables, columns, or queries in your response. + - Do not include markdown formatting. + - Keep your response under 3 sentences for simple results, under 5 for complex ones. + """ + } + + /// Builds the prompt sent to the LLM for summarization. + private func buildSummarizationPrompt( + result: QueryResult, + userQuestion: String, + context: String? + ) -> String { + var parts: [String] = [] + + parts.append("User's question: \(userQuestion)") + + if let context { + parts.append("Context: \(context)") + } + + parts.append("Query returned \(result.rowCount) row(s) with columns: \(result.columns.joined(separator: ", "))") + + // Include the result data (truncated if large) + let dataStr = formatResultData(result) + parts.append("Data:\n\(dataStr)") + + parts.append("Summarize these results in natural language, directly answering the user's question.") + + return parts.joined(separator: "\n\n") + } + + /// Formats the query result data as a compact table for the LLM prompt. + private func formatResultData(_ result: QueryResult) -> String { + let rowsToInclude = Array(result.rows.prefix(maxRowsInPrompt)) + var lines: [String] = [] + + // Header + lines.append(result.columns.joined(separator: " | ")) + + // Rows + for row in rowsToInclude { + let values = result.columns.map { col in + row[col]?.description ?? "NULL" + } + lines.append(values.joined(separator: " | ")) + } + + if result.rowCount > maxRowsInPrompt { + lines.append("(\(result.rowCount - maxRowsInPrompt) additional rows not shown)") + } + + return lines.joined(separator: "\n") + } + + /// Produces a direct answer for simple aggregate queries (1 row, few columns). + private func tryDirectAggregateSummary(result: QueryResult, userQuestion: String) -> String? { + guard result.isAggregate else { return nil } + + let row = result.rows[0] + + // Single numeric column — e.g., "COUNT(*)" → "42" + if result.columns.count == 1 { + let col = result.columns[0] + guard let value = row[col] else { return nil } + let formatted = formatNumber(value) + return "The result is \(formatted)." + } + + // Multiple aggregate columns — e.g., COUNT, AVG, SUM + let parts = result.columns.compactMap { col -> String? in + guard let value = row[col] else { return nil } + let label = humanizeColumnName(col) + let formatted = formatNumber(value) + return "\(label): \(formatted)" + } + return parts.joined(separator: ", ") + "." + } + + /// Formats a numeric Value for display. + private func formatNumber(_ value: QueryResult.Value) -> String { + switch value { + case .integer(let i): + return NumberFormatter.localizedString(from: NSNumber(value: i), number: .decimal) + case .real(let d): + if d == d.rounded() && abs(d) < 1e12 { + return NumberFormatter.localizedString(from: NSNumber(value: Int64(d)), number: .decimal) + } + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.maximumFractionDigits = 2 + return formatter.string(from: NSNumber(value: d)) ?? String(d) + default: + return value.description + } + } + + /// Converts a column name like "total_count" or "AVG(price)" into a readable label. + private func humanizeColumnName(_ name: String) -> String { + // Handle SQL function names: "COUNT(*)" → "count", "AVG(price)" → "average price" + let functionPatterns: [(pattern: String, label: String)] = [ + ("COUNT", "count"), + ("SUM", "total"), + ("AVG", "average"), + ("MIN", "minimum"), + ("MAX", "maximum"), + ] + + let upper = name.uppercased() + for (pattern, label) in functionPatterns { + if upper.hasPrefix(pattern + "(") { + // Extract the inner column name + let start = name.index(name.startIndex, offsetBy: pattern.count + 1) + let end = name.index(before: name.endIndex) + if start < end { + let inner = String(name[start.. String { + let count = result.rowCount + + if count == 1 { + // Single record — list field values + let row = result.rows[0] + let details = result.columns.prefix(5).compactMap { col -> String? in + guard let val = row[col], !val.isNull else { return nil } + return "\(humanizeColumnName(col)): \(val.description)" + } + return "Found 1 result. \(details.joined(separator: ", "))." + } + + // Multiple records + var summary = "Found \(count) results" + + // If there's a clear "name" or "title" column, list first few + let nameColumns = ["name", "title", "label", "description"] + if let nameCol = result.columns.first(where: { nameColumns.contains($0.lowercased()) }) { + let names = result.rows.prefix(3).compactMap { $0[nameCol]?.description } + if !names.isEmpty { + summary += " including \(names.joined(separator: ", "))" + if count > 3 { summary += ", and \(count - 3) more" } + } + } + + return summary + "." + } + + /// Summarizes a mutation (INSERT/UPDATE/DELETE) result. + private func summarizeMutation(result: QueryResult, affected: Int) -> String { + let sql = result.sql.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + + let operation: String + if sql.hasPrefix("INSERT") { + operation = "inserted" + } else if sql.hasPrefix("UPDATE") { + operation = "updated" + } else if sql.hasPrefix("DELETE") { + operation = "deleted" + } else { + operation = "affected" + } + + let noun = affected == 1 ? "row" : "rows" + return "Successfully \(operation) \(affected) \(noun)." + } +} diff --git a/Sources/SwiftDBAI/Schema/DatabaseSchema.swift b/Sources/SwiftDBAI/Schema/DatabaseSchema.swift new file mode 100644 index 0000000..0c72989 --- /dev/null +++ b/Sources/SwiftDBAI/Schema/DatabaseSchema.swift @@ -0,0 +1,164 @@ +// DatabaseSchema.swift +// SwiftDBAI +// +// Auto-introspected SQLite schema model types. + +import Foundation + +/// Complete schema representation of an SQLite database. +public struct DatabaseSchema: Sendable, Equatable { + /// All tables in the database, keyed by table name. + public let tables: [String: TableSchema] + + /// Ordered table names (preserves discovery order). + public let tableNames: [String] + + /// Returns a compact text description suitable for LLM system prompts. + public var schemaDescription: String { + var lines: [String] = [] + for name in tableNames { + guard let table = tables[name] else { continue } + lines.append(table.descriptionForLLM) + } + return lines.joined(separator: "\n\n") + } + + /// Returns a description suitable for LLM system prompts. + /// Alias for `schemaDescription` for API compatibility. + public func describeForLLM() -> String { + schemaDescription + } + + public init(tables: [String: TableSchema], tableNames: [String]) { + self.tables = tables + self.tableNames = tableNames + } +} + +/// Schema for a single SQLite table. +public struct TableSchema: Sendable, Equatable { + public let name: String + public let columns: [ColumnSchema] + public let primaryKey: [String] + public let foreignKeys: [ForeignKeySchema] + public let indexes: [IndexSchema] + + /// Text description for embedding in LLM prompts. + public var descriptionForLLM: String { + var parts: [String] = [] + let colDefs = columns.map { col in + var def = " \(col.name) \(col.type)" + if col.isPrimaryKey { def += " PRIMARY KEY" } + if col.isNotNull { def += " NOT NULL" } + if let defaultValue = col.defaultValue { def += " DEFAULT \(defaultValue)" } + return def + } + parts.append("TABLE \(name) (\n\(colDefs.joined(separator: ",\n"))\n)") + + if !foreignKeys.isEmpty { + let fkDescs = foreignKeys.map { + " FOREIGN KEY (\($0.fromColumn)) REFERENCES \($0.toTable)(\($0.toColumn))" + } + parts.append("FOREIGN KEYS:\n\(fkDescs.joined(separator: "\n"))") + } + + if !indexes.isEmpty { + let idxDescs = indexes.map { + " INDEX \($0.name) ON (\($0.columns.joined(separator: ", ")))\($0.isUnique ? " UNIQUE" : "")" + } + parts.append("INDEXES:\n\(idxDescs.joined(separator: "\n"))") + } + + return parts.joined(separator: "\n") + } + + public init( + name: String, + columns: [ColumnSchema], + primaryKey: [String], + foreignKeys: [ForeignKeySchema], + indexes: [IndexSchema] + ) { + self.name = name + self.columns = columns + self.primaryKey = primaryKey + self.foreignKeys = foreignKeys + self.indexes = indexes + } +} + +/// Schema for a single column. +public struct ColumnSchema: Sendable, Equatable { + /// Column position (0-based). + public let cid: Int + /// Column name. + public let name: String + /// Declared SQLite type (e.g. "TEXT", "INTEGER", "REAL", "BLOB"). + public let type: String + /// Whether the column has a NOT NULL constraint. + public let isNotNull: Bool + /// Default value expression, if any. + public let defaultValue: String? + /// Whether this column is part of the primary key. + public let isPrimaryKey: Bool + + public init( + cid: Int, + name: String, + type: String, + isNotNull: Bool, + defaultValue: String?, + isPrimaryKey: Bool + ) { + self.cid = cid + self.name = name + self.type = type + self.isNotNull = isNotNull + self.defaultValue = defaultValue + self.isPrimaryKey = isPrimaryKey + } +} + +/// Schema for a foreign key relationship. +public struct ForeignKeySchema: Sendable, Equatable { + /// Column in the source table. + public let fromColumn: String + /// Referenced table name. + public let toTable: String + /// Referenced column name. + public let toColumn: String + /// ON UPDATE action (e.g. "CASCADE", "NO ACTION"). + public let onUpdate: String + /// ON DELETE action. + public let onDelete: String + + public init( + fromColumn: String, + toTable: String, + toColumn: String, + onUpdate: String, + onDelete: String + ) { + self.fromColumn = fromColumn + self.toTable = toTable + self.toColumn = toColumn + self.onUpdate = onUpdate + self.onDelete = onDelete + } +} + +/// Schema for a database index. +public struct IndexSchema: Sendable, Equatable { + /// Index name. + public let name: String + /// Whether the index enforces uniqueness. + public let isUnique: Bool + /// Columns included in the index, in order. + public let columns: [String] + + public init(name: String, isUnique: Bool, columns: [String]) { + self.name = name + self.isUnique = isUnique + self.columns = columns + } +} diff --git a/Sources/SwiftDBAI/Schema/SchemaIntrospector.swift b/Sources/SwiftDBAI/Schema/SchemaIntrospector.swift new file mode 100644 index 0000000..47c0a47 --- /dev/null +++ b/Sources/SwiftDBAI/Schema/SchemaIntrospector.swift @@ -0,0 +1,153 @@ +// SchemaIntrospector.swift +// SwiftDBAI +// +// Auto-introspects SQLite database schema using GRDB. + +import GRDB + +/// Introspects an SQLite database schema by querying sqlite_master and PRAGMA statements. +/// +/// Usage: +/// ```swift +/// let dbPool = try DatabasePool(path: "path/to/db.sqlite") +/// let schema = try await SchemaIntrospector.introspect(database: dbPool) +/// print(schema.schemaDescription) +/// ``` +public struct SchemaIntrospector: Sendable { + + // MARK: - Public API + + /// Introspects the full schema of the given database. + /// + /// Discovers all user tables (excluding sqlite_ internal tables), + /// their columns, primary keys, foreign keys, and indexes. + /// + /// - Parameter database: A GRDB `DatabaseReader` (DatabasePool or DatabaseQueue). + /// - Returns: A complete `DatabaseSchema` representation. + public static func introspect(database: any DatabaseReader) async throws -> DatabaseSchema { + try await database.read { db in + try introspect(db: db) + } + } + + /// Synchronous introspection within an existing database access context. + /// + /// - Parameter db: A GRDB `Database` instance from within a read/write block. + /// - Returns: A complete `DatabaseSchema` representation. + public static func introspect(db: Database) throws -> DatabaseSchema { + let tableNames = try fetchTableNames(db: db) + var tables: [String: TableSchema] = [:] + + for tableName in tableNames { + let columns = try fetchColumns(db: db, table: tableName) + let primaryKey = try fetchPrimaryKey(db: db, table: tableName) + let foreignKeys = try fetchForeignKeys(db: db, table: tableName) + let indexes = try fetchIndexes(db: db, table: tableName) + + // Mark columns that are part of the primary key + let pkSet = Set(primaryKey) + let annotatedColumns = columns.map { col in + ColumnSchema( + cid: col.cid, + name: col.name, + type: col.type, + isNotNull: col.isNotNull, + defaultValue: col.defaultValue, + isPrimaryKey: pkSet.contains(col.name) + ) + } + + tables[tableName] = TableSchema( + name: tableName, + columns: annotatedColumns, + primaryKey: primaryKey, + foreignKeys: foreignKeys, + indexes: indexes + ) + } + + return DatabaseSchema(tables: tables, tableNames: tableNames) + } + + // MARK: - Private Helpers + + /// Fetches all user table names from sqlite_master. + private static func fetchTableNames(db: Database) throws -> [String] { + let sql = """ + SELECT name FROM sqlite_master + WHERE type = 'table' + AND name NOT LIKE 'sqlite_%' + ORDER BY name + """ + return try String.fetchAll(db, sql: sql) + } + + /// Fetches column metadata for a table using PRAGMA table_info. + private static func fetchColumns(db: Database, table: String) throws -> [ColumnSchema] { + let sql = "PRAGMA table_info(\(table.quotedDatabaseIdentifier))" + let rows = try Row.fetchAll(db, sql: sql) + return rows.map { row in + ColumnSchema( + cid: row["cid"], + name: row["name"], + type: (row["type"] as String?) ?? "", + isNotNull: row["notnull"] == 1, + defaultValue: row["dflt_value"], + isPrimaryKey: row["pk"] != 0 + ) + } + } + + /// Fetches primary key columns for a table. + private static func fetchPrimaryKey(db: Database, table: String) throws -> [String] { + let sql = "PRAGMA table_info(\(table.quotedDatabaseIdentifier))" + let rows = try Row.fetchAll(db, sql: sql) + return rows + .filter { ($0["pk"] as Int) > 0 } + .sorted { ($0["pk"] as Int) < ($1["pk"] as Int) } + .map { $0["name"] } + } + + /// Fetches foreign key relationships for a table. + private static func fetchForeignKeys(db: Database, table: String) throws -> [ForeignKeySchema] { + let sql = "PRAGMA foreign_key_list(\(table.quotedDatabaseIdentifier))" + let rows = try Row.fetchAll(db, sql: sql) + return rows.map { row in + ForeignKeySchema( + fromColumn: row["from"], + toTable: row["table"], + toColumn: row["to"], + onUpdate: row["on_update"] ?? "NO ACTION", + onDelete: row["on_delete"] ?? "NO ACTION" + ) + } + } + + /// Fetches indexes and their columns for a table. + private static func fetchIndexes(db: Database, table: String) throws -> [IndexSchema] { + let indexListSQL = "PRAGMA index_list(\(table.quotedDatabaseIdentifier))" + let indexRows = try Row.fetchAll(db, sql: indexListSQL) + + var indexes: [IndexSchema] = [] + for indexRow in indexRows { + let indexName: String = indexRow["name"] + let isUnique: Bool = indexRow["unique"] == 1 + + // Skip auto-generated indexes for primary keys + if indexName.hasPrefix("sqlite_autoindex_") { continue } + + let infoSQL = "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))" + let infoRows = try Row.fetchAll(db, sql: infoSQL) + let columns: [String] = infoRows + .sorted { ($0["seqno"] as Int) < ($1["seqno"] as Int) } + .map { $0["name"] } + + indexes.append(IndexSchema( + name: indexName, + isUnique: isUnique, + columns: columns + )) + } + return indexes + } +} diff --git a/Sources/SwiftDBAI/SwiftDBAIError.swift b/Sources/SwiftDBAI/SwiftDBAIError.swift new file mode 100644 index 0000000..814080b --- /dev/null +++ b/Sources/SwiftDBAI/SwiftDBAIError.swift @@ -0,0 +1,215 @@ +// SwiftDBAIError.swift +// SwiftDBAI +// +// Unified error type for the SwiftDBAI package. + +import Foundation + +/// The top-level error type for SwiftDBAI operations. +/// +/// `SwiftDBAIError` provides a single, typed error surface that covers +/// every failure mode a consumer of SwiftDBAI may encounter — from invalid +/// SQL and LLM failures to schema mismatches and safety violations. +/// +/// Every case includes a user-friendly `localizedDescription` suitable for +/// displaying directly in a chat interface. +public enum SwiftDBAIError: Error, LocalizedError, Sendable, Equatable { + + // MARK: - SQL Errors + + /// No SQL statement could be extracted from the LLM response. + case noSQLGenerated + + /// The generated SQL is syntactically invalid or failed execution. + case invalidSQL(sql: String, reason: String) + + /// The SQL uses an operation (e.g. DELETE) not in the developer's allowlist. + case operationNotAllowed(operation: String) + + /// Multiple SQL statements were generated but only single-statement execution is supported. + case multipleStatementsNotSupported + + /// A dangerous SQL keyword (DROP, ALTER, TRUNCATE) was detected. + case dangerousOperationBlocked(keyword: String) + + // MARK: - LLM Errors + + /// The LLM failed to produce a response. + case llmFailure(reason: String) + + /// The LLM response could not be parsed into an actionable result. + case llmResponseUnparseable(response: String) + + /// The LLM request timed out. + case llmTimeout(seconds: TimeInterval) + + // MARK: - Schema Errors + + /// Schema introspection of the database failed. + case schemaIntrospectionFailed(reason: String) + + /// The generated SQL references a table that does not exist in the schema. + case tableNotFound(tableName: String) + + /// The generated SQL references a column that does not exist on the given table. + case columnNotFound(columnName: String, tableName: String) + + /// The database schema is empty (no user tables found). + case emptySchema + + // MARK: - Safety & Validation Errors + + /// A destructive operation requires explicit user confirmation before execution. + case confirmationRequired(sql: String, operation: String) + + /// A mutation targets a table not in the allowed mutation tables. + case tableNotAllowedForMutation(tableName: String, operation: String) + + /// A custom query validator rejected the query. + case queryRejected(reason: String) + + // MARK: - Database Errors + + /// The underlying database operation failed. + case databaseError(reason: String) + + /// The query exceeded the configured execution timeout. + case queryTimedOut(seconds: TimeInterval) + + // MARK: - Configuration Errors + + /// The engine has not been configured correctly. + case configurationError(reason: String) + + // MARK: - Error Classification + + /// Whether this error represents a safety/permissions issue (not a bug). + public var isSafetyError: Bool { + switch self { + case .operationNotAllowed, .dangerousOperationBlocked, + .confirmationRequired, .tableNotAllowedForMutation, .queryRejected: + return true + default: + return false + } + } + + /// Whether this error is recoverable by rephrasing the user's question. + public var isRecoverable: Bool { + switch self { + case .noSQLGenerated, .llmResponseUnparseable, .invalidSQL, + .tableNotFound, .columnNotFound: + return true + default: + return false + } + } + + /// Whether this error requires user action (e.g. confirmation). + public var requiresUserAction: Bool { + if case .confirmationRequired = self { return true } + return false + } + + // MARK: - LocalizedError + + public var errorDescription: String? { + switch self { + // SQL + case .noSQLGenerated: + return "I couldn't generate a SQL query from your request. Could you rephrase your question?" + case .invalidSQL(let sql, let reason): + return "The generated query is invalid — \(reason). Query: \(sql)" + case .operationNotAllowed(let operation): + return "The \(operation.uppercased()) operation is not allowed by the current configuration." + case .multipleStatementsNotSupported: + return "Only single SQL statements are supported. Please ask one question at a time." + case .dangerousOperationBlocked(let keyword): + return "The \(keyword.uppercased()) operation is blocked for safety. This operation is never allowed." + + // LLM + case .llmFailure(let reason): + return "The language model encountered an error: \(reason)" + case .llmResponseUnparseable(let response): + return "I received a response but couldn't understand it. Raw response: \(response.prefix(200))" + case .llmTimeout(let seconds): + return "The language model did not respond within \(Int(seconds)) seconds. Please try again." + + // Schema + case .schemaIntrospectionFailed(let reason): + return "Failed to read the database schema: \(reason)" + case .tableNotFound(let tableName): + return "The table '\(tableName)' does not exist in this database." + case .columnNotFound(let columnName, let tableName): + return "The column '\(columnName)' does not exist on table '\(tableName)'." + case .emptySchema: + return "This database has no tables. There's nothing to query yet." + + // Safety + case .confirmationRequired(let sql, let operation): + return "The \(operation.uppercased()) operation requires your confirmation before running: \(sql)" + case .tableNotAllowedForMutation(let tableName, let operation): + return "The \(operation.uppercased()) operation is not allowed on table '\(tableName)'." + case .queryRejected(let reason): + return "Query rejected: \(reason)" + + // Database + case .databaseError(let reason): + return "A database error occurred: \(reason)" + case .queryTimedOut(let seconds): + return "The query timed out after \(Int(seconds)) seconds. Try a simpler query." + + // Configuration + case .configurationError(let reason): + return "Configuration error: \(reason)" + } + } +} + +// MARK: - Conversion from SQLParsingError + +extension SQLParsingError { + /// Maps a ``SQLParsingError`` to the corresponding ``SwiftDBAIError`` case. + /// + /// - Parameter rawResponse: The raw LLM response text (used for context in `.noSQLFound`). + /// - Returns: A ``SwiftDBAIError`` with the same semantic meaning. + func toSwiftDBAIError(rawResponse: String = "") -> SwiftDBAIError { + switch self { + case .noSQLFound: + if rawResponse.isEmpty { + return .noSQLGenerated + } + return .llmResponseUnparseable(response: rawResponse) + case .operationNotAllowed(let op): + return .operationNotAllowed(operation: op.rawValue) + case .confirmationRequired(let sql, let op): + return .confirmationRequired(sql: sql, operation: op.rawValue) + case .tableNotAllowed(let table, let op): + return .tableNotAllowedForMutation(tableName: table, operation: op.rawValue) + case .dangerousOperation(let keyword): + return .dangerousOperationBlocked(keyword: keyword) + case .multipleStatements: + return .multipleStatementsNotSupported + } + } +} + +// MARK: - Conversion from ChatEngineError + +extension ChatEngineError { + /// Maps a ``ChatEngineError`` to the corresponding ``SwiftDBAIError`` case. + func toSwiftDBAIError() -> SwiftDBAIError { + switch self { + case .sqlParsingFailed(let parsingError): + return parsingError.toSwiftDBAIError() + case .confirmationRequired(let sql, let operation): + return .confirmationRequired(sql: sql, operation: operation.rawValue) + case .schemaIntrospectionFailed(let reason): + return .schemaIntrospectionFailed(reason: reason) + case .queryTimedOut(let seconds): + return .queryTimedOut(seconds: seconds) + case .validationFailed(let reason): + return .queryRejected(reason: reason) + } + } +} diff --git a/Sources/SwiftDBAI/Tools/DatabaseTool.swift b/Sources/SwiftDBAI/Tools/DatabaseTool.swift new file mode 100644 index 0000000..0987a5f --- /dev/null +++ b/Sources/SwiftDBAI/Tools/DatabaseTool.swift @@ -0,0 +1,230 @@ +// DatabaseTool.swift +// SwiftDBAI +// +// A standalone tool calling API for integrating SwiftDBAI into +// existing LLM tool calling setups (OpenAI function calling, +// Anthropic tools, Apple Foundation Models, etc.). + +import Foundation +import GRDB + +/// A standalone database tool for LLM tool calling integrations. +/// +/// Provides everything needed to register a "query database" tool with any LLM: +/// - Tool name, description, and parameter schema for registration +/// - Schema context for the LLM's system prompt +/// - SQL execution with allowlist validation +/// +/// ## Usage +/// +/// ```swift +/// // 1. Create the tool +/// let tool = try await DatabaseTool(databasePath: "path/to/db.sqlite") +/// +/// // 2. Get the tool definition for your LLM +/// let definition = tool.openAIFunctionDefinition +/// // Register with your OpenAI/Anthropic/etc. client... +/// +/// // 3. Include schema in system prompt +/// let systemPrompt = "You are a helpful assistant.\n\n" + tool.systemPromptSnippet +/// +/// // 4. When the LLM calls the tool, execute it +/// let result = try tool.execute(sql: llmGeneratedSQL) +/// // Return result.jsonString back to the LLM as the tool response +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct DatabaseTool: Sendable { + + private let database: any DatabaseWriter + private let allowlist: OperationAllowlist + private let schema: DatabaseSchema + + // MARK: - Initialization + + /// Creates a database tool from a file path. + /// + /// - Parameters: + /// - databasePath: Path to the SQLite database file. + /// - allowlist: The set of permitted SQL operations. Defaults to read-only. + public init(databasePath: String, allowlist: OperationAllowlist = .readOnly) async throws { + let dbQueue = try DatabaseQueue(path: databasePath) + self.database = dbQueue + self.allowlist = allowlist + self.schema = try await SchemaIntrospector.introspect(database: dbQueue) + } + + /// Creates a database tool from an existing GRDB database connection. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (DatabaseQueue or DatabasePool). + /// - allowlist: The set of permitted SQL operations. Defaults to read-only. + public init(database: any DatabaseWriter, allowlist: OperationAllowlist = .readOnly) async throws { + self.database = database + self.allowlist = allowlist + self.schema = try await SchemaIntrospector.introspect(database: database) + } + + // MARK: - Tool Definition + + /// The tool name for LLM function calling registration. + public var name: String { "execute_sql" } + + /// The tool description for LLM function calling registration. + public var description: String { + "Execute a SQL query against a SQLite database. \(allowlist.describeForLLM())" + } + + /// JSON Schema for the tool's parameters, compatible with OpenAI/Anthropic tool definitions. + public var parametersSchema: [String: Any] { + [ + "type": "object", + "properties": [ + "sql": [ + "type": "string", + "description": "The SQL query to execute against the database.", + ] as [String: Any], + ] as [String: Any], + "required": ["sql"], + ] + } + + /// The database schema as a string, for including in the LLM's system prompt. + public var schemaContext: String { + schema.schemaDescription + } + + /// A system prompt snippet that describes the database and how to use the tool. + /// + /// Include this in your LLM's system prompt so it knows the database structure + /// and how to use the `execute_sql` tool. + public var systemPromptSnippet: String { + """ + You have access to a SQLite database with the following schema: + + \(schema.schemaDescription) + + \(allowlist.describeForLLM()) + + Use the `execute_sql` tool to query this database. Pass a single SQL statement as the `sql` parameter. + """ + } + + // MARK: - Execution + + /// Execute a SQL query, returning a structured ``ToolResult``. + /// + /// Validates the SQL against the configured allowlist before execution. + /// This is the method to call when the LLM invokes the tool. + /// + /// - Parameter sql: The SQL query to execute. + /// - Returns: A ``ToolResult`` with the query results. + /// - Throws: ``SQLParsingError`` if the SQL is not allowed, or a database error. + public func execute(sql: String) throws -> ToolResult { + let queryResult = try executeRaw(sql: sql) + return ToolResult(queryResult: queryResult) + } + + /// Execute a SQL query and return the raw ``QueryResult``. + /// + /// For advanced use cases where you need the full `QueryResult.Value` types + /// rather than the string-based ``ToolResult``. + /// + /// - Parameter sql: The SQL query to execute. + /// - Returns: A ``QueryResult`` with typed values. + /// - Throws: ``SQLParsingError`` if the SQL is not allowed, or a database error. + public func executeRaw(sql: String) throws -> QueryResult { + // Validate against the allowlist + let parser = SQLQueryParser(allowlist: allowlist) + let parsed = try parser.validate(sql) + + let startTime = CFAbsoluteTimeGetCurrent() + let trimmed = sql.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() + let isSelect = trimmed.hasPrefix("SELECT") || trimmed.hasPrefix("WITH") + + if isSelect { + let result = try database.read { db -> (columns: [String], rows: [[String: QueryResult.Value]]) in + let statement = try db.makeStatement(sql: parsed.sql) + let columnNames = statement.columnNames + + var rows: [[String: QueryResult.Value]] = [] + let cursor = try Row.fetchCursor(statement) + while let row = try cursor.next() { + var dict: [String: QueryResult.Value] = [:] + for col in columnNames { + dict[col] = Self.extractValue(row: row, column: col) + } + rows.append(dict) + } + return (columns: columnNames, rows: rows) + } + + let elapsed = CFAbsoluteTimeGetCurrent() - startTime + return QueryResult( + columns: result.columns, + rows: result.rows, + sql: parsed.sql, + executionTime: elapsed + ) + } else { + let affected = try database.write { db -> Int in + try db.execute(sql: parsed.sql) + return db.changesCount + } + + let elapsed = CFAbsoluteTimeGetCurrent() - startTime + return QueryResult( + columns: [], + rows: [], + sql: parsed.sql, + executionTime: elapsed, + rowsAffected: affected + ) + } + } + + // MARK: - Private Helpers + + private static func extractValue(row: Row, column: String) -> QueryResult.Value { + let dbValue: DatabaseValue = row[column] + switch dbValue.storage { + case .null: + return .null + case .int64(let i): + return .integer(i) + case .double(let d): + return .real(d) + case .string(let s): + return .text(s) + case .blob(let data): + return .blob(data) + } + } +} + +// MARK: - OpenAI / Anthropic Compatibility + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +extension DatabaseTool { + + /// Returns an OpenAI-compatible function definition dictionary. + /// + /// This can be serialized to JSON and passed directly to the OpenAI API's + /// `tools` parameter, or adapted for Anthropic's tool definitions. + /// + /// ```swift + /// let tool = try await DatabaseTool(databasePath: "db.sqlite") + /// let definition = tool.openAIFunctionDefinition + /// // Serialize to JSON for the API call + /// let data = try JSONSerialization.data(withJSONObject: definition) + /// ``` + public var openAIFunctionDefinition: [String: Any] { + [ + "type": "function", + "function": [ + "name": name, + "description": description, + "parameters": parametersSchema, + ] as [String: Any], + ] + } +} diff --git a/Sources/SwiftDBAI/Tools/ToolResult.swift b/Sources/SwiftDBAI/Tools/ToolResult.swift new file mode 100644 index 0000000..4a59553 --- /dev/null +++ b/Sources/SwiftDBAI/Tools/ToolResult.swift @@ -0,0 +1,108 @@ +// ToolResult.swift +// SwiftDBAI +// +// Structured result for tool calling responses. + +import Foundation + +/// A structured result from executing a SQL query via ``DatabaseTool``, +/// designed for returning to an LLM as a tool call response. +/// +/// Provides multiple output formats: +/// - ``jsonString`` for returning to the LLM as a tool response +/// - ``markdownTable`` for display in UI +/// - ``textSummary`` for plain text output +public struct ToolResult: Sendable, Codable, Equatable { + + /// Column names in the order they appear in the result set. + public let columns: [String] + + /// Row data as an array of dictionaries mapping column name to string value. + /// All values are converted to strings for reliable JSON serialization. + public let rows: [[String: String]] + + /// Total number of rows returned. + public let rowCount: Int + + /// Time taken to execute the query, in seconds. + public let executionTime: TimeInterval + + /// The SQL statement that was executed. + public let sql: String + + public init( + columns: [String], + rows: [[String: String]], + rowCount: Int, + executionTime: TimeInterval, + sql: String + ) { + self.columns = columns + self.rows = rows + self.rowCount = rowCount + self.executionTime = executionTime + self.sql = sql + } + + /// Creates a ``ToolResult`` from a ``QueryResult``. + init(queryResult: QueryResult) { + self.columns = queryResult.columns + self.rows = queryResult.rows.map { row in + var stringRow: [String: String] = [:] + for (key, value) in row { + stringRow[key] = value.description + } + return stringRow + } + self.rowCount = queryResult.rowCount + self.executionTime = queryResult.executionTime + self.sql = queryResult.sql + } + + // MARK: - Output Formats + + /// Formats the result as a JSON string for returning to the LLM as a tool response. + public var jsonString: String { + let payload: [String: Any] = [ + "columns": columns, + "rows": rows, + "row_count": rowCount, + "execution_time_seconds": executionTime, + "sql": sql, + ] + guard let data = try? JSONSerialization.data(withJSONObject: payload, options: [.sortedKeys]), + let str = String(data: data, encoding: .utf8) else { + return "{\"error\": \"Failed to serialize result\"}" + } + return str + } + + /// Formats the result as a markdown table for display. + public var markdownTable: String { + guard !rows.isEmpty else { + return "_No results._" + } + + var lines: [String] = [] + + // Header + lines.append("| " + columns.joined(separator: " | ") + " |") + lines.append("| " + columns.map { _ in "---" }.joined(separator: " | ") + " |") + + // Rows + for row in rows { + let vals = columns.map { row[$0] ?? "NULL" } + lines.append("| " + vals.joined(separator: " | ") + " |") + } + + return lines.joined(separator: "\n") + } + + /// Formats a plain text summary of the result. + public var textSummary: String { + if rows.isEmpty { + return "Query returned no results. (\(String(format: "%.3f", executionTime))s)" + } + return "Query returned \(rowCount) row\(rowCount == 1 ? "" : "s") with columns: \(columns.joined(separator: ", ")). (\(String(format: "%.3f", executionTime))s)" + } +} diff --git a/Sources/SwiftDBAI/Views/Charts/BarChartView.swift b/Sources/SwiftDBAI/Views/Charts/BarChartView.swift new file mode 100644 index 0000000..63be6b3 --- /dev/null +++ b/Sources/SwiftDBAI/Views/Charts/BarChartView.swift @@ -0,0 +1,182 @@ +// BarChartView.swift +// SwiftDBAI +// +// A SwiftUI bar chart that renders DataTable values using Swift Charts. +// Best for categorical comparisons (e.g., sales by region, counts by status). + +import SwiftUI +import Charts + +/// A bar chart view that renders a `DataTable` column pair using Swift Charts. +/// +/// Displays vertical bars with category labels on the x-axis and numeric +/// values on the y-axis. Automatically colors bars using the accent gradient +/// and supports scrolling when many categories are present. +/// +/// Usage: +/// ```swift +/// BarChartView( +/// dataTable: table, +/// categoryColumn: "department", +/// valueColumn: "total_sales" +/// ) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct BarChartView: View { + + /// The data to chart. + public let dataTable: DataTable + + /// Column name for category labels (x-axis). + public let categoryColumn: String + + /// Column name for numeric values (y-axis). + public let valueColumn: String + + /// Optional chart title. + public var title: String? + + /// Maximum number of bars to display before truncating. + public var maxBars: Int + + public init( + dataTable: DataTable, + categoryColumn: String, + valueColumn: String, + title: String? = nil, + maxBars: Int = 30 + ) { + self.dataTable = dataTable + self.categoryColumn = categoryColumn + self.valueColumn = valueColumn + self.title = title + self.maxBars = maxBars + } + + public var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let title { + Text(title) + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + } + + if chartData.isEmpty { + emptyChartView + } else { + chartContent + } + } + } + + // MARK: - Chart Content + + @ViewBuilder + private var chartContent: some View { + Chart(chartData, id: \.label) { item in + BarMark( + x: .value(categoryColumn, item.label), + y: .value(valueColumn, item.value) + ) + .foregroundStyle( + .linearGradient( + colors: [.accentColor, .accentColor.opacity(0.7)], + startPoint: .bottom, + endPoint: .top + ) + ) + .cornerRadius(4) + } + .chartXAxis { + AxisMarks(values: .automatic) { _ in + AxisValueLabel() + .font(.caption2) + } + } + .chartYAxis { + AxisMarks(position: .leading) { _ in + AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5, dash: [4, 4])) + .foregroundStyle(.secondary.opacity(0.3)) + AxisValueLabel() + .font(.caption2) + } + } + .frame(minHeight: 200) + + if isTruncated { + truncationNotice + } + } + + // MARK: - Empty State + + @ViewBuilder + private var emptyChartView: some View { + VStack(spacing: 8) { + Image(systemName: "chart.bar") + .font(.title2) + .foregroundStyle(.secondary) + Text("No chartable data") + .font(.caption) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, minHeight: 100) + } + + // MARK: - Truncation Notice + + @ViewBuilder + private var truncationNotice: some View { + Text("Showing \(maxBars) of \(dataTable.rowCount) categories") + .font(.caption2) + .foregroundStyle(.secondary) + } + + // MARK: - Data Extraction + + private var isTruncated: Bool { + dataTable.rowCount > maxBars + } + + private var chartData: [ChartDataPoint] { + let labels = dataTable.stringValues(forColumn: categoryColumn) + let values = dataTable.numericValues(forColumn: valueColumn) + + let count = min(labels.count, values.count, maxBars) + guard count > 0 else { return [] } + + return (0.. some View { + switch recommendation.chartType { + case .bar: + BarChartView( + dataTable: dataTable, + categoryColumn: recommendation.categoryColumn, + valueColumn: recommendation.valueColumn + ) + case .line: + LineChartView( + dataTable: dataTable, + categoryColumn: recommendation.categoryColumn, + valueColumn: recommendation.valueColumn + ) + case .pie: + PieChartView( + dataTable: dataTable, + categoryColumn: recommendation.categoryColumn, + valueColumn: recommendation.valueColumn + ) + } + } + + // MARK: - Resolution + + /// Resolves the chart recommendation, using the override type if provided. + private var resolvedRecommendation: ChartDataDetector.ChartRecommendation? { + if let override = chartType { + // Use forced chart type — still need column pair from detector + let all = detector.allRecommendations(for: dataTable) + // Try to find recommendation for the forced type + if let match = all.first(where: { $0.chartType == override }) { + return match + } + // Fallback: use first recommendation and override its type + if let first = all.first { + return ChartDataDetector.ChartRecommendation( + chartType: override, + categoryColumn: first.categoryColumn, + valueColumn: first.valueColumn, + confidence: first.confidence * 0.8, + reason: first.reason + ) + } + return nil + } + + // Auto-detect best chart type + return detector.detect(dataTable) + } +} + +// MARK: - Preview + +#if DEBUG +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +#Preview("Auto Chart — Bar") { + let columns: [DataTable.Column] = [ + .init(name: "city", index: 0, inferredType: .text), + .init(name: "population", index: 1, inferredType: .integer), + ] + let cities = ["NYC", "LA", "Chicago", "Houston", "Phoenix"] + let pops: [Int64] = [8_336_817, 3_979_576, 2_693_976, 2_320_268, 1_680_992] + let rows: [DataTable.Row] = cities.enumerated().map { i, city in + DataTable.Row( + id: i, + values: [.text(city), .integer(pops[i])], + columnNames: ["city", "population"] + ) + } + let table = DataTable(columns: columns, rows: rows) + + ChartResultView(dataTable: table) + .padding() + .frame(height: 300) +} +#endif diff --git a/Sources/SwiftDBAI/Views/Charts/LineChartView.swift b/Sources/SwiftDBAI/Views/Charts/LineChartView.swift new file mode 100644 index 0000000..a7cc0ba --- /dev/null +++ b/Sources/SwiftDBAI/Views/Charts/LineChartView.swift @@ -0,0 +1,206 @@ +// LineChartView.swift +// SwiftDBAI +// +// A SwiftUI line chart that renders DataTable values using Swift Charts. +// Best for time series or sequential data (e.g., revenue over months). + +import SwiftUI +import Charts + +/// A line chart view that renders a `DataTable` column pair using Swift Charts. +/// +/// Displays a connected line with optional area fill, point markers, +/// and smooth interpolation. Best suited for time series or sequential data. +/// +/// Usage: +/// ```swift +/// LineChartView( +/// dataTable: table, +/// categoryColumn: "month", +/// valueColumn: "revenue" +/// ) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct LineChartView: View { + + /// The data to chart. + public let dataTable: DataTable + + /// Column name for category/time labels (x-axis). + public let categoryColumn: String + + /// Column name for numeric values (y-axis). + public let valueColumn: String + + /// Optional chart title. + public var title: String? + + /// Whether to show an area fill below the line. + public var showAreaFill: Bool + + /// Whether to show point markers at each data point. + public var showPoints: Bool + + /// Maximum data points to display. + public var maxPoints: Int + + public init( + dataTable: DataTable, + categoryColumn: String, + valueColumn: String, + title: String? = nil, + showAreaFill: Bool = true, + showPoints: Bool = true, + maxPoints: Int = 100 + ) { + self.dataTable = dataTable + self.categoryColumn = categoryColumn + self.valueColumn = valueColumn + self.title = title + self.showAreaFill = showAreaFill + self.showPoints = showPoints + self.maxPoints = maxPoints + } + + public var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let title { + Text(title) + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + } + + if chartData.isEmpty { + emptyChartView + } else { + chartContent + } + } + } + + // MARK: - Chart Content + + @ViewBuilder + private var chartContent: some View { + Chart(chartData, id: \.label) { item in + LineMark( + x: .value(categoryColumn, item.label), + y: .value(valueColumn, item.value) + ) + .foregroundStyle(Color.accentColor) + .lineStyle(StrokeStyle(lineWidth: 2)) + .interpolationMethod(.catmullRom) + + if showAreaFill { + AreaMark( + x: .value(categoryColumn, item.label), + y: .value(valueColumn, item.value) + ) + .foregroundStyle( + .linearGradient( + colors: [ + Color.accentColor.opacity(0.2), + Color.accentColor.opacity(0.02), + ], + startPoint: .top, + endPoint: .bottom + ) + ) + .interpolationMethod(.catmullRom) + } + + if showPoints { + PointMark( + x: .value(categoryColumn, item.label), + y: .value(valueColumn, item.value) + ) + .foregroundStyle(Color.accentColor) + .symbolSize(30) + } + } + .chartXAxis { + AxisMarks(values: .automatic) { _ in + AxisValueLabel() + .font(.caption2) + } + } + .chartYAxis { + AxisMarks(position: .leading) { _ in + AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5, dash: [4, 4])) + .foregroundStyle(.secondary.opacity(0.3)) + AxisValueLabel() + .font(.caption2) + } + } + .frame(minHeight: 200) + + if isTruncated { + Text("Showing \(maxPoints) of \(dataTable.rowCount) data points") + .font(.caption2) + .foregroundStyle(.secondary) + } + } + + // MARK: - Empty State + + @ViewBuilder + private var emptyChartView: some View { + VStack(spacing: 8) { + Image(systemName: "chart.xyaxis.line") + .font(.title2) + .foregroundStyle(.secondary) + Text("No chartable data") + .font(.caption) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, minHeight: 100) + } + + // MARK: - Data Extraction + + private var isTruncated: Bool { + dataTable.rowCount > maxPoints + } + + private var chartData: [ChartDataPoint] { + let labels = dataTable.stringValues(forColumn: categoryColumn) + let values = dataTable.numericValues(forColumn: valueColumn) + + let count = min(labels.count, values.count, maxPoints) + guard count > 0 else { return [] } + + return (0..0 = donut). + public var innerRadiusRatio: CGFloat + + /// Maximum number of slices before grouping remaining into "Other". + public var maxSlices: Int + + public init( + dataTable: DataTable, + categoryColumn: String, + valueColumn: String, + title: String? = nil, + innerRadiusRatio: CGFloat = 0.4, + maxSlices: Int = 8 + ) { + self.dataTable = dataTable + self.categoryColumn = categoryColumn + self.valueColumn = valueColumn + self.title = title + self.innerRadiusRatio = innerRadiusRatio + self.maxSlices = maxSlices + } + + public var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let title { + Text(title) + .font(.caption.weight(.semibold)) + .foregroundStyle(.secondary) + } + + if chartData.isEmpty { + emptyChartView + } else { + HStack(alignment: .center, spacing: 16) { + chartContent + legendView + } + } + } + } + + // MARK: - Chart Content + + @ViewBuilder + private var chartContent: some View { + Chart(chartData, id: \.label) { item in + SectorMark( + angle: .value(valueColumn, item.value), + innerRadius: .ratio(innerRadiusRatio), + angularInset: 1.5 + ) + .foregroundStyle(by: .value(categoryColumn, item.label)) + .cornerRadius(3) + } + .chartForegroundStyleScale( + domain: chartData.map(\.label), + range: sliceColors + ) + .chartLegend(.hidden) + .frame(minWidth: 150, minHeight: 150) + .aspectRatio(1, contentMode: .fit) + } + + // MARK: - Legend + + @ViewBuilder + private var legendView: some View { + VStack(alignment: .leading, spacing: 6) { + ForEach(Array(chartData.enumerated()), id: \.element.label) { index, item in + HStack(spacing: 8) { + Circle() + .fill(sliceColors[index % sliceColors.count]) + .frame(width: 8, height: 8) + + Text(item.label) + .font(.caption) + .foregroundStyle(.primary) + .lineLimit(1) + + Spacer() + + Text(percentageText(for: item.value)) + .font(.caption) + .foregroundStyle(.secondary) + .monospacedDigit() + } + } + } + } + + // MARK: - Empty State + + @ViewBuilder + private var emptyChartView: some View { + VStack(spacing: 8) { + Image(systemName: "chart.pie") + .font(.title2) + .foregroundStyle(.secondary) + Text("No chartable data") + .font(.caption) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, minHeight: 100) + } + + // MARK: - Colors + + /// Curated color palette for pie slices. + private var sliceColors: [Color] { + [ + .blue, + .green, + .orange, + .purple, + .pink, + .cyan, + .yellow, + .indigo, + .mint, + .teal, + ] + } + + // MARK: - Helpers + + private var total: Double { + chartData.reduce(0) { $0 + $1.value } + } + + private func percentageText(for value: Double) -> String { + guard total > 0 else { return "0%" } + let pct = (value / total) * 100 + if pct >= 10 { + return String(format: "%.0f%%", pct) + } + return String(format: "%.1f%%", pct) + } + + // MARK: - Data Extraction + + private var chartData: [ChartDataPoint] { + let labels = dataTable.stringValues(forColumn: categoryColumn) + let values = dataTable.numericValues(forColumn: valueColumn) + + let count = min(labels.count, values.count) + guard count > 0 else { return [] } + + // Build all points, sorted by value descending + var points = (0.. 0 } + .sorted { $0.value > $1.value } + + // Group excess slices into "Other" + if points.count > maxSlices { + let kept = Array(points.prefix(maxSlices - 1)) + let otherValue = points.dropFirst(maxSlices - 1).reduce(0) { $0 + $1.value } + points = kept + [ChartDataPoint(label: "Other", value: otherValue)] + } + + return points + } +} + +// MARK: - Preview + +#if DEBUG +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +#Preview("Pie Chart") { + let columns: [DataTable.Column] = [ + .init(name: "status", index: 0, inferredType: .text), + .init(name: "count", index: 1, inferredType: .integer), + ] + let statuses = ["Active", "Inactive", "Pending", "Archived"] + let counts: [Int64] = [45, 20, 15, 10] + let rows: [DataTable.Row] = statuses.enumerated().map { i, status in + DataTable.Row( + id: i, + values: [.text(status), .integer(counts[i])], + columnNames: ["status", "count"] + ) + } + let table = DataTable(columns: columns, rows: rows) + + PieChartView( + dataTable: table, + categoryColumn: "status", + valueColumn: "count", + title: "Users by Status" + ) + .padding() + .frame(height: 250) +} +#endif diff --git a/Sources/SwiftDBAI/Views/ChatView.swift b/Sources/SwiftDBAI/Views/ChatView.swift new file mode 100644 index 0000000..70761a2 --- /dev/null +++ b/Sources/SwiftDBAI/Views/ChatView.swift @@ -0,0 +1,225 @@ +// ChatView.swift +// SwiftDBAI +// +// Drop-in SwiftUI view for chatting with a SQLite database. +// Renders messages with automatic data table display for query results. + +import SwiftUI + +/// A drop-in SwiftUI chat interface for querying SQLite databases +/// with natural language. +/// +/// `ChatView` renders the full conversation including: +/// - User messages (right-aligned, accent-colored) +/// - Assistant responses with text summaries +/// - **Automatic data tables** via `ScrollableDataTableView` when query results +/// contain tabular data (rows + columns) +/// - SQL query disclosure for transparency +/// - Error messages with red styling +/// - A loading indicator while the engine is processing +/// +/// Usage: +/// ```swift +/// let engine = ChatEngine(database: myPool, model: myModel) +/// let viewModel = ChatViewModel(engine: engine) +/// +/// ChatView(viewModel: viewModel) +/// ``` +/// +/// Or use the convenience initializer: +/// ```swift +/// ChatView(engine: myEngine) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct ChatView: View { + @Bindable private var viewModel: ChatViewModel + @State private var inputText: String = "" + @FocusState private var isInputFocused: Bool + @Environment(\.chatViewConfiguration) private var config + + /// Creates a ChatView with an existing view model. + /// + /// - Parameter viewModel: The `ChatViewModel` driving this view. + public init(viewModel: ChatViewModel) { + self.viewModel = viewModel + } + + /// Creates a ChatView with a `ChatEngine`, automatically creating + /// a `ChatViewModel`. + /// + /// - Parameter engine: The `ChatEngine` to power the chat. + public init(engine: ChatEngine) { + self.viewModel = ChatViewModel(engine: engine) + } + + public var body: some View { + VStack(spacing: 0) { + messageList + Divider() + inputBar + } + .background(config.backgroundColor) + .applyColorSchemeOverride(config.colorSchemeOverride) + } + + // MARK: - Message List + + @ViewBuilder + private var messageList: some View { + ScrollViewReader { proxy in + ScrollView { + LazyVStack(spacing: 12) { + if viewModel.messages.isEmpty { + emptyState + } + + ForEach(viewModel.messages) { message in + messageBubble(for: message) + .id(message.id) + } + + if viewModel.isLoading { + loadingIndicator + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + } + .onChange(of: viewModel.messages.count) { _, _ in + if let lastMessage = viewModel.messages.last { + withAnimation(.easeOut(duration: 0.3)) { + proxy.scrollTo(lastMessage.id, anchor: .bottom) + } + } + } + } + } + + // MARK: - Empty State + + @ViewBuilder + private var emptyState: some View { + VStack(spacing: 12) { + Image(systemName: config.emptyStateIcon) + .font(.system(size: 40)) + .foregroundStyle(.tertiary) + Text(config.emptyStateTitle) + .font(.headline) + .foregroundStyle(.secondary) + Text(config.emptyStateSubtitle) + .font(.subheadline) + .foregroundStyle(.tertiary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 60) + } + + // MARK: - Loading Indicator + + @ViewBuilder + private var loadingIndicator: some View { + HStack(alignment: .top) { + HStack(spacing: 8) { + ProgressView() + .controlSize(.small) + Text("Querying…") + .font(.callout) + .foregroundStyle(.secondary) + } + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background( + config.assistantBubbleColor, + in: RoundedRectangle(cornerRadius: config.bubbleCornerRadius, style: .continuous) + ) + + Spacer(minLength: 48) + } + .id("loading-indicator") + .transition(.opacity.combined(with: .move(edge: .bottom))) + } + + // MARK: - Input Bar + + @ViewBuilder + private var inputBar: some View { + HStack(spacing: 8) { + TextField(config.inputPlaceholder, text: $inputText, axis: .vertical) + .textFieldStyle(.plain) + .font(config.inputFont) + .lineLimit(1...5) + .focused($isInputFocused) + .onSubmit { sendMessage() } + .submitLabel(.send) + + Button(action: sendMessage) { + Image(systemName: "arrow.up.circle.fill") + .font(.title2) + .foregroundStyle(canSend ? config.accentColor : Color.secondary) + } + .disabled(!canSend) + .keyboardShortcut(.return, modifiers: .command) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(config.inputBarBackgroundColor) + } + + // MARK: - Message Bubble + + @ViewBuilder + private func messageBubble(for message: ChatMessage) -> some View { + if message.role == .error { + MessageBubbleView( + message: message, + onRetry: makeRetryAction(for: message) + ) + } else { + MessageBubbleView(message: message) + } + } + + private func makeRetryAction(for errorMessage: ChatMessage) -> @Sendable () async -> Void { + let vm = viewModel + let messageId = errorMessage.id + return { @MainActor [vm] in + let allMessages = await MainActor.run { vm.messages } + if let lastUserMessage = allMessages + .prefix(while: { $0.id != messageId }) + .last(where: { $0.role == .user }) { + await vm.send(lastUserMessage.content) + } + } + } + + // MARK: - Helpers + + private var canSend: Bool { + !inputText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !viewModel.isLoading + } + + private func sendMessage() { + guard canSend else { return } + let text = inputText + inputText = "" + + Task { + await viewModel.send(text) + } + } +} + +// MARK: - Color Scheme Override + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +private extension View { + @ViewBuilder + func applyColorSchemeOverride(_ scheme: ColorScheme?) -> some View { + if let scheme { + self.environment(\.colorScheme, scheme) + } else { + self + } + } +} diff --git a/Sources/SwiftDBAI/Views/ChatViewModel.swift b/Sources/SwiftDBAI/Views/ChatViewModel.swift new file mode 100644 index 0000000..3892c2b --- /dev/null +++ b/Sources/SwiftDBAI/Views/ChatViewModel.swift @@ -0,0 +1,137 @@ +// ChatViewModel.swift +// SwiftDBAI +// +// Observable view model that bridges ChatEngine with the SwiftUI ChatView. + +import Foundation +import Observation + +/// The readiness state of the schema introspection. +public enum SchemaReadiness: Sendable, Equatable { + /// Schema has not been loaded yet. + case idle + /// Schema introspection is in progress. + case loading + /// Schema is ready with the given number of tables. + case ready(tableCount: Int) + /// Schema introspection failed. + case failed(String) + + public var isReady: Bool { + if case .ready = self { return true } + return false + } +} + +/// Observable view model that drives the `ChatView`. +/// +/// Wraps `ChatEngine` to provide reactive state updates for the SwiftUI layer. +/// Manages the message list, loading state, error presentation, and schema +/// readiness. Call ``prepare()`` at view-appear time to eagerly introspect the +/// database schema. +/// +/// Usage: +/// ```swift +/// let viewModel = ChatViewModel(engine: myChatEngine) +/// ChatView(viewModel: viewModel) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +@Observable +@MainActor +public final class ChatViewModel { + + // MARK: - Public State + + /// All messages in the conversation, in chronological order. + public private(set) var messages: [ChatMessage] = [] + + /// Whether the engine is currently processing a request. + public private(set) var isLoading: Bool = false + + /// The most recent error message, if any. Cleared on next send. + public private(set) var errorMessage: String? + + /// Current schema readiness state. + public private(set) var schemaReadiness: SchemaReadiness = .idle + + // MARK: - Dependencies + + private let engine: ChatEngine + + // MARK: - Initialization + + /// Creates a new ChatViewModel. + /// + /// - Parameter engine: The `ChatEngine` to use for processing messages. + public init(engine: ChatEngine) { + self.engine = engine + } + + // MARK: - Schema Preparation + + /// Eagerly introspects the database schema so it's ready before the first query. + /// + /// This should be called from a `.task` modifier on the view. It transitions + /// `schemaReadiness` through `.loading` → `.ready` (or `.failed`). + /// If the schema is already cached, this completes immediately. + public func prepare() async { + // Don't re-prepare if already ready + if schemaReadiness.isReady { return } + + schemaReadiness = .loading + + do { + let schema = try await engine.prepareSchema() + schemaReadiness = .ready(tableCount: schema.tableNames.count) + } catch { + schemaReadiness = .failed(error.localizedDescription) + } + } + + // MARK: - Public API + + /// Sends a user message and appends the response to the conversation. + /// + /// - Parameter text: The natural language message from the user. + public func send(_ text: String) async { + let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + + errorMessage = nil + + // Add user message immediately + let userMessage = ChatMessage(role: .user, content: trimmed) + messages.append(userMessage) + + isLoading = true + defer { isLoading = false } + + do { + let response = try await engine.send(trimmed) + + let assistantMessage = ChatMessage( + role: .assistant, + content: response.summary, + queryResult: response.queryResult, + sql: response.sql + ) + messages.append(assistantMessage) + } catch { + let typedError = (error as? SwiftDBAIError) + let errorMsg = ChatMessage( + role: .error, + content: error.localizedDescription, + error: typedError + ) + messages.append(errorMsg) + errorMessage = error.localizedDescription + } + } + + /// Clears the conversation and resets the engine state. + public func reset() { + messages.removeAll() + errorMessage = nil + engine.reset() + } +} diff --git a/Sources/SwiftDBAI/Views/DataChatView.swift b/Sources/SwiftDBAI/Views/DataChatView.swift new file mode 100644 index 0000000..95efa77 --- /dev/null +++ b/Sources/SwiftDBAI/Views/DataChatView.swift @@ -0,0 +1,220 @@ +// DataChatView.swift +// SwiftDBAI +// +// Zero-config SwiftUI view: provide a database path and a model, get a chat UI. + +import AnyLanguageModel +import GRDB +import SwiftUI + +/// A convenience SwiftUI view that wraps the full chat-with-database stack. +/// +/// `DataChatView` is the simplest entry point into SwiftDBAI. It requires only +/// a database file path and a language model — no schema files, no annotations, +/// no manual setup. The view creates a GRDB connection, a `ChatEngine`, +/// a `ChatViewModel`, and renders a fully functional `ChatView`. +/// +/// Usage with just a path and model: +/// ```swift +/// DataChatView( +/// databasePath: "/path/to/mydata.sqlite", +/// model: OllamaLanguageModel(model: "llama3") +/// ) +/// ``` +/// +/// Usage with additional configuration: +/// ```swift +/// DataChatView( +/// databasePath: documentsURL.appendingPathComponent("app.db").path, +/// model: OpenAILanguageModel(apiKey: key), +/// allowlist: .standard, +/// additionalContext: "This database stores a recipe app's data." +/// ) +/// ``` +/// +/// If you already have a GRDB `DatabasePool` or `DatabaseQueue`, use +/// `ChatView` with a `ChatEngine` directly for full control. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct DataChatView: View { + @State private var viewModel: ChatViewModel + @State private var loadError: DataChatError? + + /// Creates a DataChatView from a database file path and language model. + /// + /// This is the zero-config convenience initializer. It opens a GRDB + /// `DatabasePool` at the given path, creates a `ChatEngine` with + /// read-only defaults, and wires up the full chat UI. + /// + /// - Parameters: + /// - databasePath: Absolute path to a SQLite database file. + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly` (SELECT only). + /// - additionalContext: Optional extra context about the database for the LLM system prompt + /// (e.g., "This database stores e-commerce orders and products."). + /// - maxSummaryRows: Maximum rows to include when summarizing results (default: 50). + public init( + databasePath: String, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + maxSummaryRows: Int = 50 + ) { + do { + let pool = try DatabasePool(path: databasePath) + let engine = ChatEngine( + database: pool, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + maxSummaryRows: maxSummaryRows + ) + self._viewModel = State(initialValue: ChatViewModel(engine: engine)) + self._loadError = State(initialValue: nil) + } catch { + // If the database can't be opened, create a placeholder engine + // and store the error to display in the UI. + let queue = try! DatabaseQueue() + let engine = ChatEngine( + database: queue, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + maxSummaryRows: maxSummaryRows + ) + self._viewModel = State(initialValue: ChatViewModel(engine: engine)) + self._loadError = State(initialValue: DataChatError.databaseOpenFailed( + path: databasePath, + underlying: error + )) + } + } + + /// Creates a DataChatView from an existing GRDB database connection and language model. + /// + /// Use this initializer when you already have a configured `DatabasePool` or + /// `DatabaseQueue` and want the convenience of `DataChatView` without + /// creating a `ChatEngine` yourself. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (`DatabasePool` or `DatabaseQueue`). + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly`. + /// - additionalContext: Optional extra context about the database for the LLM. + /// - maxSummaryRows: Maximum rows to include when summarizing results (default: 50). + public init( + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + maxSummaryRows: Int = 50 + ) { + let engine = ChatEngine( + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + maxSummaryRows: maxSummaryRows + ) + self._viewModel = State(initialValue: ChatViewModel(engine: engine)) + self._loadError = State(initialValue: nil) + } + + public var body: some View { + if let error = loadError { + errorView(error) + } else { + ChatView(viewModel: viewModel) + .task { + await viewModel.prepare() + } + .overlay { + if case .loading = viewModel.schemaReadiness { + schemaLoadingView + } + if case .failed(let reason) = viewModel.schemaReadiness { + schemaErrorView(reason) + } + } + } + } + + // MARK: - Schema Loading View + + @ViewBuilder + private var schemaLoadingView: some View { + VStack(spacing: 16) { + ProgressView() + .controlSize(.large) + Text("Introspecting database schema…") + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.ultraThinMaterial) + } + + // MARK: - Schema Error View + + @ViewBuilder + private func schemaErrorView(_ reason: String) -> some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 40)) + .foregroundStyle(.orange) + + Text("Schema Introspection Failed") + .font(.headline) + + Text(reason) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 32) + + Button("Retry") { + Task { + await viewModel.prepare() + } + } + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(.ultraThinMaterial) + } + + // MARK: - Database Open Error View + + @ViewBuilder + private func errorView(_ error: DataChatError) -> some View { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 40)) + .foregroundStyle(.red) + + Text("Unable to Open Database") + .font(.headline) + + Text(error.localizedDescription) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 32) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +// MARK: - Errors + +/// Errors specific to `DataChatView` initialization. +public enum DataChatError: Error, LocalizedError, Sendable { + /// The database file could not be opened at the given path. + case databaseOpenFailed(path: String, underlying: any Error) + + public var errorDescription: String? { + switch self { + case .databaseOpenFailed(let path, let underlying): + return "Could not open database at \"\(path)\": \(underlying.localizedDescription)" + } + } +} diff --git a/Sources/SwiftDBAI/Views/ErrorMessageView.swift b/Sources/SwiftDBAI/Views/ErrorMessageView.swift new file mode 100644 index 0000000..5c7c3f0 --- /dev/null +++ b/Sources/SwiftDBAI/Views/ErrorMessageView.swift @@ -0,0 +1,362 @@ +// ErrorMessageView.swift +// SwiftDBAI +// +// Reusable SwiftUI component that renders error messages with contextual +// icons, descriptions, and optional retry actions based on the error type. + +import SwiftUI + +/// A reusable SwiftUI component that renders a ``SwiftDBAIError`` with an +/// appropriate icon, human-readable message, and optional retry action. +/// +/// The view automatically selects a visual treatment based on the error +/// category: +/// +/// | Category | Icon | Color | Retry? | +/// |-------------------|-------------------------------|---------|--------| +/// | Safety / blocked | `shield.trianglebadge.excl…` | Orange | No | +/// | Confirmation | `hand.raised.fill` | Yellow | Yes* | +/// | LLM failure | `brain` | Purple | Yes | +/// | Schema / DB | `cylinder.split.1x2` | Red | No | +/// | Recoverable SQL | `arrow.clockwise` | Blue | Yes | +/// | Generic | `exclamationmark.triangle` | Red | No | +/// +/// *Confirmation retry triggers the confirm callback, not a standard retry. +/// +/// Usage: +/// ```swift +/// ErrorMessageView( +/// error: .llmTimeout(seconds: 30), +/// onRetry: { /* resend the message */ } +/// ) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct ErrorMessageView: View { + @Environment(\.chatViewConfiguration) private var config + + /// The error to display. When `nil`, the view falls back to the raw message. + private let error: SwiftDBAIError? + + /// The raw error message string (used as fallback when error is nil). + private let message: String + + /// Called when the user taps the retry button. `nil` hides the button. + private let onRetry: (@Sendable () async -> Void)? + + /// Called when the user confirms a destructive operation. + private let onConfirm: (@Sendable () async -> Void)? + + @State private var isRetrying = false + + // MARK: - Initializers + + /// Creates an ErrorMessageView from a typed ``SwiftDBAIError``. + /// + /// - Parameters: + /// - error: The ``SwiftDBAIError`` to display. + /// - onRetry: An optional async closure invoked when the user taps retry. + /// - onConfirm: An optional async closure invoked when the user confirms + /// a destructive operation (only relevant for `.confirmationRequired`). + public init( + error: SwiftDBAIError, + onRetry: (@Sendable () async -> Void)? = nil, + onConfirm: (@Sendable () async -> Void)? = nil + ) { + self.error = error + self.message = error.localizedDescription + self.onRetry = onRetry + self.onConfirm = onConfirm + } + + /// Creates an ErrorMessageView from a ``ChatMessage``. + /// + /// Extracts the typed error if available, otherwise falls back to the + /// message content string. + /// + /// - Parameters: + /// - message: The chat message with role `.error`. + /// - onRetry: An optional async closure invoked when the user taps retry. + /// - onConfirm: An optional async closure invoked when the user confirms + /// a destructive operation. + public init( + chatMessage: ChatMessage, + onRetry: (@Sendable () async -> Void)? = nil, + onConfirm: (@Sendable () async -> Void)? = nil + ) { + self.error = chatMessage.error + self.message = chatMessage.content + self.onRetry = onRetry + self.onConfirm = onConfirm + } + + /// Creates an ErrorMessageView from a plain string (untyped fallback). + /// + /// - Parameters: + /// - message: The error message string. + /// - onRetry: An optional async closure invoked when the user taps retry. + public init( + message: String, + onRetry: (@Sendable () async -> Void)? = nil + ) { + self.error = nil + self.message = message + self.onRetry = onRetry + self.onConfirm = nil + } + + // MARK: - Body + + public var body: some View { + VStack(alignment: .leading, spacing: 10) { + // Icon + message row + HStack(alignment: .firstTextBaseline, spacing: 8) { + Image(systemName: iconName) + .foregroundStyle(iconColor) + .font(.callout) + .accessibilityHidden(true) + + VStack(alignment: .leading, spacing: 4) { + if let title = errorTitle { + Text(title) + .font(.callout.weight(.semibold)) + .foregroundStyle(iconColor) + } + + Text(message) + .font(.body) + .foregroundStyle(.primary) + .textSelection(.enabled) + .fixedSize(horizontal: false, vertical: true) + + if let hint = recoveryHint { + Text(hint) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + } + + // Action buttons + if showRetryButton || showConfirmButton { + HStack(spacing: 12) { + if showConfirmButton { + confirmButton + } + if showRetryButton { + retryButton + } + } + .padding(.leading, 26) // Align with text (icon width + spacing) + } + } + .accessibilityElement(children: .combine) + .accessibilityLabel(accessibilityDescription) + } + + // MARK: - Action Buttons + + @ViewBuilder + private var retryButton: some View { + Button { + guard !isRetrying else { return } + isRetrying = true + Task { + await onRetry?() + isRetrying = false + } + } label: { + HStack(spacing: 4) { + if isRetrying { + ProgressView() + .controlSize(.mini) + } else { + Image(systemName: "arrow.clockwise") + .font(.caption) + } + Text(retryButtonLabel) + .font(.caption.weight(.medium)) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(iconColor.opacity(0.12)) + .foregroundStyle(iconColor) + .clipShape(Capsule()) + } + .buttonStyle(.plain) + .disabled(isRetrying) + } + + @ViewBuilder + private var confirmButton: some View { + Button { + Task { + await onConfirm?() + } + } label: { + HStack(spacing: 4) { + Image(systemName: "checkmark.circle") + .font(.caption) + Text("Confirm") + .font(.caption.weight(.medium)) + } + .padding(.horizontal, 10) + .padding(.vertical, 6) + .background(Color.orange.opacity(0.12)) + .foregroundStyle(.orange) + .clipShape(Capsule()) + } + .buttonStyle(.plain) + } + + // MARK: - Error Classification + + private var errorCategory: ErrorCategory { + guard let error else { return .generic } + + if error.requiresUserAction { + return .confirmation + } + if error.isSafetyError { + return .safety + } + if error.isRecoverable { + return .recoverable + } + + switch error { + case .llmFailure, .llmResponseUnparseable, .llmTimeout: + return .llm + case .schemaIntrospectionFailed, .emptySchema, .databaseError, .queryTimedOut: + return .database + case .configurationError: + return .configuration + default: + return .generic + } + } + + private enum ErrorCategory { + case safety + case confirmation + case llm + case database + case recoverable + case configuration + case generic + } + + // MARK: - Visual Properties + + private var iconName: String { + switch errorCategory { + case .safety: + return "shield.trianglebadge.exclamationmark.fill" + case .confirmation: + return "hand.raised.fill" + case .llm: + return "brain" + case .database: + return "cylinder.split.1x2" + case .recoverable: + return "arrow.clockwise" + case .configuration: + return "gearshape.triangle.fill" + case .generic: + return "exclamationmark.triangle.fill" + } + } + + private var iconColor: Color { + switch errorCategory { + case .safety: + return .orange + case .confirmation: + return .yellow + case .llm: + return .purple + case .database: + return config.errorColor + case .recoverable: + return .blue + case .configuration: + return .gray + case .generic: + return config.errorColor + } + } + + private var errorTitle: String? { + switch errorCategory { + case .safety: + return "Operation Blocked" + case .confirmation: + return "Confirmation Required" + case .llm: + return "AI Provider Error" + case .database: + return "Database Error" + case .recoverable: + return "Query Issue" + case .configuration: + return "Configuration Error" + case .generic: + return nil + } + } + + private var recoveryHint: String? { + guard let error else { return nil } + + switch error { + case .noSQLGenerated, .llmResponseUnparseable: + return "Try rephrasing your question." + case .tableNotFound: + return "Check that you're referring to an existing table." + case .columnNotFound: + return "Verify the column name matches your schema." + case .invalidSQL: + return "The AI generated an invalid query. Try asking differently." + case .llmTimeout: + return "The AI took too long. Try a simpler question." + case .llmFailure: + return "The AI service may be temporarily unavailable." + case .emptySchema: + return "Add some tables to your database first." + case .queryTimedOut: + return "Try a simpler query or add database indexes." + default: + return nil + } + } + + // MARK: - Button Visibility + + private var showRetryButton: Bool { + guard onRetry != nil else { return false } + return errorCategory == .recoverable || errorCategory == .llm + } + + private var showConfirmButton: Bool { + guard onConfirm != nil else { return false } + return errorCategory == .confirmation + } + + private var retryButtonLabel: String { + switch errorCategory { + case .llm: + return "Retry" + case .recoverable: + return "Try Again" + default: + return "Retry" + } + } + + // MARK: - Accessibility + + private var accessibilityDescription: String { + let prefix = errorTitle.map { "\($0): " } ?? "Error: " + return prefix + message + } +} diff --git a/Sources/SwiftDBAI/Views/MessageBubbleView.swift b/Sources/SwiftDBAI/Views/MessageBubbleView.swift new file mode 100644 index 0000000..c58cdd5 --- /dev/null +++ b/Sources/SwiftDBAI/Views/MessageBubbleView.swift @@ -0,0 +1,214 @@ +// MessageBubbleView.swift +// SwiftDBAI +// +// Renders a single ChatMessage as a styled bubble with optional +// data table and SQL disclosure for query results. + +import SwiftUI +import Charts + +/// Renders a single `ChatMessage` in the chat conversation. +/// +/// - **User messages** display right-aligned with an accent-colored background +/// and white text, using a continuous rounded rectangle shape. +/// - **Assistant messages** display left-aligned with a secondary background. +/// The natural language text summary is the primary content, rendered with +/// full `.body` font and `.primary` foreground for readability. +/// If the message contains a `queryResult` with tabular data, a +/// `ScrollableDataTableView` is automatically embedded below the summary. +/// An optional SQL disclosure group shows the generated query. +/// - **Error messages** display left-aligned with a red-tinted background +/// and an exclamation mark icon. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +struct MessageBubbleView: View { + @Environment(\.chatViewConfiguration) private var config + + let message: ChatMessage + + /// Whether to show the SQL query in a disclosure group. + var showSQL: Bool = true + + /// Maximum height for the data table before it scrolls. + var maxTableHeight: CGFloat = 300 + + /// Called when the user taps "Retry" on a recoverable error. + var onRetry: (@Sendable () async -> Void)? + + /// Called when the user confirms a destructive operation. + var onConfirm: (@Sendable () async -> Void)? + + var body: some View { + HStack(alignment: .top, spacing: 8) { + if message.role == .user { Spacer(minLength: 48) } + + if message.role != .user, let icon = config.assistantAvatarIcon { + assistantAvatar(icon: icon) + } + + bubbleContent + .padding(.horizontal, config.messagePadding) + .padding(.vertical, config.messagePadding * 10 / 14) + .background(bubbleBackground) + .clipShape(bubbleShape) + + if message.role != .user { Spacer(minLength: 48) } + } + } + + @ViewBuilder + private func assistantAvatar(icon: String) -> some View { + Image(systemName: icon) + .font(.system(size: 16)) + .foregroundStyle(.white) + .frame(width: 32, height: 32) + .background(config.assistantAvatarColor) + .clipShape(Circle()) + .padding(.top, 2) + } + + // MARK: - Bubble Content + + @ViewBuilder + private var bubbleContent: some View { + switch message.role { + case .user: + userContent + case .assistant: + assistantContent + case .error: + errorContent + } + } + + // MARK: - User Content + + @ViewBuilder + private var userContent: some View { + Text(message.content) + .font(config.messageFont) + .foregroundStyle(config.userTextColor) + .textSelection(.enabled) + } + + // MARK: - Assistant Content (Text Summary + Data Table + SQL) + + @ViewBuilder + private var assistantContent: some View { + VStack(alignment: .leading, spacing: 10) { + // Natural language text summary — primary content + Text(message.content) + .font(config.summaryFont) + .foregroundStyle(config.assistantTextColor) + .textSelection(.enabled) + .fixedSize(horizontal: false, vertical: true) + + // Data table — automatically shown when queryResult has tabular data + if let queryResult = message.queryResult, + !queryResult.columns.isEmpty, + !queryResult.rows.isEmpty { + dataTableSection(for: queryResult) + } + + // SQL disclosure — collapsed by default for transparency + if config.showSQLDisclosure, let sql = message.sql { + sqlDisclosure(sql: sql) + } + } + } + + // MARK: - Error Content + + @ViewBuilder + private var errorContent: some View { + ErrorMessageView( + chatMessage: message, + onRetry: onRetry, + onConfirm: onConfirm + ) + } + + /// Maximum height for the chart section. + var maxChartHeight: CGFloat = 250 + + /// Whether to show auto-detected charts. Defaults to `true`. + var showCharts: Bool = true + + // MARK: - Chart Detection + + /// The shared detector used for chart eligibility checks. + private static let chartDetector = ChartDataDetector() + + // MARK: - Data Table Section + + @ViewBuilder + private func dataTableSection(for queryResult: QueryResult) -> some View { + let dataTable = DataTable(queryResult) + + VStack(alignment: .leading, spacing: 8) { + // Chart — automatically shown when ChartDataDetector finds eligible data + if showCharts { + chartSection(for: dataTable) + } + + Divider() + + ScrollableDataTableView( + dataTable: dataTable, + showAlternatingRows: true, + showFooter: true + ) + .frame(maxHeight: maxTableHeight) + } + } + + // MARK: - Chart Section + + @ViewBuilder + private func chartSection(for dataTable: DataTable) -> some View { + let detector = Self.chartDetector + if detector.detect(dataTable) != nil { + VStack(alignment: .leading, spacing: 4) { + ChartResultView(dataTable: dataTable, detector: detector) + .frame(maxHeight: maxChartHeight) + } + } + } + + // MARK: - SQL Disclosure + + @ViewBuilder + private func sqlDisclosure(sql: String) -> some View { + DisclosureGroup { + Text(sql) + .font(config.sqlFont) + .foregroundStyle(.secondary) + .textSelection(.enabled) + .padding(8) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.primary.opacity(0.04)) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } label: { + Label("SQL Query", systemImage: "chevron.left.forwardslash.chevron.right") + .font(.caption) + .foregroundStyle(.secondary) + } + } + + // MARK: - Styling Helpers + + private var bubbleShape: RoundedRectangle { + RoundedRectangle(cornerRadius: config.bubbleCornerRadius, style: .continuous) + } + + @ViewBuilder + private var bubbleBackground: some View { + switch message.role { + case .user: + config.userBubbleColor + case .assistant: + config.assistantBubbleColor + case .error: + config.errorColor.opacity(0.1) + } + } +} diff --git a/Sources/SwiftDBAI/Views/Presentation/DataChatSheet.swift b/Sources/SwiftDBAI/Views/Presentation/DataChatSheet.swift new file mode 100644 index 0000000..265b680 --- /dev/null +++ b/Sources/SwiftDBAI/Views/Presentation/DataChatSheet.swift @@ -0,0 +1,114 @@ +// DataChatSheet.swift +// SwiftDBAI +// +// SwiftUI wrapper that adds NavigationStack chrome around DataChatView. +// Designed for use with .sheet() and .fullScreenCover(). + +import AnyLanguageModel +import GRDB +import SwiftUI + +/// A presentation-ready wrapper around ``DataChatView`` that adds a +/// `NavigationStack`, title, and **Done** button. +/// +/// Use `DataChatSheet` with SwiftUI's `.sheet()` or `.fullScreenCover()` +/// modifiers so consumers get a fully navigable chat experience out of the box. +/// +/// ```swift +/// .sheet(isPresented: $showChat) { +/// DataChatSheet( +/// databasePath: "/path/to/mydata.sqlite", +/// model: OllamaLanguageModel(model: "llama3") +/// ) +/// } +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct DataChatSheet: View { + let databasePath: String? + let database: (any DatabaseWriter)? + let model: any LanguageModel + var allowlist: OperationAllowlist + var additionalContext: String? + var title: String + @Environment(\.dismiss) private var dismiss + + /// Creates a DataChatSheet from a database file path and language model. + /// + /// - Parameters: + /// - databasePath: Absolute path to a SQLite database file. + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly`. + /// - additionalContext: Optional extra context about the database for the LLM. + /// - title: Navigation bar title. Defaults to `"AI Chat"`. + public init( + databasePath: String, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) { + self.databasePath = databasePath + self.database = nil + self.model = model + self.allowlist = allowlist + self.additionalContext = additionalContext + self.title = title + } + + /// Creates a DataChatSheet from an existing GRDB database connection. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (`DatabasePool` or `DatabaseQueue`). + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly`. + /// - additionalContext: Optional extra context about the database for the LLM. + /// - title: Navigation bar title. Defaults to `"AI Chat"`. + public init( + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) { + self.databasePath = nil + self.database = database + self.model = model + self.allowlist = allowlist + self.additionalContext = additionalContext + self.title = title + } + + public var body: some View { + NavigationStack { + dataChatView + .navigationTitle(title) + #if !os(macOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { dismiss() } + } + } + } + } + + @ViewBuilder + private var dataChatView: some View { + if let database { + DataChatView( + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext + ) + } else if let databasePath { + DataChatView( + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext + ) + } + } +} diff --git a/Sources/SwiftDBAI/Views/Presentation/DataChatSheetModifier.swift b/Sources/SwiftDBAI/Views/Presentation/DataChatSheetModifier.swift new file mode 100644 index 0000000..70e603c --- /dev/null +++ b/Sources/SwiftDBAI/Views/Presentation/DataChatSheetModifier.swift @@ -0,0 +1,212 @@ +// DataChatSheetModifier.swift +// SwiftDBAI +// +// View modifiers for presenting DataChatSheet as a sheet or full-screen cover. + +import AnyLanguageModel +import GRDB +import SwiftUI + +// MARK: - Sheet Modifier + +/// A view modifier that presents a ``DataChatSheet`` as a standard sheet. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +struct DataChatSheetModifier: ViewModifier { + @Binding var isPresented: Bool + let databasePath: String + let model: any LanguageModel + var allowlist: OperationAllowlist + var additionalContext: String? + var title: String + + func body(content: Content) -> some View { + content.sheet(isPresented: $isPresented) { + DataChatSheet( + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + } + } +} + +/// A view modifier that presents a ``DataChatSheet`` as a sheet using +/// an existing GRDB database connection. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +struct DataChatSheetDatabaseModifier: ViewModifier { + @Binding var isPresented: Bool + let database: any DatabaseWriter + let model: any LanguageModel + var allowlist: OperationAllowlist + var additionalContext: String? + var title: String + + func body(content: Content) -> some View { + content.sheet(isPresented: $isPresented) { + DataChatSheet( + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + } + } +} + +// MARK: - Full-Screen Modifier + +#if os(iOS) || os(visionOS) +/// A view modifier that presents a ``DataChatSheet`` as a full-screen cover. +@available(iOS 17.0, visionOS 1.0, *) +struct DataChatFullScreenModifier: ViewModifier { + @Binding var isPresented: Bool + let databasePath: String + let model: any LanguageModel + var allowlist: OperationAllowlist + var additionalContext: String? + var title: String + + func body(content: Content) -> some View { + content.fullScreenCover(isPresented: $isPresented) { + DataChatSheet( + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + } + } +} + +/// A view modifier that presents a ``DataChatSheet`` as a full-screen cover +/// using an existing GRDB database connection. +@available(iOS 17.0, visionOS 1.0, *) +struct DataChatFullScreenDatabaseModifier: ViewModifier { + @Binding var isPresented: Bool + let database: any DatabaseWriter + let model: any LanguageModel + var allowlist: OperationAllowlist + var additionalContext: String? + var title: String + + func body(content: Content) -> some View { + content.fullScreenCover(isPresented: $isPresented) { + DataChatSheet( + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + } + } +} +#endif + +// MARK: - View Extensions + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public extension View { + + /// Presents a database chat interface as a sheet. + /// + /// ```swift + /// .dataChatSheet( + /// isPresented: $showChat, + /// databasePath: "/path/to/db.sqlite", + /// model: myLLM + /// ) + /// ``` + func dataChatSheet( + isPresented: Binding, + databasePath: String, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) -> some View { + modifier(DataChatSheetModifier( + isPresented: isPresented, + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + )) + } + + /// Presents a database chat interface as a sheet using an existing GRDB connection. + func dataChatSheet( + isPresented: Binding, + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) -> some View { + modifier(DataChatSheetDatabaseModifier( + isPresented: isPresented, + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + )) + } +} + +#if os(iOS) || os(visionOS) +@available(iOS 17.0, visionOS 1.0, *) +public extension View { + + /// Presents a database chat interface as a full-screen cover. + /// + /// ```swift + /// .dataChatFullScreen( + /// isPresented: $showChat, + /// databasePath: "/path/to/db.sqlite", + /// model: myLLM + /// ) + /// ``` + func dataChatFullScreen( + isPresented: Binding, + databasePath: String, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) -> some View { + modifier(DataChatFullScreenModifier( + isPresented: isPresented, + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + )) + } + + /// Presents a database chat interface as a full-screen cover using an existing GRDB connection. + func dataChatFullScreen( + isPresented: Binding, + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) -> some View { + modifier(DataChatFullScreenDatabaseModifier( + isPresented: isPresented, + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + )) + } +} +#endif diff --git a/Sources/SwiftDBAI/Views/Presentation/DataChatViewController.swift b/Sources/SwiftDBAI/Views/Presentation/DataChatViewController.swift new file mode 100644 index 0000000..497cc1a --- /dev/null +++ b/Sources/SwiftDBAI/Views/Presentation/DataChatViewController.swift @@ -0,0 +1,81 @@ +// DataChatViewController.swift +// SwiftDBAI +// +// UIKit bridge: a UIHostingController subclass for presenting DataChatSheet +// in UIKit-based apps via modal presentation or navigation push. + +#if canImport(UIKit) && !os(watchOS) +import AnyLanguageModel +import GRDB +import SwiftUI +import UIKit + +/// A `UIHostingController` subclass that wraps ``DataChatSheet`` for UIKit apps. +/// +/// Present modally: +/// ```swift +/// let vc = DataChatViewController(databasePath: path, model: myLLM) +/// present(vc, animated: true) +/// ``` +/// +/// Or push onto a navigation stack: +/// ```swift +/// let vc = DataChatViewController(databasePath: path, model: myLLM) +/// navigationController?.pushViewController(vc, animated: true) +/// ``` +@available(iOS 17.0, visionOS 1.0, *) +public final class DataChatViewController: UIHostingController { + + /// Creates a DataChatViewController from a database file path and language model. + /// + /// - Parameters: + /// - databasePath: Absolute path to a SQLite database file. + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly`. + /// - additionalContext: Optional extra context about the database for the LLM. + /// - title: Navigation bar title. Defaults to `"AI Chat"`. + public convenience init( + databasePath: String, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) { + let sheet = DataChatSheet( + databasePath: databasePath, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + self.init(rootView: sheet) + self.modalPresentationStyle = .formSheet + } + + /// Creates a DataChatViewController from an existing GRDB database connection. + /// + /// - Parameters: + /// - database: A GRDB `DatabaseWriter` (`DatabasePool` or `DatabaseQueue`). + /// - model: Any `AnyLanguageModel`-compatible language model instance. + /// - allowlist: SQL operations the LLM may generate. Defaults to `.readOnly`. + /// - additionalContext: Optional extra context about the database for the LLM. + /// - title: Navigation bar title. Defaults to `"AI Chat"`. + public convenience init( + database: any DatabaseWriter, + model: any LanguageModel, + allowlist: OperationAllowlist = .readOnly, + additionalContext: String? = nil, + title: String = "AI Chat" + ) { + let sheet = DataChatSheet( + database: database, + model: model, + allowlist: allowlist, + additionalContext: additionalContext, + title: title + ) + self.init(rootView: sheet) + self.modalPresentationStyle = .formSheet + } +} +#endif diff --git a/Sources/SwiftDBAI/Views/ScrollableDataTableView.swift b/Sources/SwiftDBAI/Views/ScrollableDataTableView.swift new file mode 100644 index 0000000..7215c6f --- /dev/null +++ b/Sources/SwiftDBAI/Views/ScrollableDataTableView.swift @@ -0,0 +1,270 @@ +// ScrollableDataTableView.swift +// SwiftDBAI +// +// A SwiftUI view that renders a DataTable with horizontal and vertical +// scrolling, styled column headers, and row cells. + +import SwiftUI + +/// A scrollable table view that renders a `DataTable` with column headers +/// and row cells, supporting both horizontal and vertical scrolling. +/// +/// Usage: +/// ```swift +/// ScrollableDataTableView(dataTable: myDataTable) +/// ``` +/// +/// The view automatically sizes columns based on content, highlights +/// alternating rows for readability, and right-aligns numeric columns. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct ScrollableDataTableView: View { + @Environment(\.chatViewConfiguration) private var config + + /// The data table to render. + public let dataTable: DataTable + + /// Minimum width for each column in points. + public var minimumColumnWidth: CGFloat + + /// Maximum width for each column in points. + public var maximumColumnWidth: CGFloat + + /// Whether to show alternating row backgrounds. + public var showAlternatingRows: Bool + + /// Whether to show the row count footer. + public var showFooter: Bool + + public init( + dataTable: DataTable, + minimumColumnWidth: CGFloat = 80, + maximumColumnWidth: CGFloat = 250, + showAlternatingRows: Bool = true, + showFooter: Bool = true + ) { + self.dataTable = dataTable + self.minimumColumnWidth = minimumColumnWidth + self.maximumColumnWidth = maximumColumnWidth + self.showAlternatingRows = showAlternatingRows + self.showFooter = showFooter + } + + public var body: some View { + if dataTable.isEmpty { + emptyView + } else { + tableContent + } + } + + // MARK: - Empty State + + @ViewBuilder + private var emptyView: some View { + VStack(spacing: 8) { + Image(systemName: "tablecells") + .font(.largeTitle) + .foregroundStyle(.secondary) + Text("No results") + .font(.headline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, minHeight: 100) + } + + // MARK: - Table Content + + @ViewBuilder + private var tableContent: some View { + VStack(alignment: .leading, spacing: 0) { + ScrollView([.horizontal, .vertical]) { + LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) { + Section { + ForEach(dataTable.rows) { row in + rowView(row) + } + } header: { + headerRow + } + } + } + + if showFooter { + footerView + } + } + } + + // MARK: - Header + + @ViewBuilder + private var headerRow: some View { + HStack(spacing: 0) { + ForEach(dataTable.columns) { column in + Text(column.name) + .font(.caption.weight(.semibold)) + .foregroundStyle(.primary) + .lineLimit(1) + .frame( + width: columnWidth(for: column), + alignment: alignment(for: column) + ) + .padding(.horizontal, 8) + .padding(.vertical, 6) + + if column.index < dataTable.columnCount - 1 { + Divider() + } + } + } + .background(.bar) + .overlay(alignment: .bottom) { + Divider() + } + } + + // MARK: - Row + + @ViewBuilder + private func rowView(_ row: DataTable.Row) -> some View { + HStack(spacing: 0) { + ForEach(dataTable.columns) { column in + cellView(value: row[column.index], column: column) + + if column.index < dataTable.columnCount - 1 { + Divider() + } + } + } + .background(rowBackground(for: row)) + .overlay(alignment: .bottom) { + Divider() + } + } + + // MARK: - Cell + + @ViewBuilder + private func cellView(value: QueryResult.Value, column: DataTable.Column) -> some View { + Group { + switch value { + case .null: + Text("NULL") + .foregroundStyle(.tertiary) + .italic() + case .blob(let data): + Text("<\(data.count) bytes>") + .foregroundStyle(.secondary) + default: + Text(value.stringValue) + .foregroundStyle(.primary) + } + } + .font(.caption) + .lineLimit(2) + .frame( + width: columnWidth(for: column), + alignment: alignment(for: column) + ) + .padding(.horizontal, 8) + .padding(.vertical, 4) + } + + // MARK: - Footer + + @ViewBuilder + private var footerView: some View { + HStack { + Text("\(dataTable.rowCount) row\(dataTable.rowCount == 1 ? "" : "s")") + .font(.caption2) + .foregroundStyle(.secondary) + Spacer() + if dataTable.executionTime > 0 { + Text(String(format: "%.1f ms", dataTable.executionTime * 1000)) + .font(.caption2) + .foregroundStyle(.secondary) + } + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.bar) + } + + // MARK: - Layout Helpers + + /// Determines column width based on the column name length and type. + private func columnWidth(for column: DataTable.Column) -> CGFloat { + // Estimate based on header text length + let headerWidth = CGFloat(column.name.count) * 8 + 16 + + // Sample some row values to estimate content width + let sampleRows = dataTable.rows.prefix(20) + let maxContentWidth = sampleRows.reduce(CGFloat(0)) { maxWidth, row in + let value = row[column.index] + let textLength = CGFloat(value.stringValue.count) * 7 + return max(maxWidth, textLength) + } + + let estimatedWidth = max(headerWidth, maxContentWidth) + 16 + return min(max(estimatedWidth, minimumColumnWidth), maximumColumnWidth) + } + + /// Returns the alignment for a column based on its inferred type. + private func alignment(for column: DataTable.Column) -> Alignment { + switch column.inferredType { + case .integer, .real: + return .trailing + default: + return .leading + } + } + + /// Returns the background color for alternating rows. + @ViewBuilder + private func rowBackground(for row: DataTable.Row) -> some View { + if showAlternatingRows && row.id.isMultiple(of: 2) { + Color.clear + } else if showAlternatingRows { + Color.primary.opacity(0.03) + } else { + Color.clear + } + } +} + +// MARK: - Preview Support + +#if DEBUG +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +#Preview("Data Table") { + let columns: [DataTable.Column] = [ + .init(name: "id", index: 0, inferredType: .integer), + .init(name: "name", index: 1, inferredType: .text), + .init(name: "score", index: 2, inferredType: .real), + ] + let rows: [DataTable.Row] = (0..<25).map { i in + DataTable.Row( + id: i, + values: [ + .integer(Int64(i + 1)), + .text("Item \(i + 1)"), + .real(Double.random(in: 1.0...100.0)), + ], + columnNames: ["id", "name", "score"] + ) + } + let table = DataTable(columns: columns, rows: rows, sql: "SELECT * FROM items", executionTime: 0.023) + + ScrollableDataTableView(dataTable: table) + .frame(height: 400) + .padding() +} + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +#Preview("Empty Table") { + let table = DataTable(columns: [], rows: [], sql: "", executionTime: 0) + ScrollableDataTableView(dataTable: table) + .frame(height: 200) + .padding() +} +#endif diff --git a/Sources/SwiftDBAI/Views/Styling/ChatViewConfiguration.swift b/Sources/SwiftDBAI/Views/Styling/ChatViewConfiguration.swift new file mode 100644 index 0000000..399fc9f --- /dev/null +++ b/Sources/SwiftDBAI/Views/Styling/ChatViewConfiguration.swift @@ -0,0 +1,200 @@ +// ChatViewConfiguration.swift +// SwiftDBAI +// +// A configuration struct that controls the visual appearance of ChatView +// and its child views. Propagated via SwiftUI environment. + +import SwiftUI + +/// Controls the visual appearance of the chat interface. +/// +/// Use the built-in presets (`.default`, `.compact`, `.dark`) or create +/// a custom configuration by mutating the default: +/// +/// ```swift +/// var config = ChatViewConfiguration.default +/// config.userBubbleColor = .purple +/// config.inputPlaceholder = "Ask about your recipes..." +/// +/// DataChatView(databasePath: path, model: myLLM) +/// .chatViewConfiguration(config) +/// ``` +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +public struct ChatViewConfiguration: Sendable { + + // MARK: - Colors + + /// Background color for user message bubbles. + public var userBubbleColor: Color + + /// Text color for user messages. + public var userTextColor: Color + + /// Background color for assistant message bubbles. + public var assistantBubbleColor: Color + + /// Text color for assistant messages. + public var assistantTextColor: Color + + /// Background color for the overall chat view. + public var backgroundColor: Color + + /// Background color for the input bar area. + public var inputBarBackgroundColor: Color + + /// Accent color used for interactive elements (send button, etc.). + public var accentColor: Color + + /// Color used for error-related UI elements. + public var errorColor: Color + + // MARK: - Typography + + /// Font for chat message text. + public var messageFont: Font + + /// Font for the natural language summary text. + public var summaryFont: Font + + /// Font for SQL query display. + public var sqlFont: Font + + /// Font for the text input field. + public var inputFont: Font + + // MARK: - Layout + + /// Padding inside message bubbles. + public var messagePadding: CGFloat + + /// Corner radius for message bubbles. + public var bubbleCornerRadius: CGFloat + + /// Whether to show timestamps on messages. + public var showTimestamps: Bool + + /// Whether to show the SQL disclosure group. + public var showSQLDisclosure: Bool + + /// Placeholder text in the input field. + public var inputPlaceholder: String + + /// Title text shown when the chat has no messages. + public var emptyStateTitle: String + + /// Subtitle text shown when the chat has no messages. + public var emptyStateSubtitle: String + + /// SF Symbol name for the empty state icon. + public var emptyStateIcon: String + + /// Optional color scheme override. When set, forces the chat view to use + /// this color scheme regardless of the system setting. + public var colorSchemeOverride: ColorScheme? + + // MARK: - Avatar + + /// SF Symbol name for the assistant avatar. When set, shows a circular + /// avatar next to assistant messages (e.g. "person.crop.circle.fill", + /// "brain.head.profile", "sparkles"). + public var assistantAvatarIcon: String? + + /// Background color for the assistant avatar circle. + public var assistantAvatarColor: Color + + // MARK: - Memberwise Initializer + + /// Creates a fully custom configuration. + public init( + userBubbleColor: Color = .accentColor, + userTextColor: Color = .white, + assistantBubbleColor: Color = defaultAssistantBackgroundColor, + assistantTextColor: Color = .primary, + backgroundColor: Color = .clear, + inputBarBackgroundColor: Color = .clear, + accentColor: Color = .accentColor, + errorColor: Color = .red, + messageFont: Font = .body, + summaryFont: Font = .body, + sqlFont: Font = .system(.caption, design: .monospaced), + inputFont: Font = .body, + messagePadding: CGFloat = 14, + bubbleCornerRadius: CGFloat = 16, + showTimestamps: Bool = false, + showSQLDisclosure: Bool = true, + inputPlaceholder: String = "Ask about your data\u{2026}", + emptyStateTitle: String = "Ask a question about your data", + emptyStateSubtitle: String = "Try something like \"How many records are in the database?\"", + emptyStateIcon: String = "bubble.left.and.text.bubble.right", + colorSchemeOverride: ColorScheme? = nil, + assistantAvatarIcon: String? = nil, + assistantAvatarColor: Color = .accentColor + ) { + self.userBubbleColor = userBubbleColor + self.userTextColor = userTextColor + self.assistantBubbleColor = assistantBubbleColor + self.assistantTextColor = assistantTextColor + self.backgroundColor = backgroundColor + self.inputBarBackgroundColor = inputBarBackgroundColor + self.accentColor = accentColor + self.errorColor = errorColor + self.messageFont = messageFont + self.summaryFont = summaryFont + self.sqlFont = sqlFont + self.inputFont = inputFont + self.messagePadding = messagePadding + self.bubbleCornerRadius = bubbleCornerRadius + self.showTimestamps = showTimestamps + self.showSQLDisclosure = showSQLDisclosure + self.inputPlaceholder = inputPlaceholder + self.emptyStateTitle = emptyStateTitle + self.emptyStateSubtitle = emptyStateSubtitle + self.emptyStateIcon = emptyStateIcon + self.colorSchemeOverride = colorSchemeOverride + self.assistantAvatarIcon = assistantAvatarIcon + self.assistantAvatarColor = assistantAvatarColor + } + + // MARK: - Platform-Adaptive Defaults + + /// The default assistant bubble background, matching the platform convention. + public static var defaultAssistantBackgroundColor: Color { + #if os(macOS) + Color(nsColor: .controlBackgroundColor) + #else + Color(uiColor: .secondarySystemGroupedBackground) + #endif + } + + // MARK: - Presets + + /// The default configuration, matching the original hardcoded ChatView styling. + public static let `default` = ChatViewConfiguration() + + /// A compact configuration with smaller fonts, tighter padding, and minimal chrome. + public static let compact = ChatViewConfiguration( + messageFont: .footnote, + summaryFont: .footnote, + sqlFont: .system(.caption2, design: .monospaced), + inputFont: .footnote, + messagePadding: 8, + bubbleCornerRadius: 10, + showTimestamps: false, + showSQLDisclosure: false, + emptyStateTitle: "Ask a question", + emptyStateSubtitle: "" + ) + + /// A dark-themed configuration with muted colors suitable for dark backgrounds. + public static let dark = ChatViewConfiguration( + userBubbleColor: Color(white: 0.25), + userTextColor: .white, + assistantBubbleColor: Color(white: 0.15), + assistantTextColor: Color(white: 0.9), + backgroundColor: .black, + inputBarBackgroundColor: Color(white: 0.1), + accentColor: .blue, + errorColor: Color(red: 1.0, green: 0.4, blue: 0.4), + colorSchemeOverride: .dark + ) +} diff --git a/Sources/SwiftDBAI/Views/Styling/ChatViewConfigurationKey.swift b/Sources/SwiftDBAI/Views/Styling/ChatViewConfigurationKey.swift new file mode 100644 index 0000000..880f294 --- /dev/null +++ b/Sources/SwiftDBAI/Views/Styling/ChatViewConfigurationKey.swift @@ -0,0 +1,35 @@ +// ChatViewConfigurationKey.swift +// SwiftDBAI +// +// SwiftUI environment key for propagating ChatViewConfiguration +// through the view hierarchy. + +import SwiftUI + +/// Environment key that stores the ``ChatViewConfiguration``. +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +struct ChatViewConfigurationKey: EnvironmentKey { + static let defaultValue = ChatViewConfiguration.default +} + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +extension EnvironmentValues { + /// The chat view configuration for the current environment. + var chatViewConfiguration: ChatViewConfiguration { + get { self[ChatViewConfigurationKey.self] } + set { self[ChatViewConfigurationKey.self] = newValue } + } +} + +@available(iOS 17.0, macOS 14.0, visionOS 1.0, *) +extension View { + /// Applies a ``ChatViewConfiguration`` to this view and its descendants. + /// + /// ```swift + /// DataChatView(databasePath: path, model: myLLM) + /// .chatViewConfiguration(.dark) + /// ``` + public func chatViewConfiguration(_ config: ChatViewConfiguration) -> some View { + environment(\.chatViewConfiguration, config) + } +} diff --git a/THEMING.md b/THEMING.md new file mode 100644 index 0000000..13c5aac --- /dev/null +++ b/THEMING.md @@ -0,0 +1,131 @@ +# Theming + +SwiftDBAI's chat interface is customizable through `ChatViewConfiguration`. Pass it via the `.chatViewConfiguration()` view modifier -- it propagates through the entire view hierarchy via SwiftUI environment. + +## Built-in Presets + +### Default + +The standard look. Blue user bubbles, system-colored assistant bubbles, standard fonts. + +```swift +DataChatView(databasePath: path, model: myLLM) +// .chatViewConfiguration(.default) is implicit +``` + +![Default](screenshots/results-chart.png) + +### Dark + +Muted colors for dark backgrounds. Dark gray bubbles, light text, black background. + +```swift +DataChatView(databasePath: path, model: myLLM) + .chatViewConfiguration(.dark) +``` + +![Dark](screenshots/dark-theme.png) + +### Compact + +Smaller fonts, tighter padding, no SQL disclosure. Good for embedded or secondary views. + +```swift +DataChatView(databasePath: path, model: myLLM) + .chatViewConfiguration(.compact) +``` + +![Compact](screenshots/compact-theme.png) + +## Custom Configuration + +Start from any preset and override what you need: + +```swift +var config = ChatViewConfiguration.default +config.userBubbleColor = .purple +config.userTextColor = .white +config.accentColor = .purple +config.inputPlaceholder = "Search GitHub repos..." +config.emptyStateTitle = "Explore GitHub Data" +config.emptyStateSubtitle = "Ask about stars, forks, languages, and trends" +config.emptyStateIcon = "star.circle" + +DataChatView(databasePath: path, model: myLLM) + .chatViewConfiguration(config) +``` + +| Custom empty state | Custom with results | +|---|---| +| ![Custom empty](screenshots/custom-theme.png) | ![Custom results](screenshots/custom-results.png) | + +## Available Properties + +### Colors + +| Property | Default | Description | +|---|---|---| +| `userBubbleColor` | `.accentColor` | Background of user message bubbles | +| `userTextColor` | `.white` | Text color in user bubbles | +| `assistantBubbleColor` | System secondary | Background of assistant bubbles | +| `assistantTextColor` | `.primary` | Text color in assistant bubbles | +| `backgroundColor` | `.clear` | Overall chat view background | +| `inputBarBackgroundColor` | `.clear` | Input bar area background | +| `accentColor` | `.accentColor` | Send button and interactive elements | +| `errorColor` | `.red` | Error message icon and border | + +### Typography + +| Property | Default | Description | +|---|---|---| +| `messageFont` | `.body` | Chat message text | +| `summaryFont` | `.body` | Natural language summary | +| `sqlFont` | `.caption monospaced` | SQL query display | +| `inputFont` | `.body` | Text input field | + +### Layout & Content + +| Property | Default | Description | +|---|---|---| +| `messagePadding` | `14` | Padding inside message bubbles | +| `bubbleCornerRadius` | `16` | Corner radius of bubbles | +| `showTimestamps` | `false` | Show timestamps on messages | +| `showSQLDisclosure` | `true` | Show the " SQL Query" expandable section | +| `inputPlaceholder` | `"Ask about your data..."` | Placeholder text in input field | +| `emptyStateTitle` | `"Ask a question about your data"` | Title when no messages | +| `emptyStateSubtitle` | `"Try something like..."` | Subtitle when no messages | +| `emptyStateIcon` | `"bubble.left.and.text.bubble.right"` | SF Symbol for empty state | + +### Avatar + +| Property | Default | Description | +|---|---|---| +| `assistantAvatarIcon` | `nil` | SF Symbol for assistant avatar (e.g. `"sparkles"`, `"person.crop.circle.fill"`) | +| `assistantAvatarColor` | `.accentColor` | Background color of the avatar circle | + +When set, a circular avatar appears next to every assistant message: + +```swift +config.assistantAvatarIcon = "sparkles" +config.assistantAvatarColor = .purple +``` + +![Custom with avatar](screenshots/custom-results.png) + +## Works with All Presentation Modes + +The configuration propagates through sheets, navigation, and UIKit bridges: + +```swift +// Sheet +.sheet(isPresented: $show) { + DataChatSheet(databasePath: path, model: myLLM) + .chatViewConfiguration(.dark) +} + +// UIKit +let vc = DataChatViewController(databasePath: path, model: myLLM) +// Configuration can be set on the rootView before presenting +``` + +![Sheet presentation](screenshots/sheet-presentation.png) diff --git a/Tests/SwiftDBAITests/BinarySizeTests.swift b/Tests/SwiftDBAITests/BinarySizeTests.swift new file mode 100644 index 0000000..548bc93 --- /dev/null +++ b/Tests/SwiftDBAITests/BinarySizeTests.swift @@ -0,0 +1,254 @@ +// BinarySizeTests.swift +// SwiftDBAI +// +// Validates that the SwiftDBAI package stays within its 2 MB binary size budget. +// This test suite uses source-level heuristics since we can't measure the actual +// compiled binary size in a unit test. The constraints ensure the package remains +// lightweight by checking: +// 1. Total source code size (proxy for compiled size) +// 2. No embedded binary assets or large resources +// 3. No unnecessary heavy dependencies +// 4. File count stays reasonable (no code bloat) + +import Foundation +import Testing + +@Suite("Binary Size Budget") +struct BinarySizeTests { + + /// The maximum allowed total source code size in bytes. + /// At typical Swift optimized compilation ratios (2-4x), 500 KB of source + /// compiles to roughly 1-2 MB of binary. We set the source budget at 500 KB + /// to keep the compiled output well under 2 MB. + private static let maxSourceSizeBytes: Int = 500_000 // 500 KB + + /// Maximum number of Swift source files allowed. + /// More files generally means more code and larger binaries. + private static let maxSourceFileCount: Int = 60 + + /// Maximum size for any single source file in bytes. + /// Large individual files often indicate code that should be split or + /// contains embedded data that bloats the binary. + private static let maxSingleFileSizeBytes: Int = 50_000 // 50 KB + + /// Disallowed file extensions in the Sources directory that would bloat the binary. + private static let disallowedExtensions: Set = [ + "png", "jpg", "jpeg", "gif", "bmp", "tiff", + "mp3", "mp4", "wav", "mov", + "mlmodel", "mlmodelc", "mlpackage", + "sqlite", "db", + "zip", "tar", "gz", + "bin", "dat", + "framework", "dylib", "a" + ] + + // MARK: - Helper + + /// Recursively finds all files in the Sources/SwiftDBAI directory. + private func findSourceFiles() throws -> [URL] { + let sourcesDir = findSourcesDirectory() + guard let sourcesDir else { + Issue.record("Could not locate Sources/SwiftDBAI directory") + return [] + } + + let fileManager = FileManager.default + guard let enumerator = fileManager.enumerator( + at: sourcesDir, + includingPropertiesForKeys: [.fileSizeKey, .isRegularFileKey], + options: [.skipsHiddenFiles] + ) else { + Issue.record("Could not enumerate Sources/SwiftDBAI directory") + return [] + } + + var files: [URL] = [] + for case let fileURL as URL in enumerator { + let resourceValues = try fileURL.resourceValues(forKeys: [.isRegularFileKey]) + if resourceValues.isRegularFile == true { + files.append(fileURL) + } + } + return files + } + + /// Locates the Sources/SwiftDBAI directory by walking up from the test bundle. + private func findSourcesDirectory() -> URL? { + // Try common locations relative to the build directory + let fileManager = FileManager.default + + // In SPM test runs, we can find the package root by checking known paths + var candidateURL = URL(fileURLWithPath: #filePath) + // Walk up from Tests/SwiftDBAITests/BinarySizeTests.swift to package root + for _ in 0..<3 { + candidateURL = candidateURL.deletingLastPathComponent() + } + let sourcesDir = candidateURL.appendingPathComponent("Sources/SwiftDBAI") + if fileManager.fileExists(atPath: sourcesDir.path) { + return sourcesDir + } + + // Fallback: check current working directory + let cwdSources = URL(fileURLWithPath: fileManager.currentDirectoryPath) + .appendingPathComponent("Sources/SwiftDBAI") + if fileManager.fileExists(atPath: cwdSources.path) { + return cwdSources + } + + return nil + } + + // MARK: - Tests + + @Test("Total source code size stays under 500 KB budget") + func totalSourceCodeSizeUnderBudget() throws { + let files = try findSourceFiles() + let swiftFiles = files.filter { $0.pathExtension == "swift" } + + var totalSize: Int = 0 + for file in swiftFiles { + let attributes = try FileManager.default.attributesOfItem(atPath: file.path) + let fileSize = attributes[.size] as? Int ?? 0 + totalSize += fileSize + } + + #expect(totalSize < Self.maxSourceSizeBytes, + """ + Total Swift source size (\(totalSize) bytes) exceeds \(Self.maxSourceSizeBytes) byte budget. + At typical 2-4x compilation ratio, this would produce a binary larger than 2 MB. + Consider removing unused code or splitting into optional sub-targets. + """) + + // Log the actual size for visibility + let sizeKB = Double(totalSize) / 1024.0 + let budgetKB = Double(Self.maxSourceSizeBytes) / 1024.0 + print("📦 SwiftDBAI source size: \(String(format: "%.1f", sizeKB)) KB / \(String(format: "%.0f", budgetKB)) KB budget (\(String(format: "%.0f", (sizeKB / budgetKB) * 100))% used)") + } + + @Test("Source file count stays reasonable") + func sourceFileCountUnderLimit() throws { + let files = try findSourceFiles() + let swiftFiles = files.filter { $0.pathExtension == "swift" } + + #expect(swiftFiles.count <= Self.maxSourceFileCount, + """ + Swift source file count (\(swiftFiles.count)) exceeds limit of \(Self.maxSourceFileCount). + More files generally means more code and larger binaries. + """) + + print("📦 SwiftDBAI file count: \(swiftFiles.count) / \(Self.maxSourceFileCount) max") + } + + @Test("No individual source file exceeds 50 KB") + func noOversizedSourceFiles() throws { + let files = try findSourceFiles() + let swiftFiles = files.filter { $0.pathExtension == "swift" } + + for file in swiftFiles { + let attributes = try FileManager.default.attributesOfItem(atPath: file.path) + let fileSize = attributes[.size] as? Int ?? 0 + + #expect(fileSize < Self.maxSingleFileSizeBytes, + """ + File \(file.lastPathComponent) is \(fileSize) bytes, exceeding the \(Self.maxSingleFileSizeBytes) byte limit. + Large files may contain embedded data or code that should be split. + """) + } + } + + @Test("No binary assets or heavy resources in Sources directory") + func noBinaryAssetsInSources() throws { + let files = try findSourceFiles() + + let disallowedFiles = files.filter { file in + Self.disallowedExtensions.contains(file.pathExtension.lowercased()) + } + + #expect(disallowedFiles.isEmpty, + """ + Found \(disallowedFiles.count) disallowed file(s) in Sources directory: + \(disallowedFiles.map(\.lastPathComponent).joined(separator: "\n")) + These file types bloat the binary. Remove them or move to a separate resource bundle. + """) + } + + @Test("Package has no resource bundles that could bloat binary") + func noResourceBundles() throws { + let files = try findSourceFiles() + + let resourceFiles = files.filter { file in + let ext = file.pathExtension.lowercased() + return ["xcassets", "storyboard", "xib", "nib", "xcdatamodeld"].contains(ext) + } + + #expect(resourceFiles.isEmpty, + """ + Found resource bundle files that could bloat the binary: + \(resourceFiles.map(\.lastPathComponent).joined(separator: "\n")) + SwiftDBAI should be pure code — no bundled resources. + """) + } + + @Test("Only expected dependencies declared (GRDB + AnyLanguageModel)") + func minimalDependencies() throws { + // Read Package.swift to verify we only have the expected dependencies + var packageURL = URL(fileURLWithPath: #filePath) + for _ in 0..<3 { + packageURL = packageURL.deletingLastPathComponent() + } + let packageSwiftURL = packageURL.appendingPathComponent("Package.swift") + + guard FileManager.default.fileExists(atPath: packageSwiftURL.path) else { + // Skip if we can't find Package.swift (CI environments etc.) + return + } + + let packageContents = try String(contentsOf: packageSwiftURL, encoding: .utf8) + + // Count .package() declarations (dependencies) + let packageDeclarations = packageContents.components(separatedBy: ".package(") + .count - 1 // subtract 1 because the first segment is before any .package( + + #expect(packageDeclarations <= 3, + """ + Found \(packageDeclarations) package dependencies, expected at most 4 (GRDB + AnyLanguageModel + ViewInspector for tests). + Additional dependencies increase binary size. Evaluate if they're truly needed. + """) + + // Verify the expected dependencies are present + #expect(packageContents.contains("GRDB"), "Expected GRDB dependency") + #expect(packageContents.contains("AnyLanguageModel"), "Expected AnyLanguageModel dependency") + + print("📦 SwiftDBAI dependencies: \(packageDeclarations) (GRDB + AnyLanguageModel)") + } + + @Test("Estimated binary size under 2 MB") + func estimatedBinarySizeUnderLimit() throws { + let files = try findSourceFiles() + let swiftFiles = files.filter { $0.pathExtension == "swift" } + + var totalSize: Int = 0 + for file in swiftFiles { + let attributes = try FileManager.default.attributesOfItem(atPath: file.path) + let fileSize = attributes[.size] as? Int ?? 0 + totalSize += fileSize + } + + // Conservative estimate: optimized Swift binary is typically 2-4x source size. + // Use 4x as worst case multiplier for safety margin. + let worstCaseMultiplier = 4.0 + let estimatedBinarySize = Double(totalSize) * worstCaseMultiplier + let maxBinarySize: Double = 2.0 * 1024.0 * 1024.0 // 2 MB + + #expect(estimatedBinarySize < maxBinarySize, + """ + Estimated binary size (\(String(format: "%.1f", estimatedBinarySize / 1024.0)) KB) exceeds 2 MB limit. + Source: \(totalSize) bytes × \(worstCaseMultiplier)x multiplier = \(String(format: "%.1f", estimatedBinarySize / 1024.0)) KB + Note: This is the SwiftDBAI module only — excludes GRDB and AnyLanguageModel + which are existing dependencies the developer already includes. + """) + + let estimatedMB = estimatedBinarySize / (1024.0 * 1024.0) + print("📦 Estimated SwiftDBAI binary size: \(String(format: "%.2f", estimatedMB)) MB / 2.00 MB limit (worst case \(worstCaseMultiplier)x)") + } +} diff --git a/Tests/SwiftDBAITests/ChartDataDetectorTests.swift b/Tests/SwiftDBAITests/ChartDataDetectorTests.swift new file mode 100644 index 0000000..4ed63e1 --- /dev/null +++ b/Tests/SwiftDBAITests/ChartDataDetectorTests.swift @@ -0,0 +1,293 @@ +// ChartDataDetectorTests.swift +// SwiftDBAITests + +import Testing +@testable import SwiftDBAI + +@Suite("ChartDataDetector") +struct ChartDataDetectorTests { + + let detector = ChartDataDetector() + + // MARK: - Helpers + + private func makeQueryResult( + columns: [String], + rows: [[QueryResult.Value]], + sql: String = "SELECT *" + ) -> QueryResult { + let rowDicts = rows.map { values in + Dictionary(uniqueKeysWithValues: zip(columns, values)) + } + return QueryResult( + columns: columns, + rows: rowDicts, + sql: sql, + executionTime: 0.01 + ) + } + + private func makeTable( + columns: [String], + rows: [[QueryResult.Value]], + sql: String = "SELECT *" + ) -> DataTable { + DataTable(makeQueryResult(columns: columns, rows: rows, sql: sql)) + } + + // MARK: - Basic Eligibility + + @Test("Returns nil for single-column results") + func singleColumn() { + let table = makeTable( + columns: ["count"], + rows: [[.integer(42)]] + ) + #expect(detector.detect(table) == nil) + } + + @Test("Returns nil for empty results") + func emptyResults() { + let table = makeTable(columns: ["name", "value"], rows: []) + #expect(detector.detect(table) == nil) + } + + @Test("Returns nil for single row") + func singleRow() { + let table = makeTable( + columns: ["name", "count"], + rows: [[.text("A"), .integer(10)]] + ) + #expect(detector.detect(table) == nil) + } + + @Test("Returns nil for too many rows") + func tooManyRows() { + let rows = (0..<101).map { i in + [QueryResult.Value.text("cat\(i)"), .integer(Int64(i))] + } + let table = makeTable(columns: ["name", "count"], rows: rows) + #expect(detector.detect(table) == nil) + } + + // MARK: - Bar Chart Detection + + @Test("Recommends bar chart for categorical text + numeric") + func barChartCategorical() { + let table = makeTable( + columns: ["department", "headcount"], + rows: [ + [.text("Engineering"), .integer(45)], + [.text("Marketing"), .integer(20)], + [.text("Sales"), .integer(30)], + [.text("HR"), .integer(10)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType == .bar) + #expect(rec?.categoryColumn == "department") + #expect(rec?.valueColumn == "headcount") + #expect(rec?.confidence ?? 0 > 0.5) + } + + // MARK: - Pie Chart Detection + + @Test("Recommends pie chart for small positive proportions") + func pieChartSmallCategories() { + let table = makeTable( + columns: ["status", "count"], + rows: [ + [.text("Active"), .integer(50)], + [.text("Inactive"), .integer(30)], + [.text("Pending"), .integer(20)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType == .pie) + #expect(rec?.categoryColumn == "status") + #expect(rec?.valueColumn == "count") + } + + @Test("Does not recommend pie with negative values") + func pieRejectsNegative() { + let table = makeTable( + columns: ["category", "change"], + rows: [ + [.text("A"), .integer(50)], + [.text("B"), .integer(-10)], + [.text("C"), .integer(20)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + // Should NOT be pie since there's a negative value + #expect(rec?.chartType != .pie) + } + + @Test("Does not recommend pie with too many slices") + func pieRejectsTooManySlices() { + let rows = (0..<10).map { i in + [QueryResult.Value.text("cat\(i)"), .integer(Int64(i + 1))] + } + let table = makeTable(columns: ["category", "value"], rows: rows) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType != .pie) + } + + // MARK: - Line Chart Detection + + @Test("Recommends line chart for time-series column names") + func lineChartTimeSeries() { + let table = makeTable( + columns: ["year", "revenue"], + rows: [ + [.text("2020"), .real(1_000_000)], + [.text("2021"), .real(1_200_000)], + [.text("2022"), .real(1_500_000)], + [.text("2023"), .real(1_800_000)], + [.text("2024"), .real(2_100_000)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType == .line) + #expect(rec?.categoryColumn == "year") + #expect(rec?.valueColumn == "revenue") + } + + @Test("Recommends line chart for date-formatted text values") + func lineChartDateValues() { + let table = makeTable( + columns: ["period", "sales"], + rows: [ + [.text("2024-01"), .integer(100)], + [.text("2024-02"), .integer(120)], + [.text("2024-03"), .integer(90)], + [.text("2024-04"), .integer(150)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType == .line) + } + + @Test("Recommends line chart for sequential numeric x-axis") + func lineChartSequential() { + let table = makeTable( + columns: ["step", "value"], + rows: [ + [.integer(1), .real(2.5)], + [.integer(2), .real(3.1)], + [.integer(3), .real(4.0)], + [.integer(4), .real(3.8)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.chartType == .line) + } + + // MARK: - All Recommendations + + @Test("Returns multiple recommendations sorted by confidence") + func allRecommendations() { + let table = makeTable( + columns: ["category", "amount"], + rows: [ + [.text("A"), .integer(30)], + [.text("B"), .integer(50)], + [.text("C"), .integer(20)], + ] + ) + let recs = detector.allRecommendations(for: table) + #expect(!recs.isEmpty) + // Should be sorted by confidence descending + for i in 1..= recs[i].confidence) + } + } + + // MARK: - Two Numeric Columns Fallback + + @Test("Uses first numeric as category when no text column exists") + func numericOnlyColumns() { + let table = makeTable( + columns: ["x", "y"], + rows: [ + [.integer(1), .integer(10)], + [.integer(2), .integer(20)], + [.integer(3), .integer(30)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec?.categoryColumn == "x") + #expect(rec?.valueColumn == "y") + } + + // MARK: - Confidence & Reason + + @Test("Confidence is between 0 and 1") + func confidenceBounds() { + let table = makeTable( + columns: ["name", "score"], + rows: [ + [.text("A"), .integer(10)], + [.text("B"), .integer(20)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(rec!.confidence >= 0.0) + #expect(rec!.confidence <= 1.0) + } + + @Test("Reason is non-empty") + func reasonPresent() { + let table = makeTable( + columns: ["name", "score"], + rows: [ + [.text("A"), .integer(10)], + [.text("B"), .integer(20)], + ] + ) + let rec = detector.detect(table) + #expect(rec != nil) + #expect(!rec!.reason.isEmpty) + } + + // MARK: - Custom Configuration + + @Test("Respects custom minimumRows") + func customMinRows() { + let strict = ChartDataDetector(minimumRows: 5) + let table = makeTable( + columns: ["name", "value"], + rows: [ + [.text("A"), .integer(1)], + [.text("B"), .integer(2)], + [.text("C"), .integer(3)], + ] + ) + #expect(strict.detect(table) == nil) + } + + @Test("Respects custom maxPieSlices") + func customMaxPieSlices() { + let narrow = ChartDataDetector(maxPieSlices: 2) + let table = makeTable( + columns: ["status", "count"], + rows: [ + [.text("A"), .integer(50)], + [.text("B"), .integer(30)], + [.text("C"), .integer(20)], + ] + ) + let rec = narrow.detect(table) + // With maxPieSlices=2, 3 rows should not get pie + #expect(rec?.chartType != .pie) + } +} diff --git a/Tests/SwiftDBAITests/ChatEngineTests.swift b/Tests/SwiftDBAITests/ChatEngineTests.swift new file mode 100644 index 0000000..72f0e7f --- /dev/null +++ b/Tests/SwiftDBAITests/ChatEngineTests.swift @@ -0,0 +1,1091 @@ +// ChatEngineTests.swift +// SwiftDBAI Tests +// +// Tests for ChatEngine with TextSummaryRenderer integration. + +import AnyLanguageModel +import Foundation +import GRDB +import Testing + +@testable import SwiftDBAI + +@Suite("ChatEngine Tests") +struct ChatEngineTests { + + /// Creates an in-memory database with test data. + private func makeTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(path: ":memory:") + try db.write { db in + try db.execute(sql: """ + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL, + created_at TEXT NOT NULL + ) + """) + try db.execute(sql: """ + INSERT INTO users (name, email, created_at) VALUES + ('Alice', 'alice@example.com', '2024-01-01'), + ('Bob', 'bob@example.com', '2024-01-15'), + ('Charlie', 'charlie@example.com', '2024-02-01') + """) + try db.execute(sql: """ + CREATE TABLE orders ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + amount REAL NOT NULL, + status TEXT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + """) + try db.execute(sql: """ + INSERT INTO orders (user_id, amount, status) VALUES + (1, 99.99, 'completed'), + (1, 49.50, 'pending'), + (2, 150.00, 'completed') + """) + } + return db + } + + @Test("ChatEngine summarizes SELECT results via TextSummaryRenderer") + func selectResultSummarized() async throws { + let db = try makeTestDatabase() + + // The mock model returns SQL for the first call, then a summary for the second + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "There are 3 users in the database." + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + let response = try await engine.send("How many users are there?") + + // The summary should come from TextSummaryRenderer. + // For a single aggregate (COUNT), TextSummaryRenderer returns a direct answer + // without calling the LLM again, so the summary is template-based. + #expect(response.summary == "The result is 3.") + #expect(response.sql == "SELECT COUNT(*) FROM users") + #expect(response.queryResult != nil) + #expect(response.queryResult?.rowCount == 1) + } + + @Test("ChatEngine summarizes empty results correctly") + func emptyResultSummarized() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT * FROM users WHERE name = 'Nobody'", + "No results found." + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + let response = try await engine.send("Find a user named Nobody") + + #expect(response.summary == "No results found for your query.") + #expect(response.queryResult?.rows.isEmpty == true) + } + + @Test("ChatEngine summarizes multi-row results via LLM") + func multiRowResultSummarized() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT name, email FROM users", + "Found 3 users: Alice, Bob, and Charlie." + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + let response = try await engine.send("List all users") + + // Multi-row results go through the LLM summarization path + #expect(response.summary == "Found 3 users: Alice, Bob, and Charlie.") + #expect(response.queryResult?.rowCount == 3) + } + + @Test("ChatEngine rejects disallowed operations via SQLQueryParser") + func rejectsDisallowedOperations() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "DELETE FROM users WHERE id = 1" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .readOnly + ) + + // DELETE is not in the readOnly allowlist, so SQLQueryParser rejects it + // ChatEngine now maps this to SwiftDBAIError.operationNotAllowed + await #expect(throws: SwiftDBAIError.self) { + try await engine.send("Delete user 1") + } + } + + @Test("ChatEngine requires confirmation for DELETE even when allowed") + func requiresDeleteConfirmation() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "DELETE FROM users WHERE id = 3", + "Deleted 1 row." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted + ) + + // DELETE requires confirmation even when allowlisted + // ChatEngine now surfaces SwiftDBAIError.confirmationRequired + do { + _ = try await engine.send("Delete user 3") + Issue.record("Expected confirmationRequired error") + } catch let error as SwiftDBAIError { + if case .confirmationRequired(let sql, let operation) = error { + #expect(sql.uppercased().contains("DELETE")) + #expect(operation == "delete") + + // Now confirm and execute + let response = try await engine.sendConfirmed("Delete user 3", confirmedSQL: sql) + #expect(response.summary == "Successfully deleted 1 row.") + } else { + Issue.record("Expected confirmationRequired, got: \(error)") + } + } + } + + @Test("ChatEngine allows mutations when allowlisted") + func allowsMutationsWhenAllowlisted() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "INSERT INTO users (name, email, created_at) VALUES ('Dave', 'dave@example.com', '2024-03-01')", + "Inserted 1 row." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard + ) + + let response = try await engine.send("Add a user named Dave") + + #expect(response.summary == "Successfully inserted 1 row.") + } + + @Test("ChatEngine rejects dangerous operations via SQLQueryParser") + func rejectsDangerousOperations() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "DROP TABLE users" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted + ) + + // DROP is always rejected by SQLQueryParser regardless of allowlist + // ChatEngine now maps this to SwiftDBAIError.dangerousOperationBlocked + await #expect(throws: SwiftDBAIError.self) { + try await engine.send("Drop the users table") + } + } + + @Test("ChatEngine executes UPDATE and returns affected row count") + func updateMutationReturnsAffectedCount() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "UPDATE users SET name = 'Alice Updated' WHERE id = 1", + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard + ) + + let response = try await engine.send("Rename user 1 to Alice Updated") + + #expect(response.summary == "Successfully updated 1 row.") + #expect(response.sql?.uppercased().contains("UPDATE") == true) + #expect(response.queryResult?.rowsAffected == 1) + } + + @Test("ChatEngine UPDATE affecting multiple rows returns correct count") + func updateMultipleRowsReturnsCorrectCount() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "UPDATE orders SET status = 'archived' WHERE status = 'completed'", + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard + ) + + let response = try await engine.send("Archive all completed orders") + + // There are 2 completed orders in the test data + #expect(response.summary == "Successfully updated 2 rows.") + #expect(response.queryResult?.rowsAffected == 2) + } + + @Test("ChatEngine rejects INSERT on readOnly allowlist with clear error") + func rejectsInsertOnReadOnly() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "INSERT INTO users (name, email, created_at) VALUES ('Eve', 'eve@example.com', '2024-03-15')" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .readOnly + ) + + do { + _ = try await engine.send("Add a user named Eve") + Issue.record("Expected operationNotAllowed error for disallowed INSERT") + } catch let error as SwiftDBAIError { + if case .operationNotAllowed(let operation) = error { + #expect(operation == "insert") + } else { + Issue.record("Expected operationNotAllowed, got: \(error)") + } + } + } + + @Test("ChatEngine rejects UPDATE on readOnly allowlist with clear error") + func rejectsUpdateOnReadOnly() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "UPDATE users SET name = 'Eve' WHERE id = 1" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .readOnly + ) + + do { + _ = try await engine.send("Rename user 1 to Eve") + Issue.record("Expected operationNotAllowed error for disallowed UPDATE") + } catch let error as SwiftDBAIError { + if case .operationNotAllowed(let operation) = error { + #expect(operation == "update") + } else { + Issue.record("Expected operationNotAllowed, got: \(error)") + } + } + } + + @Test("ChatEngine with MutationPolicy rejects mutations on restricted tables") + func mutationPolicyRejectsRestrictedTables() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "INSERT INTO users (name, email, created_at) VALUES ('Eve', 'eve@example.com', '2024-03-15')" + ]) + + // Only allow mutations on the "orders" table + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders"] + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy + ) + + do { + _ = try await engine.send("Add a user named Eve") + Issue.record("Expected tableNotAllowedForMutation error for restricted table") + } catch let error as SwiftDBAIError { + if case .tableNotAllowedForMutation(let tableName, let operation) = error { + #expect(tableName == "users") + #expect(operation == "insert") + } else { + Issue.record("Expected tableNotAllowedForMutation, got: \(error)") + } + } + } + + @Test("ChatEngine with MutationPolicy allows mutations on permitted tables") + func mutationPolicyAllowsPermittedTables() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "INSERT INTO orders (user_id, amount, status) VALUES (1, 75.00, 'pending')", + ]) + + // Only allow mutations on the "orders" table + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders"] + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy + ) + + let response = try await engine.send("Add a new order for user 1") + + #expect(response.summary == "Successfully inserted 1 row.") + #expect(response.queryResult?.rowsAffected == 1) + } + + @Test("ChatEngine INSERT affecting zero rows returns correct message") + func insertZeroRowsMessage() async throws { + let db = try makeTestDatabase() + + // INSERT OR IGNORE with a conflicting primary key won't insert + let model = SequentialMockModel(responses: [ + "INSERT OR IGNORE INTO users (id, name, email, created_at) VALUES (1, 'Alice', 'alice@example.com', '2024-01-01')", + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard + ) + + let response = try await engine.send("Add user Alice if not exists") + + // With OR IGNORE, the duplicate is silently skipped → 0 rows affected + #expect(response.summary == "Successfully inserted 0 rows.") + #expect(response.queryResult?.rowsAffected == 0) + } + + @Test("ChatEngine error descriptions are human-readable") + func errorDescriptionsAreReadable() { + // SwiftDBAIError — the unified error type surfaced by ChatEngine + let opError = SwiftDBAIError.operationNotAllowed(operation: "delete") + #expect(opError.errorDescription?.contains("DELETE") == true) + #expect(opError.errorDescription?.contains("not allowed") == true) + + let confirmError = SwiftDBAIError.confirmationRequired( + sql: "DELETE FROM users WHERE id = 1", + operation: "delete" + ) + #expect(confirmError.errorDescription?.contains("confirmation") == true) + #expect(confirmError.errorDescription?.contains("DELETE") == true) + + let timeoutError = SwiftDBAIError.queryTimedOut(seconds: 30) + #expect(timeoutError.errorDescription?.contains("timed out") == true) + + let dbError = SwiftDBAIError.databaseError(reason: "disk full") + #expect(dbError.errorDescription?.contains("disk full") == true) + + let llmError = SwiftDBAIError.llmFailure(reason: "rate limited") + #expect(llmError.errorDescription?.contains("rate limited") == true) + + let schemaError = SwiftDBAIError.schemaIntrospectionFailed(reason: "permission denied") + #expect(schemaError.errorDescription?.contains("permission denied") == true) + + let noSQLError = SwiftDBAIError.noSQLGenerated + #expect(noSQLError.errorDescription?.contains("rephrase") == true) + + let dangerousError = SwiftDBAIError.dangerousOperationBlocked(keyword: "DROP") + #expect(dangerousError.errorDescription?.contains("DROP") == true) + + let emptyError = SwiftDBAIError.emptySchema + #expect(emptyError.errorDescription?.contains("no tables") == true) + + let tableError = SwiftDBAIError.tableNotAllowedForMutation(tableName: "users", operation: "insert") + #expect(tableError.errorDescription?.contains("users") == true) + #expect(tableError.errorDescription?.contains("INSERT") == true) + + let multiError = SwiftDBAIError.multipleStatementsNotSupported + #expect(multiError.errorDescription?.contains("single") == true) + } + + @Test("SwiftDBAIError classification properties") + func errorClassificationProperties() { + // Safety errors + #expect(SwiftDBAIError.operationNotAllowed(operation: "delete").isSafetyError) + #expect(SwiftDBAIError.dangerousOperationBlocked(keyword: "DROP").isSafetyError) + #expect(SwiftDBAIError.confirmationRequired(sql: "", operation: "delete").isSafetyError) + #expect(!SwiftDBAIError.llmFailure(reason: "timeout").isSafetyError) + + // Recoverable errors + #expect(SwiftDBAIError.noSQLGenerated.isRecoverable) + #expect(SwiftDBAIError.tableNotFound(tableName: "x").isRecoverable) + #expect(!SwiftDBAIError.databaseError(reason: "disk full").isRecoverable) + + // User action required + #expect(SwiftDBAIError.confirmationRequired(sql: "", operation: "delete").requiresUserAction) + #expect(!SwiftDBAIError.llmFailure(reason: "error").requiresUserAction) + } + + @Test("SQLParsingError converts to SwiftDBAIError correctly") + func sqlParsingErrorConversion() { + let noSQL = SQLParsingError.noSQLFound.toSwiftDBAIError() + #expect(noSQL == .noSQLGenerated) + + let noSQLWithResponse = SQLParsingError.noSQLFound.toSwiftDBAIError(rawResponse: "I can't do that") + if case .llmResponseUnparseable(let response) = noSQLWithResponse { + #expect(response == "I can't do that") + } else { + Issue.record("Expected llmResponseUnparseable") + } + + let opNotAllowed = SQLParsingError.operationNotAllowed(.delete).toSwiftDBAIError() + #expect(opNotAllowed == .operationNotAllowed(operation: "delete")) + + let dangerous = SQLParsingError.dangerousOperation("DROP").toSwiftDBAIError() + #expect(dangerous == .dangerousOperationBlocked(keyword: "DROP")) + + let multi = SQLParsingError.multipleStatements.toSwiftDBAIError() + #expect(multi == .multipleStatementsNotSupported) + + let tableNotAllowed = SQLParsingError.tableNotAllowed(table: "users", operation: .insert).toSwiftDBAIError() + #expect(tableNotAllowed == .tableNotAllowedForMutation(tableName: "users", operation: "insert")) + } + + @Test("ChatEngineError legacy type still has correct descriptions") + func legacyChatEngineErrorDescriptions() { + let sqlError = ChatEngineError.sqlParsingFailed(.operationNotAllowed(.delete)) + #expect(sqlError.errorDescription?.contains("DELETE") == true) + + let timeoutError = ChatEngineError.queryTimedOut(seconds: 30) + #expect(timeoutError.errorDescription?.contains("timed out") == true) + + let validationError = ChatEngineError.validationFailed("too many rows") + #expect(validationError.errorDescription?.contains("too many rows") == true) + } + + @Test("ChatEngine maintains conversation history") + func maintainsHistory() async throws { + let db = try makeTestDatabase() + + // Both queries produce aggregates, so TextSummaryRenderer won't call + // the LLM for summarization — only SQL generation consumes responses. + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM orders", + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + _ = try await engine.send("How many users?") + _ = try await engine.send("How many orders?") + + let messages = engine.messages + #expect(messages.count == 4) // 2 user + 2 assistant + #expect(messages[0].role == .user) + #expect(messages[1].role == .assistant) + #expect(messages[2].role == .user) + #expect(messages[3].role == .assistant) + } + + @Test("ChatEngine parses SQL from markdown code fences via SQLQueryParser") + func parsesCodeFences() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "```sql\nSELECT COUNT(*) FROM users\n```", + "There are 3 users." + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + let response = try await engine.send("Count users") + + #expect(response.sql == "SELECT COUNT(*) FROM users") + #expect(response.queryResult?.rowCount == 1) + } + + @Test("ChatEngine parses SQL from labeled LLM responses") + func parsesLabeledSQL() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SQL: SELECT COUNT(*) FROM users", + "There are 3 users." + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + let response = try await engine.send("Count users") + + #expect(response.sql == "SELECT COUNT(*) FROM users") + #expect(response.queryResult?.rowCount == 1) + } + + @Test("ChatEngine prepareSchema eagerly introspects and caches schema") + func prepareSchemaEagerly() async throws { + let db = try makeTestDatabase() + let model = MockLanguageModel() + + let engine = ChatEngine(database: db, model: model) + + // Before prepare, no cached schema + #expect(engine.tableCount == nil) + #expect(engine.cachedSchema == nil) + + // Prepare eagerly + let schema = try await engine.prepareSchema() + + // Schema is now cached + #expect(schema.tableNames.count == 2) + #expect(schema.tableNames.contains("users")) + #expect(schema.tableNames.contains("orders")) + #expect(engine.tableCount == 2) + #expect(engine.cachedSchema != nil) + } + + @Test("ChatEngine prepareSchema is idempotent") + func prepareSchemaIdempotent() async throws { + let db = try makeTestDatabase() + let model = MockLanguageModel() + + let engine = ChatEngine(database: db, model: model) + + let schema1 = try await engine.prepareSchema() + let schema2 = try await engine.prepareSchema() + + #expect(schema1 == schema2) + #expect(engine.tableCount == 2) + } + + @Test("ChatEngine injects conversation history into follow-up prompts") + func injectsConversationHistory() async throws { + let db = try makeTestDatabase() + + // Use a prompt-capturing mock so we can verify what the LLM receives. + // First call: SQL gen for "How many users?" → aggregate, no LLM summary needed. + // Second call: SQL gen for follow-up "What about orders?" — should contain history. + let mock = PromptCapturingMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM orders", + ]) + + let engine = ChatEngine( + database: db, + model: mock + ) + + // First turn + _ = try await engine.send("How many users?") + + // Second turn — follow-up + _ = try await engine.send("What about orders?") + + // The second prompt should contain conversation history from the first turn + let prompts = mock.capturedPrompts + #expect(prompts.count >= 2) + + let followUpPrompt = prompts[1] + // Should include conversation history markers + #expect(followUpPrompt.contains("CONVERSATION HISTORY")) + // Should include the prior user message + #expect(followUpPrompt.contains("How many users?")) + // Should include the prior assistant SQL + #expect(followUpPrompt.contains("SELECT COUNT(*) FROM users")) + // Should include the current question + #expect(followUpPrompt.contains("What about orders?")) + } + + @Test("ChatEngine respects context window size for history injection") + func respectsContextWindowSize() async throws { + let db = try makeTestDatabase() + + let mock = PromptCapturingMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM orders", + "SELECT COUNT(*) FROM users WHERE name = 'Alice'", + ]) + + // Context window of 2 messages means only the most recent 2 are included + let config = ChatEngineConfiguration( + queryTimeout: nil, + contextWindowSize: 2 + ) + let engine = ChatEngine( + database: db, + model: mock, + configuration: config + ) + + _ = try await engine.send("How many users?") + _ = try await engine.send("How many orders?") + _ = try await engine.send("Find Alice") + + let prompts = mock.capturedPrompts + #expect(prompts.count >= 3) + + let thirdPrompt = prompts[2] + // With contextWindowSize=2, only the last 2 messages (user + assistant from + // second turn) should be in the history — NOT the first turn. + #expect(thirdPrompt.contains("CONVERSATION HISTORY")) + #expect(thirdPrompt.contains("How many orders?")) + // First turn should be trimmed out + #expect(!thirdPrompt.contains("How many users?")) + } + + @Test("ChatEngine reset clears history and schema cache") + func resetClearsState() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "3 users" + ]) + + let engine = ChatEngine( + database: db, + model: model + ) + + _ = try await engine.send("Count users") + #expect(engine.messages.count == 2) + + engine.reset() + #expect(engine.messages.isEmpty) + } + + // MARK: - Configuration & Extensibility Tests + + @Test("ChatEngine clearHistory keeps schema but removes messages") + func clearHistoryKeepsSchema() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + ]) + + let engine = ChatEngine(database: db, model: model) + + _ = try await engine.send("Count users") + #expect(engine.messages.count == 2) + #expect(engine.cachedSchema != nil) + + engine.clearHistory() + #expect(engine.messages.isEmpty) + #expect(engine.cachedSchema != nil) + #expect(engine.tableCount == 2) + } + + @Test("ChatEngine reset clears both history and schema") + func resetClearsAll() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + ]) + + let engine = ChatEngine(database: db, model: model) + + _ = try await engine.send("Count users") + #expect(engine.cachedSchema != nil) + + engine.reset() + #expect(engine.messages.isEmpty) + #expect(engine.cachedSchema == nil) + #expect(engine.tableCount == nil) + } + + @Test("ChatEngine exposes currentConfiguration") + func exposesConfiguration() async throws { + let db = try makeTestDatabase() + let model = MockLanguageModel() + + var config = ChatEngineConfiguration( + queryTimeout: 15, + contextWindowSize: 10, + maxSummaryRows: 25, + additionalContext: "Test context" + ) + config.addValidator(TableAllowlistValidator(allowedTables: ["users"])) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + let readConfig = engine.currentConfiguration + #expect(readConfig.queryTimeout == 15) + #expect(readConfig.contextWindowSize == 10) + #expect(readConfig.maxSummaryRows == 25) + #expect(readConfig.additionalContext == "Test context") + #expect(readConfig.validators.count == 1) + } + + @Test("ChatEngineConfiguration default has expected values") + func defaultConfiguration() async throws { + let config = ChatEngineConfiguration.default + #expect(config.queryTimeout == 30) + #expect(config.contextWindowSize == 50) + #expect(config.maxSummaryRows == 50) + #expect(config.additionalContext == nil) + #expect(config.validators.isEmpty) + } + + @Test("ChatEngine custom validator rejects forbidden queries") + func customValidatorRejects() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT * FROM orders" + ]) + + var config = ChatEngineConfiguration(queryTimeout: nil) + config.addValidator(TableAllowlistValidator(allowedTables: ["users"])) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + await #expect(throws: QueryValidationError.self) { + try await engine.send("Show all orders") + } + } + + @Test("ChatEngine custom validator allows permitted queries") + func customValidatorAllows() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users" + ]) + + var config = ChatEngineConfiguration(queryTimeout: nil) + config.addValidator(TableAllowlistValidator(allowedTables: ["users"])) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + let response = try await engine.send("Count users") + #expect(response.sql == "SELECT COUNT(*) FROM users") + } + + @Test("MaxRowLimitValidator rejects SELECT without LIMIT") + func maxRowLimitRejectsNoLimit() throws { + let validator = MaxRowLimitValidator(maxRows: 100) + + #expect(throws: QueryValidationError.self) { + try validator.validate(sql: "SELECT * FROM users", operation: .select) + } + } + + @Test("MaxRowLimitValidator allows SELECT with acceptable LIMIT") + func maxRowLimitAllowsAcceptable() throws { + let validator = MaxRowLimitValidator(maxRows: 100) + try validator.validate(sql: "SELECT * FROM users LIMIT 50", operation: .select) + } + + @Test("MaxRowLimitValidator rejects SELECT with excessive LIMIT") + func maxRowLimitRejectsExcessive() throws { + let validator = MaxRowLimitValidator(maxRows: 100) + + #expect(throws: QueryValidationError.self) { + try validator.validate(sql: "SELECT * FROM users LIMIT 500", operation: .select) + } + } + + @Test("MaxRowLimitValidator ignores non-SELECT operations") + func maxRowLimitIgnoresNonSelect() throws { + let validator = MaxRowLimitValidator(maxRows: 100) + try validator.validate( + sql: "INSERT INTO users (name) VALUES ('Dave')", + operation: .insert + ) + } + + @Test("Multiple validators run in order, second rejects") + func multipleValidatorsRunInOrder() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT * FROM users LIMIT 200" + ]) + + var config = ChatEngineConfiguration(queryTimeout: nil) + config.addValidator(TableAllowlistValidator(allowedTables: ["users", "orders"])) + config.addValidator(MaxRowLimitValidator(maxRows: 100)) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + // Table is allowed, but LIMIT 200 exceeds MaxRowLimitValidator + await #expect(throws: QueryValidationError.self) { + try await engine.send("Show all users") + } + } + + @Test("ChatEngine nil timeout does not time out") + func nilTimeoutNoTimeout() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + ]) + + let config = ChatEngineConfiguration(queryTimeout: nil) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + let response = try await engine.send("Count users") + #expect(response.summary == "The result is 3.") + } + + @Test("ChatEngine convenience init works with backward-compatible params") + func convenienceInitBackwardCompat() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .readOnly, + additionalContext: "Test context", + maxSummaryRows: 25 + ) + + let config = engine.currentConfiguration + #expect(config.maxSummaryRows == 25) + #expect(config.additionalContext == "Test context") + + let response = try await engine.send("Count users") + #expect(response.summary == "The result is 3.") + } + + @Test("ChatEngine context window preserves full history for UI") + func contextWindowPreservesFullHistory() async throws { + let db = try makeTestDatabase() + + let model = SequentialMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM orders", + "SELECT COUNT(*) FROM users WHERE name = 'Alice'", + ]) + + let config = ChatEngineConfiguration(queryTimeout: nil, contextWindowSize: 2) + + let engine = ChatEngine( + database: db, + model: model, + configuration: config + ) + + _ = try await engine.send("Count users") + _ = try await engine.send("Count orders") + _ = try await engine.send("Find Alice") + + // Full history preserved for UI even though context window is 2 + #expect(engine.messages.count == 6) + } + + @Test("ChatEngineError queryTimedOut has correct description") + func queryTimedOutDescription() { + let error = ChatEngineError.queryTimedOut(seconds: 30) + #expect(error.errorDescription == "Query timed out after 30 seconds.") + } + + @Test("ChatEngineError validationFailed has correct description") + func validationFailedDescription() { + let error = ChatEngineError.validationFailed("test reason") + #expect(error.errorDescription == "Query validation failed: test reason") + } + + @Test("QueryValidationError rejected has correct description") + func queryValidationErrorDescription() { + let error = QueryValidationError.rejected("bad query") + #expect(error.errorDescription == "Query rejected: bad query") + } +} + +// MARK: - Prompt-Capturing Mock Model + +/// A mock that captures prompts for inspection while returning predetermined responses. +final class PromptCapturingMockModel: LanguageModel, @unchecked Sendable { + typealias UnavailableReason = Never + + let responses: [String] + private let callCounter = CallCounter() + private let _capturedPrompts: CapturedPrompts + + private final class CallCounter: @unchecked Sendable { + var count = 0 + let lock = NSLock() + func next() -> Int { + lock.lock() + defer { lock.unlock() } + let c = count + count += 1 + return c + } + } + + private final class CapturedPrompts: @unchecked Sendable { + var prompts: [String] = [] + let lock = NSLock() + func append(_ prompt: String) { + lock.lock() + defer { lock.unlock() } + prompts.append(prompt) + } + var all: [String] { + lock.lock() + defer { lock.unlock() } + return prompts + } + } + + var capturedPrompts: [String] { _capturedPrompts.all } + + init(responses: [String]) { + self.responses = responses + self._capturedPrompts = CapturedPrompts() + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + _capturedPrompts.append(prompt.description) + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + _capturedPrompts.append(prompt.description) + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } +} + +// MARK: - Sequential Mock Model + +/// A mock that returns different responses for successive calls. +struct SequentialMockModel: LanguageModel { + typealias UnavailableReason = Never + + let responses: [String] + private let callCounter = CallCounter() + + private final class CallCounter: @unchecked Sendable { + var count = 0 + let lock = NSLock() + + func next() -> Int { + lock.lock() + defer { lock.unlock() } + let c = count + count += 1 + return c + } + } + + init(responses: [String]) { + self.responses = responses + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } +} diff --git a/Tests/SwiftDBAITests/ChatViewConfigurationTests.swift b/Tests/SwiftDBAITests/ChatViewConfigurationTests.swift new file mode 100644 index 0000000..c3544bb --- /dev/null +++ b/Tests/SwiftDBAITests/ChatViewConfigurationTests.swift @@ -0,0 +1,170 @@ +// ChatViewConfigurationTests.swift +// SwiftDBAITests +// +// Tests for ChatViewConfiguration defaults, presets, and environment propagation. + +import Testing +import SwiftUI +@testable import SwiftDBAI + +@Suite("ChatViewConfiguration Tests") +struct ChatViewConfigurationTests { + + // MARK: - Default Values + + @Test("Default configuration has expected color values") + func defaultColors() { + let config = ChatViewConfiguration.default + #expect(config.userBubbleColor == .accentColor) + #expect(config.userTextColor == .white) + #expect(config.assistantTextColor == .primary) + #expect(config.backgroundColor == .clear) + #expect(config.inputBarBackgroundColor == .clear) + #expect(config.accentColor == .accentColor) + #expect(config.errorColor == .red) + } + + @Test("Default configuration has expected typography values") + func defaultTypography() { + let config = ChatViewConfiguration.default + #expect(config.messageFont == .body) + #expect(config.summaryFont == .body) + #expect(config.sqlFont == .system(.caption, design: .monospaced)) + #expect(config.inputFont == .body) + } + + @Test("Default configuration has expected layout values") + func defaultLayout() { + let config = ChatViewConfiguration.default + #expect(config.messagePadding == 14) + #expect(config.bubbleCornerRadius == 16) + #expect(config.showTimestamps == false) + #expect(config.showSQLDisclosure == true) + #expect(config.inputPlaceholder == "Ask about your data\u{2026}") + #expect(config.emptyStateTitle == "Ask a question about your data") + #expect(config.emptyStateSubtitle == "Try something like \"How many records are in the database?\"") + #expect(config.emptyStateIcon == "bubble.left.and.text.bubble.right") + } + + // MARK: - Compact Preset + + @Test("Compact preset has smaller fonts and tighter padding") + func compactPreset() { + let config = ChatViewConfiguration.compact + #expect(config.messageFont == .footnote) + #expect(config.summaryFont == .footnote) + #expect(config.sqlFont == .system(.caption2, design: .monospaced)) + #expect(config.inputFont == .footnote) + #expect(config.messagePadding == 8) + #expect(config.bubbleCornerRadius == 10) + #expect(config.showTimestamps == false) + #expect(config.showSQLDisclosure == false) + } + + // MARK: - Dark Preset + + @Test("Dark preset has dark-themed colors") + func darkPreset() { + let config = ChatViewConfiguration.dark + #expect(config.userBubbleColor == Color(white: 0.25)) + #expect(config.userTextColor == .white) + #expect(config.assistantBubbleColor == Color(white: 0.15)) + #expect(config.assistantTextColor == Color(white: 0.9)) + #expect(config.backgroundColor == .black) + #expect(config.inputBarBackgroundColor == Color(white: 0.1)) + #expect(config.accentColor == .blue) + #expect(config.errorColor == Color(red: 1.0, green: 0.4, blue: 0.4)) + } + + // MARK: - Mutability + + @Test("Configuration properties can be mutated individually") + func mutateProperties() { + var config = ChatViewConfiguration.default + config.userBubbleColor = .purple + config.inputPlaceholder = "Ask about your recipes..." + config.bubbleCornerRadius = 20 + config.showTimestamps = true + + #expect(config.userBubbleColor == .purple) + #expect(config.inputPlaceholder == "Ask about your recipes...") + #expect(config.bubbleCornerRadius == 20) + #expect(config.showTimestamps == true) + // Other properties remain at defaults + #expect(config.userTextColor == .white) + #expect(config.messageFont == .body) + } + + // MARK: - All Public Properties Accessible + + @Test("All public properties are readable and writable") + func allPropertiesAccessible() { + var config = ChatViewConfiguration.default + + // Colors + _ = config.userBubbleColor + _ = config.userTextColor + _ = config.assistantBubbleColor + _ = config.assistantTextColor + _ = config.backgroundColor + _ = config.inputBarBackgroundColor + _ = config.accentColor + _ = config.errorColor + + // Typography + _ = config.messageFont + _ = config.summaryFont + _ = config.sqlFont + _ = config.inputFont + + // Layout + _ = config.messagePadding + _ = config.bubbleCornerRadius + _ = config.showTimestamps + _ = config.showSQLDisclosure + _ = config.inputPlaceholder + _ = config.emptyStateTitle + _ = config.emptyStateSubtitle + _ = config.emptyStateIcon + + // Verify write access compiles (set and read back) + config.userBubbleColor = .green + #expect(config.userBubbleColor == .green) + + config.emptyStateIcon = "star" + #expect(config.emptyStateIcon == "star") + } + + // MARK: - Presets Are Static + + @Test("Static presets are available as expected") + func staticPresets() { + let _ = ChatViewConfiguration.default + let _ = ChatViewConfiguration.compact + let _ = ChatViewConfiguration.dark + } + + // MARK: - Sendable Conformance + + @Test("Configuration is Sendable") + func sendableConformance() async { + let config = ChatViewConfiguration.default + // Verify Sendable by passing across isolation boundary + let result: ChatViewConfiguration = await Task.detached { + return config + }.value + #expect(result.bubbleCornerRadius == config.bubbleCornerRadius) + } + + // MARK: - Environment Propagation + + @Test("Environment key default value matches ChatViewConfiguration.default") + func environmentKeyDefault() { + let defaultConfig = ChatViewConfiguration.default + let envDefault = ChatViewConfigurationKey.defaultValue + #expect(defaultConfig.bubbleCornerRadius == envDefault.bubbleCornerRadius) + #expect(defaultConfig.messagePadding == envDefault.messagePadding) + #expect(defaultConfig.showSQLDisclosure == envDefault.showSQLDisclosure) + #expect(defaultConfig.inputPlaceholder == envDefault.inputPlaceholder) + } +} diff --git a/Tests/SwiftDBAITests/ChatViewTests.swift b/Tests/SwiftDBAITests/ChatViewTests.swift new file mode 100644 index 0000000..b56551b --- /dev/null +++ b/Tests/SwiftDBAITests/ChatViewTests.swift @@ -0,0 +1,164 @@ +// ChatViewTests.swift +// SwiftDBAITests +// +// Tests for ChatView, ChatViewModel, and MessageBubbleView integration +// with ScrollableDataTableView. + +import Testing +import Foundation +@testable import SwiftDBAI + +@Suite("SchemaReadiness Tests") +struct SchemaReadinessTests { + + @Test("SchemaReadiness isReady returns true only for ready state") + func isReadyProperty() { + #expect(SchemaReadiness.idle.isReady == false) + #expect(SchemaReadiness.loading.isReady == false) + #expect(SchemaReadiness.ready(tableCount: 3).isReady == true) + #expect(SchemaReadiness.failed("error").isReady == false) + } +} + +@Suite("ChatViewModel Tests") +struct ChatViewModelTests { + + @Test("Messages with query results produce DataTable-compatible data") + func messageWithQueryResultHasTableData() { + // A ChatMessage with a queryResult should have the data needed + // for ScrollableDataTableView rendering + let result = QueryResult( + columns: ["id", "name", "score"], + rows: [ + ["id": .integer(1), "name": .text("Alice"), "score": .real(95.5)], + ["id": .integer(2), "name": .text("Bob"), "score": .real(87.3)], + ], + sql: "SELECT id, name, score FROM users", + executionTime: 0.01 + ) + + let message = ChatMessage( + role: .assistant, + content: "Found 2 users.", + queryResult: result, + sql: "SELECT id, name, score FROM users" + ) + + // Verify queryResult is present and can be converted to DataTable + #expect(message.queryResult != nil) + #expect(message.queryResult!.columns.count == 3) + #expect(message.queryResult!.rows.count == 2) + + // Verify DataTable conversion works (this is what MessageBubbleView does) + let dataTable = DataTable(message.queryResult!) + #expect(dataTable.columnCount == 3) + #expect(dataTable.rowCount == 2) + #expect(dataTable.columns[0].name == "id") + #expect(dataTable.columns[1].name == "name") + #expect(dataTable.columns[2].name == "score") + } + + @Test("Messages without query results do not trigger table rendering") + func messageWithoutQueryResult() { + let message = ChatMessage( + role: .assistant, + content: "Hello! How can I help?", + queryResult: nil, + sql: nil + ) + + #expect(message.queryResult == nil) + } + + @Test("Empty query results do not trigger table rendering") + func emptyQueryResult() { + let result = QueryResult( + columns: [], + rows: [], + sql: "SELECT * FROM empty_table", + executionTime: 0.001 + ) + + let message = ChatMessage( + role: .assistant, + content: "No results found.", + queryResult: result, + sql: "SELECT * FROM empty_table" + ) + + // Even though queryResult exists, it has no columns/rows + // MessageBubbleView checks both conditions before showing the table + #expect(message.queryResult != nil) + #expect(message.queryResult!.columns.isEmpty) + #expect(message.queryResult!.rows.isEmpty) + } + + @Test("Mutation results do not trigger table rendering") + func mutationQueryResult() { + let result = QueryResult( + columns: [], + rows: [], + sql: "INSERT INTO users (name) VALUES ('Charlie')", + executionTime: 0.005, + rowsAffected: 1 + ) + + let message = ChatMessage( + role: .assistant, + content: "Successfully inserted 1 row.", + queryResult: result, + sql: "INSERT INTO users (name) VALUES ('Charlie')" + ) + + // Mutation results have empty columns — no table shown + #expect(message.queryResult!.columns.isEmpty) + } + + @Test("Error messages never have query results") + func errorMessageHasNoQueryResult() { + let message = ChatMessage( + role: .error, + content: "SELECT operations are not allowed." + ) + + #expect(message.queryResult == nil) + #expect(message.role == .error) + } + + @Test("DataTable preserves column order from QueryResult") + func dataTableColumnOrder() { + let result = QueryResult( + columns: ["date", "revenue", "category"], + rows: [ + ["date": .text("2024-01-01"), "revenue": .real(1500.0), "category": .text("Electronics")], + ], + sql: "SELECT date, revenue, category FROM sales", + executionTime: 0.02 + ) + + let dataTable = DataTable(result) + #expect(dataTable.columnNames == ["date", "revenue", "category"]) + } + + @Test("Large result sets are renderable as DataTable") + func largeResultSet() { + var rows: [[String: QueryResult.Value]] = [] + for i in 0..<500 { + rows.append([ + "id": .integer(Int64(i)), + "value": .real(Double(i) * 1.5), + ]) + } + + let result = QueryResult( + columns: ["id", "value"], + rows: rows, + sql: "SELECT id, value FROM big_table", + executionTime: 0.15 + ) + + let dataTable = DataTable(result) + #expect(dataTable.rowCount == 500) + #expect(dataTable.columnCount == 2) + } +} diff --git a/Tests/SwiftDBAITests/DataChatViewUsageTests.swift b/Tests/SwiftDBAITests/DataChatViewUsageTests.swift new file mode 100644 index 0000000..8077033 --- /dev/null +++ b/Tests/SwiftDBAITests/DataChatViewUsageTests.swift @@ -0,0 +1,136 @@ +// DataChatViewUsageTests.swift +// SwiftDBAITests +// +// Proves DataChatView works with minimal setup — under 10 lines of code. +// A developer only needs a GRDB connection and a LanguageModel to get a +// full chat-with-database SwiftUI view. + +import Testing +import Foundation +import GRDB +@testable import SwiftDBAI + +// MARK: - Minimal Setup: DataChatView in Under 10 Lines + +/// This test suite proves the "zero_config_reads" principle: +/// A developer with an existing SQLite database can create a fully functional +/// chat UI by providing only a GRDB connection and a language model instance. +/// No schema files, no annotations, no manual configuration required. +@Suite("DataChatView Minimal Setup") +struct DataChatViewMinimalSetupTests { + + // ┌──────────────────────────────────────────────────────────┐ + // │ USAGE EXAMPLE — DataChatView in 6 lines of real code │ + // │ │ + // │ import SwiftDBAI │ + // │ import GRDB │ + // │ │ + // │ let db = try DatabaseQueue(path: "mydata.sqlite") │ + // │ let model = OllamaLanguageModel(model: "llama3") │ + // │ │ + // │ var body: some View { │ + // │ DataChatView(database: db, model: model) │ + // │ } │ + // └──────────────────────────────────────────────────────────┘ + + /// Creates a temporary in-memory database with sample data for tests. + private static func makeSampleDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue() + try db.write { db in + try db.execute(sql: """ + CREATE TABLE products ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + price REAL NOT NULL, + category TEXT + ); + INSERT INTO products (name, price, category) VALUES ('Widget', 9.99, 'Hardware'); + INSERT INTO products (name, price, category) VALUES ('Gadget', 24.99, 'Electronics'); + INSERT INTO products (name, price, category) VALUES ('Doohickey', 4.99, 'Hardware'); + """) + } + return db + } + + @Test("DataChatView initializes from database + model in 2 lines") + @MainActor + func dataChatViewMinimalInit() throws { + // LINE 1: Create (or receive) a GRDB connection + let db = try Self.makeSampleDatabase() + // LINE 2: Create the view — that's it! + let _ = DataChatView(database: db, model: MockLanguageModel()) + // The view is ready. No schema files, no annotations, no extra config. + } + + @Test("DataChatView path-based init works in 1 line given a path and model") + @MainActor + func dataChatViewPathInit() throws { + // Create a temp database file + let tempDir = FileManager.default.temporaryDirectory + let dbPath = tempDir.appendingPathComponent("test_\(UUID().uuidString).sqlite").path + let db = try DatabaseQueue(path: dbPath) + try db.write { db in + try db.execute(sql: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)") + } + + // ONE LINE to get a full chat UI: + let _ = DataChatView(databasePath: dbPath, model: MockLanguageModel()) + + // Cleanup + try? FileManager.default.removeItem(atPath: dbPath) + } + + @Test("ChatEngine headless usage works in 3 lines") + func chatEngineMinimalUsage() async throws { + // LINE 1: Database + let db = try Self.makeSampleDatabase() + // LINE 2: Engine + let engine = ChatEngine(database: db, model: MockLanguageModel(responseText: "SELECT COUNT(*) AS total FROM products")) + // LINE 3: Schema preparation verifies auto-introspection works + let schema = try await engine.prepareSchema() + + // The engine auto-discovered the schema — no manual config needed + #expect(schema.tableNames.contains("products")) + #expect(schema.tableNames.count == 1) + } + + @Test("ChatViewModel works with zero configuration beyond db + model") + @MainActor + func chatViewModelMinimalUsage() async throws { + let db = try Self.makeSampleDatabase() + let engine = ChatEngine(database: db, model: MockLanguageModel()) + let viewModel = ChatViewModel(engine: engine) + + // Prepare triggers auto-schema-introspection + await viewModel.prepare() + + #expect(viewModel.schemaReadiness.isReady) + #expect(viewModel.messages.isEmpty) // Clean slate, ready to chat + } + + @Test("Default configuration is read-only (safe by default)") + @MainActor + func defaultIsReadOnly() throws { + let db = try Self.makeSampleDatabase() + // No allowlist specified — defaults to .readOnly + let _ = DataChatView(database: db, model: MockLanguageModel()) + // This compiles and works. SELECT-only is the safe default. + // Developer must explicitly opt in to writes: + // DataChatView(database: db, model: model, allowlist: .standard) + } + + @Test("Full DataChatView with all options still under 10 lines") + @MainActor + func dataChatViewFullConfig() throws { + let db = try Self.makeSampleDatabase() // 1 + let model = MockLanguageModel() // 2 + let _ = DataChatView( // 3-8 + database: db, + model: model, + allowlist: .readOnly, + additionalContext: "Product catalog for an e-commerce store", + maxSummaryRows: 100 + ) + // Even with ALL options specified, it's under 10 lines of setup. + } +} diff --git a/Tests/SwiftDBAITests/DataTableTests.swift b/Tests/SwiftDBAITests/DataTableTests.swift new file mode 100644 index 0000000..23164fe --- /dev/null +++ b/Tests/SwiftDBAITests/DataTableTests.swift @@ -0,0 +1,285 @@ +// DataTableTests.swift +// SwiftDBAITests + +import Foundation +import Testing +@testable import SwiftDBAI + +@Suite("DataTable") +struct DataTableTests { + + // MARK: - Helpers + + private func makeQueryResult( + columns: [String], + rows: [[String: QueryResult.Value]], + sql: String = "SELECT * FROM test", + executionTime: TimeInterval = 0.01 + ) -> QueryResult { + QueryResult( + columns: columns, + rows: rows, + sql: sql, + executionTime: executionTime + ) + } + + // MARK: - Basic Construction + + @Test("Converts QueryResult columns and rows correctly") + func basicConversion() { + let result = makeQueryResult( + columns: ["id", "name", "score"], + rows: [ + ["id": .integer(1), "name": .text("Alice"), "score": .real(95.5)], + ["id": .integer(2), "name": .text("Bob"), "score": .real(87.0)], + ] + ) + + let table = DataTable(result) + + #expect(table.columnCount == 3) + #expect(table.rowCount == 2) + #expect(table.columnNames == ["id", "name", "score"]) + #expect(table.sql == "SELECT * FROM test") + #expect(table.executionTime == 0.01) + } + + @Test("Empty result produces empty table") + func emptyResult() { + let result = makeQueryResult(columns: ["id", "name"], rows: []) + + let table = DataTable(result) + + #expect(table.isEmpty) + #expect(table.rowCount == 0) + #expect(table.columnCount == 2) + #expect(table.columnNames == ["id", "name"]) + } + + // MARK: - Subscript Access + + @Test("Subscript by row and column index") + func subscriptByIndex() { + let result = makeQueryResult( + columns: ["a", "b"], + rows: [ + ["a": .integer(10), "b": .text("hello")], + ["a": .integer(20), "b": .text("world")], + ] + ) + + let table = DataTable(result) + + #expect(table[row: 0, column: 0] == .integer(10)) + #expect(table[row: 0, column: 1] == .text("hello")) + #expect(table[row: 1, column: 0] == .integer(20)) + #expect(table[row: 1, column: 1] == .text("world")) + } + + @Test("Subscript by row index and column name") + func subscriptByName() { + let result = makeQueryResult( + columns: ["x", "y"], + rows: [["x": .real(1.5), "y": .real(2.5)]] + ) + + let table = DataTable(result) + + #expect(table[row: 0, column: "x"] == .real(1.5)) + #expect(table[row: 0, column: "y"] == .real(2.5)) + #expect(table[row: 0, column: "z"] == .null) // non-existent column + } + + // MARK: - Column Data Extraction + + @Test("Extract column values by index") + func columnValuesByIndex() { + let result = makeQueryResult( + columns: ["val"], + rows: [ + ["val": .integer(1)], + ["val": .integer(2)], + ["val": .integer(3)], + ] + ) + + let table = DataTable(result) + let values = table.columnValues(at: 0) + + #expect(values == [.integer(1), .integer(2), .integer(3)]) + } + + @Test("Extract column values by name") + func columnValuesByName() { + let result = makeQueryResult( + columns: ["name"], + rows: [ + ["name": .text("A")], + ["name": .text("B")], + ] + ) + + let table = DataTable(result) + + #expect(table.columnValues(named: "name") == [.text("A"), .text("B")]) + #expect(table.columnValues(named: "missing").isEmpty) + } + + @Test("numericValues extracts doubles from numeric column") + func numericValues() { + let result = makeQueryResult( + columns: ["score"], + rows: [ + ["score": .integer(10)], + ["score": .real(20.5)], + ["score": .null], + ["score": .text("not a number")], + ] + ) + + let table = DataTable(result) + let nums = table.numericValues(forColumn: "score") + + #expect(nums.count == 2) + #expect(nums[0] == 10.0) + #expect(nums[1] == 20.5) + } + + @Test("stringValues extracts non-null strings") + func stringValues() { + let result = makeQueryResult( + columns: ["label"], + rows: [ + ["label": .text("foo")], + ["label": .null], + ["label": .text("bar")], + ] + ) + + let table = DataTable(result) + let strs = table.stringValues(forColumn: "label") + + #expect(strs == ["foo", "bar"]) + } + + // MARK: - Type Inference + + @Test("Infers integer type for all-integer column") + func inferInteger() { + let result = makeQueryResult( + columns: ["id"], + rows: [["id": .integer(1)], ["id": .integer(2)]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .integer) + } + + @Test("Infers real type for all-real column") + func inferReal() { + let result = makeQueryResult( + columns: ["price"], + rows: [["price": .real(1.99)], ["price": .real(2.50)]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .real) + } + + @Test("Infers text type for all-text column") + func inferText() { + let result = makeQueryResult( + columns: ["name"], + rows: [["name": .text("A")], ["name": .text("B")]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .text) + } + + @Test("Promotes integer + real to real") + func inferNumericPromotion() { + let result = makeQueryResult( + columns: ["val"], + rows: [["val": .integer(1)], ["val": .real(2.5)]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .real) + } + + @Test("Mixed types result in .mixed") + func inferMixed() { + let result = makeQueryResult( + columns: ["data"], + rows: [["data": .integer(1)], ["data": .text("hello")]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .mixed) + } + + @Test("All-null column infers .null") + func inferNull() { + let result = makeQueryResult( + columns: ["empty"], + rows: [["empty": .null], ["empty": .null]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .null) + } + + @Test("Null values are ignored during type inference") + func inferIgnoresNulls() { + let result = makeQueryResult( + columns: ["val"], + rows: [["val": .integer(1)], ["val": .null], ["val": .integer(3)]] + ) + let table = DataTable(result) + #expect(table.columns[0].inferredType == .integer) + } + + // MARK: - Missing Values + + @Test("Missing dictionary keys become .null") + func missingKeysBecomNull() { + let result = makeQueryResult( + columns: ["a", "b"], + rows: [["a": .integer(1)]] // "b" is missing + ) + + let table = DataTable(result) + + #expect(table[row: 0, column: 0] == .integer(1)) + #expect(table[row: 0, column: 1] == .null) + } + + // MARK: - Row Identity + + @Test("Rows have sequential IDs") + func rowIdentity() { + let result = makeQueryResult( + columns: ["x"], + rows: [["x": .integer(1)], ["x": .integer(2)], ["x": .integer(3)]] + ) + + let table = DataTable(result) + + #expect(table.rows[0].id == 0) + #expect(table.rows[1].id == 1) + #expect(table.rows[2].id == 2) + } + + // MARK: - Column Identity + + @Test("Columns are Identifiable by name") + func columnIdentity() { + let result = makeQueryResult( + columns: ["alpha", "beta"], + rows: [["alpha": .integer(1), "beta": .integer(2)]] + ) + + let table = DataTable(result) + + #expect(table.columns[0].id == "alpha") + #expect(table.columns[1].id == "beta") + #expect(table.columns[0].index == 0) + #expect(table.columns[1].index == 1) + } +} diff --git a/Tests/SwiftDBAITests/DatabaseToolTests.swift b/Tests/SwiftDBAITests/DatabaseToolTests.swift new file mode 100644 index 0000000..75c57a7 --- /dev/null +++ b/Tests/SwiftDBAITests/DatabaseToolTests.swift @@ -0,0 +1,317 @@ +// DatabaseToolTests.swift +// SwiftDBAI + +import Testing +import Foundation +import GRDB +@testable import SwiftDBAI + +@Suite("DatabaseTool") +struct DatabaseToolTests { + + // MARK: - Helper + + /// Creates an in-memory database with sample data for testing. + private func makeTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(configuration: { + var config = Configuration() + config.foreignKeysEnabled = true + return config + }()) + + try db.write { db in + try db.execute(sql: """ + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT UNIQUE + ); + """) + + try db.execute(sql: """ + CREATE TABLE posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL REFERENCES users(id), + title TEXT NOT NULL, + body TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP + ); + """) + + try db.execute(sql: """ + CREATE INDEX idx_posts_user ON posts(user_id); + """) + + // Insert sample data + try db.execute(sql: "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')") + try db.execute(sql: "INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')") + try db.execute(sql: "INSERT INTO posts (user_id, title, body) VALUES (1, 'Hello World', 'First post')") + try db.execute(sql: "INSERT INTO posts (user_id, title, body) VALUES (1, 'Second Post', 'More content')") + try db.execute(sql: "INSERT INTO posts (user_id, title, body) VALUES (2, 'Bob Post', 'Bob writes')") + } + + return db + } + + // MARK: - Creation + + @Test("Creates tool from database connection") + func testCreationFromDatabase() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + #expect(tool.name == "execute_sql") + #expect(!tool.description.isEmpty) + } + + @Test("Creates tool from database path") + func testCreationFromPath() async throws { + let tmpDir = FileManager.default.temporaryDirectory + let dbPath = tmpDir.appendingPathComponent("test_\(UUID().uuidString).sqlite").path + defer { try? FileManager.default.removeItem(atPath: dbPath) } + + // Create a database at the path + let dbQueue = try DatabaseQueue(path: dbPath) + try await dbQueue.write { db in + try db.execute(sql: "CREATE TABLE test (id INTEGER PRIMARY KEY)") + } + + let tool = try await DatabaseTool(databasePath: dbPath) + #expect(tool.name == "execute_sql") + } + + // MARK: - Schema Context + + @Test("Schema context contains table info") + func testSchemaContext() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let context = tool.schemaContext + #expect(context.contains("users")) + #expect(context.contains("posts")) + #expect(context.contains("name")) + #expect(context.contains("email")) + } + + @Test("System prompt snippet contains schema") + func testSystemPromptSnippet() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let snippet = tool.systemPromptSnippet + #expect(snippet.contains("users")) + #expect(snippet.contains("posts")) + #expect(snippet.contains("execute_sql")) + #expect(snippet.contains("SELECT")) + } + + // MARK: - SQL Execution + + @Test("Executes valid SELECT query") + func testExecuteSelect() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name, email FROM users ORDER BY name") + + #expect(result.rowCount == 2) + #expect(result.columns == ["name", "email"]) + #expect(result.rows[0]["name"] == "Alice") + #expect(result.rows[1]["name"] == "Bob") + } + + @Test("Executes query with JOIN") + func testExecuteJoin() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: """ + SELECT u.name, COUNT(p.id) as post_count + FROM users u + JOIN posts p ON p.user_id = u.id + GROUP BY u.name + ORDER BY u.name + """) + + #expect(result.rowCount == 2) + #expect(result.rows[0]["name"] == "Alice") + #expect(result.rows[0]["post_count"] == "2") + } + + @Test("Rejects INSERT with read-only allowlist") + func testRejectInsert() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db, allowlist: .readOnly) + + #expect(throws: SQLParsingError.self) { + try tool.execute(sql: "INSERT INTO users (name, email) VALUES ('Eve', 'eve@example.com')") + } + } + + @Test("Rejects DELETE with read-only allowlist") + func testRejectDelete() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db, allowlist: .readOnly) + + #expect(throws: SQLParsingError.self) { + try tool.execute(sql: "DELETE FROM users WHERE id = 1") + } + } + + @Test("Rejects DROP as dangerous operation") + func testRejectDrop() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db, allowlist: .unrestricted) + + #expect(throws: SQLParsingError.self) { + try tool.execute(sql: "DROP TABLE users") + } + } + + @Test("Executes raw query returning QueryResult") + func testExecuteRaw() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.executeRaw(sql: "SELECT COUNT(*) as cnt FROM users") + + #expect(result.rowCount == 1) + #expect(result.columns == ["cnt"]) + if case .integer(let count) = result.rows[0]["cnt"] { + #expect(count == 2) + } else { + Issue.record("Expected integer value") + } + } + + // MARK: - ToolResult Formatting + + @Test("ToolResult JSON serialization") + func testToolResultJSON() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name FROM users ORDER BY name LIMIT 1") + + let json = result.jsonString + #expect(json.contains("\"columns\"")) + #expect(json.contains("\"rows\"")) + #expect(json.contains("\"row_count\"")) + #expect(json.contains("Alice")) + + // Verify it is valid JSON + let data = json.data(using: .utf8)! + let parsed = try JSONSerialization.jsonObject(with: data) as! [String: Any] + #expect(parsed["row_count"] as? Int == 1) + } + + @Test("ToolResult markdown table formatting") + func testToolResultMarkdown() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name, email FROM users ORDER BY name") + + let md = result.markdownTable + #expect(md.contains("| name | email |")) + #expect(md.contains("| --- | --- |")) + #expect(md.contains("| Alice | alice@example.com |")) + #expect(md.contains("| Bob | bob@example.com |")) + } + + @Test("ToolResult markdown table with empty result") + func testToolResultMarkdownEmpty() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name FROM users WHERE name = 'Nobody'") + + #expect(result.markdownTable == "_No results._") + } + + @Test("ToolResult text summary") + func testToolResultTextSummary() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name FROM users") + + let summary = result.textSummary + #expect(summary.contains("2 rows")) + #expect(summary.contains("name")) + } + + @Test("ToolResult text summary with empty result") + func testToolResultTextSummaryEmpty() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let result = try tool.execute(sql: "SELECT name FROM users WHERE 1 = 0") + + #expect(result.textSummary.contains("no results")) + } + + // MARK: - Parameters Schema + + @Test("Parameters schema has correct structure") + func testParametersSchema() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let schema = tool.parametersSchema + #expect(schema["type"] as? String == "object") + + let properties = schema["properties"] as? [String: Any] + #expect(properties != nil) + + let sqlProp = properties?["sql"] as? [String: Any] + #expect(sqlProp?["type"] as? String == "string") + + let required = schema["required"] as? [String] + #expect(required == ["sql"]) + } + + // MARK: - OpenAI Function Definition + + @Test("OpenAI function definition has correct format") + func testOpenAIFunctionDefinition() async throws { + let db = try makeTestDatabase() + let tool = try await DatabaseTool(database: db) + + let def = tool.openAIFunctionDefinition + #expect(def["type"] as? String == "function") + + let function = def["function"] as? [String: Any] + #expect(function?["name"] as? String == "execute_sql") + #expect(function?["description"] as? String != nil) + #expect(function?["parameters"] as? [String: Any] != nil) + + // Verify it can be serialized to JSON + let data = try JSONSerialization.data(withJSONObject: def) + #expect(data.count > 0) + } + + // MARK: - ToolResult Codable + + @Test("ToolResult is Codable") + func testToolResultCodable() throws { + let result = ToolResult( + columns: ["name", "age"], + rows: [["name": "Alice", "age": "30"]], + rowCount: 1, + executionTime: 0.005, + sql: "SELECT name, age FROM users" + ) + + let encoder = JSONEncoder() + let data = try encoder.encode(result) + let decoder = JSONDecoder() + let decoded = try decoder.decode(ToolResult.self, from: data) + + #expect(decoded.columns == result.columns) + #expect(decoded.rows == result.rows) + #expect(decoded.rowCount == result.rowCount) + #expect(decoded.sql == result.sql) + } +} diff --git a/Tests/SwiftDBAITests/DestructiveOperationTests.swift b/Tests/SwiftDBAITests/DestructiveOperationTests.swift new file mode 100644 index 0000000..138a29c --- /dev/null +++ b/Tests/SwiftDBAITests/DestructiveOperationTests.swift @@ -0,0 +1,745 @@ +// DestructiveOperationTests.swift +// SwiftDBAITests +// +// Tests verifying that destructive operations are blocked without confirmation +// and allowed when the delegate approves. + +import AnyLanguageModel +import Foundation +import GRDB +import Testing + +@testable import SwiftDBAI + +// MARK: - Test Delegates + +/// A delegate that always rejects destructive operations and tracks calls. +private final class RejectingTrackingDelegate: SwiftDBAI.ToolExecutionDelegate, @unchecked Sendable { + private let lock = NSLock() + private var _confirmCalls: [DestructiveOperationContext] = [] + private var _willExecuteCalls: [(sql: String, classification: DestructiveClassification)] = [] + private var _didExecuteCalls: [(sql: String, success: Bool)] = [] + + var confirmCalls: [DestructiveOperationContext] { + lock.withLock { _confirmCalls } + } + + var willExecuteCalls: [(sql: String, classification: DestructiveClassification)] { + lock.withLock { _willExecuteCalls } + } + + var didExecuteCalls: [(sql: String, success: Bool)] { + lock.withLock { _didExecuteCalls } + } + + func confirmDestructiveOperation(_ context: DestructiveOperationContext) async -> Bool { + lock.withLock { _confirmCalls.append(context) } + return false + } + + func willExecuteSQL(_ sql: String, classification: DestructiveClassification) async { + lock.withLock { _willExecuteCalls.append((sql: sql, classification: classification)) } + } + + func didExecuteSQL(_ sql: String, success: Bool) async { + lock.withLock { _didExecuteCalls.append((sql: sql, success: success)) } + } +} + +/// A delegate that always approves destructive operations and tracks calls. +private final class ApprovingTrackingDelegate: SwiftDBAI.ToolExecutionDelegate, @unchecked Sendable { + private let lock = NSLock() + private var _confirmCalls: [DestructiveOperationContext] = [] + private var _willExecuteCalls: [(sql: String, classification: DestructiveClassification)] = [] + private var _didExecuteCalls: [(sql: String, success: Bool)] = [] + + var confirmCalls: [DestructiveOperationContext] { + lock.withLock { _confirmCalls } + } + + var willExecuteCalls: [(sql: String, classification: DestructiveClassification)] { + lock.withLock { _willExecuteCalls } + } + + var didExecuteCalls: [(sql: String, success: Bool)] { + lock.withLock { _didExecuteCalls } + } + + func confirmDestructiveOperation(_ context: DestructiveOperationContext) async -> Bool { + lock.withLock { _confirmCalls.append(context) } + return true + } + + func willExecuteSQL(_ sql: String, classification: DestructiveClassification) async { + lock.withLock { _willExecuteCalls.append((sql: sql, classification: classification)) } + } + + func didExecuteSQL(_ sql: String, success: Bool) async { + lock.withLock { _didExecuteCalls.append((sql: sql, success: success)) } + } +} + +// MARK: - Helpers + +/// Creates an in-memory database with test data for destructive operation tests. +/// Users 1 and 2 have orders; user 3 has no orders (safe to delete). +private func makeTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(path: ":memory:") + try db.write { db in + // Disable FK enforcement for test flexibility, then re-enable + try db.execute(sql: "PRAGMA foreign_keys = OFF") + try db.execute(sql: """ + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL + ) + """) + try db.execute(sql: """ + INSERT INTO users (name, email) VALUES + ('Alice', 'alice@example.com'), + ('Bob', 'bob@example.com'), + ('Charlie', 'charlie@example.com') + """) + try db.execute(sql: """ + CREATE TABLE orders ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + amount REAL NOT NULL + ) + """) + try db.execute(sql: """ + INSERT INTO orders (user_id, amount) VALUES + (1, 99.99), + (2, 150.00), + (3, 25.50) + """) + } + return db +} + +/// A sequential mock model for tests. Returns responses in order. +private struct TestSequentialModel: LanguageModel { + typealias UnavailableReason = Never + + let responses: [String] + private let callCounter = CallCounter() + + private final class CallCounter: @unchecked Sendable { + var count = 0 + let lock = NSLock() + + func next() -> Int { + lock.lock() + defer { lock.unlock() } + let c = count + count += 1 + return c + } + } + + init(responses: [String]) { + self.responses = responses + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + let idx = callCounter.next() + let text = idx < responses.count ? responses[idx] : "fallback response" + let rawContent = GeneratedContent(kind: .string(text)) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } +} + +// MARK: - Tests: Destructive Operations Blocked Without Confirmation + +@Suite("Destructive Operations - Blocked Without Confirmation") +struct DestructiveOperationsBlockedTests { + + @Test("DELETE is blocked when no delegate is provided") + func deleteBlockedWithoutDelegate() async throws { + let db = try makeTestDatabase() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 1" + ]) + + // Unrestricted allowlist permits DELETE, but no delegate to confirm + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted + ) + + do { + _ = try await engine.send("Delete user 1") + Issue.record("Expected confirmationRequired error but send succeeded") + } catch let error as SwiftDBAIError { + guard case .confirmationRequired(let sql, let operation) = error else { + Issue.record("Expected confirmationRequired, got: \(error)") + return + } + #expect(sql.uppercased().contains("DELETE")) + #expect(operation == "delete") + } + + // Verify the user was NOT deleted (data remains intact) + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 1") + } + #expect(count == 1, "User should NOT have been deleted") + } + + @Test("DELETE is blocked when delegate rejects") + func deleteBlockedWhenDelegateRejects() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 2" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + do { + _ = try await engine.send("Delete user 2") + Issue.record("Expected confirmationRequired error but send succeeded") + } catch let error as SwiftDBAIError { + guard case .confirmationRequired(let sql, let operation) = error else { + Issue.record("Expected confirmationRequired, got: \(error)") + return + } + #expect(sql.uppercased().contains("DELETE")) + #expect(operation == "delete") + } + + // Verify delegate was consulted + #expect(delegate.confirmCalls.count == 1) + #expect(delegate.confirmCalls[0].statementKind == .delete) + #expect(delegate.confirmCalls[0].sql.uppercased().contains("DELETE")) + + // Verify the data was NOT modified + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 2") + } + #expect(count == 1, "User should NOT have been deleted") + + // Verify no SQL was actually executed (no willExecute/didExecute calls) + #expect(delegate.willExecuteCalls.isEmpty, "No SQL should have been executed") + #expect(delegate.didExecuteCalls.isEmpty, "No SQL should have been executed") + } + + @Test("DELETE is blocked with MutationPolicy and no delegate") + func deleteBlockedWithMutationPolicyNoDelegate() async throws { + let db = try makeTestDatabase() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 3" + ]) + + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + requiresDestructiveConfirmation: true + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy + ) + + do { + _ = try await engine.send("Delete user 3") + Issue.record("Expected confirmationRequired error but send succeeded") + } catch let error as SwiftDBAIError { + guard case .confirmationRequired(let sql, let operation) = error else { + Issue.record("Expected confirmationRequired, got: \(error)") + return + } + #expect(sql.uppercased().contains("DELETE")) + #expect(operation == "delete") + } + + // Data intact + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 3") + } + #expect(count == 1, "User should NOT have been deleted") + } + + @Test("DELETE is blocked with MutationPolicy and rejecting delegate") + func deleteBlockedWithMutationPolicyRejectingDelegate() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM orders WHERE user_id = 1" + ]) + + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + requiresDestructiveConfirmation: true + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy, + delegate: delegate + ) + + do { + _ = try await engine.send("Delete all orders for user 1") + Issue.record("Expected confirmationRequired error but send succeeded") + } catch let error as SwiftDBAIError { + guard case .confirmationRequired = error else { + Issue.record("Expected confirmationRequired, got: \(error)") + return + } + } + + // Delegate was consulted and rejected + #expect(delegate.confirmCalls.count == 1) + #expect(delegate.confirmCalls[0].statementKind == .delete) + + // Orders remain + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM orders WHERE user_id = 1") + } + #expect(count == 1, "Orders should NOT have been deleted") + } + + @Test("Default delegate implementation rejects destructive operations") + func defaultDelegateRejectsDestructive() async { + struct DefaultDelegate: SwiftDBAI.ToolExecutionDelegate {} + let delegate = DefaultDelegate() + + let context = DestructiveOperationContext( + sql: "DELETE FROM users WHERE id = 1", + statementKind: .delete, + classification: .destructive(.delete), + description: "Delete from users" + ) + + let approved = await delegate.confirmDestructiveOperation(context) + #expect(approved == false, "Default delegate should reject destructive operations") + } + + @Test("DELETE not in readOnly allowlist is rejected before delegate is consulted") + func deleteNotInAllowlistRejectedEarly() async throws { + let db = try makeTestDatabase() + let delegate = ApprovingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 1" + ]) + + // Read-only allowlist does NOT include DELETE + let engine = ChatEngine( + database: db, + model: model, + allowlist: .readOnly, + delegate: delegate + ) + + do { + _ = try await engine.send("Delete user 1") + Issue.record("Expected operationNotAllowed error") + } catch let error as SwiftDBAIError { + guard case .operationNotAllowed(let operation) = error else { + Issue.record("Expected operationNotAllowed, got: \(error)") + return + } + #expect(operation == "delete") + } + + // Delegate should NOT have been consulted — the allowlist rejects before delegation + #expect(delegate.confirmCalls.isEmpty, "Delegate should not be consulted when op is not in allowlist") + } +} + +// MARK: - Tests: Destructive Operations Allowed When Delegate Approves + +@Suite("Destructive Operations - Allowed When Delegate Approves") +struct DestructiveOperationsAllowedTests { + + @Test("DELETE succeeds when delegate approves") + func deleteSucceedsWithApprovingDelegate() async throws { + let db = try makeTestDatabase() + let delegate = ApprovingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 1", + "Successfully deleted 1 user." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + let response = try await engine.send("Delete user 1") + + // Delegate was consulted and approved + #expect(delegate.confirmCalls.count == 1) + #expect(delegate.confirmCalls[0].statementKind == .delete) + #expect(delegate.confirmCalls[0].sql.uppercased().contains("DELETE")) + #expect(delegate.confirmCalls[0].targetTable == "users") + + // SQL was executed + #expect(delegate.willExecuteCalls.count == 1) + #expect(delegate.didExecuteCalls.count == 1) + #expect(delegate.didExecuteCalls[0].success == true) + + // Verify the data was actually deleted + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 1") + } + #expect(count == 0, "User should have been deleted") + + // Response should contain meaningful content + #expect(response.sql?.uppercased().contains("DELETE") == true) + #expect(response.queryResult != nil) + } + + @Test("DELETE with MutationPolicy succeeds when delegate approves") + func deleteWithPolicySucceedsWhenApproved() async throws { + let db = try makeTestDatabase() + let delegate = ApprovingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM orders WHERE user_id = 2", + "Deleted 1 order." + ]) + + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + requiresDestructiveConfirmation: true + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy, + delegate: delegate + ) + + let response = try await engine.send("Delete all orders for user 2") + + // Delegate approved + #expect(delegate.confirmCalls.count == 1) + + // Data was actually deleted + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM orders WHERE user_id = 2") + } + #expect(count == 0, "Orders should have been deleted") + #expect(response.sql?.uppercased().contains("DELETE") == true) + } + + @Test("AutoApproveDelegate allows DELETE without user interaction") + func autoApproveDelegateAllowsDelete() async throws { + let db = try makeTestDatabase() + let delegate = AutoApproveDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 3", + "Deleted 1 user." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + let response = try await engine.send("Delete user 3") + + // Should succeed without error + #expect(response.sql?.uppercased().contains("DELETE") == true) + + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 3") + } + #expect(count == 0, "User should have been deleted") + } + + @Test("sendConfirmed bypasses delegate and executes directly") + func sendConfirmedBypassesDelegate() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "Deleted 1 user." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + // sendConfirmed should execute directly without consulting the delegate for confirmation + let response = try await engine.sendConfirmed( + "Delete user 1", + confirmedSQL: "DELETE FROM users WHERE id = 1" + ) + + // Delegate was NOT asked to confirm (sendConfirmed skips confirmation) + #expect(delegate.confirmCalls.isEmpty) + + // But willExecute/didExecute hooks were still called + #expect(delegate.willExecuteCalls.count == 1) + #expect(delegate.didExecuteCalls.count == 1) + #expect(delegate.didExecuteCalls[0].success == true) + + // Data was deleted + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 1") + } + #expect(count == 0) + #expect(response.summary.contains("deleted") || response.summary.contains("Deleted") || response.summary.contains("1")) + } +} + +// MARK: - Tests: Delegate Context Correctness + +@Suite("Destructive Operations - Delegate Context") +struct DestructiveOperationContextTests { + + @Test("Delegate receives correct context for DELETE on specific table") + func delegateReceivesCorrectContext() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM orders WHERE amount < 50" + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + do { + _ = try await engine.send("Delete cheap orders") + Issue.record("Expected confirmationRequired error") + } catch is SwiftDBAIError { + // Expected + } + + #expect(delegate.confirmCalls.count == 1) + let ctx = delegate.confirmCalls[0] + #expect(ctx.statementKind == .delete) + #expect(ctx.classification == .destructive(.delete)) + #expect(ctx.classification.requiresConfirmation == true) + #expect(ctx.sql.uppercased().contains("DELETE FROM ORDERS")) + #expect(ctx.targetTable == "orders") + #expect(!ctx.description.isEmpty) + } + + @Test("Non-destructive operations do not consult delegate") + func selectDoesNotConsultDelegate() async throws { + let db = try makeTestDatabase() + let delegate = ApprovingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "SELECT COUNT(*) FROM users", + "There are 3 users." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .unrestricted, + delegate: delegate + ) + + _ = try await engine.send("How many users?") + + // Delegate should NOT have been asked to confirm (SELECT is not destructive) + #expect(delegate.confirmCalls.isEmpty) + + // But willExecute/didExecute should still be called (observation hooks) + #expect(delegate.willExecuteCalls.count == 1) + #expect(delegate.didExecuteCalls.count == 1) + } + + @Test("INSERT does not require confirmation even with delegate") + func insertDoesNotRequireConfirmation() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "INSERT INTO users (name, email) VALUES ('Dave', 'dave@example.com')", + "Inserted 1 row." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard, + delegate: delegate + ) + + let response = try await engine.send("Add user Dave") + + // No confirmation needed for INSERT + #expect(delegate.confirmCalls.isEmpty) + #expect(response.sql?.uppercased().contains("INSERT") == true) + + // Verify the insert happened + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE name = 'Dave'") + } + #expect(count == 1) + } + + @Test("UPDATE does not require confirmation even with delegate") + func updateDoesNotRequireConfirmation() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "UPDATE users SET email = 'alice-new@example.com' WHERE id = 1", + "Updated 1 row." + ]) + + let engine = ChatEngine( + database: db, + model: model, + allowlist: .standard, + delegate: delegate + ) + + let response = try await engine.send("Update Alice's email") + + // No confirmation needed for UPDATE + #expect(delegate.confirmCalls.isEmpty) + #expect(response.sql?.uppercased().contains("UPDATE") == true) + } +} + +// MARK: - Tests: MutationPolicy Confirmation Flag + +@Suite("Destructive Operations - MutationPolicy Confirmation Control") +struct MutationPolicyConfirmationTests { + + @Test("DELETE skips confirmation when requiresDestructiveConfirmation is false") + func deleteSkipsConfirmationWhenDisabled() async throws { + let db = try makeTestDatabase() + let delegate = RejectingTrackingDelegate() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 1", + "Deleted 1 user." + ]) + + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + requiresDestructiveConfirmation: false // Explicitly disabled + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy, + delegate: delegate + ) + + // Should succeed without confirmation since the policy disables it + let response = try await engine.send("Delete user 1") + + // Delegate should NOT have been consulted for confirmation + #expect(delegate.confirmCalls.isEmpty) + + // But the SQL should have executed + #expect(response.sql?.uppercased().contains("DELETE") == true) + + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 1") + } + #expect(count == 0, "User should have been deleted without confirmation") + } + + @Test("MutationPolicy.requiresConfirmation only triggers for DELETE") + func requiresConfirmationOnlyForDelete() { + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + requiresDestructiveConfirmation: true + ) + + #expect(policy.requiresConfirmation(for: .delete) == true) + #expect(policy.requiresConfirmation(for: .select) == false) + #expect(policy.requiresConfirmation(for: .insert) == false) + #expect(policy.requiresConfirmation(for: .update) == false) + } + + @Test("MutationPolicy.readOnly never requires confirmation (no delete allowed)") + func readOnlyNeverRequiresConfirmation() { + let policy = MutationPolicy.readOnly + + #expect(policy.requiresConfirmation(for: .select) == false) + #expect(policy.requiresConfirmation(for: .delete) == true) // Would require confirmation IF allowed + #expect(policy.isOperationAllowed(.delete) == false) // But it's not allowed at all + } + + @Test("Table-restricted DELETE is blocked for disallowed tables") + func tableRestrictedDeleteBlocked() async throws { + let db = try makeTestDatabase() + let model = TestSequentialModel(responses: [ + "DELETE FROM users WHERE id = 1" + ]) + + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + allowedTables: ["orders"], // Only orders, NOT users + requiresDestructiveConfirmation: true + ) + + let engine = ChatEngine( + database: db, + model: model, + mutationPolicy: policy + ) + + do { + _ = try await engine.send("Delete user 1") + Issue.record("Expected tableNotAllowedForMutation error") + } catch let error as SwiftDBAIError { + guard case .tableNotAllowedForMutation(let tableName, let operation) = error else { + Issue.record("Expected tableNotAllowedForMutation, got: \(error)") + return + } + #expect(tableName == "users") + #expect(operation == "delete") + } + + // User was not deleted + let count = try await db.read { db in + try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM users WHERE id = 1") + } + #expect(count == 1) + } +} diff --git a/Tests/SwiftDBAITests/Helpers/MockLanguageModel.swift b/Tests/SwiftDBAITests/Helpers/MockLanguageModel.swift new file mode 100644 index 0000000..c34985f --- /dev/null +++ b/Tests/SwiftDBAITests/Helpers/MockLanguageModel.swift @@ -0,0 +1,49 @@ +// MockLanguageModel.swift +// SwiftDBAI Tests +// +// A mock LanguageModel for unit tests that returns canned responses. + +import AnyLanguageModel +import Foundation + +/// A mock language model that returns a configurable canned response. +/// +/// Used in tests to avoid hitting a real LLM provider. +struct MockLanguageModel: LanguageModel { + typealias UnavailableReason = Never + + /// The text the mock will return from `respond(...)`. + let responseText: String + + init(responseText: String = "Mock summary response.") { + self.responseText = responseText + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + let rawContent = GeneratedContent(kind: .string(responseText)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + let rawContent = GeneratedContent(kind: .string(responseText)) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } +} diff --git a/Tests/SwiftDBAITests/LocalProviderConfigurationTests.swift b/Tests/SwiftDBAITests/LocalProviderConfigurationTests.swift new file mode 100644 index 0000000..9a4c759 --- /dev/null +++ b/Tests/SwiftDBAITests/LocalProviderConfigurationTests.swift @@ -0,0 +1,337 @@ +// LocalProviderConfigurationTests.swift +// SwiftDBAI Tests +// +// Tests for local/self-hosted provider configurations (Ollama, llama.cpp): +// factory methods, endpoint discovery, connection handling, and model creation. + +import AnyLanguageModel +import Foundation +import GRDB +@testable import SwiftDBAI +import Testing + +@Suite("Local Provider Configuration") +struct LocalProviderConfigurationTests { + + // MARK: - Ollama Configuration + + @Test("Ollama configuration stores provider and model") + func ollamaBasicConfiguration() { + let config = ProviderConfiguration.ollama(model: "llama3.2") + + #expect(config.provider == .ollama) + #expect(config.model == "llama3.2") + #expect(config.baseURL == OllamaLanguageModel.defaultBaseURL) + } + + @Test("Ollama configuration produces OllamaLanguageModel") + func ollamaMakeModel() { + let config = ProviderConfiguration.ollama(model: "qwen2.5") + + let model = config.makeModel() + #expect(model is OllamaLanguageModel) + } + + @Test("Ollama with custom base URL for remote instance") + func ollamaCustomBaseURL() { + let remoteURL = URL(string: "http://192.168.1.100:11434")! + let config = ProviderConfiguration.ollama( + model: "mistral", + baseURL: remoteURL + ) + + #expect(config.baseURL == remoteURL) + #expect(config.provider == .ollama) + let model = config.makeModel() + #expect(model is OllamaLanguageModel) + } + + @Test("Ollama does not require an API key") + func ollamaNoAPIKey() { + let config = ProviderConfiguration.ollama(model: "llama3.2") + + // Ollama doesn't need an API key, so the key is empty + #expect(config.apiKey == "") + // hasValidAPIKey returns false because key is empty, but that's expected + // for local providers — they don't need authentication + #expect(!config.hasValidAPIKey) + } + + @Test("Ollama model is available without API key") + func ollamaModelAvailable() { + let config = ProviderConfiguration.ollama(model: "llama3.2") + let model = config.makeModel() + #expect(model.isAvailable) + } + + // MARK: - llama.cpp Configuration + + @Test("llama.cpp configuration stores provider and model") + func llamaCppBasicConfiguration() { + let config = ProviderConfiguration.llamaCpp(model: "my-model") + + #expect(config.provider == .llamaCpp) + #expect(config.model == "my-model") + #expect(config.baseURL == LocalProviderDiscovery.defaultLlamaCppURL) + } + + @Test("llama.cpp uses 'default' model name by default") + func llamaCppDefaultModel() { + let config = ProviderConfiguration.llamaCpp() + + #expect(config.model == "default") + } + + @Test("llama.cpp configuration produces OpenAILanguageModel (compatible API)") + func llamaCppMakeModel() { + let config = ProviderConfiguration.llamaCpp(model: "my-gguf") + + let model = config.makeModel() + // llama.cpp uses OpenAI-compatible API + #expect(model is OpenAILanguageModel) + } + + @Test("llama.cpp with custom base URL") + func llamaCppCustomBaseURL() { + let customURL = URL(string: "http://localhost:9090")! + let config = ProviderConfiguration.llamaCpp( + model: "custom-model", + baseURL: customURL + ) + + #expect(config.baseURL == customURL) + let model = config.makeModel() + #expect(model is OpenAILanguageModel) + } + + @Test("llama.cpp with API key authentication") + func llamaCppWithAPIKey() { + let config = ProviderConfiguration.llamaCpp( + model: "secured-model", + apiKey: "my-secret-key" + ) + + #expect(config.apiKey == "my-secret-key") + #expect(config.hasValidAPIKey) + } + + @Test("llama.cpp without API key") + func llamaCppNoAPIKey() { + let config = ProviderConfiguration.llamaCpp(model: "open-model") + + #expect(config.apiKey == "") + } + + // MARK: - Provider Enum + + @Test("Provider enum includes ollama and llamaCpp cases") + func providerEnumHasLocalCases() { + let cases = ProviderConfiguration.Provider.allCases + #expect(cases.contains(.ollama)) + #expect(cases.contains(.llamaCpp)) + // Total: openAI, anthropic, gemini, openAICompatible, ollama, llamaCpp + #expect(cases.count == 6) + } + + // MARK: - fromEnvironment + + @Test("fromEnvironment creates Ollama configuration") + func fromEnvironmentOllama() { + let config = ProviderConfiguration.fromEnvironment( + provider: .ollama, + environmentVariable: "NONEXISTENT_OLLAMA_KEY", + model: "llama3.2" + ) + + #expect(config.provider == .ollama) + #expect(config.model == "llama3.2") + } + + @Test("fromEnvironment creates llama.cpp configuration") + func fromEnvironmentLlamaCpp() { + let config = ProviderConfiguration.fromEnvironment( + provider: .llamaCpp, + environmentVariable: "NONEXISTENT_LLAMACPP_KEY", + model: "default" + ) + + #expect(config.provider == .llamaCpp) + #expect(config.model == "default") + } + + // MARK: - ChatEngine Convenience Init with Local Providers + + @Test("ChatEngine can be created with Ollama provider") + func chatEngineWithOllama() throws { + let dbQueue = try GRDB.DatabaseQueue() + let config = ProviderConfiguration.ollama(model: "llama3.2") + let engine = ChatEngine(database: dbQueue, provider: config) + + #expect(engine.tableCount == nil) // schema not yet introspected + } + + @Test("ChatEngine can be created with llama.cpp provider") + func chatEngineWithLlamaCpp() throws { + let dbQueue = try GRDB.DatabaseQueue() + let config = ProviderConfiguration.llamaCpp() + let engine = ChatEngine(database: dbQueue, provider: config) + + #expect(engine.tableCount == nil) + } + + // MARK: - LocalProviderType + + @Test("LocalProviderType has expected raw values") + func localProviderTypeRawValues() { + #expect(LocalProviderType.ollama.rawValue == "ollama") + #expect(LocalProviderType.llamaCpp.rawValue == "llama.cpp") + } + + @Test("LocalProviderType CaseIterable includes both cases") + func localProviderTypeCases() { + let cases = LocalProviderType.allCases + #expect(cases.count == 2) + #expect(cases.contains(.ollama)) + #expect(cases.contains(.llamaCpp)) + } + + // MARK: - LocalProviderEndpoint + + @Test("LocalProviderEndpoint description includes status and model count") + func endpointDescription() { + let endpoint = LocalProviderEndpoint( + baseURL: URL(string: "http://localhost:11434")!, + providerType: .ollama, + isReachable: true, + availableModels: ["llama3.2", "qwen2.5"] + ) + + #expect(endpoint.description.contains("ollama")) + #expect(endpoint.description.contains("reachable")) + #expect(endpoint.description.contains("2 models")) + } + + @Test("LocalProviderEndpoint shows unreachable when not connected") + func endpointUnreachableDescription() { + let endpoint = LocalProviderEndpoint( + baseURL: URL(string: "http://localhost:8080")!, + providerType: .llamaCpp, + isReachable: false, + availableModels: [] + ) + + #expect(endpoint.description.contains("unreachable")) + #expect(endpoint.description.contains("0 models")) + } + + @Test("LocalProviderEndpoint equality works correctly") + func endpointEquality() { + let a = LocalProviderEndpoint( + baseURL: URL(string: "http://localhost:11434")!, + providerType: .ollama, + isReachable: true, + availableModels: ["llama3.2"] + ) + let b = LocalProviderEndpoint( + baseURL: URL(string: "http://localhost:11434")!, + providerType: .ollama, + isReachable: true, + availableModels: ["llama3.2"] + ) + let c = LocalProviderEndpoint( + baseURL: URL(string: "http://localhost:11434")!, + providerType: .ollama, + isReachable: false, + availableModels: [] + ) + + #expect(a == b) + #expect(a != c) + } + + // MARK: - Discovery (No Local Server Running) + + @Test("Discovery returns unreachable when no server is running") + func discoveryUnreachableEndpoint() async { + // Use a port that's almost certainly not running anything + let endpoint = await LocalProviderDiscovery.discover( + providerType: .ollama, + host: "127.0.0.1", + port: 59999, + timeout: 1 + ) + + #expect(!endpoint.isReachable) + #expect(endpoint.availableModels.isEmpty) + #expect(endpoint.providerType == .ollama) + } + + @Test("isOllamaRunning returns false for unreachable endpoint") + func ollamaNotRunning() async { + let unreachableURL = URL(string: "http://127.0.0.1:59998")! + let running = await LocalProviderDiscovery.isOllamaRunning( + at: unreachableURL, + timeout: 1 + ) + + #expect(!running) + } + + @Test("isLlamaCppRunning returns false for unreachable endpoint") + func llamaCppNotRunning() async { + let unreachableURL = URL(string: "http://127.0.0.1:59997")! + let running = await LocalProviderDiscovery.isLlamaCppRunning( + at: unreachableURL, + timeout: 1 + ) + + #expect(!running) + } + + @Test("listOllamaModels returns empty for unreachable endpoint") + func ollamaModelsUnreachable() async { + let unreachableURL = URL(string: "http://127.0.0.1:59996")! + let models = await LocalProviderDiscovery.listOllamaModels( + at: unreachableURL, + timeout: 1 + ) + + #expect(models.isEmpty) + } + + @Test("listLlamaCppModels returns empty for unreachable endpoint") + func llamaCppModelsUnreachable() async { + let unreachableURL = URL(string: "http://127.0.0.1:59995")! + let models = await LocalProviderDiscovery.listLlamaCppModels( + at: unreachableURL, + timeout: 1 + ) + + #expect(models.isEmpty) + } + + @Test("discoverAll returns endpoints for both provider types") + func discoverAllReturnsAllProviders() async { + // Use very short timeout since we likely don't have servers running + let endpoints = await LocalProviderDiscovery.discoverAll(timeout: 0.5) + + // Should return exactly 2 endpoints (one per well-known provider) + #expect(endpoints.count == 2) + + let types = Set(endpoints.map(\.providerType)) + #expect(types.contains(.ollama)) + #expect(types.contains(.llamaCpp)) + } + + // MARK: - Default URLs + + @Test("Default Ollama URL is correct") + func defaultOllamaURL() { + #expect(LocalProviderDiscovery.defaultOllamaURL.absoluteString == "http://localhost:11434") + } + + @Test("Default llama.cpp URL is correct") + func defaultLlamaCppURL() { + #expect(LocalProviderDiscovery.defaultLlamaCppURL.absoluteString == "http://localhost:8080") + } +} diff --git a/Tests/SwiftDBAITests/MultiTurnContextTests.swift b/Tests/SwiftDBAITests/MultiTurnContextTests.swift new file mode 100644 index 0000000..442e85a --- /dev/null +++ b/Tests/SwiftDBAITests/MultiTurnContextTests.swift @@ -0,0 +1,363 @@ +// MultiTurnContextTests.swift +// SwiftDBAI Tests +// +// Tests verifying multi-turn conversation context — follow-up queries +// correctly reference the prior query's table, columns, and results. + +import AnyLanguageModel +import Foundation +import GRDB +import Testing + +@testable import SwiftDBAI + +@Suite("Multi-Turn Context Tests") +struct MultiTurnContextTests { + + // MARK: - Test Database Setup + + /// Creates an in-memory database with users (including age) and orders. + private func makeTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(path: ":memory:") + try db.write { db in + try db.execute(sql: """ + CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + age INTEGER NOT NULL, + email TEXT NOT NULL, + city TEXT NOT NULL + ) + """) + try db.execute(sql: """ + INSERT INTO users (name, age, email, city) VALUES + ('Alice', 25, 'alice@example.com', 'New York'), + ('Bob', 35, 'bob@example.com', 'San Francisco'), + ('Charlie', 42, 'charlie@example.com', 'New York'), + ('Diana', 28, 'diana@example.com', 'Chicago'), + ('Eve', 55, 'eve@example.com', 'San Francisco') + """) + try db.execute(sql: """ + CREATE TABLE orders ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + amount REAL NOT NULL, + status TEXT NOT NULL, + created_at TEXT NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id) + ) + """) + try db.execute(sql: """ + INSERT INTO orders (user_id, amount, status, created_at) VALUES + (1, 99.99, 'completed', '2024-01-15'), + (1, 49.50, 'pending', '2024-02-20'), + (2, 150.00, 'completed', '2024-01-10'), + (3, 200.00, 'completed', '2024-03-01'), + (4, 75.00, 'cancelled', '2024-02-05') + """) + } + return db + } + + // MARK: - Multi-Turn Context Tests + + @Test("Follow-up 'filter those by age > 30' references prior 'show all users' context") + func followUpFilterReferencesUsersTable() async throws { + let db = try makeTestDatabase() + + // Turn 1: "show all users" → SELECT * FROM users (returns 5 rows, LLM summary needed) + // Turn 2: "filter those by age > 30" → should reference users table from context + let mock = PromptCapturingMockModel(responses: [ + "SELECT * FROM users", + "Here are all 5 users in the database.", + "SELECT * FROM users WHERE age > 30", + "Found 3 users over 30: Bob (35), Charlie (42), and Eve (55)." + ]) + + let engine = ChatEngine(database: db, model: mock) + + // First turn: show all users + let response1 = try await engine.send("show all users") + #expect(response1.sql == "SELECT * FROM users") + #expect(response1.queryResult?.rowCount == 5) + + // Second turn: follow-up with implicit reference + let response2 = try await engine.send("filter those by age > 30") + #expect(response2.sql == "SELECT * FROM users WHERE age > 30") + #expect(response2.queryResult?.rowCount == 3) + + // Verify the follow-up prompt includes conversation history + let prompts = mock.capturedPrompts + // Find the prompt for the second SQL generation (skip summary prompts) + let followUpSQLPrompt = prompts.first { prompt in + prompt.contains("filter those by age > 30") && prompt.contains("CONVERSATION HISTORY") + } + #expect(followUpSQLPrompt != nil, "Follow-up prompt should contain CONVERSATION HISTORY") + + // The conversation history should include the prior query and its SQL + if let prompt = followUpSQLPrompt { + #expect(prompt.contains("show all users"), "History should contain prior user message") + #expect(prompt.contains("SELECT * FROM users"), "History should contain prior SQL") + #expect(prompt.contains("filter those by age > 30"), "Prompt should contain current question") + } + } + + @Test("Follow-up correctly inherits table context across multiple turns") + func multipleFollowUpsInheritContext() async throws { + let db = try makeTestDatabase() + + // 3-turn conversation narrowing down results + let mock = PromptCapturingMockModel(responses: [ + "SELECT * FROM users", + "Here are all 5 users.", + "SELECT * FROM users WHERE city = 'New York'", + "Found 2 users in New York: Alice and Charlie.", + "SELECT * FROM users WHERE city = 'New York' AND age > 30", + "Charlie (42) is the only New York user over 30." + ]) + + let engine = ChatEngine(database: db, model: mock) + + // Turn 1 + _ = try await engine.send("show all users") + + // Turn 2 — narrows by city + let response2 = try await engine.send("only those in New York") + #expect(response2.sql == "SELECT * FROM users WHERE city = 'New York'") + #expect(response2.queryResult?.rowCount == 2) + + // Turn 3 — further narrows by age + let response3 = try await engine.send("now filter by age over 30") + #expect(response3.sql == "SELECT * FROM users WHERE city = 'New York' AND age > 30") + #expect(response3.queryResult?.rowCount == 1) + + // Verify third turn's prompt includes the full conversation history + let prompts = mock.capturedPrompts + let thirdTurnPrompt = prompts.last { prompt in + prompt.contains("now filter by age over 30") && prompt.contains("CONVERSATION HISTORY") + } + #expect(thirdTurnPrompt != nil) + + if let prompt = thirdTurnPrompt { + // Should include both prior user messages + #expect(prompt.contains("show all users")) + #expect(prompt.contains("only those in New York")) + // Should include prior SQL + #expect(prompt.contains("SELECT * FROM users")) + #expect(prompt.contains("SELECT * FROM users WHERE city = 'New York'")) + } + } + + @Test("Follow-up switching tables preserves cross-table context") + func followUpSwitchesTableWithContext() async throws { + let db = try makeTestDatabase() + + // Turn 1: query users, Turn 2: ask about their orders + let mock = PromptCapturingMockModel(responses: [ + "SELECT name, age FROM users WHERE age > 30", + "Found 3 users over 30.", + "SELECT o.id, u.name, o.amount, o.status FROM orders o JOIN users u ON o.user_id = u.id WHERE u.age > 30", + "Bob has a $150 completed order, Charlie has a $200 completed order." + ]) + + let engine = ChatEngine(database: db, model: mock) + + // Turn 1: users over 30 + let response1 = try await engine.send("show users over 30") + #expect(response1.queryResult?.rowCount == 3) + + // Turn 2: their orders — references the previous result context + let response2 = try await engine.send("show their orders") + #expect(response2.sql?.contains("JOIN") == true) + + // Verify the follow-up prompt contains the users context + let prompts = mock.capturedPrompts + let orderPrompt = prompts.first { prompt in + prompt.contains("show their orders") && prompt.contains("CONVERSATION HISTORY") + } + #expect(orderPrompt != nil) + + if let prompt = orderPrompt { + #expect(prompt.contains("show users over 30"), "Should contain prior user message") + #expect(prompt.contains("age > 30"), "Should contain prior SQL context for table reference") + } + } + + @Test("Conversation history includes SQL from prior turns for context") + func historyIncludesSQLFromPriorTurns() async throws { + let db = try makeTestDatabase() + + // Both queries are aggregates → no LLM summarization needed + let mock = PromptCapturingMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM users WHERE age > 30", + ]) + + let engine = ChatEngine(database: db, model: mock) + + // Turn 1 + let r1 = try await engine.send("how many users are there?") + #expect(r1.sql == "SELECT COUNT(*) FROM users") + + // Turn 2 — references "those" implicitly + let r2 = try await engine.send("how many of those are over 30?") + #expect(r2.sql == "SELECT COUNT(*) FROM users WHERE age > 30") + + // Verify engine history has all 4 messages (2 user + 2 assistant) + let messages = engine.messages + #expect(messages.count == 4) + #expect(messages[0].role == .user) + #expect(messages[0].content == "how many users are there?") + #expect(messages[1].role == .assistant) + #expect(messages[1].sql == "SELECT COUNT(*) FROM users") + #expect(messages[2].role == .user) + #expect(messages[2].content == "how many of those are over 30?") + #expect(messages[3].role == .assistant) + #expect(messages[3].sql == "SELECT COUNT(*) FROM users WHERE age > 30") + + // The second prompt should reference the first query SQL + let prompts = mock.capturedPrompts + #expect(prompts.count >= 2) + let secondPrompt = prompts[1] + #expect(secondPrompt.contains("CONVERSATION HISTORY")) + #expect(secondPrompt.contains("SELECT COUNT(*) FROM users")) + #expect(secondPrompt.contains("how many users are there?")) + } + + @Test("Follow-up after aggregate uses prior table context") + func followUpAfterAggregateUsesTableContext() async throws { + let db = try makeTestDatabase() + + // Turn 1: aggregate (no LLM summary needed) + // Turn 2: follow-up referencing "those" + let mock = PromptCapturingMockModel(responses: [ + "SELECT AVG(age) FROM users", + "SELECT name, age FROM users WHERE age > 35", + "Charlie (42) and Eve (55) are older than average." + ]) + + let engine = ChatEngine(database: db, model: mock) + + // Turn 1: average age → aggregate, template summary + let r1 = try await engine.send("what is the average age of users?") + #expect(r1.sql == "SELECT AVG(age) FROM users") + + // Turn 2: "who is above that?" — needs the avg context + let r2 = try await engine.send("who is above average?") + #expect(r2.queryResult?.rowCount == 2) + + // Verify context passed + let prompts = mock.capturedPrompts + let followUp = prompts.first { prompt in + prompt.contains("who is above average?") && prompt.contains("CONVERSATION HISTORY") + } + #expect(followUp != nil) + if let prompt = followUp { + #expect(prompt.contains("AVG(age)"), "Should include prior aggregate SQL for context") + #expect(prompt.contains("users"), "Should include table reference from prior turn") + } + } + + @Test("Context window limits how much history is visible in follow-ups") + func contextWindowLimitsHistoryInFollowUps() async throws { + let db = try makeTestDatabase() + + // 3 turns, but context window of 2 messages + let mock = PromptCapturingMockModel(responses: [ + "SELECT COUNT(*) FROM users", + "SELECT COUNT(*) FROM orders", + "SELECT COUNT(*) FROM users WHERE age > 30", + ]) + + let config = ChatEngineConfiguration( + queryTimeout: nil, + contextWindowSize: 2 + ) + + let engine = ChatEngine( + database: db, + model: mock, + configuration: config + ) + + _ = try await engine.send("how many users?") + _ = try await engine.send("how many orders?") + _ = try await engine.send("how many users over 30?") + + // The third prompt should only have the last 2 messages from turn 2 + let prompts = mock.capturedPrompts + #expect(prompts.count >= 3) + + let thirdPrompt = prompts[2] + #expect(thirdPrompt.contains("CONVERSATION HISTORY")) + // Turn 2 context should be present + #expect(thirdPrompt.contains("how many orders?")) + #expect(thirdPrompt.contains("SELECT COUNT(*) FROM orders")) + // Turn 1 context should be trimmed (window=2 means last 2 messages) + #expect(!thirdPrompt.contains("how many users?\n"), "First turn should be trimmed from context window") + } + + @Test("clearHistory resets context so follow-ups have no prior history") + func clearHistoryResetsFollowUpContext() async throws { + let db = try makeTestDatabase() + + let mock = PromptCapturingMockModel(responses: [ + "SELECT * FROM users", + "Here are the 5 users.", + "SELECT COUNT(*) FROM users", + ]) + + let engine = ChatEngine(database: db, model: mock) + + // Turn 1 + _ = try await engine.send("show all users") + #expect(engine.messages.count == 2) + + // Clear history + engine.clearHistory() + #expect(engine.messages.isEmpty) + + // Turn 2 after clear — should NOT have conversation history + _ = try await engine.send("count all users") + + let prompts = mock.capturedPrompts + let lastPrompt = prompts.last! + // After clearing, the prompt should NOT contain conversation history + #expect(!lastPrompt.contains("CONVERSATION HISTORY"), + "After clearHistory(), follow-up should not have prior context") + #expect(!lastPrompt.contains("show all users"), + "After clearHistory(), prior messages should be gone") + } + + @Test("Multi-turn with result data in context enables informed follow-ups") + func resultDataInContextEnablesInformedFollowUps() async throws { + let db = try makeTestDatabase() + + // Turn 1: list users → multi-row result, LLM summarizes + // Turn 2: "sort those by age" → references same table + let mock = PromptCapturingMockModel(responses: [ + "SELECT name, age, city FROM users", + "Found 5 users: Alice (25, NY), Bob (35, SF), Charlie (42, NY), Diana (28, Chicago), Eve (55, SF).", + "SELECT name, age, city FROM users ORDER BY age DESC", + "Users sorted by age: Eve (55), Charlie (42), Bob (35), Diana (28), Alice (25)." + ]) + + let engine = ChatEngine(database: db, model: mock) + + let r1 = try await engine.send("list all users with their age and city") + #expect(r1.queryResult?.rowCount == 5) + #expect(r1.queryResult?.columns.contains("age") == true) + #expect(r1.queryResult?.columns.contains("city") == true) + + let r2 = try await engine.send("sort those by age descending") + #expect(r2.sql == "SELECT name, age, city FROM users ORDER BY age DESC") + + // Verify the assistant message in history includes the SQL + let messages = engine.messages + #expect(messages.count == 4) + // First assistant message should have the SQL recorded + #expect(messages[1].sql == "SELECT name, age, city FROM users") + // Second assistant should have the sorted SQL + #expect(messages[3].sql == "SELECT name, age, city FROM users ORDER BY age DESC") + } +} diff --git a/Tests/SwiftDBAITests/OnDeviceProviderConfigurationTests.swift b/Tests/SwiftDBAITests/OnDeviceProviderConfigurationTests.swift new file mode 100644 index 0000000..a9e78ed --- /dev/null +++ b/Tests/SwiftDBAITests/OnDeviceProviderConfigurationTests.swift @@ -0,0 +1,508 @@ +// OnDeviceProviderConfigurationTests.swift +// SwiftDBAI Tests +// +// Tests for on-device provider configurations (CoreML, MLX) including +// configuration validation, inference pipeline setup, and system readiness. + +import AnyLanguageModel +import Foundation +@testable import SwiftDBAI +import Testing + +@Suite("OnDeviceProviderConfiguration") +struct OnDeviceProviderConfigurationTests { + + // MARK: - OnDeviceProviderType + + @Test("OnDeviceProviderType has CoreML and MLX cases") + func providerTypeCases() { + let cases = OnDeviceProviderType.allCases + #expect(cases.count == 2) + #expect(cases.contains(.coreML)) + #expect(cases.contains(.mlx)) + } + + @Test("OnDeviceProviderType raw values are descriptive") + func providerTypeRawValues() { + #expect(OnDeviceProviderType.coreML.rawValue == "coreML") + #expect(OnDeviceProviderType.mlx.rawValue == "mlx") + } + + // MARK: - CoreML Configuration + + @Test("CoreML configuration stores all properties") + func coreMLBasicConfiguration() { + let url = URL(fileURLWithPath: "/tmp/TestModel.mlmodelc") + let config = CoreMLProviderConfiguration( + modelURL: url, + computeUnits: .cpuAndGPU, + maxResponseTokens: 1024, + useSampling: true, + temperature: 0.3 + ) + + #expect(config.modelURL == url) + #expect(config.computeUnits == .cpuAndGPU) + #expect(config.maxResponseTokens == 1024) + #expect(config.useSampling == true) + #expect(config.temperature == 0.3) + } + + @Test("CoreML configuration uses sensible defaults") + func coreMLDefaultConfiguration() { + let url = URL(fileURLWithPath: "/tmp/TestModel.mlmodelc") + let config = CoreMLProviderConfiguration(modelURL: url) + + #expect(config.computeUnits == .all) + #expect(config.maxResponseTokens == 2048) + #expect(config.useSampling == false) + #expect(config.temperature == 0.1) + } + + @Test("CoreML validation fails for non-mlmodelc extension") + func coreMLValidateWrongExtension() { + let url = URL(fileURLWithPath: "/tmp/TestModel.onnx") + let config = CoreMLProviderConfiguration(modelURL: url) + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("CoreML validation fails for missing model file") + func coreMLValidateMissingFile() { + let url = URL(fileURLWithPath: "/nonexistent/path/Model.mlmodelc") + let config = CoreMLProviderConfiguration(modelURL: url) + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("CoreML configuration is Equatable") + func coreMLEquatable() { + let url = URL(fileURLWithPath: "/tmp/TestModel.mlmodelc") + let a = CoreMLProviderConfiguration(modelURL: url, computeUnits: .all) + let b = CoreMLProviderConfiguration(modelURL: url, computeUnits: .all) + let c = CoreMLProviderConfiguration(modelURL: url, computeUnits: .cpuOnly) + + #expect(a == b) + #expect(a != c) + } + + // MARK: - ComputeUnitPreference + + @Test("ComputeUnitPreference has all expected cases") + func computeUnitCases() { + let cases = ComputeUnitPreference.allCases + #expect(cases.count == 4) + #expect(cases.contains(.all)) + #expect(cases.contains(.cpuOnly)) + #expect(cases.contains(.cpuAndGPU)) + #expect(cases.contains(.cpuAndNeuralEngine)) + } + + // MARK: - MLX Configuration + + @Test("MLX configuration stores all properties") + func mlxBasicConfiguration() { + let dir = URL(fileURLWithPath: "/tmp/models/my-model") + let config = MLXProviderConfiguration( + modelId: "mlx-community/Test-Model-4bit", + localDirectory: dir, + gpuMemory: .minimal, + maxResponseTokens: 512, + temperature: 0.2, + topP: 0.9, + repetitionPenalty: 1.2 + ) + + #expect(config.modelId == "mlx-community/Test-Model-4bit") + #expect(config.localDirectory == dir) + #expect(config.gpuMemory == .minimal) + #expect(config.maxResponseTokens == 512) + #expect(config.temperature == 0.2) + #expect(config.topP == 0.9) + #expect(config.repetitionPenalty == 1.2) + } + + @Test("MLX configuration uses sensible defaults") + func mlxDefaultConfiguration() { + let config = MLXProviderConfiguration(modelId: "test-model") + + #expect(config.localDirectory == nil) + #expect(config.gpuMemory == .automatic) + #expect(config.maxResponseTokens == 2048) + #expect(config.temperature == 0.1) + #expect(config.topP == 0.95) + #expect(config.repetitionPenalty == 1.1) + } + + @Test("MLX validation fails for empty model ID") + func mlxValidateEmptyModelId() { + let config = MLXProviderConfiguration(modelId: "") + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("MLX validation fails for nonexistent local directory") + func mlxValidateMissingDirectory() { + let config = MLXProviderConfiguration( + modelId: "test-model", + localDirectory: URL(fileURLWithPath: "/nonexistent/directory") + ) + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("MLX validation fails for negative temperature") + func mlxValidateNegativeTemperature() { + let config = MLXProviderConfiguration( + modelId: "test-model", + temperature: -0.5 + ) + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("MLX validation fails for topP out of range") + func mlxValidateInvalidTopP() { + let configZero = MLXProviderConfiguration( + modelId: "test-model", + topP: 0.0 + ) + + #expect(throws: OnDeviceProviderError.self) { + try configZero.validate() + } + + let configOver = MLXProviderConfiguration( + modelId: "test-model", + topP: 1.5 + ) + + #expect(throws: OnDeviceProviderError.self) { + try configOver.validate() + } + } + + @Test("MLX validation fails for zero repetition penalty") + func mlxValidateInvalidRepetitionPenalty() { + let config = MLXProviderConfiguration( + modelId: "test-model", + repetitionPenalty: 0.0 + ) + + #expect(throws: OnDeviceProviderError.self) { + try config.validate() + } + } + + @Test("MLX validation succeeds for valid configuration") + func mlxValidateSuccess() throws { + let config = MLXProviderConfiguration(modelId: "test-model") + // Should not throw (no local directory set, model ID is non-empty) + try config.validate() + } + + @Test("MLX configuration is Equatable") + func mlxEquatable() { + let a = MLXProviderConfiguration(modelId: "model-a") + let b = MLXProviderConfiguration(modelId: "model-a") + let c = MLXProviderConfiguration(modelId: "model-b") + + #expect(a == b) + #expect(a != c) + } + + // MARK: - Well-Known MLX Models + + @Test("Llama 3.2 3B preset has correct model ID") + func llama3_2_3BPreset() { + let config = MLXProviderConfiguration.llama3_2_3B() + #expect(config.modelId == "mlx-community/Llama-3.2-3B-Instruct-4bit") + #expect(config.temperature == 0.1) + #expect(config.maxResponseTokens == 2048) + } + + @Test("Qwen 2.5 Coder 3B preset has correct model ID") + func qwen2_5_coder3BPreset() { + let config = MLXProviderConfiguration.qwen2_5_coder_3B() + #expect(config.modelId == "mlx-community/Qwen2.5-Coder-3B-Instruct-4bit") + #expect(config.temperature == 0.05) + } + + @Test("Phi 3.5 Mini preset has correct model ID") + func phi3_5_miniPreset() { + let config = MLXProviderConfiguration.phi3_5_mini() + #expect(config.modelId == "mlx-community/Phi-3.5-mini-instruct-4bit") + #expect(config.temperature == 0.1) + } + + @Test("Well-known models accept custom GPU memory config") + func wellKnownModelsCustomGPU() { + let config = MLXProviderConfiguration.llama3_2_3B( + gpuMemory: .minimal + ) + #expect(config.gpuMemory == .minimal) + } + + // MARK: - GPU Memory Configuration + + @Test("Automatic GPU memory config scales with RAM") + func automaticGPUMemory() { + let config = MLXGPUMemoryConfig.automatic + #expect(config.activeCacheLimit > 0) + #expect(config.idleCacheLimit == 50_000_000) + #expect(config.clearCacheOnEviction == true) + } + + @Test("Minimal GPU memory config is conservative") + func minimalGPUMemory() { + let config = MLXGPUMemoryConfig.minimal + #expect(config.activeCacheLimit == 64_000_000) + #expect(config.idleCacheLimit == 16_000_000) + #expect(config.clearCacheOnEviction == true) + } + + @Test("Unconstrained GPU memory config uses max values") + func unconstrainedGPUMemory() { + let config = MLXGPUMemoryConfig.unconstrained + #expect(config.activeCacheLimit == Int.max) + #expect(config.idleCacheLimit == Int.max) + #expect(config.clearCacheOnEviction == false) + } + + @Test("GPU memory config is Equatable") + func gpuMemoryEquatable() { + #expect(MLXGPUMemoryConfig.minimal == MLXGPUMemoryConfig.minimal) + #expect(MLXGPUMemoryConfig.minimal != MLXGPUMemoryConfig.unconstrained) + } + + // MARK: - On-Device Provider Errors + + @Test("OnDeviceProviderError has descriptive messages") + func errorDescriptions() { + let errors: [OnDeviceProviderError] = [ + .modelNotFound(URL(fileURLWithPath: "/tmp/model")), + .invalidModelFormat(expected: ".mlmodelc", actual: ".onnx"), + .emptyModelId, + .invalidParameter(name: "temperature", value: "-1", reason: "Must be non-negative"), + .providerUnavailable(.mlx, reason: "MLX build flag not enabled"), + .modelLoadFailed(reason: "Out of memory"), + .inferenceFailed(reason: "Token limit exceeded"), + ] + + for error in errors { + #expect(error.errorDescription != nil) + #expect(!error.errorDescription!.isEmpty) + } + } + + @Test("OnDeviceProviderError is Equatable") + func errorEquatable() { + let a = OnDeviceProviderError.emptyModelId + let b = OnDeviceProviderError.emptyModelId + let c = OnDeviceProviderError.modelLoadFailed(reason: "test") + + #expect(a == b) + #expect(a != c) + } + + // MARK: - Inference Pipeline + + @Test("MLX inference pipeline initializes with correct type") + func mlxPipelineInit() { + let config = MLXProviderConfiguration.llama3_2_3B() + let pipeline = OnDeviceInferencePipeline(mlxConfiguration: config) + + #expect(pipeline.providerType == .mlx) + #expect(pipeline.mlxConfiguration != nil) + #expect(pipeline.coreMLConfiguration == nil) + #expect(pipeline.status == .notLoaded) + } + + @Test("CoreML inference pipeline initializes with correct type") + func coreMLPipelineInit() { + let url = URL(fileURLWithPath: "/tmp/TestModel.mlmodelc") + let config = CoreMLProviderConfiguration(modelURL: url) + let pipeline = OnDeviceInferencePipeline(coreMLConfiguration: config) + + #expect(pipeline.providerType == .coreML) + #expect(pipeline.coreMLConfiguration != nil) + #expect(pipeline.mlxConfiguration == nil) + #expect(pipeline.status == .notLoaded) + } + + @Test("Pipeline validates MLX configuration") + func pipelineValidatesMLX() throws { + let validConfig = MLXProviderConfiguration(modelId: "test-model") + let pipeline = OnDeviceInferencePipeline(mlxConfiguration: validConfig) + try pipeline.validateConfiguration() + + let invalidConfig = MLXProviderConfiguration(modelId: "") + let invalidPipeline = OnDeviceInferencePipeline(mlxConfiguration: invalidConfig) + #expect(throws: OnDeviceProviderError.self) { + try invalidPipeline.validateConfiguration() + } + } + + @Test("Pipeline validates CoreML configuration") + func pipelineValidatesCoreML() { + let url = URL(fileURLWithPath: "/tmp/TestModel.onnx") + let config = CoreMLProviderConfiguration(modelURL: url) + let pipeline = OnDeviceInferencePipeline(coreMLConfiguration: config) + + #expect(throws: OnDeviceProviderError.self) { + try pipeline.validateConfiguration() + } + } + + @Test("Pipeline provides SQL generation hints for MLX") + func mlxSQLHints() { + let config = MLXProviderConfiguration( + modelId: "test-model", + maxResponseTokens: 512, + temperature: 0.2 + ) + let pipeline = OnDeviceInferencePipeline(mlxConfiguration: config) + let hints = pipeline.recommendedSQLGenerationHints + + #expect(hints.maxTokens == 512) + #expect(hints.temperature == 0.2) + #expect(hints.useSampling == true) + #expect(hints.systemPromptSuffix.contains("MLX")) + } + + @Test("Pipeline provides SQL generation hints for CoreML") + func coreMLSQLHints() { + let url = URL(fileURLWithPath: "/tmp/TestModel.mlmodelc") + let config = CoreMLProviderConfiguration( + modelURL: url, + maxResponseTokens: 1024, + useSampling: false, + temperature: 0.05 + ) + let pipeline = OnDeviceInferencePipeline(coreMLConfiguration: config) + let hints = pipeline.recommendedSQLGenerationHints + + #expect(hints.maxTokens == 1024) + #expect(hints.temperature == 0.05) + #expect(hints.useSampling == false) + #expect(hints.systemPromptSuffix.contains("SQL")) + } + + // MARK: - System Readiness + + @Test("System capability check returns valid data") + func systemCapability() { + let capability = OnDeviceModelReadiness.checkSystemCapability() + + #expect(capability.totalRAM > 0) + // On any modern test machine, we should have at least some RAM + #expect(capability.totalRAM > 1024 * 1024 * 1024) // > 1GB + + // On Apple silicon Macs, this should be true + #if arch(arm64) + #expect(capability.hasNeuralEngine == true) + #endif + } + + @Test("Suggested MLX model returns a valid configuration") + func suggestedMLXModel() { + let config = OnDeviceModelReadiness.suggestedMLXModel() + #expect(!config.modelId.isEmpty) + #expect(config.temperature >= 0) + #expect(config.maxResponseTokens > 0) + } + + @Test("Recommended model size enum has correct raw values") + func recommendedModelSizeRawValues() { + #expect(OnDeviceModelReadiness.RecommendedModelSize.small.rawValue == "small") + #expect(OnDeviceModelReadiness.RecommendedModelSize.medium.rawValue == "medium") + #expect(OnDeviceModelReadiness.RecommendedModelSize.large.rawValue == "large") + } + + // MARK: - ProviderConfiguration Integration + + @Test("onDeviceMLX creates a ProviderConfiguration") + func onDeviceMLXProviderConfig() { + let mlxConfig = MLXProviderConfiguration.llama3_2_3B() + let providerConfig = ProviderConfiguration.onDeviceMLX(mlxConfig) + + #expect(providerConfig.model == mlxConfig.modelId) + #expect(!providerConfig.hasValidAPIKey) // No API key needed for on-device + } + + @Test("onDeviceCoreML creates a ProviderConfiguration") + func onDeviceCoreMLProviderConfig() { + let url = URL(fileURLWithPath: "/tmp/SQLModel.mlmodelc") + let coreMLConfig = CoreMLProviderConfiguration(modelURL: url) + let providerConfig = ProviderConfiguration.onDeviceCoreML(coreMLConfig) + + #expect(providerConfig.model == "SQLModel.mlmodelc") + #expect(!providerConfig.hasValidAPIKey) + } + + // MARK: - Pipeline Status + + @Test("Pipeline status transitions") + func pipelineStatusTransitions() { + let config = MLXProviderConfiguration(modelId: "test-model") + let pipeline = OnDeviceInferencePipeline(mlxConfiguration: config) + + #expect(pipeline.status == .notLoaded) + + pipeline.setStatus(.loading) + #expect(pipeline.status == .loading) + + pipeline.setStatus(.ready) + #expect(pipeline.status == .ready) + + pipeline.setStatus(.failed("Out of memory")) + #expect(pipeline.status == .failed("Out of memory")) + } + + @Test("Pipeline Status is Equatable") + func pipelineStatusEquatable() { + #expect(OnDeviceInferencePipeline.Status.notLoaded == .notLoaded) + #expect(OnDeviceInferencePipeline.Status.loading == .loading) + #expect(OnDeviceInferencePipeline.Status.ready == .ready) + #expect(OnDeviceInferencePipeline.Status.failed("a") == .failed("a")) + #expect(OnDeviceInferencePipeline.Status.failed("a") != .failed("b")) + #expect(OnDeviceInferencePipeline.Status.notLoaded != .ready) + } + + // MARK: - SQL Generation Hints + + @Test("SQL generation hints are Equatable") + func sqlHintsEquatable() { + let a = OnDeviceSQLGenerationHints( + maxTokens: 512, + temperature: 0.1, + systemPromptSuffix: "test", + useSampling: true + ) + let b = OnDeviceSQLGenerationHints( + maxTokens: 512, + temperature: 0.1, + systemPromptSuffix: "test", + useSampling: true + ) + let c = OnDeviceSQLGenerationHints( + maxTokens: 1024, + temperature: 0.1, + systemPromptSuffix: "test", + useSampling: true + ) + + #expect(a == b) + #expect(a != c) + } +} diff --git a/Tests/SwiftDBAITests/PresentationTests.swift b/Tests/SwiftDBAITests/PresentationTests.swift new file mode 100644 index 0000000..4dbbc1f --- /dev/null +++ b/Tests/SwiftDBAITests/PresentationTests.swift @@ -0,0 +1,247 @@ +// PresentationTests.swift +// SwiftDBAITests +// +// Tests for presentation modalities: DataChatSheet, DataChatViewController, +// and view modifier helpers. + +import SwiftUI +import Testing +import ViewInspector +import GRDB +@testable import SwiftDBAI + +// MARK: - Helpers + +private func makeSampleDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue() + try db.write { db in + try db.execute(sql: """ + CREATE TABLE items ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL + ); + INSERT INTO items (name) VALUES ('Alpha'); + """) + } + return db +} + +// MARK: - DataChatSheet Tests + +@Suite("DataChatSheet Tests") +struct DataChatSheetTests { + + @Test("DataChatSheet renders NavigationStack with title") + @MainActor + func sheetRendersNavigationStackWithTitle() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel(), + title: "Test Chat" + ) + + let view = try sheet.inspect() + // NavigationStack should be the root + let navStack = try view.navigationStack() + #expect(navStack != nil) + } + + @Test("DataChatSheet has Done button") + @MainActor + func sheetHasDoneButton() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel() + ) + + let view = try sheet.inspect() + // Find the Done button in the toolbar + let button = try view.find(button: "Done") + #expect(button != nil) + } + + @Test("DataChatSheet renders DataChatView inside") + @MainActor + func sheetContainsDataChatView() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel() + ) + + let view = try sheet.inspect() + // DataChatView should be present within the NavigationStack + let dataChatView = try view.find(DataChatView.self) + #expect(dataChatView != nil) + } + + @Test("DataChatSheet path-based init works") + @MainActor + func sheetPathInit() throws { + let tempDir = FileManager.default.temporaryDirectory + let dbPath = tempDir.appendingPathComponent("sheet_test_\(UUID().uuidString).sqlite").path + let db = try DatabaseQueue(path: dbPath) + try db.write { db in + try db.execute(sql: "CREATE TABLE t (id INTEGER PRIMARY KEY)") + } + + let sheet = DataChatSheet( + databasePath: dbPath, + model: MockLanguageModel(), + title: "Path Chat" + ) + + let view = try sheet.inspect() + let navStack = try view.navigationStack() + #expect(navStack != nil) + + try? FileManager.default.removeItem(atPath: dbPath) + } + + @Test("DataChatSheet uses custom title") + @MainActor + func sheetCustomTitle() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel(), + title: "My Custom Title" + ) + + // Verify the title property is set correctly + #expect(sheet.title == "My Custom Title") + } + + @Test("DataChatSheet defaults to AI Chat title") + @MainActor + func sheetDefaultTitle() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel() + ) + + #expect(sheet.title == "AI Chat") + } + + @Test("DataChatSheet defaults to read-only allowlist") + @MainActor + func sheetDefaultAllowlist() throws { + let db = try makeSampleDatabase() + let sheet = DataChatSheet( + database: db, + model: MockLanguageModel() + ) + + #expect(sheet.allowlist == .readOnly) + } +} + +// MARK: - DataChatViewController Tests + +#if canImport(UIKit) && !os(watchOS) +@Suite("DataChatViewController Tests") +struct DataChatViewControllerTests { + + @Test("DataChatViewController can be instantiated with database path") + @MainActor + func viewControllerPathInit() throws { + let tempDir = FileManager.default.temporaryDirectory + let dbPath = tempDir.appendingPathComponent("vc_test_\(UUID().uuidString).sqlite").path + let db = try DatabaseQueue(path: dbPath) + try db.write { db in + try db.execute(sql: "CREATE TABLE t (id INTEGER PRIMARY KEY)") + } + + let vc = DataChatViewController( + databasePath: dbPath, + model: MockLanguageModel() + ) + + #expect(vc.modalPresentationStyle == .formSheet) + + try? FileManager.default.removeItem(atPath: dbPath) + } + + @Test("DataChatViewController can be instantiated with database connection") + @MainActor + func viewControllerDatabaseInit() throws { + let db = try makeSampleDatabase() + + let vc = DataChatViewController( + database: db, + model: MockLanguageModel(), + title: "VC Chat" + ) + + #expect(vc.modalPresentationStyle == .formSheet) + } +} +#endif + +// MARK: - View Modifier Tests + +@Suite("DataChatSheet Modifier Tests") +struct DataChatSheetModifierTests { + + @Test("dataChatSheet modifier creates sheet correctly") + @MainActor + func sheetModifierCreatesSheet() throws { + let db = try makeSampleDatabase() + + struct TestHost: View { + @State var showChat = false + let db: DatabaseQueue + + var body: some View { + Text("Hello") + .dataChatSheet( + isPresented: $showChat, + database: db, + model: MockLanguageModel(), + title: "Modifier Chat" + ) + } + } + + let host = TestHost(db: db) + // Verify it compiles and can be inspected + let view = try host.inspect() + let text = try view.find(text: "Hello") + #expect(text != nil) + } + + @Test("dataChatSheet path modifier creates sheet correctly") + @MainActor + func sheetPathModifierCreatesSheet() throws { + let tempDir = FileManager.default.temporaryDirectory + let dbPath = tempDir.appendingPathComponent("mod_test_\(UUID().uuidString).sqlite").path + let db = try DatabaseQueue(path: dbPath) + try db.write { db in + try db.execute(sql: "CREATE TABLE t (id INTEGER PRIMARY KEY)") + } + + struct TestHost: View { + @State var showChat = false + let dbPath: String + + var body: some View { + Text("World") + .dataChatSheet( + isPresented: $showChat, + databasePath: dbPath, + model: MockLanguageModel() + ) + } + } + + let host = TestHost(dbPath: dbPath) + let view = try host.inspect() + let text = try view.find(text: "World") + #expect(text != nil) + + try? FileManager.default.removeItem(atPath: dbPath) + } +} diff --git a/Tests/SwiftDBAITests/PromptBuilderTests.swift b/Tests/SwiftDBAITests/PromptBuilderTests.swift new file mode 100644 index 0000000..be22005 --- /dev/null +++ b/Tests/SwiftDBAITests/PromptBuilderTests.swift @@ -0,0 +1,254 @@ +// PromptBuilderTests.swift +// SwiftDBAI + +import Testing +@testable import SwiftDBAI + +@Suite("PromptBuilder") +struct PromptBuilderTests { + + // MARK: - Helpers + + /// Creates a sample schema for testing. + private func makeSampleSchema() -> DatabaseSchema { + let usersTable = TableSchema( + name: "users", + columns: [ + ColumnSchema(cid: 0, name: "id", type: "INTEGER", isNotNull: true, defaultValue: nil, isPrimaryKey: true), + ColumnSchema(cid: 1, name: "name", type: "TEXT", isNotNull: true, defaultValue: nil, isPrimaryKey: false), + ColumnSchema(cid: 2, name: "email", type: "TEXT", isNotNull: false, defaultValue: nil, isPrimaryKey: false), + ColumnSchema(cid: 3, name: "created_at", type: "TEXT", isNotNull: false, defaultValue: "CURRENT_TIMESTAMP", isPrimaryKey: false), + ], + primaryKey: ["id"], + foreignKeys: [], + indexes: [ + IndexSchema(name: "idx_users_email", isUnique: true, columns: ["email"]) + ] + ) + + let ordersTable = TableSchema( + name: "orders", + columns: [ + ColumnSchema(cid: 0, name: "id", type: "INTEGER", isNotNull: true, defaultValue: nil, isPrimaryKey: true), + ColumnSchema(cid: 1, name: "user_id", type: "INTEGER", isNotNull: true, defaultValue: nil, isPrimaryKey: false), + ColumnSchema(cid: 2, name: "total", type: "REAL", isNotNull: true, defaultValue: nil, isPrimaryKey: false), + ColumnSchema(cid: 3, name: "status", type: "TEXT", isNotNull: true, defaultValue: "'pending'", isPrimaryKey: false), + ], + primaryKey: ["id"], + foreignKeys: [ + ForeignKeySchema(fromColumn: "user_id", toTable: "users", toColumn: "id", onUpdate: "NO ACTION", onDelete: "CASCADE") + ], + indexes: [] + ) + + return DatabaseSchema( + tables: ["users": usersTable, "orders": ordersTable], + tableNames: ["users", "orders"] + ) + } + + private func makeEmptySchema() -> DatabaseSchema { + DatabaseSchema(tables: [:], tableNames: []) + } + + // MARK: - System Instructions Tests + + @Test("System instructions contain role section") + func systemInstructionsContainRole() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("ROLE")) + #expect(instructions.contains("SQL assistant")) + #expect(instructions.contains("SQLite database")) + } + + @Test("System instructions contain schema") + func systemInstructionsContainSchema() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("DATABASE SCHEMA")) + #expect(instructions.contains("TABLE users")) + #expect(instructions.contains("TABLE orders")) + #expect(instructions.contains("name TEXT")) + #expect(instructions.contains("email TEXT")) + } + + @Test("System instructions contain foreign keys from schema") + func systemInstructionsContainForeignKeys() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("FOREIGN KEY")) + #expect(instructions.contains("REFERENCES users(id)")) + } + + @Test("System instructions contain SQL generation rules") + func systemInstructionsContainRules() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("SQL GENERATION RULES")) + #expect(instructions.contains("Use ONLY the tables and columns")) + #expect(instructions.contains("Never generate DDL")) + } + + @Test("System instructions contain output format section") + func systemInstructionsContainOutputFormat() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("OUTPUT FORMAT")) + } + + @Test("Default allowlist is read-only") + func defaultAllowlistIsReadOnly() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("ONLY generate SELECT queries")) + #expect(instructions.contains("No data modifications")) + } + + @Test("Standard allowlist shows correct operations") + func standardAllowlistInstructions() { + let builder = PromptBuilder(schema: makeSampleSchema(), allowlist: .standard) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("INSERT")) + #expect(instructions.contains("SELECT")) + #expect(instructions.contains("UPDATE")) + } + + @Test("Unrestricted allowlist warns about DELETE") + func unrestrictedAllowlistWarnsAboutDelete() { + let builder = PromptBuilder(schema: makeSampleSchema(), allowlist: .unrestricted) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("DELETE")) + #expect(instructions.contains("destructive")) + #expect(instructions.contains("confirmation")) + } + + @Test("Additional context is appended") + func additionalContextAppended() { + let builder = PromptBuilder( + schema: makeSampleSchema(), + additionalContext: "All dates are stored in ISO 8601 format." + ) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("ADDITIONAL CONTEXT")) + #expect(instructions.contains("ISO 8601")) + } + + @Test("No additional context section when nil") + func noAdditionalContextWhenNil() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(!instructions.contains("ADDITIONAL CONTEXT")) + } + + @Test("No additional context section when empty string") + func noAdditionalContextWhenEmpty() { + let builder = PromptBuilder(schema: makeSampleSchema(), additionalContext: "") + let instructions = builder.buildSystemInstructions() + + #expect(!instructions.contains("ADDITIONAL CONTEXT")) + } + + @Test("Empty schema produces valid instructions") + func emptySchemaProducesValidInstructions() { + let builder = PromptBuilder(schema: makeEmptySchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("ROLE")) + #expect(instructions.contains("SQL GENERATION RULES")) + // Schema section should still be present, just empty + #expect(instructions.contains("DATABASE SCHEMA")) + } + + // MARK: - User Prompt Tests + + @Test("User prompt passes through question directly") + func userPromptPassesThrough() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let prompt = builder.buildUserPrompt("How many users signed up this week?") + + #expect(prompt == "How many users signed up this week?") + } + + // MARK: - Follow-up Prompt Tests + + @Test("Follow-up prompt includes previous context") + func followUpPromptIncludesPreviousContext() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let prompt = builder.buildFollowUpPrompt( + "Now sort them by name", + previousSQL: "SELECT * FROM users WHERE created_at > date('now', '-7 days')", + previousResultSummary: "Found 42 users who signed up this week" + ) + + #expect(prompt.contains("Previous query:")) + #expect(prompt.contains("SELECT * FROM users")) + #expect(prompt.contains("Previous result:")) + #expect(prompt.contains("42 users")) + #expect(prompt.contains("Follow-up question:")) + #expect(prompt.contains("sort them by name")) + } + + // MARK: - Schema Description Quality + + @Test("Schema includes column types and constraints") + func schemaIncludesColumnDetails() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + // Should include type info + #expect(instructions.contains("INTEGER")) + #expect(instructions.contains("TEXT")) + #expect(instructions.contains("REAL")) + + // Should include constraints + #expect(instructions.contains("NOT NULL")) + #expect(instructions.contains("PRIMARY KEY")) + } + + @Test("Schema includes index information") + func schemaIncludesIndexes() { + let builder = PromptBuilder(schema: makeSampleSchema()) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("INDEX")) + #expect(instructions.contains("idx_users_email")) + } + + // MARK: - Sendable Conformance + + @Test("PromptBuilder is Sendable") + func promptBuilderIsSendable() async { + let builder = PromptBuilder(schema: makeSampleSchema()) + + // Verify it can be sent across concurrency boundaries + let instructions = await Task.detached { + builder.buildSystemInstructions() + }.value + + #expect(instructions.contains("ROLE")) + } + + // MARK: - Custom Allowlist + + @Test("Custom allowlist with select and delete only") + func customAllowlist() { + let allowlist = OperationAllowlist([.select, .delete]) + let builder = PromptBuilder(schema: makeSampleSchema(), allowlist: allowlist) + let instructions = builder.buildSystemInstructions() + + #expect(instructions.contains("DELETE")) + #expect(instructions.contains("SELECT")) + #expect(instructions.contains("destructive")) + } +} diff --git a/Tests/SwiftDBAITests/ProviderConfigurationTests.swift b/Tests/SwiftDBAITests/ProviderConfigurationTests.swift new file mode 100644 index 0000000..4904698 --- /dev/null +++ b/Tests/SwiftDBAITests/ProviderConfigurationTests.swift @@ -0,0 +1,325 @@ +// ProviderConfigurationTests.swift +// SwiftDBAI Tests +// +// Tests for ProviderConfiguration — verifying all cloud provider configurations +// produce valid LanguageModel instances with correct settings. + +import AnyLanguageModel +import Foundation +@testable import SwiftDBAI +import Testing + +@Suite("ProviderConfiguration") +struct ProviderConfigurationTests { + + // MARK: - OpenAI Configuration + + @Test("OpenAI configuration stores provider and model") + func openAIBasicConfiguration() { + let config = ProviderConfiguration.openAI( + apiKey: "sk-test-key-123", + model: "gpt-4o" + ) + + #expect(config.provider == .openAI) + #expect(config.model == "gpt-4o") + #expect(config.apiKey == "sk-test-key-123") + #expect(config.hasValidAPIKey) + } + + @Test("OpenAI configuration produces a valid LanguageModel") + func openAIMakeModel() { + let config = ProviderConfiguration.openAI( + apiKey: "sk-test-key", + model: "gpt-4o-mini" + ) + + let model = config.makeModel() + #expect(model is OpenAILanguageModel) + } + + @Test("OpenAI with custom base URL for compatible services") + func openAICustomBaseURL() { + let customURL = URL(string: "https://my-proxy.example.com/v1/")! + let config = ProviderConfiguration.openAI( + apiKey: "sk-proxy-key", + model: "gpt-4o", + baseURL: customURL + ) + + #expect(config.baseURL == customURL) + let model = config.makeModel() + #expect(model is OpenAILanguageModel) + } + + @Test("OpenAI with Responses API variant") + func openAIResponsesVariant() { + let config = ProviderConfiguration.openAI( + apiKey: "sk-test", + model: "gpt-4o", + variant: .responses + ) + + #expect(config.openAIVariant == .responses) + let model = config.makeModel() + #expect(model is OpenAILanguageModel) + } + + @Test("OpenAI with dynamic key provider captures key by reference") + func openAIDynamicKeyProvider() { + nonisolated(unsafe) var currentKey = "sk-initial" + let config = ProviderConfiguration.openAI( + apiKeyProvider: { currentKey }, + model: "gpt-4o" + ) + + #expect(config.apiKey == "sk-initial") + currentKey = "sk-rotated" + #expect(config.apiKey == "sk-rotated") + } + + // MARK: - Anthropic Configuration + + @Test("Anthropic configuration stores provider and model") + func anthropicBasicConfiguration() { + let config = ProviderConfiguration.anthropic( + apiKey: "sk-ant-test-key", + model: "claude-sonnet-4-20250514" + ) + + #expect(config.provider == .anthropic) + #expect(config.model == "claude-sonnet-4-20250514") + #expect(config.apiKey == "sk-ant-test-key") + #expect(config.hasValidAPIKey) + } + + @Test("Anthropic configuration produces a valid LanguageModel") + func anthropicMakeModel() { + let config = ProviderConfiguration.anthropic( + apiKey: "sk-ant-test", + model: "claude-sonnet-4-20250514" + ) + + let model = config.makeModel() + #expect(model is AnthropicLanguageModel) + } + + @Test("Anthropic with API version and betas") + func anthropicWithVersionAndBetas() { + let config = ProviderConfiguration.anthropic( + apiKey: "sk-ant-test", + model: "claude-sonnet-4-20250514", + apiVersion: "2024-01-01", + betas: ["computer-use"] + ) + + #expect(config.apiVersion == "2024-01-01") + #expect(config.betas == ["computer-use"]) + let model = config.makeModel() + #expect(model is AnthropicLanguageModel) + } + + @Test("Anthropic with dynamic key provider captures key by reference") + func anthropicDynamicKeyProvider() { + nonisolated(unsafe) var currentKey = "sk-ant-initial" + let config = ProviderConfiguration.anthropic( + apiKeyProvider: { currentKey }, + model: "claude-sonnet-4-20250514" + ) + + #expect(config.apiKey == "sk-ant-initial") + currentKey = "sk-ant-rotated" + #expect(config.apiKey == "sk-ant-rotated") + } + + // MARK: - Gemini Configuration + + @Test("Gemini configuration stores provider and model") + func geminiBasicConfiguration() { + let config = ProviderConfiguration.gemini( + apiKey: "AIzaSyTest123", + model: "gemini-2.0-flash" + ) + + #expect(config.provider == .gemini) + #expect(config.model == "gemini-2.0-flash") + #expect(config.apiKey == "AIzaSyTest123") + #expect(config.hasValidAPIKey) + } + + @Test("Gemini configuration produces a valid LanguageModel") + func geminiMakeModel() { + let config = ProviderConfiguration.gemini( + apiKey: "AIzaSyTest", + model: "gemini-2.0-flash" + ) + + let model = config.makeModel() + #expect(model is GeminiLanguageModel) + } + + @Test("Gemini with custom API version") + func geminiCustomVersion() { + let config = ProviderConfiguration.gemini( + apiKey: "AIzaSyTest", + model: "gemini-2.0-flash", + apiVersion: "v1" + ) + + #expect(config.apiVersion == "v1") + let model = config.makeModel() + #expect(model is GeminiLanguageModel) + } + + @Test("Gemini with dynamic key provider captures key by reference") + func geminiDynamicKeyProvider() { + nonisolated(unsafe) var currentKey = "AIza-initial" + let config = ProviderConfiguration.gemini( + apiKeyProvider: { currentKey }, + model: "gemini-2.0-flash" + ) + + #expect(config.apiKey == "AIza-initial") + currentKey = "AIza-rotated" + #expect(config.apiKey == "AIza-rotated") + } + + // MARK: - OpenAI-Compatible Configuration + + @Test("OpenAI-compatible configuration with custom base URL") + func openAICompatibleConfiguration() { + let baseURL = URL(string: "https://api.together.xyz/v1/")! + let config = ProviderConfiguration.openAICompatible( + apiKey: "together-key", + model: "meta-llama/Llama-3.1-70B", + baseURL: baseURL + ) + + #expect(config.provider == .openAICompatible) + #expect(config.model == "meta-llama/Llama-3.1-70B") + #expect(config.baseURL == baseURL) + let model = config.makeModel() + #expect(model is OpenAILanguageModel) + } + + @Test("OpenAI-compatible with dynamic key provider") + func openAICompatibleDynamicKey() { + let baseURL = URL(string: "http://localhost:1234/v1/")! + nonisolated(unsafe) var currentKey = "local-key" + let config = ProviderConfiguration.openAICompatible( + apiKeyProvider: { currentKey }, + model: "local-model", + baseURL: baseURL + ) + + #expect(config.apiKey == "local-key") + currentKey = "new-local-key" + #expect(config.apiKey == "new-local-key") + } + + // MARK: - API Key Validation + + @Test("Empty API key reports invalid") + func emptyAPIKeyInvalid() { + let config = ProviderConfiguration.openAI( + apiKey: "", + model: "gpt-4o" + ) + + #expect(!config.hasValidAPIKey) + } + + @Test("Whitespace-only API key reports invalid") + func whitespaceAPIKeyInvalid() { + let config = ProviderConfiguration.openAI( + apiKey: " \n\t ", + model: "gpt-4o" + ) + + #expect(!config.hasValidAPIKey) + } + + @Test("Non-empty API key reports valid") + func nonEmptyAPIKeyValid() { + let config = ProviderConfiguration.openAI( + apiKey: "x", + model: "gpt-4o" + ) + + #expect(config.hasValidAPIKey) + } + + // MARK: - Environment Variable Configuration + + @Test("fromEnvironment creates configuration for each provider") + func fromEnvironmentCreatesConfig() { + let openAI = ProviderConfiguration.fromEnvironment( + provider: .openAI, + environmentVariable: "SWIFTDAI_TEST_OPENAI_KEY", + model: "gpt-4o" + ) + #expect(openAI.provider == .openAI) + #expect(openAI.model == "gpt-4o") + + let anthropic = ProviderConfiguration.fromEnvironment( + provider: .anthropic, + environmentVariable: "SWIFTDAI_TEST_ANTHROPIC_KEY", + model: "claude-sonnet-4-20250514" + ) + #expect(anthropic.provider == .anthropic) + + let gemini = ProviderConfiguration.fromEnvironment( + provider: .gemini, + environmentVariable: "SWIFTDAI_TEST_GEMINI_KEY", + model: "gemini-2.0-flash" + ) + #expect(gemini.provider == .gemini) + } + + @Test("fromEnvironment returns empty key when variable not set") + func fromEnvironmentMissingVariable() { + let config = ProviderConfiguration.fromEnvironment( + provider: .openAI, + environmentVariable: "NONEXISTENT_KEY_VAR_SWIFTDBAI_TEST", + model: "gpt-4o" + ) + + #expect(!config.hasValidAPIKey) + #expect(config.apiKey == "") + } + + // MARK: - Provider Enum + + @Test("Provider enum has all expected cases") + func providerCases() { + let cases = ProviderConfiguration.Provider.allCases + #expect(cases.count == 6) + #expect(cases.contains(.openAI)) + #expect(cases.contains(.anthropic)) + #expect(cases.contains(.gemini)) + #expect(cases.contains(.openAICompatible)) + #expect(cases.contains(.ollama)) + #expect(cases.contains(.llamaCpp)) + } + + // MARK: - Cross-Provider Model Creation + + @Test("All providers produce available models") + func allProvidersCreateAvailableModels() { + let configs: [ProviderConfiguration] = [ + .openAI(apiKey: "test", model: "gpt-4o"), + .anthropic(apiKey: "test", model: "claude-sonnet-4-20250514"), + .gemini(apiKey: "test", model: "gemini-2.0-flash"), + .openAICompatible( + apiKey: "test", + model: "local", + baseURL: URL(string: "http://localhost:8080/v1/")! + ), + ] + + for config in configs { + let model = config.makeModel() + #expect(model.isAvailable, "Model for \(config.provider) should be available") + } + } +} diff --git a/Tests/SwiftDBAITests/SQLQueryParserTests.swift b/Tests/SwiftDBAITests/SQLQueryParserTests.swift new file mode 100644 index 0000000..20e6a7e --- /dev/null +++ b/Tests/SwiftDBAITests/SQLQueryParserTests.swift @@ -0,0 +1,629 @@ +// SQLQueryParserTests.swift +// SwiftDBAITests + +import Testing +@testable import SwiftDBAI + +@Suite("SQLQueryParser") +struct SQLQueryParserTests { + + let readOnlyParser = SQLQueryParser(allowlist: .readOnly) + let standardParser = SQLQueryParser(allowlist: .standard) + let unrestrictedParser = SQLQueryParser(allowlist: .unrestricted) + + // MARK: - Extraction from code blocks + + @Test("Extracts SQL from markdown sql code block") + func extractFromSQLCodeBlock() throws { + let text = """ + Here's the query to find the top users: + + ```sql + SELECT name, COUNT(*) as count FROM users GROUP BY name ORDER BY count DESC + ``` + + This will give you the results. + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT name, COUNT(*) as count FROM users GROUP BY name ORDER BY count DESC") + #expect(result.operation == .select) + #expect(result.requiresConfirmation == false) + } + + @Test("Extracts SQL from generic code block") + func extractFromGenericCodeBlock() throws { + let text = """ + Here you go: + + ``` + SELECT * FROM products WHERE price > 100 + ``` + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM products WHERE price > 100") + } + + @Test("Extracts SQL from labeled text") + func extractFromLabel() throws { + let text = """ + I can help with that. + SQL: SELECT id, name FROM categories WHERE active = 1 + That should work. + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT id, name FROM categories WHERE active = 1") + } + + @Test("Extracts direct SQL from plain text") + func extractDirectSQL() throws { + let text = "SELECT COUNT(*) FROM orders WHERE status = 'shipped'" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT COUNT(*) FROM orders WHERE status = 'shipped'") + } + + @Test("Handles SQL with trailing semicolons") + func trailingSemicolon() throws { + let text = "```sql\nSELECT * FROM users;\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Handles multiline SQL in code block") + func multilineSQL() throws { + let text = """ + ```sql + SELECT u.name, COUNT(o.id) as order_count + FROM users u + JOIN orders o ON u.id = o.user_id + GROUP BY u.name + ORDER BY order_count DESC + LIMIT 10 + ``` + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("SELECT u.name")) + #expect(result.sql.contains("LIMIT 10")) + } + + @Test("Handles WITH (CTE) queries as SELECT") + func cteQuery() throws { + let text = """ + ```sql + WITH top_users AS ( + SELECT user_id, COUNT(*) as cnt FROM orders GROUP BY user_id + ) + SELECT * FROM top_users WHERE cnt > 5 + ``` + """ + let result = try readOnlyParser.parse(text) + #expect(result.operation == .select) + } + + // MARK: - No SQL found + + @Test("Throws noSQLFound for text without SQL") + func noSQLFound() throws { + let text = "I'm sorry, I can't help with that request." + #expect(throws: SQLParsingError.noSQLFound) { + try readOnlyParser.parse(text) + } + } + + @Test("Throws noSQLFound for empty input") + func emptyInput() throws { + #expect(throws: SQLParsingError.noSQLFound) { + try readOnlyParser.parse("") + } + } + + // MARK: - Operation detection + + @Test("Detects INSERT operation") + func detectInsert() throws { + let text = "```sql\nINSERT INTO users (name) VALUES ('Alice')\n```" + let result = try standardParser.parse(text) + #expect(result.operation == .insert) + } + + @Test("Detects UPDATE operation") + func detectUpdate() throws { + let text = "```sql\nUPDATE users SET name = 'Bob' WHERE id = 1\n```" + let result = try standardParser.parse(text) + #expect(result.operation == .update) + } + + @Test("Detects DELETE operation and requires confirmation") + func detectDeleteRequiresConfirmation() throws { + let text = "```sql\nDELETE FROM users WHERE id = 99\n```" + let result = try unrestrictedParser.parse(text) + #expect(result.operation == .delete) + #expect(result.requiresConfirmation == true) + } + + // MARK: - Allowlist enforcement + + @Test("Rejects INSERT on read-only allowlist") + func rejectInsertOnReadOnly() throws { + let text = "```sql\nINSERT INTO users (name) VALUES ('Mallory')\n```" + #expect(throws: SQLParsingError.operationNotAllowed(.insert)) { + try readOnlyParser.parse(text) + } + } + + @Test("Rejects UPDATE on read-only allowlist") + func rejectUpdateOnReadOnly() { + let text = "```sql\nUPDATE users SET name = 'Eve' WHERE id = 1\n```" + #expect(throws: SQLParsingError.operationNotAllowed(.update)) { + try readOnlyParser.parse(text) + } + } + + @Test("Rejects DELETE on standard allowlist") + func rejectDeleteOnStandard() { + let text = "```sql\nDELETE FROM users WHERE id = 1\n```" + #expect(throws: SQLParsingError.operationNotAllowed(.delete)) { + try standardParser.parse(text) + } + } + + // MARK: - Dangerous operations + + @Test("Rejects DROP TABLE") + func rejectDrop() { + let text = "```sql\nDROP TABLE users\n```" + #expect(throws: SQLParsingError.dangerousOperation("DROP")) { + try unrestrictedParser.parse(text) + } + } + + @Test("Rejects ALTER TABLE") + func rejectAlter() { + let text = "```sql\nALTER TABLE users ADD COLUMN age INTEGER\n```" + #expect(throws: SQLParsingError.dangerousOperation("ALTER")) { + try unrestrictedParser.parse(text) + } + } + + @Test("Rejects PRAGMA") + func rejectPragma() { + let text = "```sql\nPRAGMA table_info(users)\n```" + #expect(throws: SQLParsingError.dangerousOperation("PRAGMA")) { + try unrestrictedParser.parse(text) + } + } + + @Test("Does not match dangerous keywords inside identifiers") + func noFalsePositiveOnSubstring() throws { + // "DROPDOWN" contains "DROP" as substring but is not the keyword + let text = "SELECT dropdown_value FROM settings" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("dropdown_value")) + } + + // MARK: - Multiple statements + + @Test("Rejects multiple statements separated by semicolons") + func rejectMultipleStatements() { + let text = "```sql\nSELECT * FROM users; SELECT * FROM orders\n```" + #expect(throws: SQLParsingError.multipleStatements) { + try readOnlyParser.parse(text) + } + } + + @Test("Allows semicolons inside string literals") + func allowSemicolonInString() throws { + let text = "SELECT * FROM users WHERE bio = 'hello; world'" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("hello; world")) + } + + // MARK: - ParsedSQL equality + + @Test("ParsedSQL equality works") + func parsedSQLEquality() { + let a = ParsedSQL(sql: "SELECT 1", operation: .select) + let b = ParsedSQL(sql: "SELECT 1", operation: .select) + #expect(a == b) + } + + // MARK: - Error descriptions + + @Test("Error descriptions are meaningful") + func errorDescriptions() { + #expect(SQLParsingError.noSQLFound.description.contains("No SQL")) + #expect(SQLParsingError.operationNotAllowed(.insert).description.contains("INSERT")) + #expect(SQLParsingError.dangerousOperation("DROP").description.contains("DROP")) + #expect(SQLParsingError.multipleStatements.description.contains("single")) + } + + // MARK: - MutationPolicy integration + + @Test("MutationPolicy allows INSERT on permitted table") + func mutationPolicyAllowsInsertOnPermittedTable() throws { + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders", "order_items"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nINSERT INTO orders (product, qty) VALUES ('Widget', 3)\n```" + let result = try parser.parse(text) + #expect(result.operation == .insert) + #expect(result.requiresConfirmation == false) + } + + @Test("MutationPolicy rejects INSERT on non-permitted table") + func mutationPolicyRejectsInsertOnForbiddenTable() { + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nINSERT INTO users (name) VALUES ('Alice')\n```" + #expect(throws: SQLParsingError.tableNotAllowed(table: "users", operation: .insert)) { + try parser.parse(text) + } + } + + @Test("MutationPolicy rejects UPDATE on non-permitted table") + func mutationPolicyRejectsUpdateOnForbiddenTable() { + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["orders"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nUPDATE users SET name = 'Bob' WHERE id = 1\n```" + #expect(throws: SQLParsingError.tableNotAllowed(table: "users", operation: .update)) { + try parser.parse(text) + } + } + + @Test("MutationPolicy rejects DELETE on non-permitted table") + func mutationPolicyRejectsDeleteOnForbiddenTable() { + let policy = MutationPolicy( + allowedOperations: [.insert, .update, .delete], + allowedTables: ["temp_data"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nDELETE FROM users WHERE id = 99\n```" + #expect(throws: SQLParsingError.tableNotAllowed(table: "users", operation: .delete)) { + try parser.parse(text) + } + } + + @Test("MutationPolicy allows mutation on any table when allowedTables is nil") + func mutationPolicyAllowsAllTablesWhenNil() throws { + let policy = MutationPolicy(allowedOperations: [.insert, .update]) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nINSERT INTO any_table (col) VALUES ('val')\n```" + let result = try parser.parse(text) + #expect(result.operation == .insert) + } + + @Test("MutationPolicy SELECT is never restricted by table allowlist") + func mutationPolicySelectIgnoresTableRestrictions() throws { + let policy = MutationPolicy( + allowedOperations: [.insert], + allowedTables: ["orders"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + // SELECT from a table NOT in allowedTables — should still work + let text = "```sql\nSELECT * FROM users\n```" + let result = try parser.parse(text) + #expect(result.operation == .select) + #expect(result.requiresConfirmation == false) + } + + @Test("MutationPolicy DELETE requires confirmation by default") + func mutationPolicyDeleteRequiresConfirmation() throws { + let policy = MutationPolicy(allowedOperations: [.delete]) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nDELETE FROM users WHERE id = 1\n```" + let result = try parser.parse(text) + #expect(result.operation == .delete) + #expect(result.requiresConfirmation == true) + } + + @Test("MutationPolicy DELETE skips confirmation when configured") + func mutationPolicyDeleteNoConfirmation() throws { + let policy = MutationPolicy( + allowedOperations: [.delete], + requiresDestructiveConfirmation: false + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "```sql\nDELETE FROM users WHERE id = 1\n```" + let result = try parser.parse(text) + #expect(result.operation == .delete) + #expect(result.requiresConfirmation == false) + } + + @Test("MutationPolicy readOnly preset rejects all mutations") + func mutationPolicyReadOnlyRejectsAll() { + let parser = SQLQueryParser(mutationPolicy: .readOnly) + #expect(throws: SQLParsingError.operationNotAllowed(.insert)) { + try parser.parse("INSERT INTO t (a) VALUES (1)") + } + #expect(throws: SQLParsingError.operationNotAllowed(.update)) { + try parser.parse("UPDATE t SET a = 1") + } + #expect(throws: SQLParsingError.operationNotAllowed(.delete)) { + try parser.parse("DELETE FROM t WHERE id = 1") + } + } + + @Test("MutationPolicy table matching is case-insensitive") + func mutationPolicyTableCaseInsensitive() throws { + let policy = MutationPolicy( + allowedOperations: [.insert], + allowedTables: ["Orders"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + let text = "INSERT INTO orders (product) VALUES ('Widget')" + let result = try parser.parse(text) + #expect(result.operation == .insert) + } + + @Test("MutationPolicy handles quoted table names") + func mutationPolicyQuotedTableNames() throws { + let policy = MutationPolicy( + allowedOperations: [.insert, .update], + allowedTables: ["order_items"] + ) + let parser = SQLQueryParser(mutationPolicy: policy) + + // Backtick-quoted + let backtick = "INSERT INTO `order_items` (qty) VALUES (5)" + let r1 = try parser.parse(backtick) + #expect(r1.operation == .insert) + + // Double-quote-quoted + let doubleQuote = "UPDATE \"order_items\" SET qty = 10 WHERE id = 1" + let r2 = try parser.parse(doubleQuote) + #expect(r2.operation == .update) + } + + @Test("Error description for tableNotAllowed is meaningful") + func tableNotAllowedDescription() { + let error = SQLParsingError.tableNotAllowed(table: "secret", operation: .delete) + #expect(error.description.contains("secret")) + #expect(error.description.contains("DELETE")) + } + + @Test("Error description for confirmationRequired is meaningful") + func confirmationRequiredDescription() { + let error = SQLParsingError.confirmationRequired(sql: "DELETE FROM x", operation: .delete) + #expect(error.description.contains("DELETE")) + #expect(error.description.contains("confirmation")) + } + + // MARK: - Robust extraction edge cases + + @Test("Extracts plain SQL without any wrapping") + func plainSQL() throws { + let text = "SELECT * FROM users" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL from markdown sql code block") + func markdownSQLBlock() throws { + let text = "```sql\nSELECT * FROM users\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL from generic code block") + func genericCodeBlock() throws { + let text = "```\nSELECT * FROM users\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Strips trailing semicolons") + func trailingSemicolonEdge() throws { + let text = "SELECT * FROM users;" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL with preamble text") + func preambleText() throws { + let text = "Here's the query:\nSELECT * FROM users" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Handles trailing backticks only (no opening fence)") + func trailingBackticksOnly() throws { + let text = "SELECT * FROM users\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL from single-line code block") + func singleLineCodeBlock() throws { + let text = "```sql SELECT * FROM users ```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Handles no newline before closing fence") + func noNewlineBeforeClosingFence() throws { + let text = "```sql\nSELECT * FROM users```" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL inline with text prefix") + func inlineWithText() throws { + let text = "The SQL query is: SELECT * FROM users" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Handles extra whitespace around SQL") + func extraWhitespace() throws { + let text = "\n\nSELECT * FROM users\n\n" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Extracts SQL from chatty LLM response with preamble and postamble") + func chattyLLMResponse() throws { + let text = "Sure! Here's the SQL:\n\n```sql\nSELECT * FROM users\n```\n\nThis will return all users." + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Preserves SQL comments") + func sqlWithComments() throws { + let text = "SELECT * FROM users -- get all users" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("-- get all users")) + } + + @Test("Preserves backtick-quoted identifiers in SQL") + func backtickQuotedIdentifiers() throws { + let text = "SELECT `column name` FROM users" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("`column name`")) + } + + @Test("Strips think tags from Qwen-style models") + func thinkTags() throws { + let text = "I need to query the users table\nSELECT * FROM users" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + #expect(!result.sql.contains("think")) + } + + @Test("Handles 4 or 5 backtick fences") + func extraBacktickFences() throws { + let text4 = "````sql\nSELECT * FROM users\n````" + let result4 = try readOnlyParser.parse(text4) + #expect(result4.sql == "SELECT * FROM users") + + let text5 = "`````\nSELECT * FROM users\n`````" + let result5 = try readOnlyParser.parse(text5) + #expect(result5.sql == "SELECT * FROM users") + } + + @Test("Handles mixed case SQL keywords") + func mixedCaseSQL() throws { + let text = "select * from USERS" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "select * from USERS") + } + + @Test("Handles WITH clause (CTE) queries") + func withClause() throws { + let text = "WITH cte AS (SELECT id FROM orders) SELECT * FROM cte" + let result = try readOnlyParser.parse(text) + #expect(result.sql.hasPrefix("WITH")) + #expect(result.operation == .select) + } + + @Test("Handles WITH clause in code block") + func withClauseInCodeBlock() throws { + let text = "```sql\nWITH top AS (\n SELECT user_id, COUNT(*) as cnt FROM orders GROUP BY user_id\n)\nSELECT * FROM top WHERE cnt > 5\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql.hasPrefix("WITH")) + #expect(result.operation == .select) + } + + @Test("Multi-line SQL with JOINs and subqueries in code block") + func multiLineJoinsAndSubqueries() throws { + let text = """ + ```sql + SELECT u.name, o.total + FROM users u + INNER JOIN orders o ON u.id = o.user_id + WHERE o.total > (SELECT AVG(total) FROM orders) + ORDER BY o.total DESC + ``` + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("INNER JOIN")) + #expect(result.sql.contains("SELECT AVG(total)")) + #expect(result.sql.contains("ORDER BY")) + } + + @Test("Handles response with both explanation text and SQL") + func explanationAndSQL() throws { + let text = """ + To find all active users, we need to query the users table + and filter by the active column. Here's the query: + + SELECT * FROM users WHERE active = 1 + + This should give you the results you're looking for. + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users WHERE active = 1") + } + + @Test("Throws noSQLFound for empty response") + func emptyResponse() throws { + #expect(throws: SQLParsingError.noSQLFound) { + try readOnlyParser.parse("") + } + #expect(throws: SQLParsingError.noSQLFound) { + try readOnlyParser.parse(" \n\n ") + } + } + + @Test("Throws noSQLFound for response with no SQL at all") + func noSQLAtAll() throws { + #expect(throws: SQLParsingError.noSQLFound) { + try readOnlyParser.parse("I cannot help with that question. Please try asking about your data.") + } + } + + @Test("Handles response with multiple SQL statements in code block (rejects them)") + func multipleStatementsInCodeBlock() throws { + // When multiple statements are in a code block, the parser sees both and rejects + let text = "```sql\nSELECT * FROM users; SELECT * FROM orders\n```" + #expect(throws: SQLParsingError.multipleStatements) { + try readOnlyParser.parse(text) + } + } + + @Test("Extracts first SQL statement from plain text with multiple statements") + func multipleStatementsPlainText() throws { + // In plain text, the direct extraction stops at the semicolon and extracts the first statement + let text = "SELECT * FROM users; SELECT * FROM orders" + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Preserves backtick identifiers inside code blocks") + func backtickIdentifiersInCodeBlock() throws { + let text = "```sql\nSELECT `first name`, `last name` FROM `user data`\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("`first name`")) + #expect(result.sql.contains("`last name`")) + #expect(result.sql.contains("`user data`")) + } + + @Test("Strips think tags with multiline reasoning content") + func multilineThinkTags() throws { + let text = """ + + The user wants to find all users. + I should use SELECT * FROM users. + Let me think about which columns to include... + + SELECT * FROM users + """ + let result = try readOnlyParser.parse(text) + #expect(result.sql == "SELECT * FROM users") + } + + @Test("Handles mixed backtick styles in response") + func mixedBacktickStyles() throws { + // Code fences + backtick-quoted identifiers inside + let text = "```sql\nSELECT `user name` FROM users WHERE `is active` = 1\n```" + let result = try readOnlyParser.parse(text) + #expect(result.sql.contains("`user name`")) + #expect(result.sql.contains("`is active`")) + } +} diff --git a/Tests/SwiftDBAITests/SchemaIntrospectorTests.swift b/Tests/SwiftDBAITests/SchemaIntrospectorTests.swift new file mode 100644 index 0000000..8b42d27 --- /dev/null +++ b/Tests/SwiftDBAITests/SchemaIntrospectorTests.swift @@ -0,0 +1,234 @@ +// SchemaIntrospectorTests.swift +// SwiftDBAI + +import Testing +import GRDB +@testable import SwiftDBAI + +@Suite("SchemaIntrospector") +struct SchemaIntrospectorTests { + + // MARK: - Helper + + /// Creates an in-memory database with a sample schema for testing. + private func makeTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(configuration: { + var config = Configuration() + config.foreignKeysEnabled = true + return config + }()) + + try db.write { db in + try db.execute(sql: """ + CREATE TABLE authors ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT UNIQUE + ); + """) + + try db.execute(sql: """ + CREATE TABLE books ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + author_id INTEGER NOT NULL REFERENCES authors(id) ON DELETE CASCADE, + published_date TEXT, + price REAL DEFAULT 9.99 + ); + """) + + try db.execute(sql: """ + CREATE INDEX idx_books_author ON books(author_id); + """) + + try db.execute(sql: """ + CREATE INDEX idx_books_title ON books(title); + """) + + try db.execute(sql: """ + CREATE TABLE reviews ( + id INTEGER PRIMARY KEY, + book_id INTEGER NOT NULL REFERENCES books(id), + rating INTEGER NOT NULL, + comment TEXT + ); + """) + } + + return db + } + + // MARK: - Tests + + @Test("Discovers all user tables") + func discoversAllTables() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + #expect(schema.tableNames.count == 3) + #expect(schema.tableNames.contains("authors")) + #expect(schema.tableNames.contains("books")) + #expect(schema.tableNames.contains("reviews")) + } + + @Test("Excludes sqlite_ internal tables") + func excludesInternalTables() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + for name in schema.tableNames { + #expect(!name.hasPrefix("sqlite_")) + } + } + + @Test("Introspects column names and types") + func introspectsColumns() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let books = try #require(schema.tables["books"]) + #expect(books.columns.count == 5) + + let titleCol = try #require(books.columns.first { $0.name == "title" }) + #expect(titleCol.type == "TEXT") + #expect(titleCol.isNotNull == true) + #expect(titleCol.isPrimaryKey == false) + + let priceCol = try #require(books.columns.first { $0.name == "price" }) + #expect(priceCol.type == "REAL") + #expect(priceCol.defaultValue == "9.99") + } + + @Test("Detects primary keys") + func detectsPrimaryKeys() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let authors = try #require(schema.tables["authors"]) + #expect(authors.primaryKey == ["id"]) + + let idCol = try #require(authors.columns.first { $0.name == "id" }) + #expect(idCol.isPrimaryKey == true) + } + + @Test("Detects foreign keys") + func detectsForeignKeys() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let books = try #require(schema.tables["books"]) + #expect(books.foreignKeys.count == 1) + + let fk = books.foreignKeys[0] + #expect(fk.fromColumn == "author_id") + #expect(fk.toTable == "authors") + #expect(fk.toColumn == "id") + #expect(fk.onDelete == "CASCADE") + } + + @Test("Detects indexes") + func detectsIndexes() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let books = try #require(schema.tables["books"]) + let indexNames = books.indexes.map(\.name) + #expect(indexNames.contains("idx_books_author")) + #expect(indexNames.contains("idx_books_title")) + } + + @Test("Detects NOT NULL constraints") + func detectsNotNull() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let reviews = try #require(schema.tables["reviews"]) + let ratingCol = try #require(reviews.columns.first { $0.name == "rating" }) + #expect(ratingCol.isNotNull == true) + + let commentCol = try #require(reviews.columns.first { $0.name == "comment" }) + #expect(commentCol.isNotNull == false) + } + + @Test("Generates LLM-friendly schema description") + func generatesSchemaDescription() async throws { + let db = try makeTestDatabase() + let schema = try await SchemaIntrospector.introspect(database: db) + + let description = schema.schemaDescription + #expect(description.contains("TABLE authors")) + #expect(description.contains("TABLE books")) + #expect(description.contains("FOREIGN KEY")) + #expect(description.contains("REFERENCES authors(id)")) + #expect(description.contains("INDEX idx_books_author")) + } + + @Test("Handles empty database") + func handlesEmptyDatabase() async throws { + let db = try DatabaseQueue() + let schema = try await SchemaIntrospector.introspect(database: db) + + #expect(schema.tables.isEmpty) + #expect(schema.tableNames.isEmpty) + #expect(schema.schemaDescription.isEmpty) + } + + @Test("Handles composite primary keys") + func handlesCompositePrimaryKey() async throws { + let db = try DatabaseQueue() + try await db.write { db in + try db.execute(sql: """ + CREATE TABLE book_tags ( + book_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + PRIMARY KEY (book_id, tag_id) + ); + """) + } + + let schema = try await SchemaIntrospector.introspect(database: db) + let bookTags = try #require(schema.tables["book_tags"]) + #expect(bookTags.primaryKey.count == 2) + #expect(bookTags.primaryKey.contains("book_id")) + #expect(bookTags.primaryKey.contains("tag_id")) + } + + @Test("Handles tables with no explicit types (SQLite dynamic typing)") + func handlesDynamicTyping() async throws { + let db = try DatabaseQueue() + try await db.write { db in + try db.execute(sql: """ + CREATE TABLE flexible ( + id INTEGER PRIMARY KEY, + data, + info BLOB + ); + """) + } + + let schema = try await SchemaIntrospector.introspect(database: db) + let flexible = try #require(schema.tables["flexible"]) + + let dataCol = try #require(flexible.columns.first { $0.name == "data" }) + #expect(dataCol.type == "") // No declared type + + let infoCol = try #require(flexible.columns.first { $0.name == "info" }) + #expect(infoCol.type == "BLOB") + } + + @Test("Synchronous introspection works within database access") + func synchronousIntrospection() async throws { + let db = try DatabaseQueue() + try await db.write { db in + try db.execute(sql: "CREATE TABLE test (id INTEGER PRIMARY KEY, val TEXT);") + } + + let schema = try await db.read { db in + try SchemaIntrospector.introspect(db: db) + } + + #expect(schema.tableNames == ["test"]) + let table = try #require(schema.tables["test"]) + #expect(table.columns.count == 2) + } +} diff --git a/Tests/SwiftDBAITests/ScrollableDataTableViewTests.swift b/Tests/SwiftDBAITests/ScrollableDataTableViewTests.swift new file mode 100644 index 0000000..0ec16fa --- /dev/null +++ b/Tests/SwiftDBAITests/ScrollableDataTableViewTests.swift @@ -0,0 +1,133 @@ +// ScrollableDataTableViewTests.swift +// SwiftDBAITests +// +// Tests for the ScrollableDataTableView component. + +import Foundation +import Testing +@testable import SwiftDBAI + +@Suite("ScrollableDataTableView") +@MainActor +struct ScrollableDataTableViewTests { + + // MARK: - Test Helpers + + private func makeDataTable( + columnNames: [String] = ["id", "name", "score"], + inferredTypes: [DataTable.InferredType] = [.integer, .text, .real], + rowCount: Int = 5 + ) -> DataTable { + let columns = columnNames.enumerated().map { idx, name in + DataTable.Column(name: name, index: idx, inferredType: inferredTypes[idx]) + } + let rows = (0.. DataTable { + DataTable(columns: [], rows: [], sql: "", executionTime: 0) + } + + // MARK: - Initialization Tests + + @Test("Initializes with default parameters") + func initWithDefaults() { + let table = makeDataTable() + let view = ScrollableDataTableView(dataTable: table) + + #expect(view.minimumColumnWidth == 80) + #expect(view.maximumColumnWidth == 250) + #expect(view.showAlternatingRows == true) + #expect(view.showFooter == true) + } + + @Test("Initializes with custom parameters") + func initWithCustomParams() { + let table = makeDataTable() + let view = ScrollableDataTableView( + dataTable: table, + minimumColumnWidth: 100, + maximumColumnWidth: 300, + showAlternatingRows: false, + showFooter: false + ) + + #expect(view.minimumColumnWidth == 100) + #expect(view.maximumColumnWidth == 300) + #expect(view.showAlternatingRows == false) + #expect(view.showFooter == false) + } + + @Test("Handles empty data table") + func handlesEmptyTable() { + let table = makeEmptyDataTable() + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.isEmpty) + } + + @Test("Handles single row table") + func handlesSingleRow() { + let table = makeDataTable(rowCount: 1) + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.rowCount == 1) + #expect(view.dataTable.columnCount == 3) + } + + @Test("Handles single column table") + func handlesSingleColumn() { + let columns = [DataTable.Column(name: "count", index: 0, inferredType: .integer)] + let rows = [ + DataTable.Row(id: 0, values: [.integer(42)], columnNames: ["count"]) + ] + let table = DataTable(columns: columns, rows: rows, sql: "SELECT count(*) FROM t", executionTime: 0.001) + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.columnCount == 1) + #expect(view.dataTable.rowCount == 1) + } + + @Test("Handles large number of rows") + func handlesLargeRowCount() { + let table = makeDataTable(rowCount: 1000) + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.rowCount == 1000) + } + + @Test("Handles null values in cells") + func handlesNullValues() { + let columns = [ + DataTable.Column(name: "name", index: 0, inferredType: .text), + DataTable.Column(name: "value", index: 1, inferredType: .null), + ] + let rows = [ + DataTable.Row(id: 0, values: [.text("test"), .null], columnNames: ["name", "value"]) + ] + let table = DataTable(columns: columns, rows: rows) + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.rows[0][1] == .null) + } + + @Test("Handles blob values in cells") + func handlesBlobValues() { + let columns = [ + DataTable.Column(name: "data", index: 0, inferredType: .blob), + ] + let blobData = Data([0x00, 0xFF, 0xAB]) + let rows = [ + DataTable.Row(id: 0, values: [.blob(blobData)], columnNames: ["data"]) + ] + let table = DataTable(columns: columns, rows: rows) + let view = ScrollableDataTableView(dataTable: table) + #expect(view.dataTable.rows[0][0] == QueryResult.Value.blob(blobData)) + } +} diff --git a/Tests/SwiftDBAITests/TextSummaryRendererTests.swift b/Tests/SwiftDBAITests/TextSummaryRendererTests.swift new file mode 100644 index 0000000..a169522 --- /dev/null +++ b/Tests/SwiftDBAITests/TextSummaryRendererTests.swift @@ -0,0 +1,301 @@ +// TextSummaryRendererTests.swift +// SwiftDBAI + +import AnyLanguageModel +import Testing +import Foundation +@testable import SwiftDBAI + +@Suite("TextSummaryRenderer") +struct TextSummaryRendererTests { + + // MARK: - QueryResult.Value Tests + + @Test("Value description renders correctly") + func valueDescriptions() { + #expect(QueryResult.Value.text("hello").description == "hello") + #expect(QueryResult.Value.integer(42).description == "42") + #expect(QueryResult.Value.real(3.14).description == "3.14") + #expect(QueryResult.Value.null.description == "NULL") + #expect(QueryResult.Value.blob(Data([0x01, 0x02])).description == "<2 bytes>") + } + + @Test("Value doubleValue extracts numeric values") + func valueDoubleValues() { + #expect(QueryResult.Value.integer(42).doubleValue == 42.0) + #expect(QueryResult.Value.real(3.14).doubleValue == 3.14) + #expect(QueryResult.Value.text("100").doubleValue == 100.0) + #expect(QueryResult.Value.text("not a number").doubleValue == nil) + #expect(QueryResult.Value.null.doubleValue == nil) + #expect(QueryResult.Value.blob(Data()).doubleValue == nil) + } + + @Test("Value isNull works correctly") + func valueIsNull() { + #expect(QueryResult.Value.null.isNull == true) + #expect(QueryResult.Value.text("").isNull == false) + #expect(QueryResult.Value.integer(0).isNull == false) + } + + // MARK: - QueryResult Tests + + @Test("Empty result has correct properties") + func emptyResult() { + let result = QueryResult( + columns: ["id", "name"], + rows: [], + sql: "SELECT id, name FROM users", + executionTime: 0.01 + ) + #expect(result.rowCount == 0) + #expect(result.isAggregate == false) + #expect(result.tabularDescription == "(empty result set)") + } + + @Test("Single aggregate result is detected") + func aggregateDetection() { + let result = QueryResult( + columns: ["COUNT(*)"], + rows: [["COUNT(*)": .integer(42)]], + sql: "SELECT COUNT(*) FROM users", + executionTime: 0.01 + ) + #expect(result.isAggregate == true) + } + + @Test("Multi-row result is not aggregate") + func nonAggregateDetection() { + let result = QueryResult( + columns: ["name"], + rows: [ + ["name": .text("Alice")], + ["name": .text("Bob")], + ], + sql: "SELECT name FROM users", + executionTime: 0.01 + ) + #expect(result.isAggregate == false) + } + + @Test("Tabular description formats correctly") + func tabularDescription() { + let result = QueryResult( + columns: ["id", "name"], + rows: [ + ["id": .integer(1), "name": .text("Alice")], + ["id": .integer(2), "name": .text("Bob")], + ], + sql: "SELECT id, name FROM users", + executionTime: 0.01 + ) + let desc = result.tabularDescription + #expect(desc.contains("id | name")) + #expect(desc.contains("1 | Alice")) + #expect(desc.contains("2 | Bob")) + } + + @Test("values(forColumn:) extracts column values") + func valuesForColumn() { + let result = QueryResult( + columns: ["name"], + rows: [ + ["name": .text("Alice")], + ["name": .text("Bob")], + ], + sql: "SELECT name FROM users", + executionTime: 0.01 + ) + let values = result.values(forColumn: "name") + #expect(values.count == 2) + #expect(values[0] == .text("Alice")) + } + + // MARK: - Local Summary Tests (no LLM required) + + @Test("Local summary for empty result") + func localSummaryEmpty() { + let result = makeResult(columns: ["id"], rows: []) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Any users?") + #expect(summary == "No results found for your query.") + } + + @Test("Local summary for single aggregate") + func localSummarySingleAggregate() { + let result = makeResult( + columns: ["COUNT(*)"], + rows: [["COUNT(*)": .integer(42)]] + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "How many?") + #expect(summary.contains("42")) + } + + @Test("Local summary for multiple aggregates") + func localSummaryMultipleAggregates() { + let result = makeResult( + columns: ["COUNT(*)", "AVG(price)"], + rows: [["COUNT(*)": .integer(10), "AVG(price)": .real(25.5)]] + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Stats?") + #expect(summary.contains("count")) + #expect(summary.contains("average price")) + } + + @Test("Local summary for single record") + func localSummarySingleRecord() { + let result = makeResult( + columns: ["name", "email"], + rows: [["name": .text("Alice"), "email": .text("alice@example.com")]] + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Who?") + #expect(summary.contains("1 result")) + #expect(summary.contains("Alice")) + } + + @Test("Local summary for multiple records with name column") + func localSummaryMultipleWithNames() { + let result = makeResult( + columns: ["name", "age"], + rows: [ + ["name": .text("Alice"), "age": .integer(30)], + ["name": .text("Bob"), "age": .integer(25)], + ["name": .text("Charlie"), "age": .integer(35)], + ["name": .text("Diana"), "age": .integer(28)], + ] + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "List users") + #expect(summary.contains("4 results")) + #expect(summary.contains("Alice")) + #expect(summary.contains("1 more")) + } + + @Test("Local summary for mutation result") + func localSummaryMutation() { + let result = QueryResult( + columns: [], + rows: [], + sql: "INSERT INTO users (name) VALUES ('Test')", + executionTime: 0.01, + rowsAffected: 1 + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Add user") + #expect(summary == "Successfully inserted 1 row.") + } + + @Test("Local summary for delete mutation") + func localSummaryDelete() { + let result = QueryResult( + columns: [], + rows: [], + sql: "DELETE FROM users WHERE id = 5", + executionTime: 0.01, + rowsAffected: 3 + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Delete old users") + #expect(summary == "Successfully deleted 3 rows.") + } + + @Test("Local summary for update mutation") + func localSummaryUpdate() { + let result = QueryResult( + columns: [], + rows: [], + sql: "UPDATE users SET active = 0 WHERE id = 1", + executionTime: 0.01, + rowsAffected: 1 + ) + let renderer = makeMockRenderer() + let summary = renderer.localSummary(result: result, userQuestion: "Deactivate user") + #expect(summary == "Successfully updated 1 row.") + } + + // MARK: - LLM-based Summary Tests (using MockLanguageModel) + + @Test("Summarize with LLM returns mock response for multi-row results") + func summarizeWithLLM() async throws { + let result = makeResult( + columns: ["name", "age"], + rows: [ + ["name": .text("Alice"), "age": .integer(30)], + ["name": .text("Bob"), "age": .integer(25)], + ] + ) + let mockModel = MockLanguageModel(responseText: "There are 2 users: Alice (30) and Bob (25).") + let renderer = TextSummaryRenderer(model: mockModel) + let summary = try await renderer.summarize(result: result, userQuestion: "List all users") + #expect(summary == "There are 2 users: Alice (30) and Bob (25).") + } + + @Test("Summarize returns empty result message without calling LLM") + func summarizeEmptyResult() async throws { + let result = makeResult(columns: ["id"], rows: []) + let renderer = makeMockRenderer() + let summary = try await renderer.summarize(result: result, userQuestion: "Find users") + #expect(summary == "No results found for your query.") + } + + @Test("Summarize returns direct aggregate without calling LLM") + func summarizeAggregate() async throws { + let result = makeResult( + columns: ["COUNT(*)"], + rows: [["COUNT(*)": .integer(42)]] + ) + let renderer = makeMockRenderer() + let summary = try await renderer.summarize(result: result, userQuestion: "How many?") + #expect(summary.contains("42")) + } + + @Test("Summarize mutation returns template without calling LLM") + func summarizeMutation() async throws { + let result = QueryResult( + columns: [], + rows: [], + sql: "UPDATE users SET name = 'Test' WHERE id = 1", + executionTime: 0.01, + rowsAffected: 1 + ) + let renderer = makeMockRenderer() + let summary = try await renderer.summarize(result: result, userQuestion: "Update user") + #expect(summary == "Successfully updated 1 row.") + } + + @Test("Summarize passes context to LLM prompt") + func summarizeWithContext() async throws { + let result = makeResult( + columns: ["total"], + rows: [ + ["total": .real(100.0)], + ["total": .real(200.0)], + ] + ) + let mockModel = MockLanguageModel(responseText: "The totals are 100 and 200.") + let renderer = TextSummaryRenderer(model: mockModel) + let summary = try await renderer.summarize( + result: result, + userQuestion: "Show totals", + context: "Amounts are in USD" + ) + #expect(summary == "The totals are 100 and 200.") + } + + // MARK: - Helpers + + private func makeResult( + columns: [String], + rows: [[String: QueryResult.Value]], + sql: String = "SELECT * FROM test" + ) -> QueryResult { + QueryResult(columns: columns, rows: rows, sql: sql, executionTime: 0.01) + } + + /// Creates a renderer with a mock model (for localSummary tests that don't hit the LLM). + private func makeMockRenderer() -> TextSummaryRenderer { + TextSummaryRenderer(model: MockLanguageModel()) + } +} diff --git a/Tests/SwiftDBAITests/ToolExecutionDelegateTests.swift b/Tests/SwiftDBAITests/ToolExecutionDelegateTests.swift new file mode 100644 index 0000000..1e36083 --- /dev/null +++ b/Tests/SwiftDBAITests/ToolExecutionDelegateTests.swift @@ -0,0 +1,246 @@ +// ToolExecutionDelegateTests.swift +// SwiftDBAITests + +import Foundation +import Testing +@testable import SwiftDBAI + +@Suite("DestructiveClassification") +struct DestructiveClassificationTests { + + // MARK: - Safe statements + + @Test("SELECT is classified as safe") + func selectIsSafe() { + let result = classifySQL("SELECT * FROM users") + #expect(result == .safe) + #expect(!result.requiresConfirmation) + #expect(!result.isMutating) + } + + @Test("WITH (CTE) is classified as safe") + func withIsSafe() { + let result = classifySQL("WITH cte AS (SELECT 1) SELECT * FROM cte") + #expect(result == .safe) + } + + // MARK: - Mutation statements + + @Test("INSERT is classified as mutation") + func insertIsMutation() { + let result = classifySQL("INSERT INTO users (name) VALUES ('Alice')") + #expect(result == .mutation(.insert)) + #expect(!result.requiresConfirmation) + #expect(result.isMutating) + } + + @Test("UPDATE is classified as mutation") + func updateIsMutation() { + let result = classifySQL("UPDATE users SET name = 'Bob' WHERE id = 1") + #expect(result == .mutation(.update)) + #expect(!result.requiresConfirmation) + #expect(result.isMutating) + } + + // MARK: - Destructive statements + + @Test("DELETE is classified as destructive") + func deleteIsDestructive() { + let result = classifySQL("DELETE FROM users WHERE id = 1") + #expect(result == .destructive(.delete)) + #expect(result.requiresConfirmation) + #expect(result.isMutating) + } + + @Test("DROP is classified as destructive") + func dropIsDestructive() { + let result = classifySQL("DROP TABLE users") + #expect(result == .destructive(.drop)) + #expect(result.requiresConfirmation) + } + + @Test("ALTER is classified as destructive") + func alterIsDestructive() { + let result = classifySQL("ALTER TABLE users ADD COLUMN age INTEGER") + #expect(result == .destructive(.alter)) + #expect(result.requiresConfirmation) + } + + @Test("TRUNCATE is classified as destructive") + func truncateIsDestructive() { + let result = classifySQL("TRUNCATE TABLE users") + #expect(result == .destructive(.truncate)) + #expect(result.requiresConfirmation) + } + + // MARK: - Case insensitivity + + @Test("Classification is case-insensitive") + func caseInsensitive() { + #expect(classifySQL("delete from users") == .destructive(.delete)) + #expect(classifySQL("Drop Table foo") == .destructive(.drop)) + #expect(classifySQL("select 1") == .safe) + #expect(classifySQL("INSERT into t values (1)") == .mutation(.insert)) + } + + // MARK: - Leading whitespace + + @Test("Classification ignores leading whitespace") + func leadingWhitespace() { + #expect(classifySQL(" \n DELETE FROM users") == .destructive(.delete)) + #expect(classifySQL("\t SELECT 1") == .safe) + } + + // MARK: - SQLStatementKind + + @Test("Destructive kinds are correct") + func destructiveKinds() { + #expect(SQLStatementKind.delete.isDestructive) + #expect(SQLStatementKind.drop.isDestructive) + #expect(SQLStatementKind.alter.isDestructive) + #expect(SQLStatementKind.truncate.isDestructive) + #expect(!SQLStatementKind.select.isDestructive) + #expect(!SQLStatementKind.insert.isDestructive) + #expect(!SQLStatementKind.update.isDestructive) + } + + @Test("Mutation kinds are correct") + func mutationKinds() { + #expect(SQLStatementKind.insert.isMutation) + #expect(SQLStatementKind.update.isMutation) + #expect(!SQLStatementKind.select.isMutation) + #expect(!SQLStatementKind.delete.isMutation) + } +} + +@Suite("ToolExecutionDelegate") +struct ToolExecutionDelegateProtocolTests { + + @Test("AutoApproveDelegate approves all operations") + func autoApprove() async { + let delegate = AutoApproveDelegate() + let context = DestructiveOperationContext( + sql: "DELETE FROM users", + statementKind: .delete, + classification: .destructive(.delete), + description: "Delete all rows from users" + ) + let result = await delegate.confirmDestructiveOperation(context) + #expect(result == true) + } + + @Test("RejectAllDelegate rejects all operations") + func rejectAll() async { + let delegate = RejectAllDelegate() + let context = DestructiveOperationContext( + sql: "DROP TABLE users", + statementKind: .drop, + classification: .destructive(.drop), + description: "Drop the users table" + ) + let result = await delegate.confirmDestructiveOperation(context) + #expect(result == false) + } + + @Test("Default delegate implementation rejects destructive operations") + func defaultRejects() async { + struct EmptyDelegate: ToolExecutionDelegate {} + let delegate = EmptyDelegate() + let context = DestructiveOperationContext( + sql: "DELETE FROM users", + statementKind: .delete, + classification: .destructive(.delete), + description: "Delete rows" + ) + let result = await delegate.confirmDestructiveOperation(context) + #expect(result == false) + } +} + +// MARK: - Tracking Delegate for Integration Tests + +/// A delegate that records all calls for verification in tests. +private final class TrackingDelegate: ToolExecutionDelegate, @unchecked Sendable { + private let lock = NSLock() + + private var _confirmCalls: [DestructiveOperationContext] = [] + private var _willExecuteCalls: [(sql: String, classification: DestructiveClassification)] = [] + private var _didExecuteCalls: [(sql: String, success: Bool)] = [] + private var _confirmResult: Bool + + var confirmCalls: [DestructiveOperationContext] { + lock.withLock { _confirmCalls } + } + + var willExecuteCalls: [(sql: String, classification: DestructiveClassification)] { + lock.withLock { _willExecuteCalls } + } + + var didExecuteCalls: [(sql: String, success: Bool)] { + lock.withLock { _didExecuteCalls } + } + + init(confirmResult: Bool) { + self._confirmResult = confirmResult + } + + func confirmDestructiveOperation(_ context: DestructiveOperationContext) async -> Bool { + lock.withLock { _confirmCalls.append(context) } + return _confirmResult + } + + func willExecuteSQL(_ sql: String, classification: DestructiveClassification) async { + lock.withLock { _willExecuteCalls.append((sql: sql, classification: classification)) } + } + + func didExecuteSQL(_ sql: String, success: Bool) async { + lock.withLock { _didExecuteCalls.append((sql: sql, success: success)) } + } +} + +@Suite("ToolExecutionDelegate - ChatEngine Integration") +struct DelegateIntegrationTests { + + @Test("DestructiveOperationContext captures target table") + func contextCapturesTable() { + let context = DestructiveOperationContext( + sql: "DELETE FROM users WHERE id = 1", + statementKind: .delete, + classification: .destructive(.delete), + description: "Delete from users", + targetTable: "users" + ) + #expect(context.targetTable == "users") + #expect(context.statementKind == .delete) + #expect(context.classification.requiresConfirmation) + } + + @Test("classifySQL returns destructive for DELETE") + func classifySQLDestructive() { + let result = classifySQL("DELETE FROM orders WHERE id = 5") + #expect(result == .destructive(.delete)) + #expect(result.requiresConfirmation) + } + + @Test("classifySQL returns safe for SELECT") + func classifySQLSafe() { + let result = classifySQL("SELECT * FROM users") + #expect(result == .safe) + #expect(!result.requiresConfirmation) + } + + @Test("classifySQL returns mutation for INSERT") + func classifySQLMutation() { + let result = classifySQL("INSERT INTO users (name) VALUES ('test')") + #expect(result == .mutation(.insert)) + #expect(!result.requiresConfirmation) + } + + @Test("DestructiveClassification.isMutating is true for mutations and destructive") + func isMutatingCovers() { + #expect(DestructiveClassification.mutation(.insert).isMutating) + #expect(DestructiveClassification.mutation(.update).isMutating) + #expect(DestructiveClassification.destructive(.delete).isMutating) + #expect(!DestructiveClassification.safe.isMutating) + } +} diff --git a/Tests/SwiftDBAITests/UnifiedProviderTestHarness.swift b/Tests/SwiftDBAITests/UnifiedProviderTestHarness.swift new file mode 100644 index 0000000..6b7fc9c --- /dev/null +++ b/Tests/SwiftDBAITests/UnifiedProviderTestHarness.swift @@ -0,0 +1,617 @@ +// UnifiedProviderTestHarness.swift +// SwiftDBAI Tests +// +// A unified test harness that validates all seven provider types +// conform to the AnyLanguageModel protocol and produce consistent +// ChatEngine-compatible output. Covers: OpenAI, Anthropic, Gemini, +// OpenAI-Compatible, Ollama, llama.cpp, and on-device (MLX/CoreML). + +import AnyLanguageModel +import Foundation +import GRDB +import Testing + +@testable import SwiftDBAI + +// MARK: - Provider-Simulating Mock Models + +/// A mock that records which LanguageModel protocol methods were called, +/// the arguments passed, and returns configurable responses. +/// Used to validate that every provider path through ChatEngine +/// exercises the same protocol surface. +final class ProviderConformanceMock: LanguageModel, @unchecked Sendable { + typealias UnavailableReason = Never + + /// Track calls to verify protocol conformance exercised fully. + struct CallRecord: Sendable { + let method: String + let promptDescription: String + let timestamp: Date + } + + private let lock = NSLock() + private var _calls: [CallRecord] = [] + private let _responses: [String] + private var _callIndex = 0 + + /// Label for diagnostics. + let providerName: String + + var calls: [CallRecord] { + lock.lock() + defer { lock.unlock() } + return _calls + } + + init(providerName: String, responses: [String]) { + self.providerName = providerName + self._responses = responses + } + + private func nextResponse() -> String { + lock.lock() + defer { lock.unlock() } + let idx = _callIndex + _callIndex += 1 + return idx < _responses.count ? _responses[idx] : "fallback response" + } + + private func recordCall(method: String, prompt: String) { + lock.lock() + _calls.append(CallRecord(method: method, promptDescription: prompt, timestamp: Date())) + lock.unlock() + } + + func respond( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) async throws -> LanguageModelSession.Response where Content: Generable { + recordCall(method: "respond", prompt: prompt.description) + let text = nextResponse() + let rawContent = GeneratedContent(kind: .string(text)) + let content = try Content(rawContent) + return LanguageModelSession.Response( + content: content, + rawContent: rawContent, + transcriptEntries: [][...] + ) + } + + func streamResponse( + within session: LanguageModelSession, + to prompt: Prompt, + generating type: Content.Type, + includeSchemaInPrompt: Bool, + options: GenerationOptions + ) -> sending LanguageModelSession.ResponseStream where Content: Generable { + recordCall(method: "streamResponse", prompt: prompt.description) + let text = nextResponse() + let rawContent = GeneratedContent(kind: .string(text)) + let content = try! Content(rawContent) + return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent) + } +} + +// MARK: - Test Database Helper + +/// Creates a minimal in-memory database for provider integration tests. +private func makeProviderTestDatabase() throws -> DatabaseQueue { + let db = try DatabaseQueue(path: ":memory:") + try db.write { db in + try db.execute(sql: """ + CREATE TABLE products ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + price REAL NOT NULL, + category TEXT NOT NULL + ) + """) + try db.execute(sql: """ + INSERT INTO products (name, price, category) VALUES + ('Widget', 9.99, 'tools'), + ('Gadget', 24.99, 'electronics'), + ('Doohickey', 4.50, 'tools') + """) + } + return db +} + +// MARK: - Unified Provider Test Harness + +@Suite("Unified Provider Test Harness") +struct UnifiedProviderTestHarness { + + // MARK: - Provider Configuration Enumeration + + /// All seven provider types that SwiftDBAI supports. + enum TestedProvider: String, CaseIterable { + case openAI + case anthropic + case gemini + case openAICompatible + case ollama + case llamaCpp + case onDevice + } + + /// Creates a ProviderConformanceMock simulating each provider type. + private func makeMock(for provider: TestedProvider, responses: [String]) -> ProviderConformanceMock { + ProviderConformanceMock(providerName: provider.rawValue, responses: responses) + } + + // MARK: - 1. Protocol Conformance — All Providers Are LanguageModel + + @Test("All provider types produce instances conforming to LanguageModel protocol") + func allProvidersConformToLanguageModel() { + // Cloud providers via ProviderConfiguration.makeModel() + let openAI = ProviderConfiguration.openAI(apiKey: "test-key", model: "gpt-4o").makeModel() + let anthropic = ProviderConfiguration.anthropic(apiKey: "test-key", model: "claude-sonnet-4-20250514").makeModel() + let gemini = ProviderConfiguration.gemini(apiKey: "test-key", model: "gemini-2.0-flash").makeModel() + let openAICompatible = ProviderConfiguration.openAICompatible( + apiKey: "test-key", + model: "local-model", + baseURL: URL(string: "http://localhost:8080/v1/")! + ).makeModel() + let ollama = ProviderConfiguration.ollama(model: "llama3.2").makeModel() + let llamaCpp = ProviderConfiguration.llamaCpp(model: "default").makeModel() + // On-device MLX (wraps as openAICompatible internally) + let onDeviceMLX = ProviderConfiguration.onDeviceMLX( + MLXProviderConfiguration(modelId: "test-model") + ).makeModel() + + // Verify all are LanguageModel + let models: [(String, any LanguageModel)] = [ + ("OpenAI", openAI), + ("Anthropic", anthropic), + ("Gemini", gemini), + ("OpenAI-Compatible", openAICompatible), + ("Ollama", ollama), + ("llama.cpp", llamaCpp), + ("On-Device MLX", onDeviceMLX), + ] + + for (name, model) in models { + // Protocol conformance is compile-time, but we verify isAvailable works + #expect(model.isAvailable, "\(name) model should report as available") + } + } + + @Test("All provider configurations produce correct concrete model types") + func providerConfigurationsProduceCorrectTypes() { + let openAI = ProviderConfiguration.openAI(apiKey: "k", model: "m").makeModel() + #expect(openAI is OpenAILanguageModel, "OpenAI config should produce OpenAILanguageModel") + + let anthropic = ProviderConfiguration.anthropic(apiKey: "k", model: "m").makeModel() + #expect(anthropic is AnthropicLanguageModel, "Anthropic config should produce AnthropicLanguageModel") + + let gemini = ProviderConfiguration.gemini(apiKey: "k", model: "m").makeModel() + #expect(gemini is GeminiLanguageModel, "Gemini config should produce GeminiLanguageModel") + + let openAICompat = ProviderConfiguration.openAICompatible( + apiKey: "k", model: "m", baseURL: URL(string: "http://localhost:1234")! + ).makeModel() + #expect(openAICompat is OpenAILanguageModel, "OpenAI-Compatible config should produce OpenAILanguageModel") + + let ollama = ProviderConfiguration.ollama(model: "m").makeModel() + #expect(ollama is OllamaLanguageModel, "Ollama config should produce OllamaLanguageModel") + + let llamaCpp = ProviderConfiguration.llamaCpp(model: "m").makeModel() + #expect(llamaCpp is OpenAILanguageModel, "llama.cpp config should produce OpenAILanguageModel (OpenAI-compatible)") + + // On-device uses OpenAILanguageModel internally as a wrapper + let onDevice = ProviderConfiguration.onDeviceMLX( + MLXProviderConfiguration(modelId: "test") + ).makeModel() + #expect(onDevice is OpenAILanguageModel, "On-device MLX config should produce OpenAILanguageModel wrapper") + } + + // MARK: - 2. Consistent ChatEngine-Compatible Output + + @Test("Every provider mock produces valid ChatEngine responses for SELECT queries", + arguments: TestedProvider.allCases) + func providerProducesValidChatEngineResponse(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + let mock = makeMock(for: provider, responses: [ + "SELECT COUNT(*) FROM products", // SQL generation + "There are 3 products in the database.", // Summary (fallback) + ]) + + let engine = ChatEngine(database: db, model: mock) + let response = try await engine.send("How many products are there?") + + // All providers must produce: + // 1. Non-empty summary + #expect(!response.summary.isEmpty, "\(provider.rawValue): summary must not be empty") + + // 2. Valid SQL that was executed + #expect(response.sql == "SELECT COUNT(*) FROM products", + "\(provider.rawValue): SQL must match generated query") + + // 3. A QueryResult with data + #expect(response.queryResult != nil, "\(provider.rawValue): queryResult must exist") + #expect(response.queryResult?.rowCount == 1, "\(provider.rawValue): should have 1 row for COUNT") + } + + @Test("Every provider mock produces valid ChatEngine responses for multi-row SELECT", + arguments: TestedProvider.allCases) + func providerProducesMultiRowResponse(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + let mock = makeMock(for: provider, responses: [ + "SELECT name, price FROM products ORDER BY price DESC", + "Here are the products sorted by price.", + ]) + + let engine = ChatEngine(database: db, model: mock) + let response = try await engine.send("List products by price") + + #expect(response.queryResult != nil, "\(provider.rawValue): queryResult must exist") + #expect(response.queryResult?.rowCount == 3, "\(provider.rawValue): should return all 3 products") + #expect(response.queryResult?.columns.contains("name") == true, + "\(provider.rawValue): columns must include 'name'") + #expect(response.queryResult?.columns.contains("price") == true, + "\(provider.rawValue): columns must include 'price'") + } + + // MARK: - 3. Consistent LanguageModelSession Integration + + @Test("Every provider mock works through LanguageModelSession.respond(to:)", + arguments: TestedProvider.allCases) + func providerWorksWithSession(provider: TestedProvider) async throws { + let mock = makeMock(for: provider, responses: [ + "SELECT 1 AS test", + ]) + + let session = LanguageModelSession( + model: mock, + instructions: "You are a SQL assistant." + ) + + let response = try await session.respond(to: "Generate a test query") + + // Verify the response content is the expected string + #expect(response.content == "SELECT 1 AS test", + "\(provider.rawValue): session response should match mock output") + + // Verify the mock received the call + #expect(mock.calls.count == 1, "\(provider.rawValue): should have exactly 1 call") + #expect(mock.calls.first?.method == "respond", + "\(provider.rawValue): should call respond method") + } + + @Test("Every provider mock works through LanguageModelSession.streamResponse(to:)", + arguments: TestedProvider.allCases) + func providerWorksWithStreamSession(provider: TestedProvider) async throws { + let mock = makeMock(for: provider, responses: [ + "SELECT 42 AS answer", + ]) + + let session = LanguageModelSession( + model: mock, + instructions: "You are a SQL assistant." + ) + + let stream = session.streamResponse(to: "Give me a number") + let collected = try await stream.collect() + + #expect(collected.content == "SELECT 42 AS answer", + "\(provider.rawValue): stream collected response should match mock output") + #expect(mock.calls.count == 1, "\(provider.rawValue): should have exactly 1 call") + #expect(mock.calls.first?.method == "streamResponse", + "\(provider.rawValue): should call streamResponse method") + } + + // MARK: - 4. Schema Introspection Works Identically Across Providers + + @Test("Schema introspection returns same schema regardless of provider", + arguments: TestedProvider.allCases) + func schemaIntrospectionIsProviderAgnostic(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + let mock = makeMock(for: provider, responses: ["SELECT 1"]) + + let engine = ChatEngine(database: db, model: mock) + let schema = try await engine.prepareSchema() + + #expect(schema.tableNames.contains("products"), + "\(provider.rawValue): schema must include 'products' table") + #expect(schema.tableNames.count == 1, + "\(provider.rawValue): should have exactly 1 table") + + let table = schema.tables["products"] + #expect(table != nil, "\(provider.rawValue): must find products table") + #expect(table?.columns.count == 4, + "\(provider.rawValue): products table must have 4 columns") + } + + // MARK: - 5. Error Handling Consistency + + @Test("All providers handle empty schema consistently", + arguments: TestedProvider.allCases) + func emptySchemaHandledConsistently(provider: TestedProvider) async throws { + let db = try DatabaseQueue(path: ":memory:") + let mock = makeMock(for: provider, responses: ["SELECT 1"]) + + let engine = ChatEngine(database: db, model: mock) + + do { + _ = try await engine.send("Show me data") + Issue.record("\(provider.rawValue): should throw for empty schema") + } catch let error as SwiftDBAIError { + #expect(error == .emptySchema, + "\(provider.rawValue): must throw .emptySchema for database with no tables") + } + } + + @Test("All providers reject disallowed SQL operations consistently", + arguments: TestedProvider.allCases) + func disallowedSQLRejectedConsistently(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + let mock = makeMock(for: provider, responses: [ + "DELETE FROM products WHERE id = 1", + ]) + + // Default allowlist is readOnly (SELECT only) + let engine = ChatEngine(database: db, model: mock) + + do { + _ = try await engine.send("Delete the first product") + Issue.record("\(provider.rawValue): should reject DELETE when allowlist is readOnly") + } catch { + // All providers must trigger the same error path for disallowed operations + #expect(error is SwiftDBAIError, + "\(provider.rawValue): error must be SwiftDBAIError") + } + } + + // MARK: - 6. Conversation History Consistency + + @Test("Conversation history works identically for all providers", + arguments: TestedProvider.allCases) + func conversationHistoryConsistent(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + // ChatEngine calls LLM for SQL generation, then TextSummaryRenderer + // may call LLM for summarization. For aggregate queries (COUNT, AVG), + // TextSummaryRenderer uses a template and skips the LLM call. + // So the mock sequence is: SQL1, SQL2 (each followed by template summary). + let mock = makeMock(for: provider, responses: [ + "SELECT COUNT(*) FROM products", + "SELECT AVG(price) FROM products", + ]) + + let engine = ChatEngine(database: db, model: mock) + + _ = try await engine.send("How many products?") + _ = try await engine.send("What is the average price?") + + let messages = engine.messages + #expect(messages.count == 4, + "\(provider.rawValue): should have 4 messages (2 user + 2 assistant)") + #expect(messages[0].role == .user, "\(provider.rawValue): first message should be user") + #expect(messages[1].role == .assistant, "\(provider.rawValue): second message should be assistant") + #expect(messages[2].role == .user, "\(provider.rawValue): third message should be user") + #expect(messages[3].role == .assistant, "\(provider.rawValue): fourth message should be assistant") + + // Both assistant messages must have SQL + #expect(messages[1].sql != nil, "\(provider.rawValue): first response must have SQL") + #expect(messages[3].sql != nil, "\(provider.rawValue): second response must have SQL") + } + + // MARK: - 7. ProviderConfiguration Roundtrip + + @Test("All cloud provider configurations roundtrip through makeModel()") + func allCloudProvidersRoundtrip() { + let configs: [(String, ProviderConfiguration)] = [ + ("OpenAI", .openAI(apiKey: "sk-test", model: "gpt-4o")), + ("OpenAI Responses", .openAI(apiKey: "sk-test", model: "gpt-4o", variant: .responses)), + ("Anthropic", .anthropic(apiKey: "sk-ant-test", model: "claude-sonnet-4-20250514")), + ("Anthropic+version", .anthropic(apiKey: "sk-ant-test", model: "claude-sonnet-4-20250514", apiVersion: "2024-01-01")), + ("Anthropic+betas", .anthropic(apiKey: "sk-ant-test", model: "claude-sonnet-4-20250514", betas: ["computer-use"])), + ("Gemini", .gemini(apiKey: "AIza-test", model: "gemini-2.0-flash")), + ("Gemini+version", .gemini(apiKey: "AIza-test", model: "gemini-2.0-flash", apiVersion: "v1")), + ("OpenAI-Compatible", .openAICompatible( + apiKey: "key", model: "model", baseURL: URL(string: "http://localhost:1234")! + )), + ("Ollama", .ollama(model: "llama3.2")), + ("Ollama+custom URL", .ollama(model: "qwen2.5", baseURL: URL(string: "http://192.168.1.100:11434")!)), + ("llama.cpp", .llamaCpp(model: "default")), + ("llama.cpp+custom", .llamaCpp(model: "my-model", baseURL: URL(string: "http://localhost:9090")!)), + ] + + for (name, config) in configs { + let model = config.makeModel() + #expect(model.isAvailable, "\(name): model must be available after makeModel()") + } + } + + @Test("On-device provider configurations produce valid models") + func onDeviceProvidersRoundtrip() { + let mlxConfigs: [MLXProviderConfiguration] = [ + .llama3_2_3B(), + .qwen2_5_coder_3B(), + .phi3_5_mini(), + MLXProviderConfiguration(modelId: "custom-model", temperature: 0.2), + ] + + for mlxConfig in mlxConfigs { + let providerConfig = ProviderConfiguration.onDeviceMLX(mlxConfig) + let model = providerConfig.makeModel() + #expect(model.isAvailable, "MLX model '\(mlxConfig.modelId)' must be available") + } + } + + // MARK: - 8. Write Operation Allowlist Consistency + + @Test("Write operations require explicit opt-in for all providers", + arguments: TestedProvider.allCases) + func writeOperationsRequireOptIn(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + + // Mock returns an INSERT statement + let mock = makeMock(for: provider, responses: [ + "INSERT INTO products (name, price, category) VALUES ('New', 1.00, 'misc')", + ]) + + // readOnly allowlist (default) + let readOnlyEngine = ChatEngine(database: db, model: mock) + + do { + _ = try await readOnlyEngine.send("Add a new product") + Issue.record("\(provider.rawValue): INSERT should be rejected with readOnly allowlist") + } catch { + #expect(error is SwiftDBAIError, + "\(provider.rawValue): must throw SwiftDBAIError for disallowed INSERT") + } + } + + @Test("Allowed write operations work for all providers", + arguments: TestedProvider.allCases) + func allowedWriteOperationsWork(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + + let mock = makeMock(for: provider, responses: [ + "INSERT INTO products (name, price, category) VALUES ('NewItem', 1.00, 'misc')", + "Successfully added 1 product.", + ]) + + let engine = ChatEngine( + database: db, + model: mock, + allowlist: .standard + ) + + let response = try await engine.send("Add a product called NewItem") + #expect(response.sql?.uppercased().hasPrefix("INSERT") == true, + "\(provider.rawValue): SQL should be an INSERT") + } + + // MARK: - 9. Response Format Consistency + + @Test("ChatResponse structure is identical regardless of provider", + arguments: TestedProvider.allCases) + func responseStructureConsistent(provider: TestedProvider) async throws { + let db = try makeProviderTestDatabase() + let mock = makeMock(for: provider, responses: [ + "SELECT name, price, category FROM products", + "Found 3 products across 2 categories.", + ]) + + let engine = ChatEngine(database: db, model: mock) + let response = try await engine.send("Show all products") + + // ChatResponse must always have these properties populated + #expect(response.summary.count > 0, + "\(provider.rawValue): summary must be non-empty") + #expect(response.sql != nil, + "\(provider.rawValue): sql must be present") + #expect(response.queryResult != nil, + "\(provider.rawValue): queryResult must be present") + + // QueryResult structure must match the query + let qr = response.queryResult! + #expect(qr.columns == ["name", "price", "category"], + "\(provider.rawValue): columns must match SELECT clause") + #expect(qr.rowCount == 3, + "\(provider.rawValue): must return all rows") + #expect(qr.sql == "SELECT name, price, category FROM products", + "\(provider.rawValue): QueryResult.sql must match executed SQL") + #expect(qr.executionTime >= 0, + "\(provider.rawValue): execution time must be non-negative") + } + + // MARK: - 10. Provider Enum Completeness + + @Test("TestedProvider covers all ProviderConfiguration.Provider cases plus on-device") + func testedProviderCoversAllCases() { + // ProviderConfiguration.Provider has 6 cases + let configProviderCount = ProviderConfiguration.Provider.allCases.count + #expect(configProviderCount == 6, "ProviderConfiguration.Provider should have 6 cases") + + // TestedProvider adds on-device for 7 total + #expect(TestedProvider.allCases.count == 7, "TestedProvider should cover all 7 provider types") + + // Verify 1:1 mapping for the config providers + let configNames = Set(ProviderConfiguration.Provider.allCases.map(\.rawValue)) + for tested in TestedProvider.allCases where tested != .onDevice { + #expect(configNames.contains(tested.rawValue), + "\(tested.rawValue) must map to a ProviderConfiguration.Provider case") + } + } + + // MARK: - 11. ChatEngine Convenience Init Consistency + + @Test("ChatEngine convenience init with ProviderConfiguration works for all cloud providers") + func chatEngineConvenienceInitWorks() throws { + let db = try makeProviderTestDatabase() + + let configs: [ProviderConfiguration] = [ + .openAI(apiKey: "test", model: "gpt-4o"), + .anthropic(apiKey: "test", model: "claude-sonnet-4-20250514"), + .gemini(apiKey: "test", model: "gemini-2.0-flash"), + .openAICompatible(apiKey: "test", model: "m", baseURL: URL(string: "http://localhost:1234")!), + .ollama(model: "llama3.2"), + .llamaCpp(model: "default"), + ] + + for config in configs { + // This should not throw — it only creates the engine, doesn't call the LLM + let engine = ChatEngine(database: db, provider: config) + #expect(engine.tableCount == nil, "tableCount should be nil before first query") + } + } + + // MARK: - 12. Availability Reporting + + @Test("All real provider models report available by default") + func allModelsReportAvailable() { + let models: [(String, any LanguageModel)] = [ + ("OpenAI", OpenAILanguageModel(apiKey: "k", model: "m")), + ("Anthropic", AnthropicLanguageModel(apiKey: "k", model: "m")), + ("Gemini", GeminiLanguageModel(apiKey: "k", model: "m")), + ("Ollama", OllamaLanguageModel(model: "m")), + ] + + for (name, model) in models { + #expect(model.isAvailable, "\(name) should be available by default") + } + } + + // MARK: - 13. On-Device Pipeline Status + + @Test("On-device inference pipeline starts in notLoaded state") + func onDevicePipelineInitialState() { + let mlxPipeline = OnDeviceInferencePipeline( + mlxConfiguration: .llama3_2_3B() + ) + #expect(mlxPipeline.status == .notLoaded) + #expect(mlxPipeline.providerType == .mlx) + + let coreMLPipeline = OnDeviceInferencePipeline( + coreMLConfiguration: CoreMLProviderConfiguration( + modelURL: URL(fileURLWithPath: "/tmp/test.mlmodelc") + ) + ) + #expect(coreMLPipeline.status == .notLoaded) + #expect(coreMLPipeline.providerType == .coreML) + } + + @Test("On-device SQL generation hints are populated for both provider types") + func onDeviceSQLHints() { + let mlxPipeline = OnDeviceInferencePipeline(mlxConfiguration: .llama3_2_3B()) + let mlxHints = mlxPipeline.recommendedSQLGenerationHints + #expect(mlxHints.maxTokens > 0) + #expect(mlxHints.temperature >= 0) + #expect(!mlxHints.systemPromptSuffix.isEmpty) + + let coreMLPipeline = OnDeviceInferencePipeline( + coreMLConfiguration: CoreMLProviderConfiguration( + modelURL: URL(fileURLWithPath: "/tmp/test.mlmodelc") + ) + ) + let coreMLHints = coreMLPipeline.recommendedSQLGenerationHints + #expect(coreMLHints.maxTokens > 0) + #expect(coreMLHints.temperature >= 0) + #expect(!coreMLHints.systemPromptSuffix.isEmpty) + } +} diff --git a/Tests/SwiftDBAITests/ViewInspectorTests.swift b/Tests/SwiftDBAITests/ViewInspectorTests.swift new file mode 100644 index 0000000..c23b1af --- /dev/null +++ b/Tests/SwiftDBAITests/ViewInspectorTests.swift @@ -0,0 +1,489 @@ +// ViewInspectorTests.swift +// SwiftDBAITests +// +// ViewInspector-based tests for SwiftDBAI's SwiftUI views. +// Tests content and structure of MessageBubbleView, ErrorMessageView, +// ScrollableDataTableView, ChatViewConfiguration, and BarChartView. + +import SwiftUI +import Testing +import ViewInspector +@testable import SwiftDBAI + +// MARK: - Test Helpers + +/// Helper to build a DataTable for tests. +private func makeDataTable( + columnNames: [String] = ["id", "name", "score"], + inferredTypes: [DataTable.InferredType] = [.integer, .text, .real], + rowCount: Int = 3 +) -> DataTable { + let columns = columnNames.enumerated().map { idx, name in + DataTable.Column(name: name, index: idx, inferredType: inferredTypes[idx]) + } + let rows = (0.. QueryResult { + let rows: [[String: QueryResult.Value]] = (0..

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 literal 0 HcmV?d00001 diff --git a/screenshots/custom-theme.png b/screenshots/custom-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb01ca3092040f895a47dcbd89a51350e942b0c GIT binary patch literal 31898 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 DQD)s?A=;|@Qz;wOiE&f|C*yS-G=y0r8 zwSjzrPlziHs;uL1$LnpG9G50U`7l=*ZdVYu2$H3~yBJSdG7-?J%zc}&lx{P$KffXO zwYa4}{H>qTS)tW#0>oBNte@#Yi;W>jk*!#6EW*wL$?wYLQ%2mQTHI@{!zM?HZ$3E1 zH$3fCozB(fg=*TjvniDd2rnbn?0Uf{Q};dA-vRT9Qu=B4!X(K*-3B zt6EV3H!lk%7+TkPdE6ptm-(g~w`x=pG5SU81Ymb=gPIEiQ6BtD>j^^Nn@a}9!*?r{ zLjvxqvAhG^A~FS4XTyEW%oxyON@8It!r`&Dir}2H{T+3yTzrtFJ-EnKe-|f2iK~5Y zdAX-`@4G6yqUm%@N%I2#?CGoIZHAmlB?_I5dfdwtggMiMVUs@t+@^vz_Oa_#3lOqW z1@+T*qXD7_3*7?oCfnnr}8aVJiWcx!&x0aw{o zsqa4|-sRPJ;!=&!osZ*Uy=JtPCYBUkNE;S(vSG)^hAY)t^u&%-x+X*wwT8HPTaXvf zgT4f_h$e5hFB@cP;`@9-&U>7CXP5P~rurs(PeD)c=N{297vH(?Jnl9Kk|WU>Vj=%4 z%bLUXOH?S|y$sHop-&Shzo&ANb!atDqy-lp>J!Xf<2h|f13O2x7Te%@(bT$V<7k}x z6$$K_Lq6zwsI*%`_{r-W%+5&95q>a`sks_!;=w?vx_P0rU>e$LqAm`O_fF0)P-+HqmWX%uY`Rd@6-|~plqp5g&mhfqE$CeF@-VvsfpDhA=7_Bx58Jg`yQWA% z>|7oZ2=k-4F+!VWut-)0l@QPRHxvlr6^cmi7&>uG<&yued>K)4wrkXf>L13}gtS+T z8U8C{vzaT9X?o8PMKUmt7gG+CjL{7Y+MULm4h$Jf;(;4c!_IR_TE|zB`4(?jY`5PF zT<`n$+C`^iV|2kl8VNIo;JO9wcJuNiq%3qvoLm-oO?aN=UB_wJXRDD^hmH4(i;(CG zJ03({k#;-wqDuN;$4kd{6zXYpXRtWx<)N*XOM#p(u0=t)gK(MCrBkQEHGnNJaZ}>* zYG`AY$K6R1#=`>{AYEaus&*<%H)|}%)^_(lOGZ5xwI{&FePK#iZyUy?CXI$XQtOnr zLEvi(zkuxQcHg@_bsug9IE}xobx_oI0+bO%=N%ifc13PQG#8J|yg)t}YAu;2!fmqg zol-!}z}l{p2*WrNU0R~elWdVN<9UT>U#4O?iG+L)A0*`!m-hv>1Yg#*G*XiFkh|}6 z5x{+Fxs6t@xX8JLdjAy1L%H$RMTD?|!p5NUk@c_y+BGOz3VGN;u^>*pEva($=m5vH z^QF~5QG&wgGZvy|Tx4_|SShhfx=Rtx&vr)7$~_d4ip!Iu9!oHFZUr?cO?7+{6QNql zXcSW#B#vn~wt8UYXdO|qvwR}0|J{5`lC}HZfiobQ{}Jma_tz9ZnFp!#`4XVH-TGP1 z21Me=F0X2SIs8RE|B9c?%BXT@HApO>I4pW8u?gh@;~TqDKsI&}NMDhe#2V?Ir(6yl zVq_4_4@A%(@35=MvGz&2Pc#CMaojTz;`8Clq;z!dGk@9Iiz8Gh8g-rwx8+hiDYkeq zSa#6`2AZ6x?iqNdTrO;WE7ubwQX^NoBrz9Xe&iM)cUditv~?ciGss)|-r-F4g$xH| z&;d2Pkxzb$>wZl|?hkQL&=KA&mRy4=81uwiY{{<}ymiy4H1Y!1l52aHV1EPM+1lj=m5#tIFyKjqQ^` z#!M2DW3K4+%h*7;(^R@fAz~N{G1*T^n11vDL!DE(`#dO(vA8?=Oro*wZsC25__gsh zjxY~eH_Mg(vLvt>A=s7DR5D=MZ&SiU#fh5MUNi+$TIKii6*o@r$BmW#mav)?={(JjHE|1J7$=3GH#?yq8G{zo}8i<|M8;&Q%xY!p1iHJtOm^BTWeXANK zLVuoH15y-RUhq|S@BV8+d(@3_{MHGP5U~%RQZ(>nFowpce51tMG#O&> z1TkRMdIVlyZ7Vc1LkHbD~`LkB#I;l^cS-PmX5iEaT=|AA%5%KtmM4*CY`4RieHSz@n1Gn#lvA#a+ij{?-@nwTQ zMD07`dgsoq-<|z#-@j{Rbm8b#2gjpqOMj#0Hy&5_dw2A%T)F%k{_FS~{%0YL{TVwj ztoR$(Kd=8=(7_uX_uRpjRIr|MzjOCKI1vgUd?o1qo!>AVg!%7+g972_zhTe6!3Doz zx4*%4f7;ks>4R;)fbdawFV`C&yavJwZvWxB=YN3xZUz2+?(gz@<~coX+ggK1ez1xG z7XfeJ9&iV^1-Jqsz)|1|p!#35SNqdmAMgiThX8lMex86Aa3AcW2VVJ&!2^Vif!lyP zpadv@unKSrtSZ0f8SD!d(&*pkPvM`}!e;^C9GbykH2(9N2^s*(H38sY{XegrI0FDY z902gS{*LQi*FWffuf=^g!4dxf65<3;1|i`2CAm#;GN@(T*z7QHKeUr||AT~k|E-_X|H z(b?7gxu9~Id4;&Swf&2~f86`G0md}Q!1x&$fRmXCTujV702H9pPvpe{|8G{txBmg0 zgZ~3KhyDj}4*wfC-|wWSWrP*f?re6m95x)b(D}l9W?rxU)vJ#JHk`@k0IJObV^8PM zFCe?S)Dv>nCh{G9eQ)78$SIEA`(Kwf@%i})KQrbVGD>1vE4ZHX4StT$sDq2Y{Bc4< zcw~`WfU!&?@}?&yv?0=+O#x{)?%Uq#s#PR8_3TNbN+tq5H^c>~Nh>gdJ$lk{ z?`HD2?YGjA*U|W@mk#znZ>m7Pj7cI}9niP!v?NY^lxQ~^W&ow?G+agJ8NQa#9W&vtAvV8)2+ka2w7Ls6A!~lpHQ0!VxttRb|29zuV zVj=f?GXP%gP6n{e6U6{Ntspn^ptzfx+`C&&41fu|B!h&h*>4_V0Ngid_2{48EA-*#;F~@e?Kh-u$hZ080TgH0~u&CCv;bJZ#@tFQaKV$ zaGb@k>5aHq#9)M?n)F=jLKuLXvD#{XbMKiDw}J9Fq5x%=qDdcy9Ap4KFhDl3MQFMz zMl@Po{Okft)5khVT^r()fTZ4v|!4H{OR+VUdwP^hD!j>j^1eHX;Pu|L~=^$#H zR1LD(br+bp`<4L+&VErjYqjsdXLZs*gbq6c;OHj#5KvJ%n!dnzl|@Vs*7#~hMo zj-bqJB5b`wwP7f2%LfdACBtF^D)#X~RBP+cP%HaouA)FfVi&riHcBUim`AZ2decJA zo#&`qYm#^MSO1b2!j&a|i_e&oNAET?N-DYc{Tq+;{y^@DeC%wv?y2B7>}=dG6rbO>4YhLpK`sE!aB zt<0?7758+R$Z2aCG?sNN|2@T%_>VM&txz}bFTITMYBhJYHH;)iFXmAfPM zQ_#amj}6uds^kb=0j`zVecH+EY)xhnj9cWVX`#V8z2e4Bfw7$9rZNv24iV5_1V+|% zVpnyHDdKQ7e~jQf$(J083k$U#-FBKbMDqEhEX$F!%~Z{5eP6ZoajGx4z9$pJnR{(YBth)h9R{LjoqwrD)zi;H@)~u zl&>PS{__z7tITKTS4z6SE59oZ6F8Yfdj57OZprHP{2{x&d$LOz)$8%c+Q>PjO^T)l zo4rAP4(~eqj5d9HHhi_=I=1PekA;PWw+m1s8zOKe@-d>kJy~-uQH&hrG!!_?OVuEJ zO)5x`G=H+xI}{ot6Upwskmfa==5Tf2$6^n?y4TOE7j(I3iE9c?0^IR*1#Z#H4bSR} ze?Ig>J*tA@(_-$&(E9^JgCu)I6c7&(50N%hn1-&$m3e72_>*p zCE20d0=&ySc}~$X37)kt4H+%IMv&eYX%&;*c%ofW$7Gs#?nD0LiSz}YMpSxT*dFN~*SWq^BOibLu&lEUFBUpt zVw!Z<#UfQ(dBzAq@fy%R)JDcsmYAG2v03aLeqrCd;ay^nSgXPRMEDKD0wxf>*_`Cp zIvl0&L*vz&LrqSlD-B$^vazMOn!2H-K|fq`V_kr+Lb24oF$?tzSrb-8DH-$Cv*9pS z9b3YW4ulq&11FB4`?JxK=bb(hb#wM7)enDI7aa62otX)FWRr2J<9RRh`?~AhEH;)z zX2sX@`{kUgvary=`UYqo1w$3~unkLg@vRB2yAnxY-X7tI`uv}}%CAH&SdTwO1fOj0tAw093YE&YK>lsRn7 zr(2%L!JJSQQS>x^QDntjWidG+FK^>PT7t2|PQwH5JYti4OuKd2*>$<$MYDS{w54&w zUv{GpM!nYIX0Q-st7gktUnmRIXZ8>+8#`=5R9w>)87qr((>mT9AjW%Yxj}eBOybRL z@gw7{)st|w<`gq>5_a0~xNX@lysn4q_rfo?xR+f}A~$SQb@GMNagzNaA742BG9MV# zR-Y*Nsw>`q@_IQL?kwbe!I(DiYa(Y>fZr0fwTCSE!5IDTWgy)XIagP}0NRLzs|=vn z%Acn2kZO#Ygprk$sp-o58Nl~qWqO)1EXJ0Op@|?DB6^{pK((+QvQ!kGvuW+l06O<1 zS8^uqk&@y_51NSTqcbltZ^qPQ!Y`l4?oX?}MZcN#?yeR^HT{fO)|4e7os?t|nZwj{ zj}SR;p1ir!5go2Sdhrp`qq&!sX{~LcNNPM^E_VNuOp9F^|B$-3+AH;+_}h+M@=vSN ze!02tvp8NUck)7_LT-`9r*YF13V8nPRLcSuxO3IZCZy z*kvpza8kNpjWbtoznJ{tY5A?>*n!h?I<*1~94c1$;k6UJFU_I|(o!_uKzs5W~n z)IrIuG4+7=`4?wYWP~4ozGA3*lWa?rJx|sq&Ki=7a=nLq8g7Q8-WGbkT`7IyU6%L! z<(R#}iAt7MuJ^V#fJf&$gZl-7C0P4DZu__{>uuT;tu53SYzXvE&(Nvq8haagvV0wN z4V4iNJ@*W+El+rDZ%$4-I?jgb5{PI#l(4dcjf$g-2Ycv*l?>prj4q9Qt)6#w(Vlp7 zHgI@K?f8jtFDp@r>uz7Cp1)zb)`u7!bfAyT9H-^qFH>4r&X#Hzbwdxj{G6Ps$t-Cb z+u@NOIlpUtQupV=l?M&Qtw8t|BtnM+mm{iQVu8)HzMCPDq|x~P@Xu_sgvR5>%3lrf zWycdr(zNF8_$HyC%%8N>?2bQm|FTdhTqHC7-Y_F*e7ec|!dP`#Fy@DgWXu-Z+IwS6 z{~5~r@wgPbVJEHLo#qYCP>3lUM$RI%cj)p*was#~B`Qn#5<>%v$&(HgCn=u@D-4Gz zTra+6>D7+b(ftQh4ne~~bq&skg+|gf$ks$Y^U>Sevx4nK7KdiybgoQcVBRV{ij4wU z!cq}m3U9a^;j&q33CwFQYzww1rWw?q*^+ba*}7+F65u^+Y9d!qH8fT|piQ}p9@DKT z&B~Z?>_1cOuZC%%dyWdtRtd%h5%sbDiQ{IChsn&B2s4?Xw{Dp2_~%Z&TJTCV;0=#B z^z9eQtcLc!i5)k~Rr{1KV>F+oJ84Fgs4VjmLU4?_RlLUd+8eGriGE+S7F~Oh_UyiP z`n`RnrxTyNLum_ONe&LyR^jFl4lD06j~euB@@l@v8)5Ml zq;TR+*VXOJk!oU8#IJ;XW1WTZ{eChXMRHbg%CAh06(%=Nl_dzaHCdv3!Ac)fZM5#GbrjmG%0? zYth)orkr~5!1S3fgd^0Tjgzi8jFok2ex7ee|Ad2b2niOj0zHWIQ6*%y(m4mK%H;9s z2qEILYMf|i{d2{#Uy;?W{zU^fA0+&AJW;Tp;?Hs;-dY_M4`HT?5;i&z2grwV$;rY^ zZim4BCjAJph4Ga2OGMbwbzF&lh0u9#dAYwS?(?eVq@KUh^-b?|4? z(M!bJFN|m`c;vRvxVAI*Q{;l*5PD74mjUG4wG;_a<@TVT2|uX;zo6)cpyvGp&H$Xb z8Dsy3`0)nC4?%$3Hy4*ZfQ@^(MPHnNQq&Pg$G1UCbN?5D0Cf-c2qEia*zf13o15!m z!ggSXLyR<+Lux7x*hc+$hQ=WC#&iY9eDVuHi`H?QKANDxK&mR-AI& z4!BHV0HnLaE!-=!9Qdl~lLOe9!uevj-dP`veMe?oiFDIY??uI9Lm$#EmANKO^-6n7 zIRxFpJcySVw;4Bjgmh1go?#u);mPw(Sq52wpPK#0Q9IwpNuj!mkyoZu3gxA>8m{m< z>F+|fP31k+H|^UuFxp)?+)c`U*aO=G9KLprVcQwm&s(I16n_b;trpkpCOh>Uonq#< zJKaL=<6Qe{W>-ziZ;}1*3WtYW%V>8D@7h6P90_AOprGTJL&%2dk+t^I};+XI@MJ88Z z3dJWHJTc9Z=zBb`K*9btft>(3Qq&G(1NCf6Y?yLu1`8%9fvV_@L2#O(gVhhJ)hNQp!Qa+p@F-ftvs!SW1SZi#U zY`Jh;a!J(TmF??tOBcQma7&_p^mRgWOOX_33)isQP?cHI{t179k=j@(29O=q|Efp! z-6#CDU0D`S4KF-W7x=^rv*@0Y=f zPV^q~D_kqcH1_w%-P6A}$NdDMMJt2*67sm&gYDaAj*z`tPL3sO)-^q@wjUPld!}fz zZ7db%C4H<{Po(04u%1+UsW0*=3;@MI;wOY~HszCufAlX43CEv~g;DL=aqWH~Z>GA2 z)cOT*sgX-^lAE?k8i(h!O{-GE;9Q~Uh_EVD$xoM$AdE|#}y^S`PxW(5^vS}HHOTro|kz2zpl z@an;%i0@CuPFyh$KtMe2WC`dCpV%CV4{0vQQP{ot7L%L~Ra+>0F#Ld`^SlMUYUgxf zB=FSojNfFEd>3)i_-(x(|7db8N-YtE5TKe6WE0h(@naHV1c?Z&zAK^KLFC#VPXljZ z@-(LFI$D#@MD{uLsoB#oY}Co`uq*~}Bau6p&2e1E?>*IEP?4(V`G%@B2G{c|y4ELk zV*G)>|3d964*3rphwt30dVPH^I&fnWf}5#-yNbD?ku5MR)aiH&;~%p)SY_%Y7pQlq zcSF6s*W5d75Nv{<;w5N@F%DiwFP!21Un$9Yl+0z)>BEt0|(j+-FH{Sc$H%aoZ zPr9u5X*;{nGYS&FAz6cw*y`EV5{ty0JRcOJMT)({USG@>Mk_JVX z$)@<5S&PbQ6}=9u(~ktDR90J&l|~U&Iq?u49ie=0iBrRBqR0a<@?Gv~XA9S&8K0rE zE|bEok=}UM%)5ss9Sv+a+Z8OjV299N5D}_6VYK_n(aLY?{amVJ26R_aTCX zGX9i8{(Y+#OxN;(72wV%&QSZuP+vb^Lqb$XzkSDWYY+~3qz}7YQ=4N}n`xRoZk_?F za_sm0cs>osdhwa`Wq@_MP!E;ee@69ezE#t*&1>9`{rvCyUAUTM@`mC9KuyV@#}vEt zAYG?yWv(ttsaPc7wWMIa%;Zz&!nL=k2Wz`;N0NO*6}4<>iJ(|wZ#fJPvWS`$`~^AW zY@sQr=XT`sW08X&XM58ouzYccX=JHiKuXDHCW< z0mpkU6n!nSU*wx?+;-{FY=!Ce6&{Q+vdcg$ge@(1$<5F=6h?~P?yOI@l#~0hsHlv@ z$5eeBYGR`ubbMDQa{hcWY&lqs;&PeR!)0+z%O==`-R#|sPuJ_&TskfjbL0Bwm3;3_ zJ>uQ=HZ&X6bl%MOT4txjIdZdi$Xnm%O`lzd)X1p(q#uoXeKS){M<+k<9E(y=xytk- zTa{LVkO>|?Ke}xi2jyI@431Tc>%i2+9&J-F!m(|OuhP*(29jW%+C|r=Awf-pHGzKBJ;I zPLea!j(%8_N-uc;tv~r&r>J89juwpZ|6ZrqgggUXmsA0YK3riG!56$I6MD$}aEV&* zR#}mLgJ`tzvea#6uI^MVD?Pda1Mr4@mnTKiG6Gj$2KI|=Iebg~K0!`5dc3YE9S4w--q`y((-Ge<*djQZGWa&Rp}`Dc zkAps~9XGe+C#7d2=Mq1SxZHA{K%S-3P*j1r zX*+$T;sWnqt&AJjc4o2|_D^jqg`0b z4e}uJ=T*WEoudp*o(7dTK0HWMt{Ujx_W|)7U;r@=9msEOP%<-knmCu?G*(xB!N~=0+L7XEv(IS4ny#i2W0U0Z1WPTK<|@qrXN!zEjNr{xco{ zNL$XHpw2lk#IWj5*EFJ9)Ns(Bg0Z#M5(e;`o1FUEh{h?&03Jc4PW|y!ttMe%A8w5H1;9}5N^)^jzWm?XA<|07%>0`IcQ|N8y!+&_1Kx=H~feE%|~qn~v};Ag6imBFEThDQ40 z@f_6Tw@8ayvJ*WXe;=j#wnInY7ooqSbRi=NIk=!sKeuyi*qX;Bt3h`^+QykzCtp{c zo;nnr->3>`XV5`iW9~JCU}22(pg15-!tE&Mx)TCLt$hWmIHjX5Rj2I7Ik0i-yxeC5 zd%;w0y#E#2PSH#EnCeW%5L9~!b%7DN&;&UxbJN{x zW&C-2g`&-w)x$LH_6?>}!b&n{R(?7AB$02|y9>>X2Zd!m^!J^}i#>r_nBU3&pA4PL zza-Y6g0SH9uk8JLq<`+&tH6*0&pmWLyLmc);z@e0q9R}PK@+E#HIq-EI1jH$h7{!( zHb)+D7Y<6A3$(SIO1`rnfSXd-=wsQkw0!hj78DH9`dGx+pXHR0C&JpDpn^U%d&s?W z8M79?KhAb?CVyT&5|B{X!yG^U6;@e970ATyA=)5JEjsj(yRzNeZD z#)}uGqo8Nnz}i6^pbDT!M&uxmT;=FI@-s|VEf!6_{Fd-6bIyXtM?!M&K95=&1Hd@W zqYuJ*RRf5iT$XiMJcuE5g5eM|B}0sfuxN zX4V~dFlJaq`^(Ouwow+bXYN^xN%Jmd8cRt8a{@YaTwWP%9Y3=261FXdf6T^$2N* zY1yoT`pHr*Q~&aqRvCOU-y%&~5usO2=12c@)U!Lf~9Yv`pl6G&@zE5EZ4PkIn2mr)o2*KvkT_J5F95Rq$8$?aMM+ z+e=u_PMWL?8E5(YQR#E|k&&AI?%f{UL$oHe*Chm#Gu3Sn>Eq{#(~N8ZWl#rFs0zm8 zwRby?Fx+0gjQJc|fB&k2pCtWTWi22YGFMm6ew%ZLx5V`b^WHJSI8^{jQX?We(R@@9 z!g_t_6pSonMHEb($HORWHi_+Yp`3P}K1I+U@U@DxS~MYdN$Qlar^KCWZ}sIgk)BYp z+{Ri0#S(t0f^12cwIV7$t1MeqC5N|J2BX_W#v^lyx+%W3Ut7Kvjg~3qk6cf17LVur zBI%LhpB7}lWRvr87SBv9qO;dhZXx;|b(2D{Xnv{%d6H;-mGE$7<^Hcxy#I!4jz#Lt zfW%54_5%@~x88R3^v2t(Q)H+o(ByH7E&LeKK2ArS;@STWu90a;lwSPNEt;ijN*IU; z6jm$V9j{7^W>uf=515@K~3;?lq;t_ruq`@wcRD#cVtUK zcoD~($nG}jhTqf^U~3PZ9G&~s(xNWcyta6^`?2F#xp2nS_=@8K3DJZ@PV|G#1yoU5 zGeVgvPVS}4k5HBKf;m8j6x+mug&iPk+KlVyu{`V$d4U;Soc{32TYcdHVb?CtuHN>l z^AqkYQVuAJp45@;arZMcKOqxfTusMtNHIsM0_{x`bbdg<%Z0O&J~+kz%8w0Ko3gJf zD(K5ktEsqL?B@@8{ucNCj;}lEIyrRyGsyGbp{r!O6MWkp$I`L0LZDS)?T?-Iw5x%B8gsFcVWQ6UD)igRZsM2LmC5 zAEpGQgyP`1YedddUD9>Q117^|OHFb;pRQc?bC)l~dsBNO{waN#hv8M_6uCn=(tb1mSIVRbdmCyv0y;0j6 z6a~Xey_1dH3ga6aRNln7DhOwbVDOLbRdDX(Tp&3(*$(Ylp=+qF5{i5d-^wjT#an#V z4OQil{KNet4@%-3^s+9nrTglBW9p^%p$WDdtW-FyME5LxAX|>G6Wyd-2$vdljUDb3 z8ISsvrD@@J_Rw(-+J$-h=9Ze+q1I&k=aW(uxpmdHb@6I`4b7f&MpS`&q|>y#7O9^w z4(k4r;EbA8_p*dithuV$Yew7>L?dXDXB z>4)R#mxoZ)u0)$;y0<~;A@?N%(#;U58?dxTx}77)xf&$F!ta!e4=*{fqv9tf+BI+y1!3XMqDORE4pY zqrrUbJ9M>?TWYVW4@tH^lo}&W`wAV*d{grz^p)ge&i?9)$!84M0?r6fbLjm!VsvR_ zdnNr0;xmGk%tI9FYB@$uC5p~_Mo@*{2M3wNZnf=|Hh@~Pdf1t%)w8IVAD83YRU4)_ zK1Gyajh^;2g4#H!QCIr6n|aQm*YI)tJhEzYS1tw9C$N<_mSc1cGs5U5%w+Qr!4%1x8g!}M}U#Nb? zsNdQ~pwmtq9&*EXkW}*Y8PoAj*_0!_2l2XM)CaU?s3&qV6YIHFLU9ak#O`!VLgHJ5 ziMnN+bS?tEI}!z#eMfej;V1@G0BIVbX~>f=E_msM!P&3wQu8a&x~%11(-Fc1yrenWoPy4XUzUP(Zty15UvvY6!IMTBo$X*>7flJ)_VAjn4C zKt~{V4BCqW9iLMTX+tHt&lh!2^xZ!Mw|CI@wG8Qq&f$3nB{nD9_RfudNg(-YOpiom zy)b>}0@1zgs20t8?I=r%n5IUM#<=B?4@a{n?@aSYJ4@}JhrVf$ z-{iYUL!*6i0Js;~J&!(yW>-^Y4d$3PuW1n^m^bDHXP9|j9SmH}ltXRPv^&z2UHv#! zVEX)O4;b;|V24qW$G8u;5c-&+}obN_(kLU!H zI6-6muM;94V-sYYLjCckc(W>0t(p2r?G*#@DnY5;ewiCQ-OWwyU7(446&A|?xLSBt z#%83*2K?wFR7v7M8eVe4@8YW|@$4AJDv1ePSXO`4s9k%MMp1&>s^&P=C&F zrhJ+`tVp%}@bhd{jjE4^V^GZf?W&qf*((R6EOfv6M!VJR6FL+4{1E~_2#ZEnK&bo_ zQY@`pC(sX<7zyLj;RqwF_>1{ubBu0JLc}i2@=z67Uxl7~Jo;;c+rpdk@P(n)=C%2z z(0ABWaXaf&)a3g=`N^9D;b<1RAQd1yZP`bL7U<`B^ZA}Fik7aZO?D8dKIF6Me1Q)C zvObZ$5+W_Bt#|9hK|rF&fFH2@_bL8u3&6cO^cYS}Gv-tk-DAsP0f zfpoF-2O9?~@htfLkIx8qn{3Fj12+h4QW*f%kQ@b?Cxt}VGbCtG0!WYLYUuuHsva$8 zj_|BF*O{Dd8s^z1Qd5TQSFCDmG&Ah76suSsCv8~>UuPKEJlA3dw=L$#d?3&k568@cC?k`*O1KE!1OLiq+`_6 z^`-#dQjEA#ucd#`-OlHos(zEAU7s#Vg)NtBtcUegiMK0#;qEPyCF#%_=(=zRvR)v? zH5i3$9&aBAcR5x=Tu)#C2Rz;ua!2bp;Y2yu*Q!d&T;$Q)&S$kf>XUI-{chCpeq|Lo z^T~29J%?b*047l;v{!)}WEsd4odCkvEGc9h6?jes%J1yxV@71YOt@VgCXHW@IP{(Y zbX0_N-0Sn?db6eU{fMelRV+J=^sUJii{vE-!kuzwC$D*zCSURwXzR-3wR2u9|JY?4 z9@%Pj;Q9geFWwMgT_N8VF*NZ(`iHB=ZP%Pzn=L*DKjALw+U3g0yjq!EMq>8Q*@vgq;|GC-pvA7z=%h(Jc!St92|a+=(hx zcQURPHwX~B8&l8{dqFa+zj~|oINBbKy#rU10DVDES1E4e6lFZrZHo?ix_qW9*XZW6 znqQ~8Q!A0x4<2$g=aKV`YKUx6w{x4z8>7wq0&lm@Cvo2~S_lnqT}pjK^%(&}isTdf z)1zAi=;IQH$8>pGiRe!=2SyQsM zC`p}~aEfqP-zR)CX`;jT5j5o88}8mj%q*R~i2+pqz{`FJUhTp(m!Y2MSWq;AQFDeU z55Fa(Z}&Z%nfX_ua#@cx*Js}8XwHc(4Qk;%7aocLQMH9kHzc%)oNh;_&*Jw)h+zt}C`owKn39S%xavh)t# zj~GQtC9riu5-Q z3>t47{Bn@qM`a&IdmlPG+o?}_K0=i&?$b9r zq_A69q2Je^cT~ilQ5*;x@l-P$xH#hM($rkdQ4MRHkT)<4(@wJ=WUXU_lsu(pzo!m$?7c zvHanM6E`ZOtXi^Hj=)@Ra1|d_VmhiNDJ!hoX&`&_*rf%VlS8U}gV8|ZXSb)%3@(&O zo<4Wv(E)1YNuLu-LUIRqpT2_hUnYT|er%CG)84kIg1Lv(@g`|d0XpoHVfZR<@bkitcGLmI%Rl2trJFvP&rR8w^JD@Ge$v%D7t2DWgrE3F3nSD5|}E8Bk+prSiNeSdW0wdX<0 z%(4f%&4Q{&&ZXcRJ6zPkFpVN;NElkc*y!K66EWf4%=G*U4PqZX!%W&(3mDgf&SNH_K+JRYLw=; zm!#zDFL_8ih=^+TT&mqJ(GNkFc7kATUag|w3GA9Qxz$ttL5;vRe$x_#}} zu2A$0=R&>z0VDOcHj^rMvwAJf{x7(!;X$~)o#_okVylGgQr)_A2;#W+a7uCWOwv!5vvT*N17{u!o<&xBh;$I{g|`lcNm zSNlm@(2HIljO@sd=M-5%hmNxI*mj)!2kwav8^Skt>+zj|0j6SurQa$V&PLi< zKmW9OK=PeBS^b*&B}0|hA{IBt=uvBjQS6#WdNgXsUAHX_uKVy?8UX)JMgA3Mkk_^! zt-`YzH;e~{*m0Q3hbXPTy@fmDYPH@ie8!fAAaD5m(Wes5@K)AGP_6qpU`7+>i-hLb zHk!xVx=$k_cO`>0oq6)wA>MN)BwSAhOd&<;W5@OE1#OjrgvyAnFpjIlphUPT&U7|B zjeZne|IGg|8PUdlcv`X1M=%Z3WY0TgQ15p2@~bL&zN(8Tj8J&B?@Gxm5G-$*V^xP9 zR#^0dStOc_P(QZ{X#@zHv>)F-sk3~hn;0gaRZUhVd>v@pL2$*H+LgD|nf8^dZS@5x zb~x!2XDk_uAXStVb!xXXjjA6cJ$BSOsuKx`aXCdmwI)W3l-3&Apa~vJW$r~j_FkFm zyqIaFqoI!vU%VOBzqxR-q_I!xk?{xn4ul*z3UuSxjL=1$$jKBd-zq|G?Kmp0#y9I+ zHQvhk?D7jE-iwCcsh^p153K1;J!0Bc${`@zM9dary~~eOO^Rz*oHa zObhQn^0+T>7^yJ-AmWe`5I$tv3;OJc4w`Jv6$3gWWhRk$?vDu^6!$d>6(v8g1r1yZ z32pi?N%z_Df08W;I#uUlETTV%?5&!ORfqAQdXKB)ywisryH!u_Kfj)oKsoQc`-%B+ zD4l=2-JfiL#2%veLtH6Aq_^;0`Y367vSpNnR>kWg{WZVLK2v=rUrqQCr3Pj9N}CZS zOjp@G&`8>~BN5_HD&2j!X)KlIp4^daER|vQ2Ab|a$pxDIhYaRlKRNxOXPnl70-aKG z$#*?F5ND1`DBS<54Zc^H-ExptJ`1VVd=^DB0(0!X@wR|-{=`sYgEx4d-gJL}CL91; zR*v~t8;PPfeWNbtw8Po8iM_CG5^7O(Yljr~GAEI)A$8KHIvQ*{9 zH|o7d!A(PZV7yj4mRd#2U+BiO{brQbE}&njP#N|PO)wUm&LywX(f83bp@3_2)wUUk z83VW*MNhpv-nNVE^4w2bLGN82_&gwD!X00o@&tFoY@2C0XLQa`z@ zJMMlw^5ggrGb~zHBb_eqe8ZFOw$sc2mK(?MC@Sw@Eu5XG1>HUkT0jMNtoQVXLH>Gf zI_y0xL+Hr>UBz}8+LIHTK|&!>t3eGjl~U=NLH1Tt*RGid^+-MIs64GDm9hc*CPd{6 zg^dQHCbEzG((-uiz1NRVc=vo=Zr`mW_iNdY8m~WC9zthx#28_2Fn|vU)YY8q43E%`8k^>ZJv&X6;>; zGBX(bZRwoe0j329%OIYSr_Abms{{zHxwdN~Ld2+}FVy{J;1SqBl_I(~Ls&$PUxRgV z=4tV#dEq5=Lft?ceDzqnb?1_rjjN`gR904IWo>fUSZ2=fYh|30{cHPd(fyY71~8k^ zQG6iM_Q%4*UK2+ZikLf8PAsQLO{y?oxx7nt^SKJ*^+=Wd(ztVCLpg8npBCgx!ccQB z2UA0sdpwezS$4VK)IiAjNr$PNSKnGdBKGJFnpUodqVQj&sw?t4Kj7d|D9l&6_2)Ld z>Oa8M|M~8M-_IjY>er^dx2aL6L_6)qrZGTu%0ERZ1e+3nu1CkLceWq6M8lM!h#c?QcNQUo<1$fwKST_nL_Lfj!y~XxDe%BTtym zi1+_vlZ=B8DS&o@i^2F6nKtijL6vD3hi(ahi+^wfRttKzOqQkzLY-{%w_51!X9uaL ziA@E;la37FW9J5P@AfZB5=iblzgd8K1-Wt~6U>5{E832ar~?(4*z~0sf;p0yBE4w6 z>4{D4Kri#&g^p=%p{60=KL!@lP#*bp2$CsyxK33^N2j)p5f-}NUgew|A`yO`$21fdc=h}R0R>8>jP7I zy^}G^Wt&{8KMuqSFaR&{=B;PGD}AD?isrrIrsio{`1RusbJ>F2Vo7yVrKM+1etL3X zMV|bN?|J~Zw?oZ|O4WJD)>C{=!%7~J%Q`3h=Cd`9jY>6~I$Wv+X>`jMmnUrSH!-z( z@brQHej?xP=Bq<+lFuf_q*vR?7WE;6UIyyN@xi6NR|h^=#@ZD3<}Ef1X_q<8P<|pF z>k}oh(_i1IX@?;VlDB&nIn-Jn7b(B?+iYK-&i>y2Ae9=V@I*x_anVD|&gFpQb3L(z zhvVD6^U#EB1+qVddwEuHd&eoui|l!6b6u(XrS5R>$BvtK5W7~3gkS}&dG$GR%A8n* z&&z{Xbu6mWjwAZzl$?w)n77+OV31AlDxX8Vm)C*Q%z6(&;A+GRR{SuvI^!&my|i-YFF(vPXX7|MS}Is>`G=a??(}LJ0KHRlSh0D0+%2v9X&MLN-@# zK`xyJvF6X9?}7Tn3&osA5_0E|xPm{d1Jwo*`wYdN0RzEns=r&!V$ctub18m}Bm@yu zM~c(@X~&&56WH41jLUcEI*7rlN;Tvg#1w44`Nmt#L2- znA%n>`qxU)LSox@4_0Xga6gE&XTMSONn?P@$8GJum#zcyq5RvvySuUai^`JgiHk7w zERac84=S#unP!6->4UZE{o4sew;B~eZuuVeqMyp2MXcMPuodriP!)LzXYZq?CLnZm z6EWnsI`m$E09i-+%*O8cdIy*{A@Z7=0a$_jf4RdZ7;iFg9K_KJEq37@xI9a5eUFI3 zQcPP{htNTE-Es)Q?2t4X{ErZ`^e8{rW5ZkF+o+iV%zh%snm-ST(hD~ZA%8v?jY_a7 zdr6q3Md)uCwg`SDD{Ro4OhLwMQ^_rdo`&|K??Z2QPf`w(Ez!%X;GP(glPCB%SiG15 zHC4JTrJfR^JNwp zZzX5%FltR>n10R;)M$LECFJSK9Ga2@a`8R#=LK{T6f;kX7=?O5pQg3Q5`Gm3JcPoWu=pQp171KGs za}xgg%n|(o!7+}9Z6?7q!hy1%sCF2bQ)mT^w}&0BDwD-=iWsb?B9Z$OK3CWHhF~40 z45bpnpPLA>lrSeHJr6-my{BK9AK+3|5-T%#=9a#}7Z4C+KDlkaYMy2B6LEMxo;Aq4 z@G>7_zS7L01V6g50;&mc;a}K)aRspb6^Qs(m&os=Qokr>QYCSR7XSBMiY6G2P2&>5 zJj3xwa29_n3TV<~3Z%L=v72^T1>8_osBhp#)V@u$NJOu;n}BO7U4WiPF5&)P<$Y&Z zQ{A?15EW@6N)ZsE0xDg45eT9*5s?~tR5}O&DbgcIQL2J~f)EiA5dum_O6W*0p$JGx z5RjfwLLkZd&hp#)p0l6(oPF-!`#k5@f|NPensbgh-Z{oQ-cZIsAC60eE$aj9^Gu)< zC;SsBWTPwBpF|7;8ceWl<<+@E3(<&j6j)9W7`Xs)DS;8xu6=)megtltfvQj0g&v+$ z1oaMf_e3KqQ7!xeflo-z*SM&bO&mfw}Na0r6*+~_sCLHcndb~kTrKObNgn#Q`K zd7y`Hz;pV@6>%VS2y|f}a`_u9sLsXn78rM>eKiqRa6$DX924Js6?x@{g z62x=2waR7>5P%{V6es|v7x&^5X*(wuL^Stbp~(0k9oT^YSV*gg7@}R0t5yT5s3hw3 zYS{d1wHv6>Mm(B+ate!<7i&2T0k_xULq__00VvThM-aPePwWB>-8u+=XEKu=^}`&d z;E6ZiiW-Ei3n9{AM?by}HUd^ai|wa|fffsIWdd>=U?7l^gU@r}&|-DK8CvoF1R3xG zgRs$OLCuZ{@UC}bZ((^?Y+?8S(4H6n*z;vDIdri9w>`5y{P-8-b{z1n5Qx@T+wTQf zGN_u&lzV?cM%&Sc28t+{lAp^qXxHVBK_CR*TBdPZki$5Lb$g_9s6#!)N0bUfKyH8+ zF$+$_hvw09Wr3!&{t6)|8n8Rq0j$qa9~)T0>UP5)biohHu!u= z!}M&>~M@tMA!@wtaG{aupr!fGa_^qO1ur5lrbNbRy3TEhr5@$rY^bg=QJNTu&O`D z7@2C!y^Cl;P1fLRKx!5~d+-EJhCe1fhCb~uqode0Wq5nI$skG_fDzwhi-7~ zAWq5mML<_y|7^wF3JEQLTOg=3T<<<42b}8plHT++rls}FgjJ{5T2m3iYzI6<&1XW( zh3f)GsvcRT8j}kK!L!?uvJaX9KtF}_2SZ)_Qutb*S`jMeE zAoqmfX_v`UgqDf15{XHpYsB2QF2?4i(Y0HTJl0%Y_U+G>11EcY(gJ*H)qK*79Ax7I zLoqykG$Mi`+O;14?#jqs7(>bdiyew&v5Sqxy29cdPE%mY%{ic-x z8eQGv-u5^NBvhYVPb4{zbR;L**>(ia-Vk_g@hM+aq(JqWftTpxe0X4JETZM$&S}$J zUZy)I4LY_v#aA|SG6ugMfCt`_&Gb~%Z{A|?fd@c86JM5Zygp6*K?CVJvEd+e0XUK6 ztTJEd`TM#0nS)*E6y<9N1MB3K4sc8>q+x@yxr1xLF*dmaw@jAaq9rp%;30~`n7j9C zVPb85bOh50zd}A6S$M+CH7T>zltCmVkYoK-5YRUcBf5jwxC1^CafzN2AxOdE&p{aj zwrYCu^`W+gDkbCL$rrzh-xGTD>BYvCI??jbrN*gv`IDN1vVH|-+$lxMMmst^kG$n< zRc3zE#`9Zv+9qXi4|Q(fHfW5?3AIc+opPkWkq5Kk5iJx0OvDR^*vR^Eq{~b1=`LT%cO_8 z_bnfN*gC#^i9qeB$=}g#B}Q0ibBmd@w8~~xrY4%Ef<1!?e zNZOglWV?|nfpE(%xH_Qfqc~?`mfdAo_QL@bi8Rs&8vHPV*>GyU9^;KE8b7C zztuWfJm4+TJ6P3*I<+xH)^o6UanFav@K{`urq{uP^o^=@XOs&FBnS8A7C~SukpHiH zp~tWT_n=)5&|mi#1igmsfr6D0WUoOQ2mk;OevrP6y-$ezAGSxk!Gs2PJQVW#5hb(p zFef>!unnl*G0Bm_n})D&YIS!p7H#M>5C8l253p_M>bZ3^q0wDk<9*Q9_0T$KZ_=f7 z%-D}zR*{}(?c}y|6Qgcx7vr?~teiN%pWo-2nb4Al{X73rM+KVoX(?6WVjHj0fBd_=&10*%LeZIlrPV41_>V6RVf%7IJuRM0K?Oob4@g`eAFO z>yi<`+W1CSi{VF?{5%21!R=Vx-?MzTcaY=k)MeTJ z0AJCRQIr<%N7FTn!1${CLYTVc_i_+3?sL#Z|3r*ZL`JI+>;~^OIvRMh8pks7kbWHL zSFYOLT2kfiG%x<7Dj2T6_FGekB%Muo29{1AEkmB`XL~GJ|KM#Q+c0!NE9j?+h>x+& z!4kE(NuE@i_I^cE3K*fvtvxOoY50oS<@cM`e?iY992jP(Jaj$(By^&ja*Sr^{-Gw0 zW(+Z?9Orp4dz1TKL!{p3qK?_NV1wGd``>)yX^S7iU-CZFy!1OlsIjiHu6lIYkQIcJ+Rd zHoPh*+g&Qq#Scr~drs5&Xg{?!f%HlJ_?*mIRfE&1*E;fC``gzSnj0dCY*$W=f2bAK=&L07Kj{dH5Un?+pe5 zKh=!=A6~4~x0|W@AtHdfEYQ$={ksNaz_(u;%dM`EbtnI`LszKVp=sy-IQ;I}O-F+} zoYiMyE!WLow(OQkR}%{pC1>XdtnLH*!tFI7qf;04W1XwM)ljdpOme)azEh{QFWm`Ai@f!n!*85sQRoIA z&vNduGxQA?Y|j|B{YXZYcP*(UzkRu{=Vv$AIwAgHIV!nS^~&I)tXrZ_t+hRG zujuRQp?9!+U%QgWnwLrNlrUwC!F$BYct{8$p-v|8dUQoIx0ZRJ=%cFwbsM&@6YEtu zXWESMNsoF$txKdgwchk7C^IW;>zAF8IITO*_mkmxdL?6NO6MV~wa5PUwf3xYP|oro=1D-3$NPnXUQU1$F!*tND3& zHce|-dQ%gmxqCM=dJ<~$fbqM*`nyF(lYfO6o;hJ~2sad)pq*Lpa<#4*K?c|wIeWQ8 z?(XoV@?i+uw@oFJUu#30@4t|{Ug*RWP)ONn3&9LzB0u)w*dI)V7K?G-=`;*)av}Iw zMBclq>-WMpzHeOfN)Y@8_5LXTJgeX>c4=^7(&;^tFJl-*BiYp0mJ{KxR+_*`iJEB3 zy?{2(IN10wHfL6jn-tPNS$zJ@&HHa|lsI`kg!*=9sVgo>!A%>+N-OIyO}GVn33Gk> zd>32&3rP;T;_c3XwB|EUn+I)9YlWZ5Ut=y_@hsM8980Yx+{dPLryog`>oI6n`9A1D zLh#b=SJe_%?J0I1JcMxFp4nd0tWiwWNaLvc`R6}QJ-)Pl`nc|`W$q3B@!WuLp{-jd z9hEK1r=K$s4?e+Rv3>|O4@=t1=)$yL{zgh6M} zI$(UX!!ba#c%|FF#-!DXH~7eN<;|{CuK8-seeOkvYeTfqQayr^_%_a=K6hb*IX^sp z<2+8YXPBTE05=_Ry_WuJQ{AUfaklMaP@EoVDXl~*dS|*9|7q8~;8`9|4Xb$fH)qL{ zeXgQ@BRMYwC86Bv*OUs_v#%HFApIVbLCEO|Hw16r{Ni_?2$!Q`=fyOb%Wjpua00bH z1YM?Y8@UW3(B{?S>!nrJo8Ck2_=~k{{a!4Z<98cUUT{McJC0%+Z@cqN|H>d6c3*~dMzX-2h2rhCDOs?9*bXJ9a{AtyHtg*b+Yb$4{>ygvZ zS{>-s?Lj7uP|GoWo-3bs?;eX6Z{%Wl5MQPPv3g%Tto7%`It2!iR1uTP@)w`qRmlE^ z{7OS~U*3=+)ew*yXwt#D!r3k42Er7SUI3ugz2e%S#@OwR9cRYAj2lC~FXCnCE^{`e zd5%hGu?w!x@ekCVteU?(m>}6MAX$TY=QG=9;4Ks7_U_-BVm=u^z}bE$_CR`=Ifsh9 zGUU#lif}^SupP3E+(UJMr&U-x`Xs<>6+~^F0x}83A8@6L(9`bx#)G1XJh-aLeV)Fv z^v|=i7P5mEBfmC1$IaWgjDbS`p$MublA^;LB3QbS&iw2cLO(o?w-H zD=pUS`}v767*W40Hf!#gCTxyon0(n@zHLf@Jo5A2x@SGT=$zK><=&IO0wJ!SFs{V< zNV=$r4tFQh9|DS4LDW!xb>8K=cN}lA&Zs97(2B>cD!^I0)#t^PA=L#%^l}yUFBgHu z%_G6izo?Pq<;N-mOt%iNe&H5?6=;owL{zOTTXzay?HKQRu-I=}^7g}e`t)P(Zjq|> zTfVUuzh-9&~2* zYwj|j`h-5FTn)(@+!2^fk;Q`1KUfUJE%@o^@DhiaHQ(M_njmG69=>_ zI|B5w9m?I~ao}r0_x2RgOUhKlGBLjoNyZN1wfMo;6#hTyVE-LE2RLRXm-E1Mk^*%k zxbulZd)$wSPHYwL`gLL`xnO$_eOjvXC4n{Q<|)rZmpRpZM7?19L#5jZ=u8#o-z7}k zdqc0y6QmTTesFZaPNG`)6!ZhqGdprM>c8Q}w#N@6vN9%4)~zgcEA)Iw#%i>i{DY$JKL}6ww^8VA@%-KaCe2rqP6el196hFzCZ|5v zM{2~?)NN>;ew`sAKl4S0@v;aT$Df;5elSAv(mamh4U&Us_(ViiDvlXhdw^}(i z2~3fj;YMMc)JMRC5mh8i^~;1pxEi~e?f8~c`3RGbhg@cyGl@=qvr|UyRQoWkKZ|^j$ab(^Xcl3R<7|lIRd8k`YfBmsj@<^MMmE@V(CUvx(?9KWX3yg{$O0*VL74#&OXETUEUXLR z3Z9y5MDARSOZ(@Ul_sIl()%I-NA#DRRmh5T_Qb>UzJWqQI8O1a_KOyx`;`$UH@f;_ zOX|eLQ^Q}$9HMcj!>%tcSHq&wbdSk$QuRy!mP>M4#9uW7o{U7)CW`X>96pSOhXepTRc9c zGNN6hy5USl3B8pzRzLGyD=^GTf-TTegV}>6!;rRPky3=)$ zhkW|qomG#TmCx?sr+A-vS6V+g+Q1eO^qdYKny8$%YkPdeZu*N;z|Rs5iDeGEuNnLK zQd@|#zznpeYkdMl^y?HcF)7}VKqTG7l*G{Z_ zdHfpo!Pb-o>>*+j*@CL1W8!p*@5A+HX+<*7jbx1{WBZvqY`6p#Ca+ zD*ce~>wouXpaYS%olUH16y<`{Aw%S5chf10;6%BAv7={hXb|i-%f!#I0)mv-omMRG zy5^}>Ax)y_%tovfGNvR}42+ja=X=`L*Q+CQe|QHCkM(eOL!e(L|1`Bnnp-cT-ltum ze=JojW2C`GHx*|bo2%6n(MgJ)wtD>9p33B?;F5by7CbvMwhPL4+uNJ%Er?x=>uM0Y znnpfk$gL|3k7jqB8&||q4Y@cg_t}d0#U2Nq7yq1?YA_E`8#?;t?~Z8KBf!%LF`%

Q1 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 literal 0 HcmV?d00001 diff --git a/screenshots/presentation-modes.png b/screenshots/presentation-modes.png new file mode 100644 index 0000000000000000000000000000000000000000..146c203751eccce2a71c1eee2adc3f80ce5aa3e7 GIT binary patch literal 70453 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 %S&LE;mmSh2nF9YZar`i#wNm*lC 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

eXqOwGRwAs~! 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 literal 0 HcmV?d00001 diff --git a/screenshots/results-chart.png b/screenshots/results-chart.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d3b989a42266fe3acf8d3a7c9d0308343f0388 GIT binary patch literal 130107 zcmeFZ2V7InwlKO86af)w(uqoys&o*DihzKCG!YO`0cir#d!o{$iF5@a(glP_mm29J z(wl&kBuYmTfe=VY-u8d$J@>o!oOj>%-uu1xJCjWI%HA_;&suw}HEY()Z0c9)B5+Dy zM^6W!p#gyF;0K_hfydfGt`7jf&=8OS0N^-ql!gtU1tA*n1JLjSNB@8UK%a*1udpeN z_@B!h0e~1+fbP#_tij{&1Y-VO`_IQCZ;t$>1TFA}_Al`7N?;2%0hOCBUVdJ_E?$q$ z$je*+RIch7(*5b;pJ0(c3Z5DlNSICns_9lAGrZg&UWHLHN5u8CwCr!18tdrY(Efv{ zBi#2MJ$m@NvWKUikE!m}Gqs$fP{Knt}!g_!w-~h+~ zQXnh`TmZA&Z$5){!96x`z?z{9)J@1s{{a;tpI?17_4LC`#@gqkLxrit^&Z(c`9{V6aeT`0pRcj zl}gH|QV$CNfc6^zeDtJ#1Xz#Lff7qga}GGdN<+&^L+t_}Ag7Mf{E_}Z1pd$*p`|-| zjQ%(SBNJGl?i6r@hL-jS9qrMhzXg&e9J~(Du^weRd-2LKc9VPb=e#-Oo+RfT7rI*2 z&S^S=6PCa4^OS+{G#57yuZXCa_<0EhMI~hwRW+?^+B&+|_4LimEpAzYN!TL19sG$=lMh>YCcR`i2jUO&y(G-95dZ z`aX}2jZaKYeVv{`W3b>7oJY{7d>1W&c1ID@fN7Iyzc9 z`rmZX90~kQI4j-JvloxCT`{4*=godj?#XeEtI2s)?F>TlrZ~>~J|m2$g%!{u_}`@c zLD|1X*wgv^1bF(Xs+ifI<<^j|2XB&brMA_;d24j*%&8 zbDNjhO>TO+$<6Q3XmB30nNMhF1&p(~sS;5~{<$CA(c=|Uk9u)l#JH-x?52lO zMwhDasF&{~>hJ2gP=QDa%l@txX#Xj_-1SqHd%(JQV@%137~J?k2l zYr62c`WLfa6!J7~vRNiIpz69_L-PCe!%_G%xHX|HIX0?*$v_(rJ)Iye%>lf$zEG!S zK2h6xvd7TOy0M|AK3>qHdpw>)F9rDa^J+p0(6??mk)9^^;tCI)-?_&pq`9rF#c3|o z-g9+%L)S49_11cATFk~WnXZ!IdOz@m-jv?#`L4#_p|7pIdE}l;$LPpqhJUTx zJ=c-pPr0tP80k*f{Hxh1!5tcbyXwp1dV%&F;GfGr9rRji3XI4~Wsn3Qs-_+=x-PxaJ9~Q&CZU!?5yC zx&i9we=oh~`JxnRr%1Il4A`u<%W<_Kyrp8a3N%+Al&l817mHQDhTVUYT_kV#Qa6Ma z&MisI0)B1!+{(V)nB27g1qwWZraM{cVVb$BCyVZDS|w%0G-$xBmsV(hLC|{}KX46% zn8PxdT0{47e}h*FaQ4+(1o85BuP<5GzEyw zSVSy@UQ=jQP@o<^SaPx;*xmPyOrH9&dC2)A22$-lG!8j$_!FK>SfTFPGsu42Z{wRG zMwr0a1~AXa{$h#9b9z6E@N`o8-wbFj@k+7f-ufAt)3t3sxf`?)gFl%gg%zygcGYFz z9@Vc{X?j##*4IE+a2l)9XX@i?+FF@RuaGjx3w#wCo;8{y?bID7(@>HrqpowlH z`pl-7s}N^5)t_hHeo>K;OZz_gOaS6WDK7X<_f() z&|>Ck+~*=Qsp8eKBGWn^qtAs{5k`Mc3ZsYaOsE2;z@<^:eawED#ApvrXV;G+@~ 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 literal 0 HcmV?d00001 diff --git a/screenshots/sheet-presentation.png b/screenshots/sheet-presentation.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f99ae7078491bd072c7ddf9ecb186d24f4ca50 GIT binary patch literal 78846 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%0D75vhS9vCSg*!!fp-U#WNo+Q^D*N8b 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 j1$ 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 L|GS*?YwW)OLvfWA literal 0 HcmV?d00001

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