calcfinc 0.1.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.
Files changed (84) hide show
  1. calcfinc-0.1.0/.gitignore +17 -0
  2. calcfinc-0.1.0/CHANGELOG.md +44 -0
  3. calcfinc-0.1.0/CONTRIBUTING.md +76 -0
  4. calcfinc-0.1.0/LICENSE +21 -0
  5. calcfinc-0.1.0/PKG-INFO +167 -0
  6. calcfinc-0.1.0/README.md +130 -0
  7. calcfinc-0.1.0/SECURITY.md +37 -0
  8. calcfinc-0.1.0/docs/adapters.md +171 -0
  9. calcfinc-0.1.0/docs/fact-schema.md +93 -0
  10. calcfinc-0.1.0/docs/manual.md +817 -0
  11. calcfinc-0.1.0/docs/ratios.md +254 -0
  12. calcfinc-0.1.0/examples/01_usd_company.py +23 -0
  13. calcfinc-0.1.0/examples/02_inr_consolidated_vs_standalone.py +16 -0
  14. calcfinc-0.1.0/examples/03_bank_and_insurer.py +16 -0
  15. calcfinc-0.1.0/examples/04_monthly_sme_dscr.py +24 -0
  16. calcfinc-0.1.0/examples/05_cross_currency.py +14 -0
  17. calcfinc-0.1.0/examples/06_sec_companyfacts.py +31 -0
  18. calcfinc-0.1.0/examples/07_ind_as_xbrl.py +21 -0
  19. calcfinc-0.1.0/examples/data/bank_and_insurer.csv +22 -0
  20. calcfinc-0.1.0/examples/data/inr_company.csv +7 -0
  21. calcfinc-0.1.0/examples/data/monthly_sme.csv +11 -0
  22. calcfinc-0.1.0/examples/data/sec_companyfacts_synthetic.json +388 -0
  23. calcfinc-0.1.0/examples/data/synthetic_consolidated_30-Jun-2025.xbrl +24 -0
  24. calcfinc-0.1.0/examples/data/two_currencies.csv +7 -0
  25. calcfinc-0.1.0/examples/data/usd_company.csv +16 -0
  26. calcfinc-0.1.0/pyproject.toml +75 -0
  27. calcfinc-0.1.0/scripts/gen_ratio_docs.py +111 -0
  28. calcfinc-0.1.0/src/calcfinc/__init__.py +30 -0
  29. calcfinc-0.1.0/src/calcfinc/adapters/__init__.py +1 -0
  30. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/__init__.py +29 -0
  31. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/canonical.py +171 -0
  32. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/load.py +231 -0
  33. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/tags.py +144 -0
  34. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/vocab.py +129 -0
  35. calcfinc-0.1.0/src/calcfinc/adapters/ind_as_xbrl/xbrl.py +128 -0
  36. calcfinc-0.1.0/src/calcfinc/adapters/sec_companyfacts/__init__.py +24 -0
  37. calcfinc-0.1.0/src/calcfinc/adapters/sec_companyfacts/fetch.py +70 -0
  38. calcfinc-0.1.0/src/calcfinc/adapters/sec_companyfacts/load.py +81 -0
  39. calcfinc-0.1.0/src/calcfinc/adapters/sec_companyfacts/parse.py +393 -0
  40. calcfinc-0.1.0/src/calcfinc/adapters/sec_companyfacts/tags.py +93 -0
  41. calcfinc-0.1.0/src/calcfinc/engine/__init__.py +11 -0
  42. calcfinc-0.1.0/src/calcfinc/engine/check.py +95 -0
  43. calcfinc-0.1.0/src/calcfinc/engine/decompose.py +80 -0
  44. calcfinc-0.1.0/src/calcfinc/engine/engine.py +690 -0
  45. calcfinc-0.1.0/src/calcfinc/engine/evaluate.py +189 -0
  46. calcfinc-0.1.0/src/calcfinc/engine/growth.py +28 -0
  47. calcfinc-0.1.0/src/calcfinc/engine/records.py +157 -0
  48. calcfinc-0.1.0/src/calcfinc/engine/segments.py +179 -0
  49. calcfinc-0.1.0/src/calcfinc/entity.py +41 -0
  50. calcfinc-0.1.0/src/calcfinc/fact.py +177 -0
  51. calcfinc-0.1.0/src/calcfinc/formula.py +180 -0
  52. calcfinc-0.1.0/src/calcfinc/loaders/__init__.py +6 -0
  53. calcfinc-0.1.0/src/calcfinc/loaders/csv.py +86 -0
  54. calcfinc-0.1.0/src/calcfinc/loaders/dataframe.py +93 -0
  55. calcfinc-0.1.0/src/calcfinc/loaders/records.py +255 -0
  56. calcfinc-0.1.0/src/calcfinc/num.py +96 -0
  57. calcfinc-0.1.0/src/calcfinc/period.py +112 -0
  58. calcfinc-0.1.0/src/calcfinc/py.typed +0 -0
  59. calcfinc-0.1.0/src/calcfinc/registry/__init__.py +6 -0
  60. calcfinc-0.1.0/src/calcfinc/registry/metrics.py +159 -0
  61. calcfinc-0.1.0/src/calcfinc/registry/ratios.py +427 -0
  62. calcfinc-0.1.0/src/calcfinc/store/__init__.py +5 -0
  63. calcfinc-0.1.0/src/calcfinc/store/base.py +91 -0
  64. calcfinc-0.1.0/src/calcfinc/store/schema.sql +104 -0
  65. calcfinc-0.1.0/src/calcfinc/store/sqlite.py +418 -0
  66. calcfinc-0.1.0/tests/__init__.py +0 -0
  67. calcfinc-0.1.0/tests/_fixture.py +74 -0
  68. calcfinc-0.1.0/tests/test_docs.py +118 -0
  69. calcfinc-0.1.0/tests/test_engine.py +333 -0
  70. calcfinc-0.1.0/tests/test_exactness.py +157 -0
  71. calcfinc-0.1.0/tests/test_examples.py +46 -0
  72. calcfinc-0.1.0/tests/test_formula.py +309 -0
  73. calcfinc-0.1.0/tests/test_generalised.py +253 -0
  74. calcfinc-0.1.0/tests/test_guardrails.py +108 -0
  75. calcfinc-0.1.0/tests/test_ind_as.py +408 -0
  76. calcfinc-0.1.0/tests/test_india.py +114 -0
  77. calcfinc-0.1.0/tests/test_loaders.py +219 -0
  78. calcfinc-0.1.0/tests/test_records.py +106 -0
  79. calcfinc-0.1.0/tests/test_robustness.py +302 -0
  80. calcfinc-0.1.0/tests/test_sec.py +479 -0
  81. calcfinc-0.1.0/tests/test_segments.py +84 -0
  82. calcfinc-0.1.0/tests/test_store.py +115 -0
  83. calcfinc-0.1.0/tests/test_ttm.py +145 -0
  84. calcfinc-0.1.0/tests/test_valuation.py +197 -0
