dividendcase 0.2.0__tar.gz → 0.3.0__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.
- {dividendcase-0.2.0 → dividendcase-0.3.0}/CHANGELOG.md +5 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/PKG-INFO +2 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/README.md +1 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/pyproject.toml +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/portfolio.py +43 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/router.py +2 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/user.py +8 -0
- dividendcase-0.3.0/src/dividendcase/api/v1/withholding.py +73 -0
- dividendcase-0.3.0/src/dividendcase/migrations/versions/0004_withholding_overrides.py +27 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/user_preferences.py +2 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/user_investment.py +11 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/user_preferences.py +23 -1
- dividendcase-0.3.0/src/dividendcase/services/withholding.py +147 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/404/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/404.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/__next.__PAGE__.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/__next._full.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/__next._tree.txt +2 -2
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/0awgjdi9fz2lt.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/0b2z-at3befil.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/0n6g6b54gzf5x.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/1bqpwkv1vyxpj.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/1hhlyzonxd9i-.js +1 -0
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/0c87cux8r1ss5.js → dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/1v39mvwqbje6r.js +1 -1
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/20i38r94ivt58.css +4 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/24b9a-jv8jte6.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/262l2h_0-9l6p.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/2a7ptk2e1-m_w.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/2jv28ru5lkbdo.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/3_zz4bh7-d5v3.js +1 -0
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/3ffizu307ndoz.js +1 -0
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2hixbsi924bp7.js → dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/3gycc_xsviaej.js +2 -2
- dividendcase-0.3.0/src/dividendcase/web/_next/static/chunks/3wvr-sx1cadm4.js +1 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_not-found/__next._full.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_not-found/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_not-found/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_not-found/index.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/__next.dashboard.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/about/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/about/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/about/__next.dashboard.about.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/about/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/about/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/calendar/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/calendar/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/calendar/__next.dashboard.calendar.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/calendar/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/calendar/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/compare/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/compare/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/compare/__next.dashboard.compare.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/compare/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/compare/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/data/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/data/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/data/__next.dashboard.data.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/data/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/data/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/drip/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/drip/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/drip/__next.dashboard.drip.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/drip/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/drip/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/__next.dashboard.investments.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/portfolio/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/portfolio/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/portfolio/__next.dashboard.investments.portfolio.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/portfolio/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/investments/portfolio/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/screener/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/screener/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/screener/__next.dashboard.screener.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/screener/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/screener/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/settings/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/settings/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/settings/__next.dashboard.settings.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/settings/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/settings/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/stock/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/stock/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/stock/__next.dashboard.stock.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/stock/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/stock/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/watchlist/__next._full.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/watchlist/__next._tree.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/watchlist/__next.dashboard.watchlist.__PAGE__.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/watchlist/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/dashboard/watchlist/index.txt +4 -4
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/index.html +1 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/index.txt +2 -2
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/conftest.py +7 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_api.py +1 -1
- dividendcase-0.3.0/tests/test_withholding.py +113 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/uv.lock +1 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/0lfebwmsk92ym.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/0ozczpntpufql.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/1-ift1mtsuqvh.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/1g926afh71co9.css +0 -4
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/1xrz4-jij46i_.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2_gbzm-n4fs0v.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2aqpz6z087bo4.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2ft48jvqut7tm.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2hoqsdgpu5ma3.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/2o72ftwupypgx.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/3cju952ndgmmn.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/3il2yixgws-43.js +0 -1
- dividendcase-0.2.0/src/dividendcase/web/_next/static/chunks/431n5e16l34_0.js +0 -1
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.dockerignore +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/ISSUE_TEMPLATE/idea.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/workflows/build.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/workflows/release.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.github/workflows/tests.yml +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.gitignore +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/.python-version +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/CONTRIBUTING.md +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/Dockerfile +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/LICENSE +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/SECURITY.md +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/scripts/build_web.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/scripts/check_dist.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/scripts/smoke_test.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/__main__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/data.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/deps.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/dividends.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/excel_io.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/fx.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/health.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/investment.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/portfolios.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/search.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/stocks.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/api/v1/watchlist.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/cli.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/config.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/crud/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/crud/dividend.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/crud/stock.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/database.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/main.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrate.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrations/env.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrations/script.py.mako +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrations/versions/0001_baseline.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrations/versions/0002_check_for_updates.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/migrations/versions/0003_fx_rates_and_setup.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/dividend.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/fx_rate.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/scheduler_run.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/stock.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/user_investment.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/user_portfolio.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/user_watchlist.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/models/user_watchlist_group.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/scheduler/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/scheduler/tasks.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/dividend.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/investment.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/stock.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/schemas/watchlist.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/__init__.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/benchmark.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/derived.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/fx.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/refresh.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/universe.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/updates.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/services/yahoo_fetcher.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/.gitkeep +0 -0
- {dividendcase-0.2.0/src/dividendcase/web/_next/static/jykyXrPtkR3XGdQ9oSYUT → dividendcase-0.3.0/src/dividendcase/web/_next/static/ZWBqBpak7xVqht9vRBBgL}/_buildManifest.js +0 -0
- {dividendcase-0.2.0/src/dividendcase/web/_next/static/jykyXrPtkR3XGdQ9oSYUT → dividendcase-0.3.0/src/dividendcase/web/_next/static/ZWBqBpak7xVqht9vRBBgL}/_clientMiddlewareManifest.js +0 -0
- {dividendcase-0.2.0/src/dividendcase/web/_next/static/jykyXrPtkR3XGdQ9oSYUT → dividendcase-0.3.0/src/dividendcase/web/_next/static/ZWBqBpak7xVqht9vRBBgL}/_ssgManifest.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/0cz1d0mv5g_q7.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/0g-bslilw6tiu.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/0nmuijbkm3ipd.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/0vm7tw6itjxih.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/10oy-ed_zrrd9.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/13-u82nfffhrp.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/138wmfea-yk08.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/14jt62gawmtd9.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/1l-w55cdq629z.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/1q2bxv0xj7vse.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/1s5tue3q2i-8b.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/24f36pdyspt8c.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/2i51e627rllld.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/33-cg7-y-5zm5.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/3mc6dra1m0098.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/453cd1tlxz5wi.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/chunks/turbopack-1che7mqzsn0nh.js +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/7ebf22b5a21034f8-s.p.3j3877k49yy0l.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/fc0a7394b875fa43-s.2ytmazkdrg7t9.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/_next/static/media/icon.0kxjuvpgwz_c6.svg +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/icon.svg +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/logo-full.svg +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/logo-icon.svg +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/src/dividendcase/web/tickers.json +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_benchmark.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_cli.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_derived_and_updates.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_fx_and_setup.py +0 -0
- {dividendcase-0.2.0 → dividendcase-0.3.0}/tests/test_migrations.py +0 -0
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
All notable changes to DividendCase. Versions follow [semantic versioning](https://semver.org/);
|
|
4
4
|
while the version starts with 0, a minor release (0.2, 0.3…) may change things.
|
|
5
5
|
|
|
6
|
+
## 0.3.0
|
|
7
|
+
|
|
8
|
+
- **Income after tax:** the Income and Calendar pages show dividends after the tax the paying country withholds (switch to "Before tax" any time). Rates for residents of Ireland, the UK, India, Canada, Australia and the US, by treaty where one applies, with the paperwork each assumes (a W-8BEN for US dividends, for example). Countries it doesn't cover yet are shown before tax and named. Settings lists every rate and lets you set the one your broker actually uses; stock pages show what's withheld for you.
|
|
9
|
+
- Stock suggestions (Compare, Watchlists, DRIP, Add holding) no longer get cut off by the card or dialog they're in.
|
|
10
|
+
|
|
6
11
|
## 0.2.0
|
|
7
12
|
|
|
8
13
|
- **Screener:** a "Beat their index" filter and a "Vs index" column: price change plus dividends over the last ten years (or since the first stored payment, if at least three years ago) against the stock's local index over the same period, recomputed weekly and after each screener refresh.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dividendcase
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A free, open-source dividend tracker that runs on your own computer. Your data stays with you.
|
|
5
5
|
Project-URL: Homepage, https://dividendcase.com
|
|
6
6
|
Project-URL: Source, https://github.com/dividendcase/dividendcase
|
|
@@ -99,6 +99,7 @@ Then open <http://127.0.0.1:8765/dashboard/>. Keep `127.0.0.1:` in the port mapp
|
|
|
99
99
|
## What it does
|
|
100
100
|
|
|
101
101
|
- **Income home:** what your holdings should pay over the next 12 months, month by month, all in your home currency or one currency at a time, with upcoming payments and where the income comes from.
|
|
102
|
+
- **After tax:** what's left once the paying country withholds its tax, for residents of Ireland, the UK, India, Canada, Australia and the US (treaty rates, with the paperwork each assumes); set your broker's actual rate for any country in Settings.
|
|
102
103
|
- **Stock pages:** 10 years of dividend history, TTM yield, a safety score, dividend growth (CAGR) and growth streaks.
|
|
103
104
|
- **Screener:** index members from six markets, filtered by market, yield, how often they pay and whether they beat their local index over ten years.
|
|
104
105
|
- **Portfolios:** individual purchase lots in any currency, a 12-month income calendar, diversification charts, a benchmark comparison and a DRIP calculator.
|
|
@@ -51,6 +51,7 @@ Then open <http://127.0.0.1:8765/dashboard/>. Keep `127.0.0.1:` in the port mapp
|
|
|
51
51
|
## What it does
|
|
52
52
|
|
|
53
53
|
- **Income home:** what your holdings should pay over the next 12 months, month by month, all in your home currency or one currency at a time, with upcoming payments and where the income comes from.
|
|
54
|
+
- **After tax:** what's left once the paying country withholds its tax, for residents of Ireland, the UK, India, Canada, Australia and the US (treaty rates, with the paperwork each assumes); set your broker's actual rate for any country in Settings.
|
|
54
55
|
- **Stock pages:** 10 years of dividend history, TTM yield, a safety score, dividend growth (CAGR) and growth streaks.
|
|
55
56
|
- **Screener:** index members from six markets, filtered by market, yield, how often they pay and whether they beat their local index over ten years.
|
|
56
57
|
- **Portfolios:** individual purchase lots in any currency, a 12-month income calendar, diversification charts, a benchmark comparison and a DRIP calculator.
|
|
@@ -30,7 +30,8 @@ from dividendcase.schemas.user_investment import (
|
|
|
30
30
|
)
|
|
31
31
|
from dividendcase.api.v1.deps import _get_user_id
|
|
32
32
|
from dividendcase.api.v1.investment import EXCHANGE_BENCHMARKS, DEFAULT_BENCHMARK, _get_benchmark_data
|
|
33
|
-
from dividendcase.services import fx
|
|
33
|
+
from dividendcase.services import fx, withholding
|
|
34
|
+
from dividendcase.models.user_preferences import UserPreferences
|
|
34
35
|
from dividendcase.models.stock import Stock
|
|
35
36
|
|
|
36
37
|
router = APIRouter()
|
|
@@ -622,6 +623,14 @@ async def income_calendar(
|
|
|
622
623
|
today = date.today()
|
|
623
624
|
entries: list[CalendarEntry] = []
|
|
624
625
|
|
|
626
|
+
# Withholding at source depends on where the user is resident for tax
|
|
627
|
+
prefs = (await db.execute(
|
|
628
|
+
select(UserPreferences).where(UserPreferences.user_id == user_id)
|
|
629
|
+
)).scalar_one_or_none()
|
|
630
|
+
residence = prefs.tax_residence if prefs else None
|
|
631
|
+
overrides = (prefs.withholding_overrides or {}) if prefs else {}
|
|
632
|
+
rules: dict[str, tuple] = {} # ticker → (source, rule)
|
|
633
|
+
|
|
625
634
|
for ticker, total_shares in shares_per_ticker.items():
|
|
626
635
|
stock = await get_stock(db, ticker)
|
|
627
636
|
if not stock:
|
|
@@ -658,6 +667,8 @@ async def income_calendar(
|
|
|
658
667
|
|
|
659
668
|
month_step = FREQ_MONTHS.get(freq, 3)
|
|
660
669
|
currency = stock.currency or "USD"
|
|
670
|
+
source = withholding.source_country(stock.country, stock.exchange)
|
|
671
|
+
rules[ticker] = (source, withholding.rule_for(source, residence, overrides))
|
|
661
672
|
|
|
662
673
|
# Project next 12 months of payments
|
|
663
674
|
next_date = last_div_date + relativedelta(months=month_step)
|
|
@@ -682,15 +693,39 @@ async def income_calendar(
|
|
|
682
693
|
# Sort by date
|
|
683
694
|
entries.sort(key=lambda e: e.expected_date)
|
|
684
695
|
|
|
696
|
+
# India's TDS for residents applies only above ₹10,000 a year from one company
|
|
697
|
+
yearly_inr: dict[str, float] = defaultdict(float)
|
|
698
|
+
for e in entries:
|
|
699
|
+
if e.currency == "INR":
|
|
700
|
+
yearly_inr[e.ticker_symbol] += e.estimated_amount
|
|
701
|
+
unestimated: set[str] = set()
|
|
702
|
+
for e in entries:
|
|
703
|
+
source, rule = rules.get(e.ticker_symbol, (None, None))
|
|
704
|
+
e.source_country = source
|
|
705
|
+
if rule is None:
|
|
706
|
+
e.net_amount = e.estimated_amount
|
|
707
|
+
if source:
|
|
708
|
+
unestimated.add(source)
|
|
709
|
+
continue
|
|
710
|
+
rate = withholding.withheld_rate(rule, source, residence, yearly_inr.get(e.ticker_symbol))
|
|
711
|
+
e.withholding_rate = rate
|
|
712
|
+
e.withholding_basis = rule.basis
|
|
713
|
+
e.withholding_note = rule.note
|
|
714
|
+
e.net_amount = round(e.estimated_amount * (1 - rate / 100), 2)
|
|
715
|
+
|
|
685
716
|
# Compute monthly, currency, and monthly-by-currency totals
|
|
686
717
|
monthly_totals: dict[str, float] = defaultdict(float)
|
|
687
718
|
currency_totals: dict[str, float] = defaultdict(float)
|
|
688
719
|
monthly_by_currency: dict[str, dict[str, float]] = defaultdict(lambda: defaultdict(float))
|
|
720
|
+
net_currency_totals: dict[str, float] = defaultdict(float)
|
|
721
|
+
net_monthly_by_currency: dict[str, dict[str, float]] = defaultdict(lambda: defaultdict(float))
|
|
689
722
|
for e in entries:
|
|
690
723
|
key = e.expected_date.strftime("%Y-%m")
|
|
691
724
|
monthly_totals[key] += e.estimated_amount
|
|
692
725
|
currency_totals[e.currency] += e.estimated_amount
|
|
693
726
|
monthly_by_currency[e.currency][key] += e.estimated_amount
|
|
727
|
+
net_currency_totals[e.currency] += e.net_amount
|
|
728
|
+
net_monthly_by_currency[e.currency][key] += e.net_amount
|
|
694
729
|
|
|
695
730
|
annual_total = sum(e.estimated_amount for e in entries)
|
|
696
731
|
|
|
@@ -703,4 +738,11 @@ async def income_calendar(
|
|
|
703
738
|
curr: {m: round(v, 2) for m, v in months.items()}
|
|
704
739
|
for curr, months in monthly_by_currency.items()
|
|
705
740
|
},
|
|
741
|
+
residence=residence,
|
|
742
|
+
net_currency_totals={k: round(v, 2) for k, v in net_currency_totals.items()},
|
|
743
|
+
net_monthly_totals_by_currency={
|
|
744
|
+
curr: {m: round(v, 2) for m, v in months.items()}
|
|
745
|
+
for curr, months in net_monthly_by_currency.items()
|
|
746
|
+
},
|
|
747
|
+
unestimated_sources=sorted(unestimated),
|
|
706
748
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from fastapi import APIRouter
|
|
2
|
-
from dividendcase.api.v1 import stocks, dividends, search, watchlist, health, investment, portfolio, portfolios, excel_io, user, data, fx
|
|
2
|
+
from dividendcase.api.v1 import stocks, dividends, search, watchlist, health, investment, portfolio, portfolios, excel_io, user, data, fx, withholding
|
|
3
3
|
|
|
4
4
|
api_router = APIRouter()
|
|
5
5
|
|
|
@@ -15,3 +15,4 @@ api_router.include_router(excel_io.router, prefix="/excel", tags=["excel"])
|
|
|
15
15
|
api_router.include_router(user.router, prefix="/user", tags=["user"])
|
|
16
16
|
api_router.include_router(data.router, prefix="/data", tags=["data"])
|
|
17
17
|
api_router.include_router(fx.router, prefix="/fx", tags=["exchange rates"])
|
|
18
|
+
api_router.include_router(withholding.router, prefix="/withholding", tags=["withholding tax"])
|
|
@@ -70,6 +70,14 @@ async def update_preferences(
|
|
|
70
70
|
prefs.screener_markets = updates.screener_markets
|
|
71
71
|
if updates.complete_setup:
|
|
72
72
|
prefs.setup_completed_at = datetime.now(timezone.utc)
|
|
73
|
+
if updates.withholding_overrides is not None:
|
|
74
|
+
merged = dict(prefs.withholding_overrides or {})
|
|
75
|
+
for country, rate in updates.withholding_overrides.items():
|
|
76
|
+
if rate is None:
|
|
77
|
+
merged.pop(country, None)
|
|
78
|
+
else:
|
|
79
|
+
merged[country] = rate
|
|
80
|
+
prefs.withholding_overrides = merged
|
|
73
81
|
|
|
74
82
|
await db.commit()
|
|
75
83
|
await db.refresh(prefs)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Withholding tax estimates for the user's tax residence (Settings, stock pages)."""
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, Query
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
8
|
+
|
|
9
|
+
from dividendcase.api.v1.deps import _get_user_id
|
|
10
|
+
from dividendcase.database import get_db
|
|
11
|
+
from dividendcase.models import Stock, UserInvestment, UserPreferences
|
|
12
|
+
from dividendcase.services import withholding
|
|
13
|
+
|
|
14
|
+
router = APIRouter()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("")
|
|
18
|
+
async def withholding_table(
|
|
19
|
+
ticker: Optional[str] = Query(None, description="Also say what applies to this stock"),
|
|
20
|
+
db: AsyncSession = Depends(get_db),
|
|
21
|
+
user_id: UUID = Depends(_get_user_id),
|
|
22
|
+
):
|
|
23
|
+
"""The rate for each supported source country, and where the user's holdings pay from."""
|
|
24
|
+
prefs = (await db.execute(
|
|
25
|
+
select(UserPreferences).where(UserPreferences.user_id == user_id)
|
|
26
|
+
)).scalar_one_or_none()
|
|
27
|
+
residence = prefs.tax_residence if prefs else None
|
|
28
|
+
overrides = (prefs.withholding_overrides or {}) if prefs else {}
|
|
29
|
+
|
|
30
|
+
held = (await db.execute(
|
|
31
|
+
select(Stock.ticker_symbol, Stock.country, Stock.exchange)
|
|
32
|
+
.join(UserInvestment, UserInvestment.ticker_symbol == Stock.ticker_symbol)
|
|
33
|
+
.where(UserInvestment.user_id == user_id)
|
|
34
|
+
.distinct()
|
|
35
|
+
)).all()
|
|
36
|
+
sources: dict[str, list[str]] = {}
|
|
37
|
+
for held_ticker, country, exchange in held:
|
|
38
|
+
source = withholding.source_country(country, exchange)
|
|
39
|
+
if source:
|
|
40
|
+
sources.setdefault(source, []).append(held_ticker)
|
|
41
|
+
|
|
42
|
+
rows = withholding.table_for(residence, overrides)
|
|
43
|
+
known = {row["source"] for row in rows}
|
|
44
|
+
for source in sorted(set(sources) - known):
|
|
45
|
+
rule = withholding.rule_for(source, residence, overrides)
|
|
46
|
+
rows.append({
|
|
47
|
+
"source": source, "name": source,
|
|
48
|
+
"rate": rule.rate if rule else None, "basis": rule.basis if rule else None,
|
|
49
|
+
"note": rule.note if rule else "Not estimated yet: dividends from here are shown before tax. Set the rate your broker withholds to include it.",
|
|
50
|
+
"default_rate": None, "default_note": None,
|
|
51
|
+
})
|
|
52
|
+
for row in rows:
|
|
53
|
+
row["your_stocks"] = sorted(sources.get(row["source"], []))
|
|
54
|
+
for_stock = None
|
|
55
|
+
if ticker:
|
|
56
|
+
stock = (await db.execute(select(Stock).where(Stock.ticker_symbol == ticker.upper()))).scalar_one_or_none()
|
|
57
|
+
if stock is not None:
|
|
58
|
+
source = withholding.source_country(stock.country, stock.exchange)
|
|
59
|
+
rule = withholding.rule_for(source, residence, overrides)
|
|
60
|
+
for_stock = {
|
|
61
|
+
"ticker": stock.ticker_symbol,
|
|
62
|
+
"source": source,
|
|
63
|
+
"source_name": withholding.NAMES.get(source or "", source),
|
|
64
|
+
"rate": rule.rate if rule else None,
|
|
65
|
+
"basis": rule.basis if rule else None,
|
|
66
|
+
"note": rule.note if rule else None,
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
"residence": residence,
|
|
70
|
+
"residence_name": withholding.NAMES.get(residence or "", residence),
|
|
71
|
+
"rows": rows,
|
|
72
|
+
"for_stock": for_stock,
|
|
73
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""withholding overrides
|
|
2
|
+
|
|
3
|
+
Revision ID: 0004
|
|
4
|
+
Revises: 0003
|
|
5
|
+
Created: 2026-09-29 16:20:58.714377
|
|
6
|
+
"""
|
|
7
|
+
from typing import Sequence, Union
|
|
8
|
+
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
from alembic import op
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
revision: str = '0004'
|
|
14
|
+
down_revision: Union[str, None] = '0003'
|
|
15
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
16
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upgrade() -> None:
|
|
20
|
+
with op.batch_alter_table('user_preferences', schema=None) as batch_op:
|
|
21
|
+
batch_op.add_column(sa.Column('withholding_overrides', sa.JSON(), nullable=True))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def downgrade() -> None:
|
|
25
|
+
with op.batch_alter_table('user_preferences', schema=None) as batch_op:
|
|
26
|
+
batch_op.drop_column('withholding_overrides')
|
|
27
|
+
|
|
@@ -19,4 +19,6 @@ class UserPreferences(Base):
|
|
|
19
19
|
tax_residence = Column(String(2))
|
|
20
20
|
screener_markets = Column(JSON)
|
|
21
21
|
setup_completed_at = Column(DateTime(timezone=True))
|
|
22
|
+
# Withholding rates the user set by hand, percent by source country: {"US": 30}
|
|
23
|
+
withholding_overrides = Column(JSON)
|
|
22
24
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
@@ -95,6 +95,12 @@ class CalendarEntry(BaseModel):
|
|
|
95
95
|
total_shares: float
|
|
96
96
|
currency: str
|
|
97
97
|
payment_frequency: str
|
|
98
|
+
# Withholding at source for the user's tax residence (services/withholding.py)
|
|
99
|
+
source_country: Optional[str] = None # "US", or a country name v1 doesn't estimate
|
|
100
|
+
withholding_rate: Optional[float] = None # percent; None = not estimated (shown gross)
|
|
101
|
+
withholding_basis: Optional[str] = None # treaty, statutory, domestic, none, override
|
|
102
|
+
withholding_note: Optional[str] = None
|
|
103
|
+
net_amount: float = 0.0 # estimated_amount after withholding
|
|
98
104
|
|
|
99
105
|
|
|
100
106
|
class IncomeCalendarResponse(BaseModel):
|
|
@@ -103,6 +109,11 @@ class IncomeCalendarResponse(BaseModel):
|
|
|
103
109
|
annual_total: float
|
|
104
110
|
currency_totals: dict[str, float] # {"USD": 500.0, "INR": 2000.0}
|
|
105
111
|
monthly_totals_by_currency: dict[str, dict[str, float]] = {} # {"USD": {"2026-01": 100.0, ...}}
|
|
112
|
+
# After withholding, and what wasn't estimated
|
|
113
|
+
residence: Optional[str] = None
|
|
114
|
+
net_currency_totals: dict[str, float] = {}
|
|
115
|
+
net_monthly_totals_by_currency: dict[str, dict[str, float]] = {}
|
|
116
|
+
unestimated_sources: list[str] = []
|
|
106
117
|
|
|
107
118
|
|
|
108
119
|
# ── Excel Import/Export schemas ─────────────────────────────────────────
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Literal, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic import BaseModel, Field, field_validator
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
VALID_BENCHMARKS = Literal["SP500", "FTSE100", "NIFTY50", "ASX200", "TSX60"]
|
|
@@ -10,6 +10,11 @@ VALID_MARKETS = Literal["SP500", "NIFTY50", "TSX60", "ASX200", "FTSE100", "ISEQ2
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class UserPreferences(BaseModel):
|
|
13
|
+
@field_validator("withholding_overrides", mode="before")
|
|
14
|
+
@classmethod
|
|
15
|
+
def _none_is_empty(cls, v):
|
|
16
|
+
return v or {}
|
|
17
|
+
|
|
13
18
|
default_benchmark: str = "SP500"
|
|
14
19
|
date_format: str = "DD/MM/YYYY"
|
|
15
20
|
watchlist_collapsed: bool = False
|
|
@@ -18,6 +23,7 @@ class UserPreferences(BaseModel):
|
|
|
18
23
|
tax_residence: Optional[str] = None
|
|
19
24
|
screener_markets: Optional[list[str]] = None # None = every market
|
|
20
25
|
setup_completed_at: Optional[datetime] = None
|
|
26
|
+
withholding_overrides: dict[str, float] = {}
|
|
21
27
|
|
|
22
28
|
model_config = {"from_attributes": True}
|
|
23
29
|
|
|
@@ -32,3 +38,19 @@ class UserPreferencesUpdate(BaseModel):
|
|
|
32
38
|
screener_markets: Optional[list[VALID_MARKETS]] = None
|
|
33
39
|
# True when the first-run setup screen is finished
|
|
34
40
|
complete_setup: Optional[bool] = None
|
|
41
|
+
# Rates to use instead of the estimates, percent by source country; null removes one
|
|
42
|
+
withholding_overrides: Optional[dict[str, Optional[float]]] = None
|
|
43
|
+
|
|
44
|
+
@field_validator("withholding_overrides")
|
|
45
|
+
@classmethod
|
|
46
|
+
def _check_overrides(cls, v):
|
|
47
|
+
return None if v is None else valid_overrides(v)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def valid_overrides(value: dict[str, Optional[float]]) -> dict[str, Optional[float]]:
|
|
51
|
+
for country, rate in value.items():
|
|
52
|
+
if not (1 <= len(country) <= 40):
|
|
53
|
+
raise ValueError(f"Not a country: {country!r}")
|
|
54
|
+
if rate is not None and not (0 <= rate <= 100):
|
|
55
|
+
raise ValueError(f"A withholding rate is a percentage from 0 to 100, not {rate}")
|
|
56
|
+
return value
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Dividend withholding tax: how much the paying country keeps before a dividend reaches you.
|
|
2
|
+
|
|
3
|
+
v1 covers six countries, as sources and as tax residences: Ireland, the UK, India, Canada,
|
|
4
|
+
Australia and the US. Rates are for an individual holding shares directly (portfolio
|
|
5
|
+
dividends, well under 10% of the company). These are estimates of tax taken **at source**,
|
|
6
|
+
not the final tax you owe where you live, and not tax advice. People can set their own
|
|
7
|
+
rate for any country in Settings, which always wins.
|
|
8
|
+
|
|
9
|
+
Where a treaty rate depends on paperwork (a W-8BEN for US dividends, a tax residency
|
|
10
|
+
certificate for India) the table assumes it's done and says so in the note; without it the
|
|
11
|
+
statutory rate applies.
|
|
12
|
+
|
|
13
|
+
Sources (checked September 2026, to be reviewed by a tax professional before 1.0):
|
|
14
|
+
US IRS Publication 515 and treaty tables; Canada Revenue Agency Part XIII rates; HMRC
|
|
15
|
+
(no UK withholding on ordinary dividends); Irish Revenue DWT (25% from 2020); India Income
|
|
16
|
+
Tax Act s.195 / s.194 and the DTAAs with each country; Australian Taxation Office
|
|
17
|
+
(franked dividends aren't withheld).
|
|
18
|
+
"""
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
SUPPORTED = ("IE", "GB", "IN", "CA", "AU", "US")
|
|
23
|
+
|
|
24
|
+
# Rate for residents of countries without a treaty rule below (percent)
|
|
25
|
+
STATUTORY = {"US": 30.0, "CA": 25.0, "GB": 0.0, "IE": 25.0, "IN": 20.8, "AU": 0.0}
|
|
26
|
+
|
|
27
|
+
NAMES = {
|
|
28
|
+
"IE": "Ireland", "GB": "United Kingdom", "IN": "India", "CA": "Canada", "AU": "Australia",
|
|
29
|
+
"US": "United States", "ZZ": "somewhere else",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Yahoo's company country (Stock.country) → ISO code
|
|
33
|
+
COUNTRY_CODES = {
|
|
34
|
+
"united states": "US", "usa": "US", "united kingdom": "GB", "uk": "GB", "ireland": "IE",
|
|
35
|
+
"india": "IN", "canada": "CA", "australia": "AU",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Where a listing is when the company's country isn't stored (screener stocks)
|
|
39
|
+
EXCHANGE_COUNTRIES = {
|
|
40
|
+
"NYSE": "US", "NASDAQ": "US", "NMS": "US", "NYQ": "US", "NGM": "US", "NCM": "US", "PCX": "US", "BTS": "US",
|
|
41
|
+
"TSX": "CA", "TOR": "CA", "LSE": "GB", "ISE": "IE", "NSE": "IN", "BSE": "IN", "BOM": "IN", "ASX": "AU",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
W8BEN = "with a W-8BEN on file with your broker; 30% without it"
|
|
45
|
+
TRC = "with a tax residency certificate and Form 10F given to the company or broker; 20.8% without them"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class Rule:
|
|
50
|
+
rate: float # percent withheld
|
|
51
|
+
basis: str # "treaty", "statutory", "domestic", "none" or "override"
|
|
52
|
+
note: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _source_rules(source: str, residence: str) -> Rule:
|
|
56
|
+
if source == "GB":
|
|
57
|
+
return Rule(0.0, "none", "The UK doesn't withhold tax on ordinary dividends (REIT property income is taxed at 20%).")
|
|
58
|
+
if source == "AU":
|
|
59
|
+
if residence == "AU":
|
|
60
|
+
return Rule(0.0, "domestic", "No withholding for Australian residents; franking credits are claimed in your return.")
|
|
61
|
+
return Rule(0.0, "none", "Franked dividends aren't withheld. Any unfranked part is withheld at 15% under a treaty (30% otherwise).")
|
|
62
|
+
|
|
63
|
+
if source == residence:
|
|
64
|
+
return {
|
|
65
|
+
"US": Rule(0.0, "domestic", "No withholding for US residents; dividends are taxed in your return."),
|
|
66
|
+
"CA": Rule(0.0, "domestic", "No withholding for Canadian residents; dividends are taxed in your return."),
|
|
67
|
+
"IE": Rule(25.0, "domestic", "Irish dividend withholding tax, credited against your income tax."),
|
|
68
|
+
"IN": Rule(10.0, "domestic", "10% TDS when a company pays you more than ₹10,000 in a financial year; credited against your income tax."),
|
|
69
|
+
}[source]
|
|
70
|
+
|
|
71
|
+
if source == "US":
|
|
72
|
+
if residence == "IN":
|
|
73
|
+
return Rule(25.0, "treaty", f"India–US treaty rate {W8BEN}.")
|
|
74
|
+
if residence in ("IE", "GB", "CA", "AU"):
|
|
75
|
+
return Rule(15.0, "treaty", f"Treaty rate {W8BEN}.")
|
|
76
|
+
if source == "CA":
|
|
77
|
+
if residence == "IN":
|
|
78
|
+
return Rule(25.0, "treaty", "Canada–India treaty rate for portfolio holdings (the same as Canada's statutory rate).")
|
|
79
|
+
if residence in ("US", "GB", "IE", "AU"):
|
|
80
|
+
return Rule(15.0, "treaty", "Treaty rate, applied by your broker once it knows your residence (25% otherwise).")
|
|
81
|
+
if source == "IE":
|
|
82
|
+
return Rule(25.0, "statutory", "Irish dividend withholding tax. Residents of treaty countries can be exempt with a non-resident declaration, or reclaim the excess.")
|
|
83
|
+
if source == "IN":
|
|
84
|
+
treaty = {"IE": 10.0, "GB": 15.0, "AU": 15.0}
|
|
85
|
+
if residence in treaty:
|
|
86
|
+
return Rule(treaty[residence], "treaty", f"Treaty rate {TRC}.")
|
|
87
|
+
if residence in ("US", "CA"):
|
|
88
|
+
return Rule(20.8, "statutory", "20% plus 4% cess for non-residents; the treaty rate (25%) is higher, so it doesn't help.")
|
|
89
|
+
|
|
90
|
+
return Rule(STATUTORY[source], "statutory", "The country's standard rate for non-residents; a treaty may lower it.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
INDIA_TDS_THRESHOLD_INR = 10_000
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def rule_for(source: Optional[str], residence: Optional[str], overrides: Optional[dict] = None) -> Optional[Rule]:
|
|
97
|
+
"""The withholding that applies to dividends from `source` for a resident of `residence`.
|
|
98
|
+
|
|
99
|
+
None when the source country isn't one v1 estimates (the dividend is shown gross).
|
|
100
|
+
"""
|
|
101
|
+
if not source:
|
|
102
|
+
return None
|
|
103
|
+
overrides = overrides or {}
|
|
104
|
+
if source in overrides and overrides[source] is not None:
|
|
105
|
+
return Rule(float(overrides[source]), "override", "The rate you set in Settings.")
|
|
106
|
+
if source not in SUPPORTED:
|
|
107
|
+
return None
|
|
108
|
+
return _source_rules(source, residence if residence in SUPPORTED else "ZZ")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def source_country(country: Optional[str], exchange: Optional[str]) -> Optional[str]:
|
|
112
|
+
"""ISO code of the country a dividend comes from: the company's, else its exchange's."""
|
|
113
|
+
name = (country or "").strip()
|
|
114
|
+
if name and name.lower() != "unknown":
|
|
115
|
+
# A country v1 doesn't estimate (an ADR of a Spanish company, say) stays by name,
|
|
116
|
+
# so it's shown gross and labelled rather than mistaken for its exchange's country
|
|
117
|
+
return COUNTRY_CODES.get(name.lower(), name)
|
|
118
|
+
return EXCHANGE_COUNTRIES.get((exchange or "").upper())
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def withheld_rate(rule: Optional[Rule], source: Optional[str], residence: Optional[str],
|
|
122
|
+
yearly_amount_in_inr: Optional[float] = None) -> float:
|
|
123
|
+
"""The percentage actually withheld, after thresholds (India's ₹10,000 TDS for residents)."""
|
|
124
|
+
if rule is None:
|
|
125
|
+
return 0.0
|
|
126
|
+
if rule.basis == "domestic" and source == "IN" and residence == "IN":
|
|
127
|
+
if yearly_amount_in_inr is not None and yearly_amount_in_inr <= INDIA_TDS_THRESHOLD_INR:
|
|
128
|
+
return 0.0
|
|
129
|
+
return rule.rate
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def table_for(residence: Optional[str], overrides: Optional[dict] = None) -> list[dict]:
|
|
133
|
+
"""Every supported source country and the rate a resident of `residence` would see."""
|
|
134
|
+
rows = []
|
|
135
|
+
for source in SUPPORTED:
|
|
136
|
+
rule = rule_for(source, residence, overrides)
|
|
137
|
+
default = rule_for(source, residence, None)
|
|
138
|
+
rows.append({
|
|
139
|
+
"source": source,
|
|
140
|
+
"name": NAMES[source],
|
|
141
|
+
"rate": rule.rate,
|
|
142
|
+
"basis": rule.basis,
|
|
143
|
+
"note": rule.note,
|
|
144
|
+
"default_rate": default.rate,
|
|
145
|
+
"default_note": default.note,
|
|
146
|
+
})
|
|
147
|
+
return rows
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en" class="dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/
|
|
1
|
+
<!DOCTYPE html><html lang="en" class="dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/20i38r94ivt58.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/3mc6dra1m0098.js"/><script src="/_next/static/chunks/2i51e627rllld.js" async=""></script><script src="/_next/static/chunks/14jt62gawmtd9.js" async=""></script><script src="/_next/static/chunks/138wmfea-yk08.js" async=""></script><script src="/_next/static/chunks/turbopack-1che7mqzsn0nh.js" async=""></script><script src="/_next/static/chunks/453cd1tlxz5wi.js" async=""></script><meta name="robots" content="noindex"/><meta name="theme-color" content="#0e0e13"/><meta name="color-scheme" content="dark"/><title>DividendCase</title><meta name="description" content="A free, open-source dividend tracker that runs on your own computer"/><link rel="icon" href="/logo-icon.svg"/><link rel="apple-touch-icon" href="/logo-icon.svg"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/3mc6dra1m0098.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/20i38r94ivt58.css\",\"style\"]\n7:X\n0:{\"P\":null,\"c\":[\"\",\"_not-found\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4624],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/20i38r94ivt58.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/453cd1tlxz5wi.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:style\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style\",\"children\":404}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style\",\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$7\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/20i38r94ivt58.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"r\":\"$undefined\",\"s\":\"$undefined\",\"a\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"ZWBqBpak7xVqht9vRBBgL\"}\n7:C\n9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"2\",{\"name\":\"theme-color\",\"content\":\"#0e0e13\"}],[\"$\",\"meta\",\"3\",{\"name\":\"color-scheme\",\"content\":\"dark\"}]]\nd:I[27201,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"IconMark\"]\n6:null\nb:[[\"$\",\"title\",\"0\",{\"children\":\"DividendCase\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"A free, open-source dividend tracker that runs on your own computer\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/logo-icon.svg\"}],[\"$\",\"link\",\"3\",{\"rel\":\"apple-touch-icon\",\"href\":\"/logo-icon.svg\"}],[\"$\",\"$Ld\",\"4\",{}]]\n"])</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en" class="dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/
|
|
1
|
+
<!DOCTYPE html><html lang="en" class="dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/20i38r94ivt58.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/3mc6dra1m0098.js"/><script src="/_next/static/chunks/2i51e627rllld.js" async=""></script><script src="/_next/static/chunks/14jt62gawmtd9.js" async=""></script><script src="/_next/static/chunks/138wmfea-yk08.js" async=""></script><script src="/_next/static/chunks/turbopack-1che7mqzsn0nh.js" async=""></script><script src="/_next/static/chunks/453cd1tlxz5wi.js" async=""></script><meta name="robots" content="noindex"/><meta name="theme-color" content="#0e0e13"/><meta name="color-scheme" content="dark"/><title>DividendCase</title><meta name="description" content="A free, open-source dividend tracker that runs on your own computer"/><link rel="icon" href="/logo-icon.svg"/><link rel="apple-touch-icon" href="/logo-icon.svg"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/3mc6dra1m0098.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[39756,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\"]\n3:I[37457,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\"]\n4:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"ViewportBoundary\"]\na:I[97367,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"MetadataBoundary\"]\nc:I[68027,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/20i38r94ivt58.css\",\"style\"]\n7:X\n0:{\"P\":null,\"c\":[\"\",\"_not-found\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4624],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/20i38r94ivt58.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/453cd1tlxz5wi.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"dark geist_9c6cb61b-module__8NX9hq__variable geist_mono_d6617093-module__z61v7q__variable instrument_serif_2ef3d2eb-module__gc1GGW__variable\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:style\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style\",\"children\":404}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style\",\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$7\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/20i38r94ivt58.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"r\":\"$undefined\",\"s\":\"$undefined\",\"a\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"ZWBqBpak7xVqht9vRBBgL\"}\n7:C\n9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"2\",{\"name\":\"theme-color\",\"content\":\"#0e0e13\"}],[\"$\",\"meta\",\"3\",{\"name\":\"color-scheme\",\"content\":\"dark\"}]]\nd:I[27201,[\"/_next/static/chunks/453cd1tlxz5wi.js\"],\"IconMark\"]\n6:null\nb:[[\"$\",\"title\",\"0\",{\"children\":\"DividendCase\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"A free, open-source dividend tracker that runs on your own computer\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/logo-icon.svg\"}],[\"$\",\"link\",\"3\",{\"rel\":\"apple-touch-icon\",\"href\":\"/logo-icon.svg\"}],[\"$\",\"$Ld\",\"4\",{}]]\n"])</script></body></html>
|