Skip to content

About

local mobile app for finances

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FinanceOS Hub

Offline-first Android finance app that reads bank SMS messages and automatically tracks your spending — zero manual input required.

Features

Feature Details
Auto-import Reads SMS from 13 banks (12 RU + МБанк KG); imports last 90 days on request; SMS is opt-in — a fresh install never reads messages until you say so
Push capture PushNotificationListener captures bank app notifications in real-time alongside SMS; reads every text extra (title/text/bigText/subText/summary/info/inbox lines/ticker) so a balance line in any field is seen
Автопереподключение чтения пушей Разрешение на доступ к уведомлениям и работающая служба — разные вещи: Android рвёт привязку при каждом обновлении APK и при перезагрузке, а разрешение остаётся, и экран настроек уверенно пишет «обрабатываются». Приложение чинит привязку само — после обновления и перезагрузки, плюс ежечасная мягкая проверка; если система сбросила само разрешение, приходит уведомление со ссылкой в нужный экран настроек
Real-time New transactions appear instantly via SmsReceiver BroadcastReceiver (with goAsync()); cross-channel SMS↔push dedup within ±5 min
Marketing filter PromoFilter drops credit-card offers and cashback ads before any parser runs — a "лимит 163 000 ₽" promo is never booked as a transfer
Smart categorization Deterministic dictionary classifier (~216 merchant rules across 18 categories incl. transit, marketplaces, bookmakers, subscriptions, income); optional pre-trained TFLite ML layer (inference-only, no on-device learning). Rules always win over the model — it is frozen at 13 labels and cannot name a category added later
Subscriptions category Netflix, Яндекс Плюс, Кинопоиск, СберПрайм, YouTube, iCloud, Adobe и ещё три десятка сервисов идут в отдельную категорию «Подписки», а не в «Развлечения»: билет в кино — разовая покупка, ежемесячное списание — совсем другая строка бюджета
Account linking Card mask from SMS/push (e.g. ··2548) auto-links transactions to the correct account; the bank's «Остаток» is applied as an authoritative snapshot, with a recency guard so a fresher manual edit is never reverted
Credit cards AccountKind.CREDIT — the balance is a debt, never mixed into net worth. Dashboard tile (free limit + debt + deadline) opens a dedicated screen: payment amount and date, interest-free period bar, rate, utilisation, repayment, per-card history
Credit push parsing On a credit card Сбер prints the free limit under the same «Баланс» label a debit card uses — only the account kind can tell them apart, so it is converted to a debt using the card's limit. The «Внесите платёж … до …» reminder is filed as a fact about the card, not an operation, and the bank's own figure outranks anything the app infers
Repayment «Погасить» books a TRANSFER off one of your accounts, never an expense (the purchases were already counted). Two rows, one per account, so deleting either undoes its own side. Money arriving on a credit card is classified as repayment, not income
Interest estimate «Переплата сейчас» — an exact zero inside the interest-free period, an estimate once the deadline passes. «Если платить только минимум» simulates the real cost of carrying a balance. Both are labelled estimates; both are hidden without a rate
Multi-currency RUB / USD / EUR / KGS (сом) per account and per transaction; hero shows each currency on its own line
Защита ввода Смахнуть заполненную форму вниз больше нельзя молча: приложение спросит, выйти ли без сохранения. Пустая или не тронутая форма закрывается сразу — вопрос на каждое закрытие приучал бы отвечать «да» не глядя. Работает во всех формах: операция, цель, категория, бюджет, счёт, условия кредитки, погашение, обязательство
Выбор счёта Двухшаговый: карточка банка → её счета с маской и остатком. Один и тот же выбор в ручной операции и в обязательстве
Manual entry Add (bank→account picker, income presets, Расход/Доход/Перевод), edit, delete. Перевод пишется двумя строками с общей меткой пары — по одной на каждый счёт, поэтому удаление откатывает оба, а не только тот, на котором лежит запись
Правка счёта и даты операции Пуш без реквизитов приходит без счёта — сумма есть, а откуда ушли деньги, приложение не знает. Раньше такую операцию приходилось удалять и вводить заново, теряя исходный текст и остаток банка. Теперь в карточке правятся счёт списания/зачисления (выбор банк → счёт, повторное нажатие отвязывает), вторая сторона перевода и дата. Баланс счёта сдвигается на сумму операции, кроме случая, когда банк уже прислал остаток по этому счёту позже — его цифра эти деньги уже учла. Что будет с балансом, карточка пишет до нажатия «Сохранить». Список счетов в карточке открывается свёрнутым — выбранный счёт виден в строке банка («•• 6703»), а сам список раскрывается только по нажатию
Фильтры операций Одна строка: лупа-поиск (раскрывается по нажатию), меню «Тип операции» (Все / Расходы / Доходы / Переводы) и «Дата» — календарь на один день или период. Пустой список при выставленном фильтре честно пишет «Ничего не найдено», а не «Операций пока нет»
Чья это карта На каждой операции видно источник — «•• 6703» в цвете банка. Заметность зависит от того, насколько источник редок среди видимых строк: карта, с которой идёт большинство покупок, подписана тихо-серым, а редкая — цветом банка. При 90 % операций с одного банка одинаковая цветная метка на всех строках превратилась бы в фон и перестала что-либо различать
Пополнение брокера Деньги, ушедшие брокеру («Получатель платежа BKS Mir Investitsiy» и другие брокеры), — это перевод своих денег, а не трата: в списке «БКС · ↔ 10 000 ₽ · → брокеру» с категорией «Инвестиции» (брокер назван по-русски, хотя банк пишет «BKS Mir Investitsiy»), и они не попадают ни в траты, ни в бюджет, ни в оценку финансового здоровья. Вывод от брокера — тоже перевод, а не заработок. Брокер узнаётся по названию (БКС, Т-/Альфа-/ВТБ-/Сбер-Инвестиции, Финам, Freedom Finance, «Открытие Брокер»), а не по слову «инвестиции» — застройщик или «инвестиционный банк» тратой остаются. Уже записанные пополнения переразмечаются при обновлении и при восстановлении старой копии, если категорию у них не меняли руками. Восстановление копии больше не сбрасывает категории у операций на устройстве
Пуши Сбера с «игривым» заголовком «Шикарный перекус в DODO PIZZA PERM-5 1 034 ₽» — это трата 1 034 ₽ в «DODO PIZZA PERM-5», а не 51 034 ₽ в «Шикарный перекус в …»: цифры в конце названия больше не прилипают к сумме, весёлая фраза Сбера отрезается. Оплата по СБП в магазине — покупка, «Выплата процентов + 77 ₽» — доход, «Деньги отправились в Альфа-Банк» — перевод. Уже записанная история исправляется при обновлении (кроме строк, которые вы правили руками)
Карты, которых нет в приложении Операция с карты, не заведённой в приложении, больше не попадает на единственный счёт того же банка и не переписывает его баланс чужим «Остатком». На главной появляется подсказка с номерами таких карт: нажмите номер — откроется новый счёт с этим номером; добавленная карта забирает свои операции сама
Режим «Инвестор» Переключатель «Кошелёк | Инвестор» в шапке главной — выпуклый ползунок, который переезжает под выбранный режим и меняет цвет: серебро — кошелёк, индиго — инвестор. В режиме инвестора — своя палитра (индиго) и свой экран: портфель, брокеры, позиции, движения денег, заявки и сделки; покупок, переводов и карт кошелька там нет. Приложение само замечает, из какого приложения приходят пуши брокера, и записывает их: пополнения, выводы (только подтверждённые — «в обработке» не считается, иначе вывод посчитался бы дважды), переводы между счетами брокера (итог они не меняют), заявки и сделки. Экран устроен как в приложении БКС: выбор счёта («Весь портфель» или один счёт), крупная сумма и результат за выбранный период — «24 часа», «Месяц» или «Всё время», кнопки «История» и «Заявки», ниже — сворачиваемые группы «Валюта / Акции / Облигации / Фонды / Внебиржевые активы». Сумма считается по цене ваших сделок, а не по рынку, поэтому может отличаться от БКС — это подписано под суммой. Предупреждение «критично низкий баланс» — янтарная карточка вверху; она закрывается сама, когда на этот счёт придут деньги, а пока она открыта, на «Инвестор» в переключателе горит точка. Всё, что приходит пушем, можно ввести и вручную — кнопка «Добавить»: операция (пополнение, вывод, перевод между счетами, покупка, продажа), актив (бумага, купленная раньше) или счёт; у бумаги можно указать текущую цену. Нажатие на любую операцию — и ручную, и из пуша — открывает карточку: все поля можно исправить (вид, сумму, валюту, тикер, количество, цену, счёт, дату) или удалить операцию; актив, заведённый вручную, правится целиком — вместе с деньгами на его покупку. Нажатие на бумагу открывает её карточку: текущая цена, сделки, из которых сложилась позиция (каждую можно открыть и исправить), и удаление актива целиком. Счёт удаляется в выборе счёта. Котировки Мосбиржи (Настройки → «Инвестиции», по умолчанию выключено): раз в сутки приложение спрашивает у Мосбиржи цены своих бумаг и курсы валют (доллар и евро — по курсу ЦБ) — портфель считается по рынку, итог показывается одной суммой в рублях, результат «за 24 часа» и «за месяц» — по рыночным ценам, размер лота — с биржи, группа бумаги — с биржи только для бумаг, которых нет во встроенном справочнике (известные фонды вроде LQDT всегда остаются в «Фондах»). Уходят только тикеры — без сумм, количества и счетов, только по HTTPS и только на iss.moex.com; биржа отдаёт цены с задержкой 15 минут, и под суммой написано, на какое время цены. Пополнение брокерского счёта склеивается с переводом из кошелька: у пополнения видно, откуда пришли деньги («из Альфа-Банк •• 1139»), а перевод брокеру, о котором брокер так и не прислал пуш, предлагается записать пополнением (или отметить «это не пополнение»). В кошельке под «Свободно» — плитка «У брокера»: сколько денег у брокера, без бумаг и сделок и не входя в «Всего». Рыночная заявка приходит от брокера без цены: она всё равно записывается, считается по последней известной цене бумаги с пометкой «≈», а точную цену можно вписать в её карточке. Деньги вводятся в рублях, долларах, юанях, евро или гонконгских долларах, с копейками и центами («0,41 $», «41,60 ¥»); остаток валюты под тикером USD000SMALL / CNY000SMALL, введённый как актив, записывается суммой в своей валюте. У режима своя нижняя панель из пяти вкладок: Операции (вся лента у брокера с фильтрами по виду и счёту), Аналитика (всё у брокера, результат за периоды, состав по группам, бумаги по результату), Портфель посередине (крупнее остальных и подсвечен), Календарь (пока — открытые предупреждения брокера; выплаты появятся с их пушами) и Счета (что пришло и ушло по каждому счёту, добавить/удалить, приложение брокера). Пока своих данных нет — пустое состояние, ручной ввод и пример по кнопке
Swipe-to-delete Swipe left to reveal a red trash button, tap it to confirm — a flick alone never deletes
PDF import Import bank statements (Alfa-Bank "Операции по счету" layout) via SAF
Financial score 0–100 across 4 pillars; rendered as a multi-colour donut (one arc per pillar, dimmed shortfall) so a weak pillar is visible at a glance
Behavioral analytics Heatmap, fatigue curve, payday effect, impulse classification, anomaly detection, subscription gaps
Analytics period Месяц / 6 мес / Год / Всё время chips drive the category + daily breakdown (the health score keeps its own point-in-time window by design)
Category drill-down Interactive pseudo-3D pie — tap a slice to explode it, then open every operation of that category for this and last month
Transfer routing Переводы (СБП/перевод) распознаются как TRANSFER и сами засчитываются в цель по привязке к счёту / карте / слову в СМС — включая переводы, внесённые вручную: перевод НА привязанный счёт добавляется к цели, С него — вычитается. Переводы между своими счетами спариваются, и net worth не меняется
Budget envelopes Monthly/weekly limits per category, dynamic color bar (green→amber→red), alerts throttled to once per budget per month, max 2/day (persisted, survives restart)
Savings goals Компактные карточки с пиксель-артом по теме цели; кольцо выполнения показывает процент внутри себя и красится в цвет темы. Расчёт прямо в форме цели: сколько осталось, сколько откладывать в месяц к сроку, за сколько наберётся вашим темпом и хватает ли его. Правка суммы — вводом текущего остатка, разницу считает приложение. Привязка к счёту: цель следует за деньгами на нём — приход прибавляется, расход вычитается. Форма: иконки по девяти категориям в два столбца, «Начало накопления» и «Срок» рядом, счета чипами банк→счёт; удаление спрашивает подтверждение, выполненные цели — под сворачиваемым заголовком
Subscriptions Auto-detected recurring expenses, missed-payment alerts, monthly total. Одно и то же списание, которое банк присылает под разными описаниями («ChatGPT», «VTS OPENAI *CHATGPT SUBSC»), показывается ОДНОЙ строкой: одинаковая цена у одного сервиса — это одна подписка. Две настоящие подписки на один сервис по разной цене (19,99 и 22,40) остаются двумя строками
Insights & narratives 8 Russian narrative templates, CRITICAL/WARNING/INFO severity alerts
What-if simulator Interactive sliders for 6/12/24-month savings projections
Месяц к месяцу В самом верху вкладки «Тренды» — траты и доход по месяцам. Каждый месяц сравнивается со своим предыдущим, в том числе через границу года: январь — с декабрём. Бары наложены: яркий бар месяца стоит поверх бледного бара предыдущего, над баром — изменение в процентах (в режиме «Год» — стрелка, цифра — по касанию); рост трат — красным, рост дохода — зелёным, падение дохода — янтарным. Окно — 6 месяцев или год. Сравнивается полный месяц с полным; текущий месяц помечен незаконченным, и его изменение показано серым. Касание по месяцу раскрывает, из каких категорий сложилась разница
За всё время В аналитике после «Финансового здоровья» — плитка «Всего потрачено / всего заработано». По касанию — отдельный экран: итоги за год, 2, 10, 20 лет или всё время; две нарастающие кривые на одном графике (зелёная — заработано, красная — потрачено) с шагом месяц / полгода / год / 2 года, касание показывает цифры на конец периода; разноцветные бары по годам, внутри которых видны категории, — касание раскрывает год; круговые диаграммы по категориям трат и дохода; источники — кому ушли деньги и от кого пришли, с суммой и числом операций, пересчитываются с каждой новой операцией. Переводы между своими счетами не считаются ни тратой, ни заработком; другие валюты показаны отдельно, в рубли не пересчитываются
Календарь и «Свободно» Главное число экрана — не остаток и не прогноз, а «сколько можно потратить, ничего не сломав»: деньги на счетах минус незакрытые обязательства до горизонта минус ваш резерв. Горизонт по умолчанию — до следующего поступления, а не до конца месяца. Ожидаемая зарплата показывается, но НЕ прибавляется. Источники: объявленные платежи, платёж по кредитке, конец беспроцентного периода, найденные подписки, дедлайны целей
Платёж по кредитке, внесённый раньше срока Банк присылает «внесите 989,84 ₽ до 30 сентября» и замолкает — подтверждения оплаты он не шлёт. Приложение само вычитает погашения, сделанные после напоминания: карта показывает «Платёж за этот период внесён», календарь перестаёт вычитать его из «Свободно», плитка на главной больше не подгоняет «платёж через 4 дня». Платёж, сделанный ДО напоминания, не засчитывается: банк уже знал о нём, когда считал свою цифру
Закрытие обязательств Когда в истории появляется подходящая операция (та же валюта, то же направление, ±15 % по сумме, окно −5/+7 дней, счёт если указан), обязательство помечается оплаченным и перестаёт вычитаться из «Свободно». Сопоставление намеренно осторожное: при сомнении обязательство остаётся открытым, а любую отметку можно снять кнопкой «Отвязать»
Счёт, оплаченный по частям «Телефон, интернет 2 000 ₽», закрытые двумя переводами по 550 и 1 500 ₽ в один день, больше не висят просрочкой: приложение складывает операции ОДНОГО дня, если их не больше трёх, каждая — заметная часть суммы (≥ 20 %), а их категории не противоречат друг другу
«оплачено» — один раз, дальше само Кнопка «оплачено» на строке объявленного платежа открывает «Чем оплачено?»: операции рядом со сроком, можно отметить несколько (телефон и интернет отдельными переводами). Приложение запоминает, кому идёт этот платёж, и со следующего месяца закрывает его само — даже если части пришли в разные дни, а цена подписки немного выросла. Наличные и чужая карта — «Без операции»: закрывается ровно эта дата. «Отвязать» возвращает всё назад и забывает продавца отвязанной операции
«Не подписка» Найденную приложением «подписку», которая на деле просто частая покупка (пироги по субботам), можно скрыть кнопкой «не подписка» — в календаре, в предложениях и на экране подписок. Она перестаёт вычитаться из «Свободно»; «Вернуть» внизу списка показывает скрытое снова
Savings calculator Отдельный экран с тремя режимами: что накопится за срок, за сколько наберётся нужная сумма, сколько для этого откладывать. Капитализация (месяц/квартал/год/без), взнос в начале или конце месяца, ежегодная индексация взноса, инфляция → «в сегодняшних деньгах», НДФЛ 13 %, эффективная ставка, разбивка «своё / проценты» столбиками и таблицей по годам, и точка перелома — год, когда проценты начинают приносить больше ваших взносов. Подставляет ваш собственный темп накопления и суммы ваших целей
Backup / restore Экспорт девяти наборов данных (счета, карты, категории, цели, бюджеты, привязки, плановые платежи, операции, события брокера) в файл; восстановление additive, идемпотентное и не ломающее внешние ключи. Файл НЕ зашифрован — это обычный JSON, читаемый любым текстовым редактором: ключ Android Keystore привязан к устройству, и зашифрованная копия не открывалась ровно там, где копия и нужна, — на новом телефоне и после переустановки. Храните её как документ с полной историей ваших финансов, потому что это она и есть
Notifications Budget alerts, weekly summaries, critical insights, update-available (4 channels)
Deep-links Notification taps navigate directly to the relevant screen (allowlisted routes)
Settings Hero variant, animations/atmosphere/cat mode, budget alert threshold, biometric lock, ML toggle, SMS opt-in, categories CRUD, backup, updates
Biometric lock Off by default and never prompts until the preference is confirmed; always offers a device-PIN escape hatch so a broken sensor can't lock you out
Shimmer & Cat mode Optional atmosphere layer (particles, tilt/sheen cards, breathing hero, bioluminescent ripple) and a mood-matched cat mascot with paw-print particles
In-app update Checks GitHub Releases, downloads and installs a newer APK; a 12 h background worker pushes a notification when one appears
Home-screen widget 2×2 balance widget via AppWidgetProvider
Offline by design The updater is the only networked component; no financial data ever leaves the device

