AI-Асистент для фінансового планування у мобільних застосунках
Фінансовий AI-асистент у мобільному застосунку працює з чутливими даними й повинен давати практичні поради, не перетворюючись на інструмент маніпуляції. Технічне завдання—агрегувати дані з різних джерел (банківські трансакції, ручний ввід, OpenBanking API), побудувати картину витрат і дати конкретні рекомендації.
Джерела фінансових даних
Три шари даних:
Open Banking / PFM API: Plaid (США/Європа), Salt Edge (СНД та Європа), Tinkoff API для Росії. Повертають трансакції з категоріями, залишки рахунків, історію 12+ місяців. Вимагають OAuth авторизацію.
// iOS - ініціювання Plaid Link
import LinkKit
func openPlaidLink() {
var config = LinkTokenConfiguration(token: plaidLinkToken) { result in
switch result {
case .success(let success):
self.exchangePublicToken(success.publicToken)
case .failure(let error):
print("Plaid error: \(error.localizedDescription)")
}
}
let result = Plaid.create(config)
switch result {
case .success(let handler):
handler.open(presentUsing: .viewController(self))
case .failure:
break
}
}
Apple Pay / Google Pay трансакції через PassKit / Google Wallet API—обмежений доступ, тільки для застосунків з відповідними дозволами.
Ручний ввід—завжди необхідний як резервне рішення, з AI-автозаповненням категорії та суми через NLTagger / розпізнавання чека камерою.
Категоризація трансакцій
Банки дають свої категорії—неоднорідні й неповні. Побудуйте послідовну класифікацію для всіх джерел.
func categorizeTransaction(_ transaction: RawTransaction) async throws -> Category {
// Спочатку спробуйте правила (швидко, безплатно)
if let ruleCategory = ruleBasedCategorizer.categorize(transaction) {
return ruleCategory
}
// Потім AI для нестандартних випадків
let prompt = """
Categorize this transaction into ONE category.
Categories: food_groceries, food_restaurants, transport, housing, utilities, entertainment, health, education, shopping, travel, income, transfer, other
Transaction: "\(transaction.merchantName)", amount: \(transaction.amount) \(transaction.currency)
MCC code: \(transaction.mccCode ?? "unknown")
Return only the category name, nothing else.
"""
let category = try await openAI.complete(prompt: prompt, maxTokens: 10)
return Category(rawValue: category.trimmingCharacters(in: .whitespacesAndNewlines)) ?? .other
}
Спочатку правила—70–80% трансакцій по паттернам (MCC коди, відомі мережі). AI для решти.
Аналіз бюджету та рекомендації
Аналіз витрат—детермінований код, не AI. AI—для інтерпретації й рекомендацій.
struct FinancialSnapshot {
let monthlyIncome: Decimal
let expensesByCategory: [Category: Decimal]
let savingsRate: Double // %
let recurringExpenses: [RecurringExpense]
let unusualExpenses: [Transaction] // вище середнього по категорії
}
func generateInsight(snapshot: FinancialSnapshot) async throws -> String {
let expenseSummary = snapshot.expensesByCategory
.sorted { $0.value > $1.value }
.prefix(5)
.map { "\($0.key.displayName): \($0.value.formatted(.currency(code: "RUB")))" }
.joined(separator: "\n")
let prompt = """
Financial data for this month:
Income: \(snapshot.monthlyIncome.formatted(.currency(code: "RUB")))
Savings rate: \(String(format: "%.1f", snapshot.savingsRate))%
Top expenses:
\(expenseSummary)
Unusual this month: \(snapshot.unusualExpenses.map { $0.description }.prefix(3).joined(separator: ", "))
Give 2-3 specific, actionable insights. Be direct. No generic advice.
Example of good insight: "Café spending increased 40% vs last month—18 transactions instead of 12."
"""
return try await openAI.complete(prompt: prompt, maxTokens: 200)
}
«No generic advice» у запиті критичне. Без цього модель дає «скоротьте видатки на їжу» замість конкретних спостережень.
Прогнозування та цілі
// Android - розрахунок часу досягнення цілі
data class SavingsGoal(
val name: String,
val targetAmount: BigDecimal,
val savedAmount: BigDecimal,
val monthlyContribution: BigDecimal
)
fun calculateGoalTimeline(goal: SavingsGoal): GoalTimeline {
val remaining = goal.targetAmount - goal.savedAmount
if (goal.monthlyContribution <= BigDecimal.ZERO) {
return GoalTimeline.Unachievable
}
val months = (remaining / goal.monthlyContribution).toLong()
val achieveDate = LocalDate.now().plusMonths(months)
return GoalTimeline.Achievable(achieveDate, months)
}
AI підключається, коли потрібно пропонувати збільшення накопичень: знаходить категорії з найбільшим потенціалом скорочення з історії користувача.
Безпека й конфіденційність
Ніколи не відправляйте фінансові дані в LLM у сирому вигляді. Перед відправкою:
- Округліть суми до порядків (не 47 839 рублів, а ~48 000)
- Захешуйте або замініть назви магазинів категорією
- Ніколи не відправляйте номери рахунків, реквізити, ФІ
На iOS—DataProtection.complete для локального сховища трансакцій: файл зашифрований ключем, недоступним поки пристрій заблокований. На Android—EncryptedSharedPreferences + EncryptedFile з security-crypto.
На сервері: усі запити до LLM логуються без ідентифікаторів користувачів (тільки hash сеансу), дані у vector store—громадські нормативи, жодних персональних даних.
Орієнтири за часом
Базовий аналіз із ручним вводом трансакцій + AI-інсайти—1 тиждень. Повна реалізація з інтеграцією Open Banking (Plaid/Salt Edge), автокатегоризацією, цілями й прогнозами—6–10 тижнів (значну частину складають інтеграції й сертифікація).