@@ -0,0 +1,17 @@
1
+ .venv/
2
+ .cache/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ *.db
12
+ *.sqlite
13
+ .coverage
14
+ htmlcov/
15
+
16
+ # kept locally, not published
17
+ docs/regulatory-definitions.md
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes are listed here. The project follows [semantic versioning](https://semver.org/);
4
+ while the version is 0.x, a minor release may change the public API, and any such change is listed
5
+ under "Changed". A change to a built-in ratio definition raises its `definition_version`.
6
+
7
+ ## 0.1.0 - first release
8
+
9
+ ### What it is
10
+
11
+ An exact-decimal engine that turns periodic statements into ratios, growth, CAGR, valuation,
12
+ DuPont, trailing twelve months and segment attribution, with the formula, the input facts and the
13
+ limitations on every result. No runtime dependencies; Python 3.11 to 3.13.
14
+
15
+ ### Added
16
+
17
+ - `FinancialEngine` with `get_metric`, `get_ratio`, `get_valuation`, `get_growth`, `get_cagr`,
18
+ `compare_periods`, `compare_companies`, `decompose_metric`, `calculate`, `check`, `check_periods`,
19
+ segment queries, and loaders for CSV (long and wide), records and pandas DataFrames.
20
+ - More than a hundred ratios and derived quantities as formula strings in an open registry
21
+ (`register_metric`, `register_ratio`); trailing-twelve-month forms through `ttm(x)`; average
22
+ balances through `prior(x)`; a restricted Decimal formula language.
23
+ - Definitions reconciled across ACCA, the FTC Quarterly Financial Report, MCA/ICAI, RBI and SEBI,
24
+ with the Indian forms as `india.*` and RBI-form bank ratios (`bank.*`).
25
+ - Sector handling: ratios that do not describe a bank or an insurer return `None` with the reason;
26
+ sector is declared or inferred from the facts.
27
+ - Source adapters: SEC `companyfacts` (exact periods, restatement versions, derived fourth
28
+ quarters, US bank lines, a polite download helper) and Indian exchange XBRL (Ind-AS), tested on
29
+ 19 US and 20 Indian companies' real filings.
30
+ - Restatements kept as versions by `reported_at`; each input of a result carries it; a result warns
31
+ when one period's inputs come from filings far apart.
32
+ - A SQLite store that keeps every value as exact decimal text.
33
+ - A manual whose examples are run by the test suite, and a ratio reference generated from the code.
34
+
35
+ ### Changed
36
+
37
+ Nothing: this is the first release.
38
+
39
+ ### Known limitations
40
+
41
+ - Point-in-time views (`as_of`) for restated data are not implemented.
42
+ - IFRS filers, SEC insurers and 20-F/40-F filers are not mapped.
43
+ - EBIT is `profit before exceptional items + finance costs`, so it includes non-operating gains.
44
+ - LLM function-calling schemas and an MCP server are planned for a later release.
@@ -0,0 +1,76 @@
1
+ # Contributing
2
+
3
+ Thanks for helping. This project cares about exactness and honesty more than breadth, so a few
4
+ rules matter more than usual.
5
+
6
+ ## Set up
7
+
8
+ ```bash
9
+ git clone https://github.com/Am1n1602/calcfinc && cd calcfinc
10
+ python -m venv .venv
11
+ .venv/bin/python -m pip install -e ".[dev]" # Windows: .venv\Scripts\python
12
+ .venv/bin/python -m pytest && .venv/bin/ruff check . && .venv/bin/mypy
13
+ ```
14
+
15
+ All three must pass. CI runs them on Python 3.11, 3.12 and 3.13 on Linux, Windows and macOS.
16
+
17
+ ## The rules
18
+
19
+ 1. **No floats.** Numbers are `Decimal`, `int` or `str`. Anything that lets a float through to a
20
+ result is a bug. Arithmetic goes through `calcfinc.num`.
21
+ 2. **Missing is `None` with a reason.** Never zero, never an estimate. If a definition needs a
22
+ substitute, make it a labelled fallback so the result announces it.
23
+ 3. **The core stays generic.** No country, exchange, regulator or vendor names in `src/calcfinc`
24
+ outside `adapters/` (a test enforces this). Country-specific inputs and ratios belong in an
25
+ adapter (see `adapters/ind_as_xbrl/vocab.py`).
26
+ 4. **Standard library only** at runtime. The network may be used only in
27
+ `adapters/sec_companyfacts/fetch.py` (a test enforces this).
28
+ 5. **No real data in the repository.** Test and example data is synthetic and written for the test.
29
+ Do not commit exchange, SEC or vendor data or anything derived from it.
30
+ 6. **Definitions need evidence.** A new or changed ratio comes with the source of its definition.
31
+ If regulators or standards disagree, say so in the pull request and in the ratio's `label`, and
32
+ pick one with a reason. Changing an existing definition bumps its `version`.
33
+
34
+ ## Tests
35
+
36
+ Write a test when there is behaviour to pin down, with expected values **worked out by hand**
37
+ (show the arithmetic in a comment), never copied from the code's output. Fix the code, not the
38
+ test, when they disagree. A bug fix comes with a test that fails without it.
39
+
40
+ The documentation is tested too: every ```` ```python ```` block in `README.md` and
41
+ `docs/manual.md` is run, and `expr # -> value` lines are checked. `docs/ratios.md` is generated;
42
+ after changing a ratio or metric run
43
+
44
+ ```bash
45
+ python scripts/gen_ratio_docs.py
46
+ ```
47
+
48
+ and commit the result (a test fails if it is stale).
49
+
50
+ ## Adding a source adapter
51
+
52
+ An adapter reads one source's tags and conventions and produces `FinancialFact`s through the
53
+ loaders; it never changes how a ratio is computed. Test it on synthetic data in the repository and,
54
+ if you can, on real filings *outside* it, and record what was and was not verified in
55
+ `docs/adapters.md`.
56
+
57
+ ## Style
58
+
59
+ `ruff` (line length 120) and `mypy --strict` are the style guide. Comments explain why, not what.
60
+
61
+ ## Releasing (maintainers)
62
+
63
+ 1. Set `__version__` in `src/calcfinc/__init__.py`, move the changelog entry from "Unreleased" to the
64
+ new version, regenerate `docs/ratios.md`, and make sure CI is green on `main`.
65
+ 2. One-time setup: on PyPI (and on TestPyPI) add a **trusted publisher** for owner `Am1n1602`,
66
+ repository `calcfinc`, workflow `release.yml`, environment `pypi` (`testpypi` on TestPyPI), and create
67
+ those two environments in the repository settings. No API token is ever stored.
68
+ 3. Dry run: Actions, "release", **Run workflow** publishes the build to TestPyPI. Install it with
69
+ `pip install --index-url https://test.pypi.org/simple/ --no-deps calcfinc` and try it.
70
+ 4. Release: `git tag v0.1.0 && git push origin v0.1.0`. The workflow refuses to publish if the tag
71
+ and `__version__` disagree.
72
+
73
+ ## Pull requests
74
+
75
+ Keep one concern per PR, update `CHANGELOG.md` for anything a user would notice, and describe
76
+ how you checked it. By contributing you agree your work is released under the MIT licence.
calcfinc-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Am1n1602 (Aman Gautam)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,167 @@
1
+ Metadata-Version: 2.5
2
+ Name: calcfinc
3
+ Version: 0.1.0
4
+ Summary: Auditable financial metrics from periodic statements of any entity: every result carries its formula, input facts and limitations.
5
+ Project-URL: Homepage, https://github.com/Am1n1602/calcfinc
6
+ Project-URL: Documentation, https://github.com/Am1n1602/calcfinc/blob/main/docs/manual.md
7
+ Project-URL: Source, https://github.com/Am1n1602/calcfinc
8
+ Project-URL: Issues, https://github.com/Am1n1602/calcfinc/issues
9
+ Project-URL: Changelog, https://github.com/Am1n1602/calcfinc/blob/main/CHANGELOG.md
10
+ Author: Am1n1602 (Aman Gautam)
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: audit,decimal,finance,financial-ratios,financial-statements,provenance,sec,xbrl
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Financial and Insurance Industry
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business :: Financial
25
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Provides-Extra: dev
29
+ Requires-Dist: coverage>=7; extra == 'dev'
30
+ Requires-Dist: mypy>=1.11; extra == 'dev'
31
+ Requires-Dist: pandas>=2; extra == 'dev'
32
+ Requires-Dist: pytest>=8; extra == 'dev'
33
+ Requires-Dist: ruff>=0.6; extra == 'dev'
34
+ Provides-Extra: pandas
35
+ Requires-Dist: pandas>=2; extra == 'pandas'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # calcfinc
39
+
40
+ [![ci](https://github.com/Am1n1602/calcfinc/actions/workflows/ci.yml/badge.svg)](https://github.com/Am1n1602/calcfinc/actions/workflows/ci.yml)
41
+
42
+ **Auditable financial metrics from the statements of any entity.** Give it periodic figures (a
43
+ CSV, a pandas frame, an SEC filing, an Indian exchange filing) and ask for ratios, growth,
44
+ CAGR, valuation, DuPont, trailing twelve months. Every answer carries the formula that produced
45
+ it, the exact figures it used, and a plain-language note whenever something was substituted or
46
+ could not be computed.
47
+
48
+ ```python skip
49
+ roe = eng.get_ratio("Acme Inc", "roe")
50
+ roe.value # Decimal('25')
51
+ roe.formula # '100 * net_profit / total_equity'
52
+ roe.inputs # the facts used: metric, value, period, currency, source, date reported
53
+ roe.limitations # () - or why the value is None, or what was substituted
54
+ ```
55
+
56
+ - **Exact.** Every number is a `Decimal`; floats are refused at every entrance. A result is the
57
+ same on any machine and any Python version.
58
+ - **Honest.** A missing input gives `None` and the reason, never zero and never an estimate.
59
+ - **Open.** More than a hundred ratios, each one a readable formula string
60
+ ([list](docs/ratios.md)). Add your own with `register_ratio`.
61
+ - **Any entity.** Any currency, any fiscal calendar (March, 52/53-week...), months, quarters
62
+ and years, consolidated and standalone, banks and insurers.
63
+ - **Zero runtime dependencies.** Python 3.11 or later; pandas is optional.
64
+
65
+ > **Status: 0.1, beta.** Tested on 19 US and 20 Indian companies' real filings (see
66
+ > [what has and has not been verified](docs/manual.md#15-what-has-and-has-not-been-verified)).
67
+ > The API may still change in 0.x; changes are listed in [CHANGELOG.md](CHANGELOG.md).
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install calcfinc
73
+ ```
74
+
75
+ ## Quick start
76
+
77
+ A CSV with one row per metric and one column per fiscal year:
78
+
79
+ ```csv acme.csv
80
+ metric,FY2025,FY2026
81
+ revenue,1000,1200
82
+ net_profit,100,150
83
+ total_equity,500,600
84
+ total_assets,2000,2400
85
+ ```
86
+
87
+ ```python
88
+ from calcfinc import FinancialEngine
89
+
90
+ eng = FinancialEngine.from_csv("acme.csv", entity="Acme Inc", currency="USD")
91
+
92
+ roe = eng.get_ratio("Acme Inc", "roe")
93
+ roe.value # -> 25
94
+ roe.formula # -> 100 * net_profit / total_equity
95
+
96
+ # growth in percent, FY2025 to FY2026, and compound annual growth
97
+ eng.get_growth("Acme Inc", "revenue").value # -> 20
98
+ eng.get_cagr("Acme Inc", "revenue").value # -> 20
99
+ ```
100
+
101
+ A figure that cannot be computed says why instead of guessing. This file has no cash flow
102
+ statement:
103
+
104
+ ```python
105
+ fcf = eng.get_ratio("Acme Inc", "free_cash_flow")
106
+ fcf.value # -> None
107
+ print(fcf.limitations[0])
108
+ ```
109
+
110
+ ## What you can ask
111
+
112
+ | | |
113
+ |---|---|
114
+ | `get_ratio`, `get_metric` | margins, returns, leverage, liquidity, efficiency, per-share, trailing twelve months |
115
+ | `get_valuation` | P/E, P/B, EV/EBITDA, yields, market cap (you supply prices) |
116
+ | `get_growth`, `get_cagr` | year on year, quarter on quarter, month on month, compound |
117
+ | `compare_periods`, `compare_companies` | two periods side by side; rank entities (never across currencies) |
118
+ | `decompose_metric` | DuPont (three and five factor), net-margin bridge |
119
+ | `check`, `check_periods` | accounting identities; do the quarters add up to the year |
120
+ | `get_segment_data`, `segment_growth` | segment revenue, contribution, growth |
121
+ | `calculate` | any formula over numbers you give it |
122
+
123
+ ## Where data comes from
124
+
125
+ CSV (long or wide), dicts, pandas, or your own storage; a SQLite file if you want it to persist.
126
+ Two adapters read real filings:
127
+
128
+ - **SEC `companyfacts`** (US-GAAP): exact periods, restatements kept as versions, the fourth
129
+ quarter derived, bank lines for banks.
130
+ - **Indian exchange XBRL** (Ind-AS): April-March year, `india.*` ratios in the Schedule III and ICAI
131
+ forms, placeholder zeros not loaded.
132
+
133
+ Both tie every fact to its filing. See [docs/adapters.md](docs/adapters.md). No market or vendor
134
+ data is bundled, and there is no network access except one explicit SEC download helper.
135
+
136
+ ## Things it deliberately will not do
137
+
138
+ - Convert currencies. Amounts in different currencies are listed, never ranked.
139
+ - Annualise a quarterly ratio. It says "not annualised" and offers the trailing-twelve-month form.
140
+ - Hide a substitution. A fallback definition, a derived quarter or a mixed restatement vintage is
141
+ always in `limitations`.
142
+ - Apply operating-company ratios to a bank or an insurer. Interest cover, debt and the
143
+ working-capital ratios return `None` with the reason for financial companies.
144
+
145
+ ## Documentation
146
+
147
+ - **[Manual](docs/manual.md)**: concepts, loading data, periods, every question you can ask,
148
+ reading results, custom ratios, running it in production, troubleshooting. Its examples are run
149
+ by the test suite.
150
+ - [Ratio and metric reference](docs/ratios.md) (generated from the code)
151
+ - [Fact format and CSV layouts](docs/fact-schema.md)
152
+ - [Adapters and real-data results](docs/adapters.md)
153
+ - [Examples](examples): seven runnable scripts with synthetic data
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ python -m venv .venv
159
+ .venv/bin/python -m pip install -e ".[dev]" # Windows: .venv\Scripts\python
160
+ .venv/bin/python -m pytest && .venv/bin/ruff check . && .venv/bin/mypy
161
+ ```
162
+
163
+ See [CONTRIBUTING.md](CONTRIBUTING.md). To report a vulnerability, see [SECURITY.md](SECURITY.md).
164
+
165
+ ## Licence
166
+
167
+ MIT. Results are calculations, not investment advice.
@@ -0,0 +1,130 @@
1
+ # calcfinc
2
+
3
+ [![ci](https://github.com/Am1n1602/calcfinc/actions/workflows/ci.yml/badge.svg)](https://github.com/Am1n1602/calcfinc/actions/workflows/ci.yml)
4
+
5
+ **Auditable financial metrics from the statements of any entity.** Give it periodic figures (a
6
+ CSV, a pandas frame, an SEC filing, an Indian exchange filing) and ask for ratios, growth,
7
+ CAGR, valuation, DuPont, trailing twelve months. Every answer carries the formula that produced
8
+ it, the exact figures it used, and a plain-language note whenever something was substituted or
9
+ could not be computed.
10
+
11
+ ```python skip
12
+ roe = eng.get_ratio("Acme Inc", "roe")
13
+ roe.value # Decimal('25')
14
+ roe.formula # '100 * net_profit / total_equity'
15
+ roe.inputs # the facts used: metric, value, period, currency, source, date reported
16
+ roe.limitations # () - or why the value is None, or what was substituted
17
+ ```
18
+
19
+ - **Exact.** Every number is a `Decimal`; floats are refused at every entrance. A result is the
20
+ same on any machine and any Python version.
21
+ - **Honest.** A missing input gives `None` and the reason, never zero and never an estimate.
22
+ - **Open.** More than a hundred ratios, each one a readable formula string
23
+ ([list](docs/ratios.md)). Add your own with `register_ratio`.
24
+ - **Any entity.** Any currency, any fiscal calendar (March, 52/53-week...), months, quarters
25
+ and years, consolidated and standalone, banks and insurers.
26
+ - **Zero runtime dependencies.** Python 3.11 or later; pandas is optional.
27
+
28
+ > **Status: 0.1, beta.** Tested on 19 US and 20 Indian companies' real filings (see
29
+ > [what has and has not been verified](docs/manual.md#15-what-has-and-has-not-been-verified)).
30
+ > The API may still change in 0.x; changes are listed in [CHANGELOG.md](CHANGELOG.md).
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install calcfinc
36
+ ```
37
+
38
+ ## Quick start
39
+
40
+ A CSV with one row per metric and one column per fiscal year:
41
+
42
+ ```csv acme.csv
43
+ metric,FY2025,FY2026
44
+ revenue,1000,1200
45
+ net_profit,100,150
46
+ total_equity,500,600
47
+ total_assets,2000,2400
48
+ ```
49
+
50
+ ```python
51
+ from calcfinc import FinancialEngine
52
+
53
+ eng = FinancialEngine.from_csv("acme.csv", entity="Acme Inc", currency="USD")
54
+
55
+ roe = eng.get_ratio("Acme Inc", "roe")
56
+ roe.value # -> 25
57
+ roe.formula # -> 100 * net_profit / total_equity
58
+
59
+ # growth in percent, FY2025 to FY2026, and compound annual growth
60
+ eng.get_growth("Acme Inc", "revenue").value # -> 20
61
+ eng.get_cagr("Acme Inc", "revenue").value # -> 20
62
+ ```
63
+
64
+ A figure that cannot be computed says why instead of guessing. This file has no cash flow
65
+ statement:
66
+
67
+ ```python
68
+ fcf = eng.get_ratio("Acme Inc", "free_cash_flow")
69
+ fcf.value # -> None
70
+ print(fcf.limitations[0])
71
+ ```
72
+
73
+ ## What you can ask
74
+
75
+ | | |
76
+ |---|---|
77
+ | `get_ratio`, `get_metric` | margins, returns, leverage, liquidity, efficiency, per-share, trailing twelve months |
78
+ | `get_valuation` | P/E, P/B, EV/EBITDA, yields, market cap (you supply prices) |
79
+ | `get_growth`, `get_cagr` | year on year, quarter on quarter, month on month, compound |
80
+ | `compare_periods`, `compare_companies` | two periods side by side; rank entities (never across currencies) |
81
+ | `decompose_metric` | DuPont (three and five factor), net-margin bridge |
82
+ | `check`, `check_periods` | accounting identities; do the quarters add up to the year |
83
+ | `get_segment_data`, `segment_growth` | segment revenue, contribution, growth |
84
+ | `calculate` | any formula over numbers you give it |
85
+
86
+ ## Where data comes from
87
+
88
+ CSV (long or wide), dicts, pandas, or your own storage; a SQLite file if you want it to persist.
89
+ Two adapters read real filings:
90
+
91
+ - **SEC `companyfacts`** (US-GAAP): exact periods, restatements kept as versions, the fourth
92
+ quarter derived, bank lines for banks.
93
+ - **Indian exchange XBRL** (Ind-AS): April-March year, `india.*` ratios in the Schedule III and ICAI
94
+ forms, placeholder zeros not loaded.
95
+
96
+ Both tie every fact to its filing. See [docs/adapters.md](docs/adapters.md). No market or vendor
97
+ data is bundled, and there is no network access except one explicit SEC download helper.
98
+
99
+ ## Things it deliberately will not do
100
+
101
+ - Convert currencies. Amounts in different currencies are listed, never ranked.
102
+ - Annualise a quarterly ratio. It says "not annualised" and offers the trailing-twelve-month form.
103
+ - Hide a substitution. A fallback definition, a derived quarter or a mixed restatement vintage is
104
+ always in `limitations`.
105
+ - Apply operating-company ratios to a bank or an insurer. Interest cover, debt and the
106
+ working-capital ratios return `None` with the reason for financial companies.
107
+
108
+ ## Documentation
109
+
110
+ - **[Manual](docs/manual.md)**: concepts, loading data, periods, every question you can ask,
111
+ reading results, custom ratios, running it in production, troubleshooting. Its examples are run
112
+ by the test suite.
113
+ - [Ratio and metric reference](docs/ratios.md) (generated from the code)
114
+ - [Fact format and CSV layouts](docs/fact-schema.md)
115
+ - [Adapters and real-data results](docs/adapters.md)
116
+ - [Examples](examples): seven runnable scripts with synthetic data
117
+
118
+ ## Development
119
+
120
+ ```bash
121
+ python -m venv .venv
122
+ .venv/bin/python -m pip install -e ".[dev]" # Windows: .venv\Scripts\python
123
+ .venv/bin/python -m pytest && .venv/bin/ruff check . && .venv/bin/mypy
124
+ ```
125
+
126
+ See [CONTRIBUTING.md](CONTRIBUTING.md). To report a vulnerability, see [SECURITY.md](SECURITY.md).
127
+
128
+ ## Licence
129
+
130
+ MIT. Results are calculations, not investment advice.
@@ -0,0 +1,37 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ Only the latest release receives fixes. While the version is 0.x, upgrade to the newest 0.x.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Please **do not open a public issue** for a security problem. Use GitHub's private reporting:
10
+ the repository's **Security** tab, then **Report a vulnerability**
11
+ (<https://github.com/Am1n1602/calcfinc/security/advisories/new>).
12
+
13
+ Include what you found, the smallest input that shows it, and the version. You can expect an
14
+ acknowledgement within a week. A confirmed problem is fixed in a new release and credited in the
15
+ changelog unless you prefer otherwise.
16
+
17
+ ## What is in scope
18
+
19
+ calcfinc parses text it is given, so these are the surfaces that matter:
20
+
21
+ - **The formula evaluator.** Formulas are parsed with Python's `ast` and run by a small
22
+ interpreter that allows numbers, arithmetic, names and six functions. `eval` is never used. Input
23
+ that escapes this (attribute access, calls to other functions, a crash on deeply nested or
24
+ enormous input, unbounded memory or time) is a vulnerability.
25
+ - **File readers.** The CSV loader, the SEC `companyfacts` JSON reader and the XBRL reader, which
26
+ uses the standard library's XML parser (it does not fetch external entities). Anything that makes
27
+ them execute code, read other files or exhaust resources on a reasonably sized input is in scope.
28
+ - **Storage.** All SQL uses bound parameters; an injection is in scope.
29
+ - **The one network call.** `sec_companyfacts.fetch_companyfacts` is the only code that can touch
30
+ the network. Anything that makes other code do so is in scope.
31
+
32
+ ## Out of scope
33
+
34
+ - Wrong financial results are bugs, not vulnerabilities: please open a normal issue with the data.
35
+ - Running calcfinc on untrusted XBRL with the stock parser is documented as a limit; see the manual's
36
+ security section for the hardened-parser route.
37
+ - Denial of service by loading an enormous file you chose to load.
@@ -0,0 +1,171 @@
1
+ # Source adapters
2
+
3
+ An adapter reads one source's tags and conventions and produces calcfinc facts. The core never
4
+ imports an adapter, and an adapter never changes how a ratio is computed.
5
+
6
+ | Adapter | Source | Notes |
7
+ |---|---|---|
8
+ | `calcfinc.adapters.ind_as_xbrl` | Indian exchange XBRL filings (.xbrl file, raw fact rows, or canonical records) | April-March year, INR, `india.*` ratios |
9
+ | `calcfinc.adapters.sec_companyfacts` | The SEC's public `companyfacts` JSON | US-GAAP filers; IFRS is not mapped yet |
10
+
11
+ ## Ind-AS XBRL
12
+
13
+ ```python
14
+ from calcfinc.adapters import ind_as_xbrl
15
+ ind_as_xbrl.load_xbrl_file(repos, "results_consolidated.xbrl", entity="ACME")
16
+ ```
17
+
18
+ - Three entry points, one per stage: `load_xbrl_file` (a filing), `load_raw_facts` (rows from
19
+ `parse_xbrl_file`), `load_canonical` / `load_canonical_file` (records already mapped to names).
20
+ - The file's basis comes from its name (`consolidated` or `standalone`), or pass `basis=`.
21
+ - Only whole-company contexts are read (`OneD`, `OneI`, `PY_D`...). Segment and note breakdowns
22
+ are ignored. Filings that tag the older `in-bse-fin:` prefix are matched by local concept name.
23
+ - Some filers declare their reporting period as plain facts and leave the context without one;
24
+ those are recovered. Banks that omit a start date get it worked out from the financial year and
25
+ the quarter label, and only when that agrees with the declared end.
26
+ - Numbers are parsed from the filing's text straight to `Decimal`. Indian formatting, accounting
27
+ negatives and a `sign="-"` attribute are handled.
28
+ - Bank totals (equity, cash, liabilities) are built from their parts only when every part is
29
+ present; a reported total always wins.
30
+ - **A record whose own arithmetic fails still loads**, but each of its facts carries a review
31
+ reason (`source record failed arithmetic checks: ...`) and the report lists the period.
32
+ - **Zeros that mean "not applicable" are not loaded.** Real filings report an exact 0 where a
33
+ figure does not apply: a consolidated bank filing gives 0 for its whole NPA block (gross and net
34
+ NPA, both ratios, ROA, CET1, AT1); paid-up capital, face value and CET1 are never genuinely 0;
35
+ a coverage ratio of exactly 0 is a placeholder. Stored as numbers they would give a 0% NPA ratio
36
+ instead of "not reported". The block rule needs all four NPA figures to be zero together, so a
37
+ real zero (no exceptional items, no borrowings, no minority interest) is kept. Dropped names are
38
+ listed in `report.skipped`; pass `placeholder_zeros=False` to keep every zero.
39
+ - **Two contexts that contradict each other.** A cumulative context can carry the quarter's own
40
+ dates with different values, and a context can carry a stale prior-year balance. The current
41
+ period's context (`OneD`/`OneI`, then `TwoD`..., then `PY_`) wins, the winner's fact is flagged
42
+ with what the other context said, and `report.conflicts` lists each one. The store never
43
+ decides by load order.
44
+ - `paid_up_equity_capital / face_value_per_share` becomes a `shares_outstanding` fact marked as
45
+ derived, so valuation works.
46
+ - The XML is read with the standard library, which does not fetch external entities. For a file
47
+ you do not trust, parse it with a hardened parser and use `map_facts`.
48
+ - Pass `reported_at=` (the filing date) so a later revised filing is kept beside the original
49
+ instead of overwriting it.
50
+ - **Original and Revision filings** carry the same board-meeting date and no filing date, so the
51
+ file cannot say which is later. Without `reported_at` a later load overwrites an earlier one for
52
+ the same period. `load_xbrl_files(repos, paths, entity=...)` loads every "Revision" after its
53
+ "Original" whatever order you give, so the correction wins. (19 revisions exist in the
54
+ author's 2,960 files; most have no original beside them.)
55
+
56
+ ## SEC companyfacts
57
+
58
+ ```python
59
+ from calcfinc.adapters import sec_companyfacts as sec
60
+ data = sec.read_companyfacts("CIK0000320193.json") # a file you downloaded once
61
+ sec.load_companyfacts(repos, data, ticker="AAPL")
62
+ ```
63
+
64
+ - **Periods are matched on exact start and end dates.** The `fy` and `fp` fields in the JSON
65
+ describe the filing, not the period a figure covers, so they are never used to place a number.
66
+ - **Tag choice is per period.** For each metric, the first concept in `tags.CANDIDATES` that the
67
+ filer reported for that exact period wins, and a lower-priority concept is flagged
68
+ `alternate_tag` in the fact's mapping reason. Filers change concepts over time, so the choice is
69
+ never made once for a whole history.
70
+ - **Restatements are kept.** A figure that changes in a later filing becomes a second version with
71
+ `reported_at` set to that filing's date; the engine uses the latest. A later filing that merely
72
+ repeats a comparative figure adds nothing.
73
+ - **The fourth quarter is derived.** The SEC reports no stand-alone Q4, so it is the year less the
74
+ nine-month figure (or less the three reported quarters). Cash-flow statements, which are
75
+ reported year-to-date only, become single quarters the same way. Derived facts are marked
76
+ `derived` with the arithmetic in the reason. This is what makes trailing-twelve-month ratios
77
+ work for SEC filers.
78
+ - **Only currency amounts are derived.** Per-share figures are not additive, so a Q4 EPS is never
79
+ invented.
80
+ - **Every fact points to its filing.** Each accession number becomes a source with the form and
81
+ the filing URL, so `result.inputs[...].source_id` leads back to the document.
82
+ - The fiscal year end is inferred from the full-year figures (a 52/53-week year ending on 1 Feb is
83
+ a January year end). Pass `fiscal_year_end_month=` to override.
84
+ - Facts carrying a dimension are ignored; amounts are already in base units; only 10-K, 10-Q, 20-F
85
+ and 40-F family filings are read (`forms=` to change). 8-Ks are ignored.
86
+ - **US GAAP has no exceptional-items line**, so `pbt_before_exceptional` is set equal to pre-tax
87
+ income, marked `derived`, so EBIT-based ratios work.
88
+
89
+ ### Downloading, politely
90
+
91
+ `fetch_companyfacts` is the only function in calcfinc that can use the network, and nothing else
92
+ calls it. The SEC's [fair access rules](https://www.sec.gov/os/accessing-edgar-data) require:
93
+
94
+ - a User-Agent that identifies you with a real contact (there is deliberately no default);
95
+ - fewer than 10 requests per second (the helper keeps to 5 and never retries by itself);
96
+ - downloading once and keeping the file; the data changes only when a company files.
97
+
98
+ ```python
99
+ raw = sec.fetch_companyfacts(320193, "Jane Doe jane@example.com")
100
+ open("CIK0000320193.json", "wb").write(raw)
101
+ ```
102
+
103
+ ## What is and is not verified
104
+
105
+ - Every test in the repository uses synthetic data written for the test. No SEC or exchange data
106
+ is stored in this repository, and no test uses the network.
107
+ - **Ind-AS, checked on real filings outside the repository** (the installed wheel, in a separate
108
+ environment, reading the author's earlier extraction output read-only): 2,938 raw `.xbrl`
109
+ filings were parsed and mapped, and compared with the earlier pipeline's output for the same
110
+ filing: 138,313 fields, all identical. Facts from the canonical files were compared with the
111
+ earlier database for eight companies: all 22,293 overlapping facts matched exactly. The
112
+ comparison is what found the placeholder zeros and the contradicting contexts above. It also
113
+ showed that the earlier database stored a 6-month cash-flow figure under the 3-month quarter
114
+ for one filing; this adapter does not.
115
+ - **SEC, checked live** (the installed wheel, in a separate environment, downloading with
116
+ `fetch_companyfacts`): Apple, Microsoft and JPMorgan companyfacts, 3.8-8.0 MB each, loaded in
117
+ about 0.2 s each.
118
+ - The fiscal year end was inferred correctly for all three (Apple's 52/53-week September year,
119
+ Microsoft's June, JPMorgan's December).
120
+ - Revenue and net income matched published figures to the dollar for all seven company-years
121
+ checked (reference values written from memory, so a mismatch would have been investigated, not
122
+ trusted).
123
+ - The four quarters, with Q4 derived, added up to the reported year in 65 of 68 tests; the three
124
+ that did not are restatement-vintage cases (below).
125
+ - That run found and fixed: a quarter whose start date differed by a day between a filing and its
126
+ recast (a duplicate period); a bank's quarterly revenue concept missing from the list; a safety
127
+ rule that wrongly blocked deriving Q4 for a bank that reports one quantity under two concepts;
128
+ and an "alternate concept" flag that fired on filers that only ever use the second-choice
129
+ concept.
130
+ - **A second, broader run** (19 US filers: Apple, Microsoft, JPMorgan, Wells Fargo, Citigroup,
131
+ Goldman Sachs, MetLife, Walmart, Costco, Amazon, NVIDIA, Alphabet, Exxon, Johnson & Johnson,
132
+ Caterpillar, Duke, Prologis, Coca-Cola, AT&T; and 20 Indian companies, 5 of them banks, with
133
+ about 600 consolidated filings) found and fixed: `latest` landing on an SEC cover-page date;
134
+ Amazon's trailing-twelve-month 10-Q figures counted as fiscal years; Costco's 52/53-week years
135
+ labelled a year late; MetLife and other non-banks receiving `bank.*` lines; Indian banks not
136
+ recognised as banks; `paid_up_equity_capital` stored as a flow; and one filing whose declared
137
+ reporting period had day and month swapped. After the fixes, revenue and net income matched
138
+ published figures for all nine companies checked (written from memory; one differs by a
139
+ minority-interest amount), every company loaded in about 1 s, and the quarters add up to the year
140
+ for FY2019 onwards except where a company restated (ITC and Hindustan Unilever after demergers;
141
+ HDFC Bank after its merger).
142
+ - Identity checks still fail on some real statements for real reasons: regulatory deferral balances
143
+ (NTPC), redeemable minority interests and other temporary equity, minorities inside continuing
144
+ profit (Citigroup, AT&T), and utilities' deferred-tax presentation (Duke). They report; they
145
+ never correct.
146
+ - The candidate-concept list uses standard `us-gaap` names and has now met nineteen real filers, but
147
+ not hundreds. Expect other filers to use concepts that are not in the list; those metrics are
148
+ simply absent (never guessed), and the list is meant to be extended.
149
+ - **US banks.** Seven `bank.*` lines are mapped (interest income and expense, provisions, employee
150
+ cost, deposits, gross loans, and net loans where tagged), and only for a filer that reports both
151
+ `NoninterestExpense` and `Deposits`: insurers and industrials file `Deposits` and `InterestExpense`
152
+ too, where they do not mean what `bank.*` means. Interest income less interest expense
153
+ matched reported net interest income for every year checked on one large US bank (2010-2023). Not
154
+ mapped, so their ratios return None with a reason: non-performing assets (the US "nonaccrual" is a
155
+ different definition from the RBI's), CASA and interest-earning assets (not tagged), and "loans net
156
+ of allowance" (that bank's value exceeded its own gross loans, so it cannot be trusted). Loan to
157
+ deposit and credit cost therefore use gross loans, with a note; net interest margin uses total assets,
158
+ with a note. Only one bank has been checked.
159
+ - **Restatement vintages.** The engine uses the latest-reported figure for each metric and period.
160
+ When a company recasts only some figures (Microsoft's ASC 606 recast changed 2016 annual revenue
161
+ and equity but not the 2016 quarters or total assets), a period can combine figures from
162
+ different filings, and the four quarters need not add up to the year. The accounting-identity
163
+ check (`engine.check()`) flags the balance-sheet cases; 6-8 periods per company were flagged in
164
+ the live run and every one examined was this effect. Three aids exist: every input in a result
165
+ carries `reported_at`; a result says so in `limitations` when inputs for one period were first
166
+ reported more than 120 days apart; and `engine.check_periods()` tests, per year, whether the
167
+ four quarters add up to the year. Point-in-time views (`as_of`) are the planned fix.
168
+ - Debt lines use only the first available concept, never a sum, so a filer that reports several
169
+ overlapping debt concepts may show a partial figure.
170
+ - Not covered yet: IFRS filers (`ifrs-full`), insurer concepts from the SEC, and the
171
+ period-end details of 4-4-5 retail calendars beyond what the 52/53-week handling covers.