Supported Banks

Tier Bank SMS sender Push package
P1 Сбербанк SBERBANK, 900 ru.sberbankmobile, ru.sberbank.sbbol
P1 Т-Банк TINKOFF, TBANK, 2200 ru.tinkoff.cardsnew, com.idamob.tinkoff.android
P1 ВТБ VTB ru.vtb24.mobilebanking.android
P1 Альфа-Банк ALFABANK, ALFA ru.alfabank.mobile.android
P1 Газпромбанк GAZPROMBANK, GPB ru.gazprombank.android.mobilebank
P2 Райффайзен RAIFFEISEN ru.raiffeisenmobile.android
P2 Росбанк ROSBANK ru.rosbank.android
P2 Открытие OTKRITIE ru.ftc.otkritie
P3 МТС Банк MTSB ru.mtsbank.mobilebank
P3 Почта Банк POSTABANK ru.pochtabank.android
P3 Россельхозбанк RSHB ru.rshb.mbank
P3 МКБ MKB, МКБ, MKBMOBILE ru.mkb.mobilebank, ru.mkb.android
KG МБанк (Кыргызстан) MBANK com.maanavan.mb_kyrgyzstan

Каждый банк — отдельный BankParser, подключённый одной строкой @Binds @IntoSet. Двенадцать из тринадцати закрыты юнит-тестами; у MkbParser теста пока нет.

