dinary 1.2.3__tar.gz → 1.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dinary-1.2.3 → dinary-1.2.4}/.github/workflows/ci.yml +23 -3
- {dinary-1.2.3 → dinary-1.2.4}/.github/workflows/static.yml +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/PKG-INFO +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/activate.sh +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/docs/mkdocs.yml +1 -0
- dinary-1.2.4/docs/src/en/analytics.md +33 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/index.md +1 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/operations.md +12 -12
- dinary-1.2.4/docs/src/ru/analytics.md +33 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/index.md +1 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/operations.md +12 -12
- {dinary-1.2.3 → dinary-1.2.4}/pyproject.toml +12 -0
- dinary-1.2.4/specs/plans/analytics-ai.md +70 -0
- dinary-1.2.4/specs/plans/analytics-pwa.md +78 -0
- dinary-1.2.4/specs/reference/analytics-ai.md +179 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/architecture.md +15 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/components.md +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/screens.md +1 -0
- dinary-1.2.4/src/dinary/__about__.py +1 -0
- dinary-1.2.4/src/dinary_analytics/__init__.py +0 -0
- dinary-1.2.4/src/dinary_analytics/backup.py +99 -0
- dinary-1.2.4/src/dinary_analytics/charts.py +225 -0
- dinary-1.2.4/src/dinary_analytics/connection.py +96 -0
- dinary-1.2.4/src/dinary_analytics/mcp_server.py +58 -0
- dinary-1.2.4/src/dinary_analytics/notebooks/dashboard.py +509 -0
- dinary-1.2.4/src/dinary_analytics/settings.py +41 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/__init__.py +12 -0
- dinary-1.2.4/tasks/analytics.py +77 -0
- dinary-1.2.4/tasks/backups/analytics_backup.py +162 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backups_replica.py +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backups_restore.py +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backups_yandex.py +2 -2
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/restore_utils.py +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/tasks/deploy.py +3 -3
- {dinary-1.2.3 → dinary-1.2.4}/tasks/devtools/constants.py +1 -1
- {dinary-1.2.3 → dinary-1.2.4}/tasks/healthcheck.py +42 -1
- {dinary-1.2.3 → dinary-1.2.4}/tasks/setup.py +5 -1
- dinary-1.2.4/tests/analytics/__init__.py +0 -0
- dinary-1.2.4/tests/analytics/test_backup.py +78 -0
- dinary-1.2.4/tests/analytics/test_connection.py +106 -0
- dinary-1.2.4/tests/analytics/test_dashboard.py +344 -0
- dinary-1.2.4/tests/analytics/test_mcp_server.py +75 -0
- dinary-1.2.4/tests/analytics/test_settings.py +68 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/imports/test_income_extract.py +9 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_backups_restore.py +3 -3
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_server_receipt.py +46 -1
- {dinary-1.2.3 → dinary-1.2.4}/uv.lock +599 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ExpenseEditSheet.vue +32 -14
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ExpenseForm.vue +5 -13
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/catalog.js +24 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/ExpenseEditSheet.test.js +157 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-expense-form.test.js +55 -0
- dinary-1.2.3/specs/plans/analytics.md +0 -348
- dinary-1.2.3/src/dinary/__about__.py +0 -1
- {dinary-1.2.3 → dinary-1.2.4}/.claudeignore +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.coveragerc +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.deploy.example/.env +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.deploy.example/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.deploy.example/import_sources.json +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.deploy.example/llm_providers.toml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.github/workflows/docs.yml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.github/workflows/pip_publish.yml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.gitignore +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/.pre-commit-config.yaml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/AGENTS.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/CLAUDE.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/Dockerfile +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/LICENSE +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docker-compose.yml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/common/images/about.jpg +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/common/reference.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/cloudflare-setup.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/deploy-oracle.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/deploy-selfhost.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/development.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/google-sheets-setup.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/installation.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/en/pwa-install.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/cloudflare-setup.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/deploy-oracle.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/deploy-selfhost.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/development.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/google-sheets-setup.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/installation.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/pwa-install.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/docs/src/ru/taxonomy.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/invoke.yml +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/pytest.ini +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/scripts/verup.sh +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/catalog-api.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/classification-pipeline.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/currencies.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/frontend-cache.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/income-import.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/llm-providers.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/pwa-offline.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/receipt-fetching.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/sheets.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/sql-tool.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/stores.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/reference/timestamps.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/design-language.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/future-screens-guide.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/specs/ui/patterns.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/__init__.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/exchange_rates.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/llm_storage.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/llmbroker.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/nbp.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/nbs.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/rate_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/serbian_receipt_parser.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/adapters/sheets_client.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/catalog.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog_writer.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog_writer_categories.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog_writer_errors.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog_writer_events.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/catalog_writer_groups.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/expense_corrections.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/llm.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/qr_parser.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/controllers/rules.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/currencies.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/expense_corrections.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/llm.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/qr.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/receipts.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/api/rules.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/classification/item_normalizer.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/classification/persist.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/classification/receipt_classifier.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/classification/store_resolver.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/classification/task.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/rate_prefetch/task.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/sheet_logging/income_sheet_logging.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/sheet_logging/logging_jobs.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/sheet_logging/sheet_logging.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/sheet_logging/sheets_write.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/background/sheet_logging/task.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/config.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/catalog.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/classification_rules.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/currencies.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/db_migrations.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0001_initial_schema.rollback.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0001_initial_schema.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0002_exchange_rates_source_target.rollback.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0002_exchange_rates_source_target.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0003_app_currencies.rollback.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0003_app_currencies.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0004_receipt_pipeline.rollback.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0004_receipt_pipeline.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0005_income_logging.rollback.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/0005_income_logging.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/migrations/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/receipts.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/__init__.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/get_category_by_name.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/get_existing_expense.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/get_month_expenses.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/insert_expense.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/insert_income.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/list_categories.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/list_incomes.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/logging_projection.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/resolve_mapping_for_year.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql/seed_load_categories.sql +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/sql_loader.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/db/storage.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/main.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/sheets/sheet_mapping.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/src/dinary/sheets/sheets.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backup_retention.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backup_snapshots.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/backups/backups_status.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/db.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/devtools/build_docs.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/devtools/dev.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/devtools/env.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/README.md +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/expense_import.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/import_tasks.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/income_extract.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/income_import.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/report_2d_3d.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/seed.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/seed_config.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/seed_derivation.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/verify_equivalence.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/imports/verify_income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/receipt.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/report_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/report_tasks.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/verify_budget.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/reports/verify_income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/server.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/sql.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tasks/ssh_utils.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/_admin_catalog_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/_api_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_admin_catalog_add.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_admin_catalog_delete.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_admin_catalog_meta.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_admin_catalog_patch.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_admin_llm.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_catalog.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_concurrency.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_conflict.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_currencies.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_delete_expense.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_delete_receipt.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_expenses_recent_patch.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_get_expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_post_expense.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_receipts.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_rules_approve.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_api_validation.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_receipt_pipeline_e2e.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_receipt_review.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/api/test_review_page_ux.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/conftest.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/_currency_rates_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/test_currency_rates_misc.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/test_currency_rates_nbp.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/test_currency_rates_resolve.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/test_rate_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/currency/test_rate_prefetch_task.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/imports/test_expense_import.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/imports/test_seed_config.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/_catalog_writer_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/_ledger_repo_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_catalog_writer_invariants.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_catalog_writer_patch.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_income_db.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_catalog.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_expenses_insert.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_expenses_lookup.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_expenses_race.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_jobs.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_ledger_repo_logging_projection.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/ledger/test_migrations.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/_report_2d_3d_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_report_2d_3d.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_report_2d_3d_aggregate.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_report_2d_3d_render.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_report_2d_3d_resolve.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_reports_expenses.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_reports_income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_reports_verify_budget.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/reports/test_reports_verify_income.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_classification_rules.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_item_normalizer.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_llm_storage.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_llmbroker.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_qr_parser.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_receipt_classification.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_receipt_classifier.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_receipt_parser.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_sql_loader.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/services/test_store_resolver.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/_sheet_logging_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/_sheet_mapping_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/_sheets_helpers.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_income_drain.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_logging.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_logging_derive.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_logging_drain.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_logging_drain_one.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_mapping_parse.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_mapping_reload.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheet_mapping_resolve.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheets_read.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/sheets/test_sheets_rows.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_receipt_drain.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_receipt_pipeline.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_reclassify_receipts.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_backups_retention.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_backups_status.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_backups_yandex_setup.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_db.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_deploy.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_dev.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_imports.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_reports.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_restore_utils.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_server.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_setup_replica.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_ssh_utils.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tasks_ssh_utils_scripts.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/tasks/test_tools_sql.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/test_config.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/test_main.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/tests/test_webapp_api_contract.py +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/index.html +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/package-lock.json +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/package.json +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/apple-touch-icon-precomposed.png +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/apple-touch-icon.png +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/favicon.ico +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/icons/icon-180.png +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/icons/icon-192.png +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/icons/icon-512.png +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/public/manifest.json +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/App.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/_request.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/adminLlm.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/catalog.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/currencies.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/expenseCorrections.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/expenses.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/income.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/receipts.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/api/review.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/assets/base.css +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/BaseModal.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/BaseSheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CatalogSelectField.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CategoryQuickPicks.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CategorySheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ConfirmDeleteSheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CorrectionSheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CurrencyAmountRow.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/CurrencyPicker.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ExpenseRow.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/HeaderSegmented.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/HealthSummaryCard.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/IconBtn.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/IncomeEditSheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/IncomeForm.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/IncomeRow.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/InlineCreateEvent.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/InlineCreateRow.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/KeyboardSaveBar.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ManageList.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ProviderCard.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ProviderSheet.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/QrScanner.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/QueueModal.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ReceiptCascadeCard.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/RuleRow.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/ScopeSelector.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/StatusDot.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/components/TagPicker.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/addResult.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/catalogManage.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/flushQueue.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/flushReceiptQueue.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/receipt.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/useExpenseDeleteFlow.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/useKeyboardVisible.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/useOnline.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/useStaleCache.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/useSwipeRow.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/composables/zbar.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/legacy-pwa-cleanup.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/main.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/modals/EditModal.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/currency.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/frequentCategories.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/income.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/llm.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/queue.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/receiptQueue.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/review.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/stores/toast.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/views/AddView.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/views/IncomeView.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/views/LLMView.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/src/views/ReviewView.vue +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/BaseSheet.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/CategoryQuickPicks.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/CategorySheet.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/CurrencyAmountRow.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/ExpenseRow.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/ReceiptCascadeCard.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/ScopeSelector.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-adminLlm.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-catalog.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-currencies.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-expenses.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-income.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-receipts.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-request.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/api-review.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-app.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-catalog-select-field.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-correction-sheet.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-currency-picker.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-edit-modal.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-expense-edit-sheet.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-header-segmented.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-health-summary-card.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-inline-create-event.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-inline-create-row.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-manage-list.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-provider-card.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-provider-sheet.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-queue-modal.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-review-view.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-rule-row.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-status-dot.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/component-tag-picker.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-add-result.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-catalog-manage.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-flush-queue.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-flush-receipt-queue.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-keyboard-visible.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-receipt.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-stale-cache.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/composable-use-online.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/frequentCategories.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/legacy-pwa-cleanup.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/setup.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-catalog.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-currency.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-income.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-llm.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-queue.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-receipt-queue-durability.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-receipt-queue.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-review.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/store-toast.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/useExpenseDeleteFlow.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/tests/useSwipeRow.test.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/vite.config.js +0 -0
- {dinary-1.2.3 → dinary-1.2.4}/webapp/vitest.config.js +0 -0
|
@@ -25,6 +25,7 @@ env:
|
|
|
25
25
|
--junitxml=pytest.xml
|
|
26
26
|
--cov-report=term-missing:skip-covered
|
|
27
27
|
--cov=src
|
|
28
|
+
--ignore=tests/analytics
|
|
28
29
|
tests/
|
|
29
30
|
|
|
30
31
|
on:
|
|
@@ -90,7 +91,6 @@ jobs:
|
|
|
90
91
|
with:
|
|
91
92
|
python-version: ${{ env.PRIMARY_PYTHON_VERSION }}
|
|
92
93
|
|
|
93
|
-
|
|
94
94
|
- name: Install uv environment
|
|
95
95
|
uses: andgineer/uv-venv@v3
|
|
96
96
|
|
|
@@ -108,8 +108,28 @@ jobs:
|
|
|
108
108
|
- name: Run JS tests with Allure
|
|
109
109
|
run: npm --prefix webapp test
|
|
110
110
|
|
|
111
|
-
- name: Test with pytest and Allure
|
|
112
|
-
run:
|
|
111
|
+
- name: Test server with pytest and Allure
|
|
112
|
+
run: >-
|
|
113
|
+
python -m pytest
|
|
114
|
+
--junitxml=pytest.xml
|
|
115
|
+
--cov=src
|
|
116
|
+
--cov-report=term-missing:skip-covered
|
|
117
|
+
--ignore=tests/analytics
|
|
118
|
+
--alluredir=./allure-results
|
|
119
|
+
tests/
|
|
120
|
+
|
|
121
|
+
- name: Install analytics dependencies
|
|
122
|
+
run: uv sync --frozen --group analytics
|
|
123
|
+
|
|
124
|
+
- name: Test analytics with pytest and Allure
|
|
125
|
+
run: >-
|
|
126
|
+
python -m pytest
|
|
127
|
+
--junitxml=pytest-analytics.xml
|
|
128
|
+
--cov=src
|
|
129
|
+
--cov-append
|
|
130
|
+
--cov-report=term-missing:skip-covered
|
|
131
|
+
--alluredir=./allure-results
|
|
132
|
+
tests/analytics/
|
|
113
133
|
|
|
114
134
|
- name: Load Allure test report history
|
|
115
135
|
uses: actions/checkout@v4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinary
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Server for [Dinary - your dinar diary](https://github.com/andgineer/dinary). Track expenses, scan receipts, analyze spending with AI
|
|
5
5
|
Project-URL: Homepage, https://andgineer.github.io/dinary/
|
|
6
6
|
Project-URL: Documentation, https://andgineer.github.io/dinary/
|
|
@@ -41,7 +41,7 @@ if [[ ! -d ${VENV_FOLDER} ]] ; then
|
|
|
41
41
|
if uv venv ${VENV_FOLDER} --python=python${PRIMARY_PYTHON_VERSION}; then
|
|
42
42
|
|
|
43
43
|
. ${VENV_FOLDER}/bin/activate
|
|
44
|
-
uv sync --frozen
|
|
44
|
+
uv sync --frozen --group analytics
|
|
45
45
|
END_TIME=$(date +%s)
|
|
46
46
|
echo "Environment created in $((END_TIME - $START_TIME)) seconds"
|
|
47
47
|
else
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Your Personal Financial Analyst
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
inv analytics
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Opens a browser page at `http://localhost:2718` where you can **chat with your own spending data** in plain language — and browse interactive charts while you do it.
|
|
8
|
+
|
|
9
|
+
Ask things like:
|
|
10
|
+
|
|
11
|
+
- *"What did I spend most on last month?"*
|
|
12
|
+
- *"How does my food spending compare to last year?"*
|
|
13
|
+
- *"How much did the Italy trip cost, broken down by category?"*
|
|
14
|
+
- *"What's my savings rate for 2025?"*
|
|
15
|
+
|
|
16
|
+
The analyst knows your full expense history, categories, events, and tags. It queries your local database live — nothing leaves your machine.
|
|
17
|
+
|
|
18
|
+
## Charts
|
|
19
|
+
|
|
20
|
+
Alongside the chat, four visual summaries give you the big picture at a glance:
|
|
21
|
+
|
|
22
|
+
| | What it shows |
|
|
23
|
+
|---|---|
|
|
24
|
+
| **12-month rolling** | Top-10 categories stacked by month, with income and monthly savings |
|
|
25
|
+
| **Year comparison** | Any previous year overlaid on the rolling view |
|
|
26
|
+
| **Event** | Where the money went during a trip or project |
|
|
27
|
+
| **Tag** | Spending pattern for a label (e.g. "work", "dog") across a chosen year |
|
|
28
|
+
|
|
29
|
+
## Setup
|
|
30
|
+
|
|
31
|
+
The chat requires a Gemini API key configured as a provider in `.deploy/llm_providers.toml`. Without it, the charts still work — only the chat shows a warning.
|
|
32
|
+
|
|
33
|
+
The **year comparison**, **tag**, and **tag year** selectors remember your last choice across restarts. The **event** selector always opens on the most recent completed event.
|
|
@@ -18,3 +18,4 @@ Dinary server is a FastAPI backend that:
|
|
|
18
18
|
- [Your own computer](deploy-selfhost.md) — $0 (Tailscale Funnel or Cloudflare Tunnel)
|
|
19
19
|
3. Set up HTTPS access — see deployment guides above.
|
|
20
20
|
4. [Install the PWA](pwa-install.md) on your phone.
|
|
21
|
+
5. Run `inv analytics` to talk to [your personal financial analyst](analytics.md).
|
|
@@ -333,7 +333,7 @@ within hours rather than the next morning.
|
|
|
333
333
|
|
|
334
334
|
### Local Yandex.Disk access: `inv setup-yadisk`
|
|
335
335
|
|
|
336
|
-
`restore-
|
|
336
|
+
`restore-yadisk` reads from `yandex:` on whichever machine it
|
|
337
337
|
runs. `inv setup-yadisk` configures that remote locally — on VM 1
|
|
338
338
|
during disaster recovery, or on the operator laptop for debug
|
|
339
339
|
bootstraps:
|
|
@@ -346,16 +346,16 @@ Uses the same WebDAV + app-password flow as `inv setup-replica` (no
|
|
|
346
346
|
browser OAuth needed). Idempotent: skips the prompt if `yandex:` is
|
|
347
347
|
already configured and healthy.
|
|
348
348
|
|
|
349
|
-
`restore-
|
|
349
|
+
`restore-yadisk` calls this automatically when `yandex:` is
|
|
350
350
|
absent, so running it beforehand is optional.
|
|
351
351
|
|
|
352
352
|
## Point-in-time restore from Yandex.Disk
|
|
353
353
|
|
|
354
354
|
```bash
|
|
355
|
-
inv restore-
|
|
356
|
-
inv restore-
|
|
357
|
-
inv restore-
|
|
358
|
-
inv restore-
|
|
355
|
+
inv restore-yadisk --list-only # show inventory
|
|
356
|
+
inv restore-yadisk # restore latest
|
|
357
|
+
inv restore-yadisk --snapshot 2026-03-15 # specific date
|
|
358
|
+
inv restore-yadisk --yes # skip confirm
|
|
359
359
|
```
|
|
360
360
|
|
|
361
361
|
### Two intended use cases
|
|
@@ -369,10 +369,10 @@ inv restore-cloud-backup --yes # skip confirm
|
|
|
369
369
|
(via SSH) when both the local DB and the Litestream replica on
|
|
370
370
|
VM 2 are unusable. The SSH + `cd ~/dinary` + interactive
|
|
371
371
|
confirmation hops are intentional friction so a one-word
|
|
372
|
-
`inv restore-
|
|
372
|
+
`inv restore-yadisk` on the wrong terminal cannot silently
|
|
373
373
|
overwrite prod.
|
|
374
374
|
|
|
375
|
-
`restore-
|
|
375
|
+
`restore-yadisk` is **local-only** — it writes to
|
|
376
376
|
`./data/dinary.db` relative to the cwd and has no `--remote` mode.
|
|
377
377
|
There is no way to invoke it against a remote host from the
|
|
378
378
|
operator machine.
|
|
@@ -391,7 +391,7 @@ operator machine.
|
|
|
391
391
|
present on VM 1 via `inv setup-server`. On macOS: `brew install
|
|
392
392
|
rclone sqlite zstd`.
|
|
393
393
|
- A `yandex:` rclone remote configured locally. If it is absent,
|
|
394
|
-
**`restore-
|
|
394
|
+
**`restore-yadisk` prompts automatically** (same WebDAV +
|
|
395
395
|
app-password flow as `inv setup-replica`). To pre-configure before
|
|
396
396
|
the first restore run: `inv setup-yadisk`.
|
|
397
397
|
|
|
@@ -415,7 +415,7 @@ unusable:
|
|
|
415
415
|
ssh ubuntu@dinary # or the public IP / Tailscale IP
|
|
416
416
|
sudo systemctl stop dinary litestream # avoid a half-written DB
|
|
417
417
|
cd ~/dinary
|
|
418
|
-
inv restore-
|
|
418
|
+
inv restore-yadisk --snapshot 2026-03-15
|
|
419
419
|
# confirmation prompt: shows row count / size / mtime of the
|
|
420
420
|
# current DB plus compressed size of the incoming snapshot, then
|
|
421
421
|
# asks for literal 'yes'.
|
|
@@ -430,7 +430,7 @@ directory (so `./data/dinary.db` is the snapshot, not prod):
|
|
|
430
430
|
```bash
|
|
431
431
|
cd /tmp/restore-preview
|
|
432
432
|
mkdir -p data
|
|
433
|
-
inv restore-
|
|
433
|
+
inv restore-yadisk --snapshot 2026-03-15 --yes
|
|
434
434
|
sqlite3 data/dinary.db 'SELECT COUNT(*) FROM expense'
|
|
435
435
|
```
|
|
436
436
|
|
|
@@ -448,7 +448,7 @@ To deploy a specific version with a DB restore (e.g. rollback after a bad deploy
|
|
|
448
448
|
|
|
449
449
|
```bash
|
|
450
450
|
inv deploy --ref=v0.4.0 --no-start # deploy code but skip service start
|
|
451
|
-
inv restore-
|
|
451
|
+
inv restore-yadisk # restore DB from Yandex.Disk
|
|
452
452
|
inv restart-server # start; yoyo applies forward migrations
|
|
453
453
|
```
|
|
454
454
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Персональный финансовый аналитик
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
inv analytics
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Открывает страницу в браузере на `http://localhost:2718`, где можно **разговаривать со своими данными о расходах** на обычном языке — и параллельно смотреть интерактивные графики.
|
|
8
|
+
|
|
9
|
+
Спрашивайте что угодно:
|
|
10
|
+
|
|
11
|
+
- *«На что я потратил больше всего в прошлом месяце?»*
|
|
12
|
+
- *«Как изменились траты на еду по сравнению с прошлым годом?»*
|
|
13
|
+
- *«Сколько стоила поездка в Белград по категориям?»*
|
|
14
|
+
- *«Какая у меня норма сбережений за 2025 год?»*
|
|
15
|
+
|
|
16
|
+
Аналитик знает полную историю расходов, категории, события и теги. Запросы выполняются к локальной базе данных — данные никуда не уходят.
|
|
17
|
+
|
|
18
|
+
## Визуальные сводки
|
|
19
|
+
|
|
20
|
+
Рядом с чатом — четыре диаграммы для быстрого взгляда на картину в целом:
|
|
21
|
+
|
|
22
|
+
| | Что показывает |
|
|
23
|
+
|---|---|
|
|
24
|
+
| **Скользящие 12 месяцев** | Топ-10 категорий по месяцам, доходы и ежемесячная экономия |
|
|
25
|
+
| **Сравнение по годам** | Любой прошлый год рядом с текущим |
|
|
26
|
+
| **По событию** | Куда ушли деньги во время поездки или проекта |
|
|
27
|
+
| **По тегу** | Структура трат по метке («работа», «собака» и т.д.) за выбранный год |
|
|
28
|
+
|
|
29
|
+
## Настройка
|
|
30
|
+
|
|
31
|
+
Чат требует API-ключ Gemini, настроенный как провайдер в `.deploy/llm_providers.toml`. Без него диаграммы работают в штатном режиме — только в области чата появляется предупреждение.
|
|
32
|
+
|
|
33
|
+
Селекторы **сравнения лет**, **тега** и **года тега** запоминают выбор между запусками. Селектор **события** всегда открывается на последнем завершённом событии.
|
|
@@ -19,3 +19,4 @@ Dinary server — бэкенд на FastAPI, который:
|
|
|
19
19
|
3. Первоначально загружается [Классификатор](taxonomy.md) который вы далее можете корректировать
|
|
20
20
|
4. Настройте HTTPS-доступ — см. инструкции по деплою выше.
|
|
21
21
|
4. [Установите PWA](pwa-install.md) на телефон.
|
|
22
|
+
5. Запустите `inv analytics` — своего [персонального финансового аналитика](analytics.md).
|
|
@@ -346,7 +346,7 @@ UTC уже через несколько часов, а не следующим
|
|
|
346
346
|
|
|
347
347
|
### Локальный доступ к Яндекс.Диску: `inv setup-yadisk`
|
|
348
348
|
|
|
349
|
-
`restore-
|
|
349
|
+
`restore-yadisk` читает из `yandex:` на той машине, где
|
|
350
350
|
запущена. `inv setup-yadisk` настраивает этот remote локально — на
|
|
351
351
|
VM 1 при disaster recovery или на ноутбуке для отладочного
|
|
352
352
|
бутстрапа:
|
|
@@ -359,16 +359,16 @@ inv setup-yadisk
|
|
|
359
359
|
setup-replica`, — браузерный OAuth не нужен. Идемпотентна:
|
|
360
360
|
пропускает промпт, если `yandex:` уже настроен и работает.
|
|
361
361
|
|
|
362
|
-
`restore-
|
|
362
|
+
`restore-yadisk` вызывает её автоматически при отсутствии
|
|
363
363
|
`yandex:`, так что ручной запуск заранее необязателен.
|
|
364
364
|
|
|
365
365
|
## Восстановление на конкретную дату из Яндекс.Диска
|
|
366
366
|
|
|
367
367
|
```bash
|
|
368
|
-
inv restore-
|
|
369
|
-
inv restore-
|
|
370
|
-
inv restore-
|
|
371
|
-
inv restore-
|
|
368
|
+
inv restore-yadisk --list-only # список снапшотов
|
|
369
|
+
inv restore-yadisk # восстановить самый свежий
|
|
370
|
+
inv restore-yadisk --snapshot 2026-03-15 # конкретную дату
|
|
371
|
+
inv restore-yadisk --yes # без подтверждения
|
|
372
372
|
```
|
|
373
373
|
|
|
374
374
|
### Два предполагаемых сценария
|
|
@@ -382,10 +382,10 @@ inv restore-cloud-backup --yes # без подтвер
|
|
|
382
382
|
(через SSH), когда и локальная БД, и Litestream-реплика на VM 2
|
|
383
383
|
непригодны. Тройная защита «SSH + `cd ~/dinary` + интерактивное
|
|
384
384
|
подтверждение» — это намеренное трение, чтобы одним словом
|
|
385
|
-
`inv restore-
|
|
385
|
+
`inv restore-yadisk` в случайном терминале нельзя было
|
|
386
386
|
молча затереть прод.
|
|
387
387
|
|
|
388
|
-
`restore-
|
|
388
|
+
`restore-yadisk` — **local-only**: пишет в `./data/dinary.db`
|
|
389
389
|
относительно cwd, режима `--remote` нет. Запустить задачу на
|
|
390
390
|
удалённом хосте с операторской машины невозможно.
|
|
391
391
|
|
|
@@ -403,7 +403,7 @@ inv restore-cloud-backup --yes # без подтвер
|
|
|
403
403
|
есть через `inv setup-server`. На macOS: `brew install rclone
|
|
404
404
|
sqlite zstd`.
|
|
405
405
|
- Настроен удалённый `yandex:` в rclone. Если его нет,
|
|
406
|
-
**`restore-
|
|
406
|
+
**`restore-yadisk` сам запросит авторизацию** (та же схема
|
|
407
407
|
WebDAV + app-пароль, что у `inv setup-replica`). Для
|
|
408
408
|
предварительной настройки до первого restore: `inv setup-yadisk`.
|
|
409
409
|
|
|
@@ -427,7 +427,7 @@ inv restore-cloud-backup --yes # без подтвер
|
|
|
427
427
|
ssh ubuntu@dinary # или публичный IP / Tailscale IP
|
|
428
428
|
sudo systemctl stop dinary litestream # чтобы не получить наполовину переписанную БД
|
|
429
429
|
cd ~/dinary
|
|
430
|
-
inv restore-
|
|
430
|
+
inv restore-yadisk --snapshot 2026-03-15
|
|
431
431
|
# промпт подтверждения: печатает row count / size / mtime текущей
|
|
432
432
|
# БД плюс сжатый размер входящего снапшота и требует напечатать
|
|
433
433
|
# буквально 'yes'.
|
|
@@ -442,7 +442,7 @@ inv verify-db # integrity + FK check
|
|
|
442
442
|
```bash
|
|
443
443
|
cd /tmp/restore-preview
|
|
444
444
|
mkdir -p data
|
|
445
|
-
inv restore-
|
|
445
|
+
inv restore-yadisk --snapshot 2026-03-15 --yes
|
|
446
446
|
sqlite3 data/dinary.db 'SELECT COUNT(*) FROM expense'
|
|
447
447
|
```
|
|
448
448
|
|
|
@@ -461,7 +461,7 @@ sqlite3 data/dinary.db 'SELECT COUNT(*) FROM expense'
|
|
|
461
461
|
|
|
462
462
|
```bash
|
|
463
463
|
inv deploy --ref=v0.4.0 --no-start # задеплоить код, сервис не запускать
|
|
464
|
-
inv restore-
|
|
464
|
+
inv restore-yadisk # восстановить БД с Яндекс.Диска
|
|
465
465
|
inv restart-server # запустить; yoyo применит прямые миграции
|
|
466
466
|
```
|
|
467
467
|
|
|
@@ -57,6 +57,9 @@ search_path = ["src", "."]
|
|
|
57
57
|
[tool.ruff]
|
|
58
58
|
line-length = 99
|
|
59
59
|
|
|
60
|
+
[tool.ruff.lint]
|
|
61
|
+
per-file-ignores = { "src/dinary_analytics/notebooks/*.py" = ["PLC0415", "N803", "N806", "B018"] }
|
|
62
|
+
|
|
60
63
|
[tool.ruff.lint.pylint]
|
|
61
64
|
max-args = 8
|
|
62
65
|
|
|
@@ -82,6 +85,15 @@ dev = [
|
|
|
82
85
|
"pytest-timeout>=2.3.1",
|
|
83
86
|
"zensical>=0.0.41",
|
|
84
87
|
]
|
|
88
|
+
analytics = [
|
|
89
|
+
"duckdb>=1.2.0",
|
|
90
|
+
"lmdb>=1.6.0",
|
|
91
|
+
"marimo>=0.10.0",
|
|
92
|
+
"google-genai>=1.0.0",
|
|
93
|
+
"mcp>=1.9.0",
|
|
94
|
+
"altair>=5.0.0",
|
|
95
|
+
"polars>=1.0.0",
|
|
96
|
+
]
|
|
85
97
|
|
|
86
98
|
[project.scripts]
|
|
87
99
|
dinary = "dinary.main:main"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Analytics AI — implementation plan
|
|
2
|
+
|
|
3
|
+
See `specs/reference/analytics-ai.md` for architecture, storage design, LLM
|
|
4
|
+
strategy, invariants, and Analytics Views design.
|
|
5
|
+
|
|
6
|
+
## Remaining deliverables
|
|
7
|
+
|
|
8
|
+
### Template notebooks and extended dashboard
|
|
9
|
+
|
|
10
|
+
1. `notebooks/events.py` — event/trip cost breakdown notebook.
|
|
11
|
+
2. `notebooks/tags.py` — tag-bucket comparison notebook.
|
|
12
|
+
3. `dashboard.py` extended to full configurable widget set.
|
|
13
|
+
|
|
14
|
+
### MCP server extensions
|
|
15
|
+
|
|
16
|
+
4. `get_config(key)` and `set_config(key, value)` tools in `mcp_server.py`.
|
|
17
|
+
|
|
18
|
+
### PWA sync
|
|
19
|
+
|
|
20
|
+
5. `PUT /api/analytics/config` endpoint in dinary server + `analytics_pwa_config`
|
|
21
|
+
migration (coordinated with `analytics-pwa.md` work).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### AI Views feature
|
|
26
|
+
|
|
27
|
+
6. **`queries/spending_summary.sql`** — aggregates last-12-months expenses into
|
|
28
|
+
three result sets: events (id, name, total_amount, date_from, date_to),
|
|
29
|
+
tags (id, name, expense_count, total_amount), category groups (id, name,
|
|
30
|
+
total_amount). Used by the LLM before proposing a new view.
|
|
31
|
+
|
|
32
|
+
7. **`queries/view_data.sql`** — given a basket config passed as a JSON parameter,
|
|
33
|
+
assigns each expense to its first-matching basket (event match checked before
|
|
34
|
+
tag match, unmatched → default basket name), then aggregates by
|
|
35
|
+
(basket_name, year_month, group_name). Returns one row per
|
|
36
|
+
(basket, month, group) triple.
|
|
37
|
+
|
|
38
|
+
8. **`settings.py` extensions** — `list_view_ids() → list[str]`, `get_view(id)`,
|
|
39
|
+
`save_view(config: dict)`, `delete_view(id)`. Keys in LMDB: `view:<uuid>`.
|
|
40
|
+
|
|
41
|
+
9. **MCP server** — expose `list_views`, `get_view(id)`, `save_view(config)`,
|
|
42
|
+
`delete_view(id)` tools so Claude Desktop / Claude Code can manage views
|
|
43
|
+
externally.
|
|
44
|
+
|
|
45
|
+
10. **In-session LLM tools in `dashboard.py`** (Gemini chat tool definitions):
|
|
46
|
+
- `query_spending_summary()` → runs `spending_summary.sql`, returns JSON
|
|
47
|
+
- `propose_view(baskets, default_basket, chart_type)` → sets the in-memory
|
|
48
|
+
draft view config and triggers chart re-render; does not save
|
|
49
|
+
- `update_basket(name, event_ids, tag_ids)` → modifies a basket in the draft
|
|
50
|
+
- `remove_basket(name)` → removes a basket from the draft
|
|
51
|
+
- `set_chart_type(type)` → updates draft chart type
|
|
52
|
+
- `save_current_view(name)` → persists draft via `settings.save_view()`
|
|
53
|
+
- `delete_view(id)` → removes a saved view via `settings.delete_view()`
|
|
54
|
+
|
|
55
|
+
11. **Altair chart for basket views** — stacked bar: X = year_month, Y = amount,
|
|
56
|
+
color = basket_name. On click of a bar segment: filter to that basket + period
|
|
57
|
+
and show a secondary stacked bar by group_name as a drill-down panel below.
|
|
58
|
+
Use `alt.selection_point` on basket + month for the drill-down interaction.
|
|
59
|
+
|
|
60
|
+
12. **View selector UI in `dashboard.py`** — `mo.ui.dropdown` populated from
|
|
61
|
+
`settings.list_view_ids()` + labels from stored configs; "New view" button
|
|
62
|
+
clears the draft and triggers the LLM with the `query_spending_summary()`
|
|
63
|
+
result plus instructions to propose baskets with chart. Period selector
|
|
64
|
+
(year / custom range) shown alongside the chart.
|
|
65
|
+
|
|
66
|
+
13. **"New view" LLM prompt** — system prompt instructs the LLM: (a) call
|
|
67
|
+
`query_spending_summary()` first, (b) produce a `propose_view()` call
|
|
68
|
+
immediately with a concrete basket set, (c) explain each basket choice with
|
|
69
|
+
the numbers from the summary, (d) invite the user to react — never to describe
|
|
70
|
+
what they want in terms of categories or tags.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Analytics — PWA embedded view
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
A dedicated `/analytics` route in the existing Vue PWA. Shows a summary of the
|
|
6
|
+
ledger drawn from the dinary server. Complements the standalone `dinary-analytics`
|
|
7
|
+
app (`analytics-ai.md`) — the PWA view is always-accessible and mobile-friendly;
|
|
8
|
+
the standalone app is where deep exploration and AI interaction happen.
|
|
9
|
+
|
|
10
|
+
Out of scope: OLTP writes, sheet logging, imports, migrations.
|
|
11
|
+
|
|
12
|
+
## Data source
|
|
13
|
+
|
|
14
|
+
New FastAPI endpoints reading dinary SQLite directly. DuckDB is not used on the
|
|
15
|
+
server (1 GB RAM constraint — see `architecture.md`). All queries are plain SQLite
|
|
16
|
+
GROUP BY aggregations.
|
|
17
|
+
|
|
18
|
+
## Default views (zero configuration)
|
|
19
|
+
|
|
20
|
+
These views are always present regardless of whether analytics-ai has ever been run.
|
|
21
|
+
|
|
22
|
+
1. **Monthly trend** — total expenses per month for the selected year, broken down
|
|
23
|
+
by category group. Query: expenses JOIN categories JOIN category_groups,
|
|
24
|
+
GROUP BY year_month, group_name.
|
|
25
|
+
|
|
26
|
+
2. **Events** — all events with total cost in accounting currency, sorted by date
|
|
27
|
+
descending. Query: expenses JOIN events, GROUP BY event_id.
|
|
28
|
+
|
|
29
|
+
## Config-driven basket views
|
|
30
|
+
|
|
31
|
+
When the user has run analytics-ai and saved one or more Analytics Views, those
|
|
32
|
+
views appear as additional tabs in `/analytics`. The PWA has no view editor — the
|
|
33
|
+
standalone app is the only configuration tool.
|
|
34
|
+
|
|
35
|
+
Config is written by analytics-ai via `PUT /api/analytics/config` and stored in
|
|
36
|
+
`analytics_pwa_config` as a JSON blob under key `views`. The server executes basket
|
|
37
|
+
assignment on request using a parameterised SQLite query; no basket logic lives in
|
|
38
|
+
Python. Basket assignment: for each expense, check event triggers first, then tag
|
|
39
|
+
triggers, first match wins, unmatched go to the default basket.
|
|
40
|
+
|
|
41
|
+
## Config table
|
|
42
|
+
|
|
43
|
+
`analytics_pwa_config` in dinary SQLite:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
key TEXT PRIMARY KEY
|
|
47
|
+
value JSON
|
|
48
|
+
updated_at TIMESTAMP
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Only key in use: `views` — JSON array of view config objects (same schema as
|
|
52
|
+
stored in analytics.db, see `analytics-ai.md`). The server never writes this
|
|
53
|
+
table; only `PUT /api/analytics/config` does.
|
|
54
|
+
|
|
55
|
+
## Implementation outline
|
|
56
|
+
|
|
57
|
+
**Backend**
|
|
58
|
+
|
|
59
|
+
- New FastAPI router `api/analytics.py`.
|
|
60
|
+
- New migration: `analytics_pwa_config` table.
|
|
61
|
+
- `GET /api/analytics/config` — returns current `analytics_pwa_config` rows as JSON.
|
|
62
|
+
- `PUT /api/analytics/config` — replaces one or more keys; called by analytics-ai only.
|
|
63
|
+
- `GET /api/analytics/monthly?year=<year>` — monthly trend data.
|
|
64
|
+
- `GET /api/analytics/events` — events with totals.
|
|
65
|
+
- `GET /api/analytics/view/<view_id>?year=<year>` — executes basket assignment for
|
|
66
|
+
the named view config stored in `analytics_pwa_config.views`, returns
|
|
67
|
+
(basket_name, year_month, group_name, amount) rows.
|
|
68
|
+
- New SQL files: `db/sql/analytics_monthly.sql`, `db/sql/analytics_events.sql`,
|
|
69
|
+
`db/sql/analytics_view.sql` (parameterised basket assignment).
|
|
70
|
+
|
|
71
|
+
**Frontend**
|
|
72
|
+
|
|
73
|
+
- New Vue route `/analytics`, new `AnalyticsView.vue`.
|
|
74
|
+
- On load: fetches config + default view data in parallel.
|
|
75
|
+
- Tab bar: "Monthly" tab + "Events" tab always present; one tab per basket view
|
|
76
|
+
from config (if any).
|
|
77
|
+
- Period selector (year) applies to all tabs.
|
|
78
|
+
- Chart library: to be decided at implementation time — keep it lightweight.
|