dinary 1.2.1__tar.gz → 1.2.2__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.1 → dinary-1.2.2}/PKG-INFO +1 -1
- dinary-1.2.2/src/dinary/__about__.py +1 -0
- dinary-1.2.2/tasks/imports/income_original_export.py +180 -0
- dinary-1.2.1/src/dinary/__about__.py +0 -1
- {dinary-1.2.1 → dinary-1.2.2}/.claudeignore +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.coveragerc +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.deploy.example/.env +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.deploy.example/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.deploy.example/import_sources.json +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.deploy.example/llm_providers.toml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.github/workflows/ci.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.github/workflows/docs.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.github/workflows/pip_publish.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.github/workflows/static.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.gitignore +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/.pre-commit-config.yaml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/AGENTS.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/CLAUDE.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/Dockerfile +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/LICENSE +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/activate.sh +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docker-compose.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/mkdocs.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/common/images/about.jpg +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/common/reference.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/cloudflare-setup.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/deploy-oracle.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/deploy-selfhost.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/development.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/google-sheets-setup.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/index.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/installation.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/operations.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/en/pwa-install.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/cloudflare-setup.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/deploy-oracle.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/deploy-selfhost.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/development.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/google-sheets-setup.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/index.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/installation.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/operations.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/pwa-install.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/docs/src/ru/taxonomy.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/invoke.yml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/pyproject.toml +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/pytest.ini +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/scripts/verup.sh +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/plans/analytics.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/architecture.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/catalog-api.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/classification-pipeline.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/currencies.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/frontend-cache.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/income-import.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/llm-providers.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/pwa-offline.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/receipt-fetching.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/sheets.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/sql-tool.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/stores.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/reference/timestamps.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/components.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/design-language.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/future-screens-guide.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/patterns.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/specs/ui/screens.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/__init__.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/exchange_rates.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/llm_storage.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/llmbroker.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/nbp.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/nbs.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/rate_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/serbian_receipt_parser.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/adapters/sheets_client.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/catalog.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_categories.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_errors.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_events.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_groups.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/expense_corrections.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/llm.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/qr_parser.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/controllers/rules.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/currencies.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/expense_corrections.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/llm.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/qr.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/receipts.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/api/rules.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/classification/item_normalizer.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/classification/persist.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/classification/receipt_classifier.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/classification/store_resolver.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/classification/task.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/rate_prefetch/task.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/sheet_logging/income_sheet_logging.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/sheet_logging/logging_jobs.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/sheet_logging/sheet_logging.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/sheet_logging/sheets_write.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/background/sheet_logging/task.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/config.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/catalog.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/classification_rules.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/currencies.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/db_migrations.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0001_initial_schema.rollback.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0001_initial_schema.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0002_exchange_rates_source_target.rollback.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0002_exchange_rates_source_target.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0003_app_currencies.rollback.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0003_app_currencies.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0004_receipt_pipeline.rollback.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0004_receipt_pipeline.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0005_income_logging.rollback.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0005_income_logging.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/receipts.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/__init__.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/get_category_by_name.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/get_existing_expense.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/get_month_expenses.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/insert_expense.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/insert_income.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/list_categories.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/list_incomes.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/logging_projection.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/resolve_mapping_for_year.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql/seed_load_categories.sql +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/sql_loader.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/storage.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/main.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/sheets/sheet_mapping.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/src/dinary/sheets/sheets.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/__init__.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backup_retention.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backup_snapshots.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backups_replica.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backups_restore.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backups_status.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/backups_yandex.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/backups/restore_utils.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/db.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/deploy.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/devtools/build_docs.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/devtools/constants.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/devtools/dev.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/devtools/env.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/healthcheck.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/README.md +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/expense_import.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/import_tasks.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/income_import.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/report_2d_3d.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/seed.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/seed_config.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/seed_derivation.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/verify_equivalence.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/imports/verify_income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/receipt.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/report_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/report_tasks.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/verify_budget.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/reports/verify_income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/server.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/setup.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/sql.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tasks/ssh_utils.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/_admin_catalog_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/_api_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_admin_catalog_add.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_admin_catalog_delete.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_admin_catalog_meta.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_admin_catalog_patch.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_admin_llm.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_catalog.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_concurrency.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_conflict.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_currencies.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_delete_expense.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_delete_receipt.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_expenses_recent_patch.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_get_expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_post_expense.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_receipts.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_rules_approve.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_api_validation.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_receipt_pipeline_e2e.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_receipt_review.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/api/test_review_page_ux.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/conftest.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/_currency_rates_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/test_currency_rates_misc.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/test_currency_rates_nbp.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/test_currency_rates_resolve.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/test_rate_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/currency/test_rate_prefetch_task.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/imports/test_expense_import.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/imports/test_seed_config.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/_catalog_writer_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/_ledger_repo_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_catalog_writer_invariants.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_catalog_writer_patch.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_income_db.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_catalog.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_insert.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_lookup.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_race.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_jobs.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_ledger_repo_logging_projection.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/ledger/test_migrations.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/_report_2d_3d_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_report_2d_3d.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_report_2d_3d_aggregate.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_report_2d_3d_render.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_report_2d_3d_resolve.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_reports_expenses.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_reports_income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_reports_verify_budget.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/reports/test_reports_verify_income.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_classification_rules.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_item_normalizer.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_llm_storage.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_llmbroker.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_qr_parser.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_receipt_classification.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_receipt_classifier.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_receipt_parser.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_sql_loader.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/services/test_store_resolver.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/_sheet_logging_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/_sheet_mapping_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/_sheets_helpers.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_income_drain.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_logging.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_logging_derive.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_logging_drain.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_logging_drain_one.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_parse.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_reload.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_resolve.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheets_read.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/sheets/test_sheets_rows.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_receipt_drain.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_receipt_pipeline.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_reclassify_receipts.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_backups_restore.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_backups_retention.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_backups_status.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_backups_yandex_setup.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_db.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_deploy.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_dev.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_imports.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_reports.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_restore_utils.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_server.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_server_receipt.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_setup_replica.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_ssh_utils.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tasks_ssh_utils_scripts.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/tasks/test_tools_sql.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/test_config.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/test_main.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/tests/test_webapp_api_contract.py +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/uv.lock +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/index.html +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/package-lock.json +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/package.json +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/apple-touch-icon-precomposed.png +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/apple-touch-icon.png +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/favicon.ico +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/icons/icon-180.png +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/icons/icon-192.png +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/icons/icon-512.png +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/public/manifest.json +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/App.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/_request.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/adminLlm.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/catalog.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/currencies.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/expenseCorrections.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/expenses.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/income.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/receipts.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/api/review.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/assets/base.css +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/BaseModal.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/BaseSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CatalogSelectField.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CategoryQuickPicks.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CategorySheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ConfirmDeleteSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CorrectionSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CurrencyAmountRow.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/CurrencyPicker.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ExpenseEditSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ExpenseForm.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ExpenseRow.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/HeaderSegmented.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/HealthSummaryCard.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/IconBtn.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/IncomeEditSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/IncomeForm.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/IncomeRow.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/InlineCreateEvent.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/InlineCreateRow.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/KeyboardSaveBar.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ManageList.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ProviderCard.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ProviderSheet.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/QrScanner.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/QueueModal.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ReceiptCascadeCard.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/RuleRow.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/ScopeSelector.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/StatusDot.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/components/TagPicker.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/addResult.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/catalogManage.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/flushQueue.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/flushReceiptQueue.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/receipt.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/useExpenseDeleteFlow.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/useKeyboardVisible.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/useOnline.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/useStaleCache.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/useSwipeRow.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/composables/zbar.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/legacy-pwa-cleanup.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/main.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/modals/EditModal.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/catalog.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/currency.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/frequentCategories.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/income.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/llm.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/queue.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/receiptQueue.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/review.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/stores/toast.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/views/AddView.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/views/IncomeView.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/views/LLMView.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/src/views/ReviewView.vue +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/BaseSheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/CategoryQuickPicks.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/CategorySheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/CurrencyAmountRow.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/ExpenseEditSheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/ExpenseRow.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/ReceiptCascadeCard.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/ScopeSelector.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-adminLlm.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-catalog.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-currencies.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-expenses.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-income.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-receipts.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-request.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/api-review.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-app.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-catalog-select-field.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-correction-sheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-currency-picker.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-edit-modal.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-expense-edit-sheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-expense-form.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-header-segmented.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-health-summary-card.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-inline-create-event.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-inline-create-row.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-manage-list.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-provider-card.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-provider-sheet.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-queue-modal.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-review-view.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-rule-row.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-status-dot.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/component-tag-picker.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-add-result.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-catalog-manage.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-flush-queue.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-flush-receipt-queue.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-keyboard-visible.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-receipt.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-stale-cache.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/composable-use-online.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/frequentCategories.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/legacy-pwa-cleanup.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/setup.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-catalog.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-currency.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-income.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-llm.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-queue.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-receipt-queue-durability.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-receipt-queue.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-review.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/store-toast.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/useExpenseDeleteFlow.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/tests/useSwipeRow.test.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/vite.config.js +0 -0
- {dinary-1.2.1 → dinary-1.2.2}/webapp/vitest.config.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dinary
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.2"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Extract monthly income from Google Sheets in original currency (no EUR conversion).
|
|
2
|
+
|
|
3
|
+
Reads every registered income source from ``.deploy/import_sources.json``
|
|
4
|
+
and aggregates amounts per (year, month) in the currency the sheet is
|
|
5
|
+
denominated in — RUB for years up to mid-2022, RSD from mid-2022 onward
|
|
6
|
+
(exact boundary is the ``transition_month`` in each year's ``IncomeLayout``).
|
|
7
|
+
|
|
8
|
+
The result is written as a JSON file with a flat list of entries:
|
|
9
|
+
|
|
10
|
+
.. code-block:: json
|
|
11
|
+
|
|
12
|
+
{
|
|
13
|
+
"generated_at": "2026-05-31",
|
|
14
|
+
"entries": [
|
|
15
|
+
{"year": 2019, "month": 1, "amount": "85000.00", "currency": "RUB"},
|
|
16
|
+
...
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
``inv export-income-original`` is the operator entry point. The module
|
|
21
|
+
can also be run with ``uv run python -m tasks.imports.income_original_export``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import logging
|
|
27
|
+
import sys
|
|
28
|
+
from collections import defaultdict
|
|
29
|
+
from datetime import date
|
|
30
|
+
from decimal import Decimal
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from dinary.adapters.sheets_client import get_sheet
|
|
34
|
+
from dinary.config import IMPORT_SOURCES_DOC_HINT, read_import_sources
|
|
35
|
+
from tasks.imports.expense_import import MONTHS_IN_YEAR
|
|
36
|
+
from tasks.imports.income_import import (
|
|
37
|
+
INCOME_LAYOUTS,
|
|
38
|
+
IncomeLayout,
|
|
39
|
+
_cell,
|
|
40
|
+
_parse_amount,
|
|
41
|
+
_parse_date,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
DEFAULT_OUTPUT = Path("data/income_original.json")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def aggregate_original_currency(
|
|
50
|
+
year: int,
|
|
51
|
+
spreadsheet_id: str,
|
|
52
|
+
worksheet_name: str,
|
|
53
|
+
layout: IncomeLayout,
|
|
54
|
+
) -> dict[tuple[int, str], Decimal]:
|
|
55
|
+
"""Read one Income/Balance worksheet; return ``{(month, currency): total}``.
|
|
56
|
+
|
|
57
|
+
Does NOT convert to accounting currency. Each month carries exactly
|
|
58
|
+
one currency (the layout's ``currency`` before ``transition_month``,
|
|
59
|
+
``transition_currency`` from that month onward).
|
|
60
|
+
"""
|
|
61
|
+
ss = get_sheet(spreadsheet_id)
|
|
62
|
+
ws = ss.worksheet(worksheet_name)
|
|
63
|
+
all_values = ws.get_all_values()
|
|
64
|
+
|
|
65
|
+
monthly: dict[tuple[int, str], Decimal] = defaultdict(Decimal)
|
|
66
|
+
|
|
67
|
+
for row_idx in range(layout.header_rows, len(all_values)):
|
|
68
|
+
row = all_values[row_idx]
|
|
69
|
+
parsed = _parse_date(_cell(row, layout.col_date))
|
|
70
|
+
if parsed is None:
|
|
71
|
+
continue
|
|
72
|
+
row_year, month = parsed
|
|
73
|
+
if row_year != year:
|
|
74
|
+
continue
|
|
75
|
+
if not 1 <= month <= MONTHS_IN_YEAR:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
amount = _parse_amount(_cell(row, layout.col_amount))
|
|
79
|
+
if amount is None:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
currency = layout.currency
|
|
83
|
+
if (
|
|
84
|
+
layout.transition_month is not None
|
|
85
|
+
and layout.transition_currency is not None
|
|
86
|
+
and month >= layout.transition_month
|
|
87
|
+
):
|
|
88
|
+
currency = layout.transition_currency
|
|
89
|
+
|
|
90
|
+
monthly[(month, currency)] += amount
|
|
91
|
+
|
|
92
|
+
return dict(monthly)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def extract_all_years() -> list[dict]:
|
|
96
|
+
"""Return a flat list of ``{year, month, amount, currency}`` dicts.
|
|
97
|
+
|
|
98
|
+
Iterates every import source that has an income worksheet. Years with
|
|
99
|
+
no registered source or no income worksheet are silently skipped.
|
|
100
|
+
Amounts are ``str`` (canonical decimal notation) to avoid float rounding.
|
|
101
|
+
"""
|
|
102
|
+
sources = read_import_sources()
|
|
103
|
+
entries: list[dict] = []
|
|
104
|
+
|
|
105
|
+
for source in sorted(sources, key=lambda s: s.year):
|
|
106
|
+
if not source.income_worksheet_name:
|
|
107
|
+
continue
|
|
108
|
+
if not source.income_layout_key or source.income_layout_key not in INCOME_LAYOUTS:
|
|
109
|
+
logger.warning(
|
|
110
|
+
"Year %d: unknown income_layout_key %r — skipping. %s",
|
|
111
|
+
source.year,
|
|
112
|
+
source.income_layout_key,
|
|
113
|
+
IMPORT_SOURCES_DOC_HINT,
|
|
114
|
+
)
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
layout = INCOME_LAYOUTS[source.income_layout_key]
|
|
118
|
+
logger.info(
|
|
119
|
+
"Extracting year %d from worksheet %r",
|
|
120
|
+
source.year,
|
|
121
|
+
source.income_worksheet_name,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
monthly = aggregate_original_currency(
|
|
126
|
+
source.year,
|
|
127
|
+
source.spreadsheet_id,
|
|
128
|
+
source.income_worksheet_name,
|
|
129
|
+
layout,
|
|
130
|
+
)
|
|
131
|
+
except Exception:
|
|
132
|
+
logger.exception("Failed to read year %d — skipping", source.year)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
for (month, currency), total in sorted(monthly.items()):
|
|
136
|
+
entries.append(
|
|
137
|
+
{
|
|
138
|
+
"year": source.year,
|
|
139
|
+
"month": month,
|
|
140
|
+
"amount": format(total, "f"),
|
|
141
|
+
"currency": currency,
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return entries
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def export_to_file(output: Path) -> int:
|
|
149
|
+
"""Extract all income in original currency and write to ``output``.
|
|
150
|
+
|
|
151
|
+
Returns the number of entries written. Raises on I/O errors.
|
|
152
|
+
"""
|
|
153
|
+
entries = extract_all_years()
|
|
154
|
+
payload = {
|
|
155
|
+
"generated_at": str(date.today()),
|
|
156
|
+
"entries": entries,
|
|
157
|
+
}
|
|
158
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
160
|
+
return len(entries)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main(argv: list[str] | None = None) -> int:
|
|
164
|
+
parser = argparse.ArgumentParser(
|
|
165
|
+
description="Extract income from all sheets in original currency to JSON.",
|
|
166
|
+
)
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"--output",
|
|
169
|
+
default=str(DEFAULT_OUTPUT),
|
|
170
|
+
help=f"destination JSON file (default: {DEFAULT_OUTPUT})",
|
|
171
|
+
)
|
|
172
|
+
args = parser.parse_args(argv)
|
|
173
|
+
output = Path(args.output)
|
|
174
|
+
count = export_to_file(output)
|
|
175
|
+
print(f"Wrote {count} entries to {output}")
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
sys.exit(main())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dinary-1.2.1 → dinary-1.2.2}/src/dinary/db/migrations/0002_exchange_rates_source_target.sql
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|