Имя, фирменный цвет, буква значка и ключевые слова каждого банка записаны один раз, в core/bank/BankRegistry. Выбор банка при добавлении счёта показывает все банки из реестра (раньше восемь; остальные можно было завести только через «Другой»). Операция Россельхозбанка без маски карты теперь привязывается к единственному счёту этого банка, как у остальных.

Screens

  1. Dashboard — net worth hero (3 variants: Calm/Contrast/Minimal), current-month label, income/expense/forecast metrics, credit-card tile (free limit + debt + nearest deadline), «Свободно» tile (без обязательств — приглашение в календарь, с ними — само число), accounts with volumetric bank cards, clickable recent transactions
  2. Transactions — сгруппированный по дням список; одна строка фильтров — 🔍 (поиск раскрывается по нажатию), «Тип операции» (Все / Расходы / Доходы / Переводы) и «Дата» (один день или период); swipe-left-to-reveal delete, detail/edit sheet with source diagnostics, «↑ Экспорт» (CSV: дата, тип — расход / доход / перевод исходящий или входящий, сумма, валюта, получатель, категория), «↓ Импорт» (PDF)
  3. Analytics — period chips + 4 tabs:
    • Обзор — multi-colour score donut with a per-pillar legend, expense pyramid, what-if simulator, archetype card
    • Категории — interactive 3D pie (tap to explode), ТОП-3 траты, full category list; tap any category to see its operations for the SELECTED period (month, 6 months, year, all time) compared with the same stretch before it
    • Тренды — daily spending curve, «Когда ты тратишь» as two tappable donuts (weekday / 4-hour bucket), «Усталость бюджета» bar chart, «Месяц к месяцу» diverging bars with было → стало, «Импульсивность» with the actual flagged purchases. Every section has a «?» badge explaining the heuristic in plain language
    • Инсайты — alerts, anomalies, narratives
  4. Budget — envelope cards with dynamic progress bars, subscriptions button
  5. Goals — карточки целей с артом по теме, кольцо с процентом внутри, ряд действий (история / ± / 🔗 / удалить), «Калькулятор» по центру шапки; форма цели с категориями иконок, датами начала и срока, выбором счетов чипами
  6. Калькулятор — three modes over one monthly simulation; fine-tuning panel; «ваш темп» and goal chips prefill from your own data; every figure is explicitly labelled an estimate
  7. Календарь — «Свободно» героем с разложенной арифметикой (счета − обязательства − резерв), два режима — полоса ближайших дат и сетка месяца (точки по видам событий, выбранный день фильтрует список), список событий с пометкой источника (цифра банка / объявлено вами / найдено), подтверждение найденных подписок одним касанием, раздел «уже прошло» с возможностью отвязать
  8. Кредитные карты — total free limit and debt, per-card block (payment amount and date large, interest-free period bar, rate, utilisation, «Погасить»), combined history across cards
  9. Subscriptions — auto-detected recurring expenses, missed-payment alerts
  10. Settings — hero variant, customization (animations / atmosphere / cat mode), SMS opt-in + 90-day import, bank push listener, ML toggle, budget alert threshold, biometric, categories CRUD, backup/restore, updates
  11. Onboarding — explicit choice between "импортировать из SMS за 90 дней" and "добавлю вручную"

