dinary 1.2.0__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.
Files changed (421) hide show
  1. {dinary-1.2.0 → dinary-1.2.2}/PKG-INFO +1 -1
  2. dinary-1.2.2/src/dinary/__about__.py +1 -0
  3. {dinary-1.2.0 → dinary-1.2.2}/tasks/__init__.py +2 -0
  4. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/import_tasks.py +15 -0
  5. dinary-1.2.2/tasks/imports/income_original_export.py +180 -0
  6. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/IncomeForm.vue +7 -4
  7. dinary-1.2.0/src/dinary/__about__.py +0 -1
  8. {dinary-1.2.0 → dinary-1.2.2}/.claudeignore +0 -0
  9. {dinary-1.2.0 → dinary-1.2.2}/.coveragerc +0 -0
  10. {dinary-1.2.0 → dinary-1.2.2}/.deploy.example/.env +0 -0
  11. {dinary-1.2.0 → dinary-1.2.2}/.deploy.example/README.md +0 -0
  12. {dinary-1.2.0 → dinary-1.2.2}/.deploy.example/import_sources.json +0 -0
  13. {dinary-1.2.0 → dinary-1.2.2}/.deploy.example/llm_providers.toml +0 -0
  14. {dinary-1.2.0 → dinary-1.2.2}/.github/workflows/ci.yml +0 -0
  15. {dinary-1.2.0 → dinary-1.2.2}/.github/workflows/docs.yml +0 -0
  16. {dinary-1.2.0 → dinary-1.2.2}/.github/workflows/pip_publish.yml +0 -0
  17. {dinary-1.2.0 → dinary-1.2.2}/.github/workflows/static.yml +0 -0
  18. {dinary-1.2.0 → dinary-1.2.2}/.gitignore +0 -0
  19. {dinary-1.2.0 → dinary-1.2.2}/.pre-commit-config.yaml +0 -0
  20. {dinary-1.2.0 → dinary-1.2.2}/AGENTS.md +0 -0
  21. {dinary-1.2.0 → dinary-1.2.2}/CLAUDE.md +0 -0
  22. {dinary-1.2.0 → dinary-1.2.2}/Dockerfile +0 -0
  23. {dinary-1.2.0 → dinary-1.2.2}/LICENSE +0 -0
  24. {dinary-1.2.0 → dinary-1.2.2}/README.md +0 -0
  25. {dinary-1.2.0 → dinary-1.2.2}/activate.sh +0 -0
  26. {dinary-1.2.0 → dinary-1.2.2}/docker-compose.yml +0 -0
  27. {dinary-1.2.0 → dinary-1.2.2}/docs/mkdocs.yml +0 -0
  28. {dinary-1.2.0 → dinary-1.2.2}/docs/src/common/images/about.jpg +0 -0
  29. {dinary-1.2.0 → dinary-1.2.2}/docs/src/common/reference.md +0 -0
  30. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/cloudflare-setup.md +0 -0
  31. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/deploy-oracle.md +0 -0
  32. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/deploy-selfhost.md +0 -0
  33. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/development.md +0 -0
  34. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/google-sheets-setup.md +0 -0
  35. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/index.md +0 -0
  36. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/installation.md +0 -0
  37. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/operations.md +0 -0
  38. {dinary-1.2.0 → dinary-1.2.2}/docs/src/en/pwa-install.md +0 -0
  39. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/cloudflare-setup.md +0 -0
  40. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/deploy-oracle.md +0 -0
  41. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/deploy-selfhost.md +0 -0
  42. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/development.md +0 -0
  43. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/google-sheets-setup.md +0 -0
  44. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/index.md +0 -0
  45. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/installation.md +0 -0
  46. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/operations.md +0 -0
  47. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/pwa-install.md +0 -0
  48. {dinary-1.2.0 → dinary-1.2.2}/docs/src/ru/taxonomy.md +0 -0
  49. {dinary-1.2.0 → dinary-1.2.2}/invoke.yml +0 -0
  50. {dinary-1.2.0 → dinary-1.2.2}/pyproject.toml +0 -0
  51. {dinary-1.2.0 → dinary-1.2.2}/pytest.ini +0 -0
  52. {dinary-1.2.0 → dinary-1.2.2}/scripts/verup.sh +0 -0
  53. {dinary-1.2.0 → dinary-1.2.2}/specs/README.md +0 -0
  54. {dinary-1.2.0 → dinary-1.2.2}/specs/plans/analytics.md +0 -0
  55. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/architecture.md +0 -0
  56. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/catalog-api.md +0 -0
  57. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/classification-pipeline.md +0 -0
  58. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/currencies.md +0 -0
  59. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/frontend-cache.md +0 -0
  60. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/income-import.md +0 -0
  61. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/llm-providers.md +0 -0
  62. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/pwa-offline.md +0 -0
  63. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/receipt-fetching.md +0 -0
  64. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/sheets.md +0 -0
  65. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/sql-tool.md +0 -0
  66. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/stores.md +0 -0
  67. {dinary-1.2.0 → dinary-1.2.2}/specs/reference/timestamps.md +0 -0
  68. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/README.md +0 -0
  69. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/components.md +0 -0
  70. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/design-language.md +0 -0
  71. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/future-screens-guide.md +0 -0
  72. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/patterns.md +0 -0
  73. {dinary-1.2.0 → dinary-1.2.2}/specs/ui/screens.md +0 -0
  74. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/README.md +0 -0
  75. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/__init__.py +0 -0
  76. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/exchange_rates.py +0 -0
  77. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/llm_storage.py +0 -0
  78. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/llmbroker.py +0 -0
  79. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/nbp.py +0 -0
  80. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/nbs.py +0 -0
  81. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/rate_helpers.py +0 -0
  82. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/serbian_receipt_parser.py +0 -0
  83. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/adapters/sheets_client.py +0 -0
  84. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/catalog.py +0 -0
  85. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog.py +0 -0
  86. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer.py +0 -0
  87. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_categories.py +0 -0
  88. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_errors.py +0 -0
  89. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_events.py +0 -0
  90. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/catalog_writer_groups.py +0 -0
  91. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/expense_corrections.py +0 -0
  92. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/expenses.py +0 -0
  93. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/income.py +0 -0
  94. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/llm.py +0 -0
  95. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/qr_parser.py +0 -0
  96. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/controllers/rules.py +0 -0
  97. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/currencies.py +0 -0
  98. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/expense_corrections.py +0 -0
  99. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/expenses.py +0 -0
  100. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/income.py +0 -0
  101. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/llm.py +0 -0
  102. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/qr.py +0 -0
  103. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/receipts.py +0 -0
  104. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/api/rules.py +0 -0
  105. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/classification/item_normalizer.py +0 -0
  106. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/classification/persist.py +0 -0
  107. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/classification/receipt_classifier.py +0 -0
  108. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/classification/store_resolver.py +0 -0
  109. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/classification/task.py +0 -0
  110. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/rate_prefetch/task.py +0 -0
  111. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/sheet_logging/income_sheet_logging.py +0 -0
  112. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/sheet_logging/logging_jobs.py +0 -0
  113. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/sheet_logging/sheet_logging.py +0 -0
  114. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/sheet_logging/sheets_write.py +0 -0
  115. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/background/sheet_logging/task.py +0 -0
  116. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/config.py +0 -0
  117. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/catalog.py +0 -0
  118. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/classification_rules.py +0 -0
  119. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/currencies.py +0 -0
  120. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/db_migrations.py +0 -0
  121. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/expenses.py +0 -0
  122. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/income.py +0 -0
  123. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0001_initial_schema.rollback.sql +0 -0
  124. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0001_initial_schema.sql +0 -0
  125. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0002_exchange_rates_source_target.rollback.sql +0 -0
  126. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0002_exchange_rates_source_target.sql +0 -0
  127. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0003_app_currencies.rollback.sql +0 -0
  128. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0003_app_currencies.sql +0 -0
  129. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0004_receipt_pipeline.rollback.sql +0 -0
  130. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0004_receipt_pipeline.sql +0 -0
  131. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0005_income_logging.rollback.sql +0 -0
  132. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/0005_income_logging.sql +0 -0
  133. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/migrations/README.md +0 -0
  134. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/receipts.py +0 -0
  135. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/__init__.py +0 -0
  136. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/get_category_by_name.sql +0 -0
  137. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/get_existing_expense.sql +0 -0
  138. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/get_month_expenses.sql +0 -0
  139. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/insert_expense.sql +0 -0
  140. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/insert_income.sql +0 -0
  141. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/list_categories.sql +0 -0
  142. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/list_incomes.sql +0 -0
  143. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/logging_projection.sql +0 -0
  144. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/resolve_mapping_for_year.sql +0 -0
  145. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql/seed_load_categories.sql +0 -0
  146. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/sql_loader.py +0 -0
  147. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/db/storage.py +0 -0
  148. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/main.py +0 -0
  149. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/sheets/sheet_mapping.py +0 -0
  150. {dinary-1.2.0 → dinary-1.2.2}/src/dinary/sheets/sheets.py +0 -0
  151. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backup_retention.py +0 -0
  152. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backup_snapshots.py +0 -0
  153. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backups_replica.py +0 -0
  154. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backups_restore.py +0 -0
  155. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backups_status.py +0 -0
  156. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/backups_yandex.py +0 -0
  157. {dinary-1.2.0 → dinary-1.2.2}/tasks/backups/restore_utils.py +0 -0
  158. {dinary-1.2.0 → dinary-1.2.2}/tasks/db.py +0 -0
  159. {dinary-1.2.0 → dinary-1.2.2}/tasks/deploy.py +0 -0
  160. {dinary-1.2.0 → dinary-1.2.2}/tasks/devtools/build_docs.py +0 -0
  161. {dinary-1.2.0 → dinary-1.2.2}/tasks/devtools/constants.py +0 -0
  162. {dinary-1.2.0 → dinary-1.2.2}/tasks/devtools/dev.py +0 -0
  163. {dinary-1.2.0 → dinary-1.2.2}/tasks/devtools/env.py +0 -0
  164. {dinary-1.2.0 → dinary-1.2.2}/tasks/healthcheck.py +0 -0
  165. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/README.md +0 -0
  166. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/expense_import.py +0 -0
  167. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/income_import.py +0 -0
  168. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/report_2d_3d.py +0 -0
  169. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/seed.py +0 -0
  170. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/seed_config.py +0 -0
  171. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/seed_derivation.py +0 -0
  172. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/verify_equivalence.py +0 -0
  173. {dinary-1.2.0 → dinary-1.2.2}/tasks/imports/verify_income.py +0 -0
  174. {dinary-1.2.0 → dinary-1.2.2}/tasks/receipt.py +0 -0
  175. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/expenses.py +0 -0
  176. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/income.py +0 -0
  177. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/report_helpers.py +0 -0
  178. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/report_tasks.py +0 -0
  179. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/verify_budget.py +0 -0
  180. {dinary-1.2.0 → dinary-1.2.2}/tasks/reports/verify_income.py +0 -0
  181. {dinary-1.2.0 → dinary-1.2.2}/tasks/server.py +0 -0
  182. {dinary-1.2.0 → dinary-1.2.2}/tasks/setup.py +0 -0
  183. {dinary-1.2.0 → dinary-1.2.2}/tasks/sql.py +0 -0
  184. {dinary-1.2.0 → dinary-1.2.2}/tasks/ssh_utils.py +0 -0
  185. {dinary-1.2.0 → dinary-1.2.2}/tests/api/_admin_catalog_helpers.py +0 -0
  186. {dinary-1.2.0 → dinary-1.2.2}/tests/api/_api_helpers.py +0 -0
  187. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_admin_catalog_add.py +0 -0
  188. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_admin_catalog_delete.py +0 -0
  189. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_admin_catalog_meta.py +0 -0
  190. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_admin_catalog_patch.py +0 -0
  191. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_admin_llm.py +0 -0
  192. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api.py +0 -0
  193. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_catalog.py +0 -0
  194. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_concurrency.py +0 -0
  195. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_conflict.py +0 -0
  196. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_currencies.py +0 -0
  197. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_delete_expense.py +0 -0
  198. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_delete_receipt.py +0 -0
  199. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_expenses_recent_patch.py +0 -0
  200. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_get_expenses.py +0 -0
  201. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_income.py +0 -0
  202. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_post_expense.py +0 -0
  203. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_receipts.py +0 -0
  204. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_rules_approve.py +0 -0
  205. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_api_validation.py +0 -0
  206. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_receipt_pipeline_e2e.py +0 -0
  207. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_receipt_review.py +0 -0
  208. {dinary-1.2.0 → dinary-1.2.2}/tests/api/test_review_page_ux.py +0 -0
  209. {dinary-1.2.0 → dinary-1.2.2}/tests/conftest.py +0 -0
  210. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/_currency_rates_helpers.py +0 -0
  211. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/test_currency_rates_misc.py +0 -0
  212. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/test_currency_rates_nbp.py +0 -0
  213. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/test_currency_rates_resolve.py +0 -0
  214. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/test_rate_helpers.py +0 -0
  215. {dinary-1.2.0 → dinary-1.2.2}/tests/currency/test_rate_prefetch_task.py +0 -0
  216. {dinary-1.2.0 → dinary-1.2.2}/tests/imports/test_expense_import.py +0 -0
  217. {dinary-1.2.0 → dinary-1.2.2}/tests/imports/test_seed_config.py +0 -0
  218. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/_catalog_writer_helpers.py +0 -0
  219. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/_ledger_repo_helpers.py +0 -0
  220. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_catalog_writer_invariants.py +0 -0
  221. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_catalog_writer_patch.py +0 -0
  222. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_income_db.py +0 -0
  223. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_catalog.py +0 -0
  224. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_insert.py +0 -0
  225. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_lookup.py +0 -0
  226. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_expenses_race.py +0 -0
  227. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_jobs.py +0 -0
  228. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_ledger_repo_logging_projection.py +0 -0
  229. {dinary-1.2.0 → dinary-1.2.2}/tests/ledger/test_migrations.py +0 -0
  230. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/_report_2d_3d_helpers.py +0 -0
  231. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_report_2d_3d.py +0 -0
  232. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_report_2d_3d_aggregate.py +0 -0
  233. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_report_2d_3d_render.py +0 -0
  234. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_report_2d_3d_resolve.py +0 -0
  235. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_reports_expenses.py +0 -0
  236. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_reports_income.py +0 -0
  237. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_reports_verify_budget.py +0 -0
  238. {dinary-1.2.0 → dinary-1.2.2}/tests/reports/test_reports_verify_income.py +0 -0
  239. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_classification_rules.py +0 -0
  240. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_item_normalizer.py +0 -0
  241. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_llm_storage.py +0 -0
  242. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_llmbroker.py +0 -0
  243. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_qr_parser.py +0 -0
  244. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_receipt_classification.py +0 -0
  245. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_receipt_classifier.py +0 -0
  246. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_receipt_parser.py +0 -0
  247. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_sql_loader.py +0 -0
  248. {dinary-1.2.0 → dinary-1.2.2}/tests/services/test_store_resolver.py +0 -0
  249. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/_sheet_logging_helpers.py +0 -0
  250. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/_sheet_mapping_helpers.py +0 -0
  251. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/_sheets_helpers.py +0 -0
  252. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_income_drain.py +0 -0
  253. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_logging.py +0 -0
  254. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_logging_derive.py +0 -0
  255. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_logging_drain.py +0 -0
  256. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_logging_drain_one.py +0 -0
  257. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_parse.py +0 -0
  258. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_reload.py +0 -0
  259. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheet_mapping_resolve.py +0 -0
  260. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheets_read.py +0 -0
  261. {dinary-1.2.0 → dinary-1.2.2}/tests/sheets/test_sheets_rows.py +0 -0
  262. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_receipt_drain.py +0 -0
  263. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_receipt_pipeline.py +0 -0
  264. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_reclassify_receipts.py +0 -0
  265. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_backups_restore.py +0 -0
  266. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_backups_retention.py +0 -0
  267. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_backups_status.py +0 -0
  268. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_backups_yandex_setup.py +0 -0
  269. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_db.py +0 -0
  270. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_deploy.py +0 -0
  271. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_dev.py +0 -0
  272. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_imports.py +0 -0
  273. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_reports.py +0 -0
  274. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_restore_utils.py +0 -0
  275. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_server.py +0 -0
  276. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_server_receipt.py +0 -0
  277. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_setup_replica.py +0 -0
  278. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_ssh_utils.py +0 -0
  279. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tasks_ssh_utils_scripts.py +0 -0
  280. {dinary-1.2.0 → dinary-1.2.2}/tests/tasks/test_tools_sql.py +0 -0
  281. {dinary-1.2.0 → dinary-1.2.2}/tests/test_config.py +0 -0
  282. {dinary-1.2.0 → dinary-1.2.2}/tests/test_main.py +0 -0
  283. {dinary-1.2.0 → dinary-1.2.2}/tests/test_webapp_api_contract.py +0 -0
  284. {dinary-1.2.0 → dinary-1.2.2}/uv.lock +0 -0
  285. {dinary-1.2.0 → dinary-1.2.2}/webapp/index.html +0 -0
  286. {dinary-1.2.0 → dinary-1.2.2}/webapp/package-lock.json +0 -0
  287. {dinary-1.2.0 → dinary-1.2.2}/webapp/package.json +0 -0
  288. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/apple-touch-icon-precomposed.png +0 -0
  289. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/apple-touch-icon.png +0 -0
  290. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/favicon.ico +0 -0
  291. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/icons/icon-180.png +0 -0
  292. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/icons/icon-192.png +0 -0
  293. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/icons/icon-512.png +0 -0
  294. {dinary-1.2.0 → dinary-1.2.2}/webapp/public/manifest.json +0 -0
  295. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/App.vue +0 -0
  296. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/_request.js +0 -0
  297. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/adminLlm.js +0 -0
  298. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/catalog.js +0 -0
  299. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/currencies.js +0 -0
  300. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/expenseCorrections.js +0 -0
  301. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/expenses.js +0 -0
  302. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/income.js +0 -0
  303. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/receipts.js +0 -0
  304. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/api/review.js +0 -0
  305. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/assets/base.css +0 -0
  306. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/BaseModal.vue +0 -0
  307. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/BaseSheet.vue +0 -0
  308. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CatalogSelectField.vue +0 -0
  309. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CategoryQuickPicks.vue +0 -0
  310. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CategorySheet.vue +0 -0
  311. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ConfirmDeleteSheet.vue +0 -0
  312. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CorrectionSheet.vue +0 -0
  313. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CurrencyAmountRow.vue +0 -0
  314. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/CurrencyPicker.vue +0 -0
  315. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ExpenseEditSheet.vue +0 -0
  316. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ExpenseForm.vue +0 -0
  317. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ExpenseRow.vue +0 -0
  318. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/HeaderSegmented.vue +0 -0
  319. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/HealthSummaryCard.vue +0 -0
  320. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/IconBtn.vue +0 -0
  321. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/IncomeEditSheet.vue +0 -0
  322. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/IncomeRow.vue +0 -0
  323. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/InlineCreateEvent.vue +0 -0
  324. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/InlineCreateRow.vue +0 -0
  325. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/KeyboardSaveBar.vue +0 -0
  326. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ManageList.vue +0 -0
  327. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ProviderCard.vue +0 -0
  328. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ProviderSheet.vue +0 -0
  329. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/QrScanner.vue +0 -0
  330. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/QueueModal.vue +0 -0
  331. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ReceiptCascadeCard.vue +0 -0
  332. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/RuleRow.vue +0 -0
  333. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/ScopeSelector.vue +0 -0
  334. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/StatusDot.vue +0 -0
  335. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/components/TagPicker.vue +0 -0
  336. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/addResult.js +0 -0
  337. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/catalogManage.js +0 -0
  338. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/flushQueue.js +0 -0
  339. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/flushReceiptQueue.js +0 -0
  340. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/receipt.js +0 -0
  341. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/useExpenseDeleteFlow.js +0 -0
  342. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/useKeyboardVisible.js +0 -0
  343. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/useOnline.js +0 -0
  344. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/useStaleCache.js +0 -0
  345. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/useSwipeRow.js +0 -0
  346. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/composables/zbar.js +0 -0
  347. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/legacy-pwa-cleanup.js +0 -0
  348. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/main.js +0 -0
  349. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/modals/EditModal.vue +0 -0
  350. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/catalog.js +0 -0
  351. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/currency.js +0 -0
  352. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/frequentCategories.js +0 -0
  353. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/income.js +0 -0
  354. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/llm.js +0 -0
  355. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/queue.js +0 -0
  356. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/receiptQueue.js +0 -0
  357. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/review.js +0 -0
  358. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/stores/toast.js +0 -0
  359. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/views/AddView.vue +0 -0
  360. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/views/IncomeView.vue +0 -0
  361. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/views/LLMView.vue +0 -0
  362. {dinary-1.2.0 → dinary-1.2.2}/webapp/src/views/ReviewView.vue +0 -0
  363. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/BaseSheet.test.js +0 -0
  364. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/CategoryQuickPicks.test.js +0 -0
  365. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/CategorySheet.test.js +0 -0
  366. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/CurrencyAmountRow.test.js +0 -0
  367. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/ExpenseEditSheet.test.js +0 -0
  368. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/ExpenseRow.test.js +0 -0
  369. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/ReceiptCascadeCard.test.js +0 -0
  370. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/ScopeSelector.test.js +0 -0
  371. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-adminLlm.test.js +0 -0
  372. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-catalog.test.js +0 -0
  373. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-currencies.test.js +0 -0
  374. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-expenses.test.js +0 -0
  375. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-income.test.js +0 -0
  376. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-receipts.test.js +0 -0
  377. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-request.test.js +0 -0
  378. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/api-review.test.js +0 -0
  379. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-app.test.js +0 -0
  380. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-catalog-select-field.test.js +0 -0
  381. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-correction-sheet.test.js +0 -0
  382. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-currency-picker.test.js +0 -0
  383. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-edit-modal.test.js +0 -0
  384. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-expense-edit-sheet.test.js +0 -0
  385. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-expense-form.test.js +0 -0
  386. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-header-segmented.test.js +0 -0
  387. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-health-summary-card.test.js +0 -0
  388. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-inline-create-event.test.js +0 -0
  389. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-inline-create-row.test.js +0 -0
  390. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-manage-list.test.js +0 -0
  391. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-provider-card.test.js +0 -0
  392. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-provider-sheet.test.js +0 -0
  393. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-queue-modal.test.js +0 -0
  394. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-review-view.test.js +0 -0
  395. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-rule-row.test.js +0 -0
  396. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-status-dot.test.js +0 -0
  397. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/component-tag-picker.test.js +0 -0
  398. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-add-result.test.js +0 -0
  399. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-catalog-manage.test.js +0 -0
  400. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-flush-queue.test.js +0 -0
  401. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-flush-receipt-queue.test.js +0 -0
  402. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-keyboard-visible.test.js +0 -0
  403. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-receipt.test.js +0 -0
  404. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-stale-cache.test.js +0 -0
  405. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/composable-use-online.test.js +0 -0
  406. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/frequentCategories.test.js +0 -0
  407. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/legacy-pwa-cleanup.test.js +0 -0
  408. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/setup.js +0 -0
  409. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-catalog.test.js +0 -0
  410. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-currency.test.js +0 -0
  411. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-income.test.js +0 -0
  412. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-llm.test.js +0 -0
  413. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-queue.test.js +0 -0
  414. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-receipt-queue-durability.test.js +0 -0
  415. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-receipt-queue.test.js +0 -0
  416. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-review.test.js +0 -0
  417. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/store-toast.test.js +0 -0
  418. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/useExpenseDeleteFlow.test.js +0 -0
  419. {dinary-1.2.0 → dinary-1.2.2}/webapp/tests/useSwipeRow.test.js +0 -0
  420. {dinary-1.2.0 → dinary-1.2.2}/webapp/vite.config.js +0 -0
  421. {dinary-1.2.0 → 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.0
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"
@@ -20,6 +20,7 @@ from .devtools.constants import ALLOWED_VERSION_TYPES
20
20
  from .devtools.dev import build_static, dev, pre, reqs, test, uv, ver_task_factory, version
21
21
  from .healthcheck import healthcheck
22
22
  from .imports.import_tasks import (
23
+ export_income_original,
23
24
  import_budget,
24
25
  import_budget_all,
25
26
  import_catalog,
@@ -47,6 +48,7 @@ __all__ = [
47
48
  "dev",
48
49
  "docs_task_factory",
49
50
  "healthcheck",
51
+ "export_income_original",
50
52
  "import_budget",
51
53
  "import_budget_all",
52
54
  "import_catalog",
@@ -2,10 +2,12 @@
2
2
 
3
3
  import json as _json
4
4
  import sys
5
+ from pathlib import Path
5
6
  from datetime import datetime as _dt
6
7
 
7
8
  from invoke import task
8
9
 
10
+ from tasks.imports import income_original_export
9
11
  from tasks.imports import report_2d_3d as _report_2d_3d_module
10
12
  from tasks.reports import verify_budget, verify_income
11
13
  from tasks.ssh_utils import remote_snapshot_cmd, ssh_capture_bytes, ssh_json, ssh_run
@@ -248,6 +250,19 @@ def verify_income_equivalence_all(c, json=False): # noqa: A002
248
250
  sys.exit(verify_income.exit_code_for_batch(results))
249
251
 
250
252
 
253
+ @task(name="export-income-original")
254
+ def export_income_original(c, output=""):
255
+ """Export income from all sheets in original currency (RUB/RSD) to a JSON file.
256
+
257
+ Reads every registered income source from .deploy/import_sources.json and
258
+ writes a flat list of {year, month, amount, currency} entries to JSON.
259
+ Default output path: data/income_original.json. Override with --output.
260
+ """
261
+ dest = Path(output) if output else income_original_export.DEFAULT_OUTPUT
262
+ count = income_original_export.export_to_file(dest)
263
+ print(f"Wrote {count} entries to {dest}")
264
+
265
+
251
266
  def _render_2d3d_locally(raw: bytes, *, as_csv: bool) -> None:
252
267
  """Render a JSON envelope produced by ``tasks.imports.report_2d_3d --json``.
253
268
 
@@ -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())
@@ -20,14 +20,17 @@ function today() {
20
20
  return new Date().toISOString().slice(0, 10);
21
21
  }
22
22
 
23
- function currentMonth() {
23
+ function defaultIncomeMonth() {
24
24
  const now = new Date();
25
- return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
25
+ // Salary for the previous month is paid on the 5th (or earlier if weekend),
26
+ // so on days 1–5 the income being entered is for the previous month.
27
+ const d = now.getDate() <= 5 ? new Date(now.getFullYear(), now.getMonth() - 1, 1) : now;
28
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
26
29
  }
27
30
 
28
31
  const amount = ref("");
29
32
  const selectedCurrency = ref("");
30
- const monthValue = ref(currentMonth());
33
+ const monthValue = ref(defaultIncomeMonth());
31
34
  const dateValue = ref(today());
32
35
  const comment = ref("");
33
36
  const currencyPickerOpen = ref(false);
@@ -66,7 +69,7 @@ async function save() {
66
69
  currency.setLastUsed(code);
67
70
  amount.value = "";
68
71
  comment.value = "";
69
- monthValue.value = currentMonth();
72
+ monthValue.value = defaultIncomeMonth();
70
73
  dateValue.value = today();
71
74
  emit("saved");
72
75
  } catch {
@@ -1 +0,0 @@
1
- __version__ = "1.2.0"
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