Tech Stack

  • Kotlin + Jetpack Compose (BOM 2024.06, Material 3, custom dark theme)
  • Hilt — dependency injection with @IntoSet multibinding for parsers
  • Room 2.6.1 — local SQLite, schema v23 (22 миграции, каждая зарегистрирована в DatabaseModule), amounts as Long kopecks (×100). Account writes use @Upsert (never @Insert(REPLACE), which would CASCADE-delete the account's cards)
  • DataStore — ~20 preference keys (hero variant, notifications, ML, shimmer/cat mode, SMS opt-in, budget-alert throttle state, update prefs)
  • WorkManager + HiltWorkerFactory — daily analytics job + 12 h update check
  • TFLite 2.14.0 — optional ML layer (graceful fallback when model files absent)
  • PdfBox-Android 2.0.27.0 — statement import
  • Android Keystore (AES-GCM-256) — только расшифровка старых .fose-копий; новые пишутся открытым JSON
  • Clean Architecture + MVVM

Project Structure

app/
├── core/
│   ├── database/       # Entities, DAOs, FosDatabase (v23 — 19 categories, ~253 merchant rules, broker_events)
│   ├── parser/         # BankParser, ParserEngine, 13 bank parsers, TransferPatterns, PromoFilter, CreditNoticeParser, AmountParser, MerchantNames, ciRegex
│   ├── classifier/     # DictionaryClassifier, CategoryDefaults, CategoryClassifier interface
│   ├── sms/            # SmsReceiver (real-time), SmsReader (90-day import) — SMS only
│   ├── auth/           # BiometricHelper
│   ├── account/        # AccountLinker (card→account resolution, authoritative balance, orphan re-link)
│   ├── bank/           # BankRegistry — the one list of banks (name, colour, badge letter, link keywords)
│   ├── credit/         # CreditMath (debt, free limit, cycle, min payment, interest), CreditNoticeApplier
│   ├── transfer/       # TransferRouter (goal routing, internal pairing, counterparty leg)
│   ├── finance/        # SavingsMath (прогноз накоплений, срок до цели, требуемый взнос)
│   ├── calendar/       # CalendarEvent, PaymentDates, CalendarBuilder, FreeMoney, ObligationMatcher, ObligationSyncer
│   ├── analytics/      # AnalyticsEngine, ScoreCalculator, InsightGenerator, BehavioralAnalyzer, NarrativeEngine
│   ├── ml/             # ModelLoader, TextFeatureExtractor, MLCategoryClassifier, SpendingPredictor, BehavioralCluster
│   ├── pdf/            # PdfImporter, PdfTransactionParser
│   ├── backup/         # BackupManager (plain JSON), BackupCrypto (legacy .fose decryption only)
│   ├── update/         # UpdateChecker, UpdateCheckWorker
│   └── notifications/  # NotificationHelper (4 channels + deep-links + permission guard),
│                       # PushNotificationListener, ListenerHealth, ListenerRebindReceiver,
│                       # ListenerWatchdogWorker, ListenerNotice
├── data/
│   ├── repositories/   # Tx, Account, Card, Category, Budget, Goal, TransferRoute
│   └── preferences/    # UserPreferences (DataStore)
├── di/                 # DatabaseModule, ParserModule, RepositoryModule, MLModule, AnalyticsModule
├── features/           # dashboard, transactions, analytics, budget, goals, calculator, calendar, subscriptions, categories, credit, onboarding, settings, auth (LockScreen)
├── navigation/         # FosNavHost, FosRoutes, bottom nav
├── widget/             # BalanceWidget
└── ui/
    ├── theme/          # FosColors, FosType, FosDimens, FosSurface (огранка карточек), FosTheme, FosFormatter,
    │                   # AmountVisualTransformation, Shimmer, BankColors, BioluminescentIndication
    └── components/     # FosFormSheet (подтверждение выхода из формы), AccountPicker (банк → счёт),
                        # TransactionRow, FosSection (заголовки + «?»), LineChart, ScoreRing, ScoreDonut, Pie3D,
                        # AnalyticsCharts, WaterfallChart, SpendSeries, SpendTimeline, ForecastExplanation,
                        # GoalRing, GoalArt, HeatmapGrid, ExpensePyramid, WhatIfSimulator, AnimatedAmount,
                        # SwipeToRevealDelete, CatMascot, ParticleLayer, CurrencyReef, ShimmerCardFx

Build

./gradlew assembleDebug
./gradlew test
./gradlew lintDebug

CI (.github/workflows/android.yml) runs all three on every PR targeting dev or main.

Unit tests (app/src/test/) — 29 файлов, 364 случая:

Suite Coverage
12 × *ParserTest по банку, ~7 случаев каждый (MkbParser пока не покрыт)
GoalIconsTest каждая иконка цели получает подложку своей категории, глифы не повторяются
TransferPatternsTest transfer detection, card-mask extraction, stem anchoring
RealPushFormatsTest форматы пушей, снятые с устройства: входящий СБП, RUR, списание по счёту
SberCreditPushTest покупка по кредитке и напоминание о платеже
CyrillicCaseTest ciRegex против IGNORE_CASE: «ПОКУПКА» не теряется
PromoFilterTest marketing pushes are dropped, real operations pass
MerchantNamesTest нормализация названий продавца
PdfTransactionParserTest Alfa statement layout, logical-row reconstruction
CreditMathTest долг, свободный лимит, цикл, минимальный платёж, проценты
PaymentDatesTest · FreeMoneyTest · ObligationMatcherTest календарь: даты, «Свободно», сопоставление, счёт по частям
SavingsMathTest калькулятор накоплений (24 случая)
BehavioralAnalyzerTest all 7 public methods + edge cases (28 cases)
InsightGeneratorTest all 6 rules + sort order (28 cases)
ScoreCalculatorCushionTest · SubscriptionDetectorTest · SpendTimelineTest подушка, подписки, полоса трат

There are no instrumented/UI tests — screen behaviour, gestures and Compose rendering are verified by review, not automatically.

Install & Update (sideload)

The app ships as a sideloadable APK — no Play Store needed.

Get the APK. Every push to main triggers the Release APK workflow (.github/workflows/release-apk.yml), which builds the debug APK and publishes it as a GitHub Release. Download FinanceOS-Hub-vX.apk from the Releases page, open it on an Android device, and allow installation from this source.

Self-update. Inside the app, Настройки → Обновления → Проверить обновления queries the GitHub Releases API, compares the latest tag against the installed versionName, and — if newer — downloads the APK and launches the system installer. This is the only feature that uses the network (INTERNET + REQUEST_INSTALL_PACKAGES); no user data leaves the device.

Stable signing. All debug APKs (CI and local) are signed with the repo-committed app/debug.keystore (password android, debug-only). A single shared signature is what lets the in-app updater install a newer build over an older one without a signature mismatch. The distributed debug build drops the .debug applicationId suffix so updates replace the same package the user installed.

ML Classification (pre-trained, inference-only)

The ML layer uses frozen, pre-trained TFLite models bundled in app/src/main/assets/models/ — they run inference on-device but never train or learn on-device. The weights are fixed at build time; correcting a transaction's category does not retrain anything. To improve the model you retrain offline and ship a new .tflite.

File Shape Purpose
merchant_classifier.tflite float[1][256] → float[1][13] SMS merchant → category (13 expense classes, ≥0.40 confidence else dictionary)
spending_predictor.tflite float[1][30][1] → float[1][1] End-of-month spend forecast
behavioral_cluster.tflite float[1][7] → float[1][5] User archetype (5 clusters)

Without the model files the app runs entirely on the rule-based classifiers — no runtime errors. Income categories and any merchant the model can't place fall back to the dictionary rules + CategoryDefaults.

Design System

Dark theme, custom color tokens:

Token Hex Use
Positive #4DFFA0 Income, success, savings — ONLY positive
Negative #FF6B6B Expenses, errors, overrun — ONLY negative
Warning #FFB84D 70–90% budget usage
Info #4D9FFF Links, selection
Background #0A0D12 Main background

All monetary Text uses fontFeatureSettings = "tnum" (tabular-nums).

Branch Strategy

main  ← stable releases only
dev   ← integration branch
  └── feature/... ← PR to dev → PR to main

Security Notes

  • SmsReceiver is protected with android:permission="android.permission.BROADCAST_SMS" — only the system can broadcast to it
  • SmsReceiver.onReceive() uses goAsync() so the process is not killed before the DB write completes
  • All POST_NOTIFICATIONS calls are guarded by runtime permission check on API 33+
  • All Room database inserts in FosDatabase.PREPOPULATE_CALLBACK use parameterized execSQL() — no SQL injection surface
  • Coroutine errors in SmsReceiver are caught by CoroutineExceptionHandler — no silent crashes
  • CSV export uses RFC 4180 quoting + formula-injection neutralization (=, +, -, @ prefix with ')
  • Deep-link routes are validated against an allowlist before navigation (prevents attacker-controlled crash via exported Activity)
  • PushNotificationListener → TransactionSource.PUSH requires user to explicitly enable the notification listener in system Settings
  • SMS reading is opt-in (sms_realtime_enabled, default false) — a fresh install never touches the inbox until the user enables it
  • Backups are NOT encrypted. The export is plain JSON: the earlier AES-GCM-256 key lived in the Android Keystore and was device-bound, so an encrypted copy could not be restored on a new phone or after a reinstall — it protected the file from its owner. BackupCrypto.decrypt is kept only to read legacy .fose files. A backup therefore holds the full transaction history in clear text; treat the file accordingly and don't drop it in a shared folder. Restoring real protection means a user passphrase, never a device key
  • UpdateChecker refuses any release asset URL that is not https:// before opening a connection

About

local mobile app for finances

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages