pyproforma 0.2.2__tar.gz → 0.2.5__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 (71) hide show
  1. pyproforma-0.2.5/PKG-INFO +301 -0
  2. pyproforma-0.2.5/README.md +260 -0
  3. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/__init__.py +26 -21
  4. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/chart/renderers/matplotlib_renderer.py +2 -2
  5. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/charts/chart_def.py +1 -2
  6. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/charts/charts.py +4 -1
  7. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/compare/model_comparison.py +3 -1
  8. pyproforma-0.2.5/pyproforma/engine/__init__.py +14 -0
  9. {pyproforma-0.2.2/pyproforma → pyproforma-0.2.5/pyproforma/engine}/calculation_engine.py +23 -15
  10. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/engine}/line_item_values.py +44 -2
  11. {pyproforma-0.2.2/pyproforma → pyproforma-0.2.5/pyproforma/engine}/model_namespace.py +22 -2
  12. pyproforma-0.2.5/pyproforma/explorer/__init__.py +4 -0
  13. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/explorer/app.py +165 -66
  14. pyproforma-0.2.5/pyproforma/explorer/components.py +128 -0
  15. pyproforma-0.2.5/pyproforma/explorer/templates/base.html +139 -0
  16. pyproforma-0.2.5/pyproforma/explorer/templates/chart_view.html +28 -0
  17. pyproforma-0.2.5/pyproforma/explorer/templates/index.html +80 -0
  18. pyproforma-0.2.5/pyproforma/explorer/templates/line_item.html +173 -0
  19. pyproforma-0.2.5/pyproforma/explorer/templates/table_view.html +28 -0
  20. pyproforma-0.2.5/pyproforma/explorer/templates/view.html +191 -0
  21. pyproforma-0.2.5/pyproforma/proforma_model.py +259 -0
  22. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/reserved_words.py +0 -1
  23. pyproforma-0.2.5/pyproforma/results/__init__.py +13 -0
  24. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/results}/line_item_result.py +26 -82
  25. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/results}/line_item_selection.py +1 -1
  26. pyproforma-0.2.5/pyproforma/results/line_item_stat.py +133 -0
  27. pyproforma-0.2.5/pyproforma/results/scalar_result.py +41 -0
  28. pyproforma-0.2.5/pyproforma/results/tags_namespace.py +34 -0
  29. pyproforma-0.2.5/pyproforma/specs/__init__.py +33 -0
  30. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/specs}/debt_line.py +59 -52
  31. pyproforma-0.2.5/pyproforma/specs/fixed_line.py +35 -0
  32. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/specs}/formula_line.py +1 -1
  33. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/specs}/input_line.py +47 -12
  34. {pyproforma-0.2.2/pyproforma/line_items → pyproforma-0.2.5/pyproforma/specs}/line_item.py +25 -9
  35. pyproforma-0.2.5/pyproforma/specs/scalar_input_line.py +54 -0
  36. pyproforma-0.2.5/pyproforma/specs/scalar_line.py +38 -0
  37. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/colors.py +25 -26
  38. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/excel.py +63 -60
  39. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/format_value.py +6 -2
  40. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/table_class.py +20 -5
  41. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/tables/row_types.py +18 -8
  42. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/tables/table_def.py +1 -2
  43. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/tables/tables.py +5 -2
  44. pyproforma-0.2.5/pyproforma.egg-info/PKG-INFO +301 -0
  45. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma.egg-info/SOURCES.txt +24 -12
  46. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma.egg-info/requires.txt +11 -6
  47. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproject.toml +26 -14
  48. pyproforma-0.2.2/PKG-INFO +0 -242
  49. pyproforma-0.2.2/README.md +0 -203
  50. pyproforma-0.2.2/pyproforma/explorer/__init__.py +0 -3
  51. pyproforma-0.2.2/pyproforma/explorer/components.py +0 -42
  52. pyproforma-0.2.2/pyproforma/line_items/__init__.py +0 -38
  53. pyproforma-0.2.2/pyproforma/line_items/fixed_line.py +0 -72
  54. pyproforma-0.2.2/pyproforma/proforma_model.py +0 -191
  55. pyproforma-0.2.2/pyproforma/tags_namespace.py +0 -220
  56. pyproforma-0.2.2/pyproforma.egg-info/PKG-INFO +0 -242
  57. {pyproforma-0.2.2 → pyproforma-0.2.5}/LICENSE +0 -0
  58. {pyproforma-0.2.2 → pyproforma-0.2.5}/MANIFEST.in +0 -0
  59. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/chart/__init__.py +0 -0
  60. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/chart/chart.py +0 -0
  61. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/chart/renderers/__init__.py +0 -0
  62. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/chart/renderers/base.py +0 -0
  63. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/charts/__init__.py +0 -0
  64. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/compare/__init__.py +0 -0
  65. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/__init__.py +0 -0
  66. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/bootstrap_html_renderer.py +0 -0
  67. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/table/html_renderer.py +0 -0
  68. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma/tables/__init__.py +0 -0
  69. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma.egg-info/dependency_links.txt +0 -0
  70. {pyproforma-0.2.2 → pyproforma-0.2.5}/pyproforma.egg-info/top_level.txt +0 -0
  71. {pyproforma-0.2.2 → pyproforma-0.2.5}/setup.cfg +0 -0
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyproforma
3
+ Version: 0.2.5
4
+ Summary: A Python package for financial modeling and reporting
5
+ Author-email: Robert Hannay <rhannay@gmail.com>
6
+ Maintainer-email: Robert Hannay <rhannay@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/rhannay/pyproforma
9
+ Project-URL: Repository, https://github.com/rhannay/pyproforma
10
+ Project-URL: Documentation, https://pyproforma.readthedocs.io/en/latest/
11
+ Project-URL: Issues, https://github.com/rhannay/pyproforma/issues
12
+ Keywords: finance,modeling,reporting,excel,proforma
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Topic :: Office/Business :: Financial
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: pandas
26
+ Requires-Dist: pandas>=1.3.0; extra == "pandas"
27
+ Provides-Extra: excel
28
+ Requires-Dist: openpyxl>=3.0.0; extra == "excel"
29
+ Provides-Extra: charts
30
+ Requires-Dist: matplotlib>=3.5.0; extra == "charts"
31
+ Provides-Extra: notebook
32
+ Requires-Dist: ipython>=7.0.0; extra == "notebook"
33
+ Provides-Extra: explorer
34
+ Requires-Dist: flask>=2.0.0; extra == "explorer"
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=6.0; extra == "dev"
37
+ Requires-Dist: pytest-cov; extra == "dev"
38
+ Requires-Dist: pandas>=1.3.0; extra == "dev"
39
+ Requires-Dist: openpyxl>=3.0.0; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # pyproforma
43
+
44
+ A Python library for building financial models — a code-first alternative to Excel for pro formas, projections, and structured financial tables.
45
+
46
+ ```bash
47
+ pip install pyproforma
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Why
53
+
54
+ Spreadsheets are the default tool for financial modeling, but they have real problems: no version control, no testing, formulas hidden inside cells, and no easy way to generate the same model for multiple scenarios. pyproforma is designed for analysts who want the benefits of code — reproducibility, testability, version history — without giving up the tabular output that finance people actually use.
55
+
56
+ ---
57
+
58
+ ## How it works
59
+
60
+ Define a model by subclassing `ProformaModel` and declaring line items as class attributes. Instantiate it with a list of periods and the library calculates everything.
61
+
62
+ ```python
63
+ from pyproforma import ProformaModel, FixedLine, FormulaLine, ScalarLine, Format
64
+
65
+ class IncomeStatement(ProformaModel):
66
+ default_periods = [2024, 2025, 2026]
67
+
68
+ tax_rate = ScalarLine(value=0.21, label="Tax Rate")
69
+
70
+ revenue = FixedLine(
71
+ values={2024: 500_000, 2025: 550_000, 2026: 605_000},
72
+ label="Revenue",
73
+ tags=["operating"],
74
+ value_format=Format.CURRENCY_NO_DECIMALS,
75
+ )
76
+ cogs = FormulaLine(
77
+ formula=lambda li, t: li.revenue[t] * 0.55,
78
+ label="Cost of Goods Sold",
79
+ value_format=Format.CURRENCY_NO_DECIMALS,
80
+ )
81
+ gross_profit = FormulaLine(
82
+ formula=lambda li, t: li.revenue[t] - li.cogs[t],
83
+ label="Gross Profit",
84
+ value_format=Format.CURRENCY_NO_DECIMALS,
85
+ )
86
+ net_income = FormulaLine(
87
+ formula=lambda li, t: li.gross_profit[t] * (1 - li.tax_rate),
88
+ label="Net Income",
89
+ value_format=Format.CURRENCY_NO_DECIMALS,
90
+ )
91
+
92
+ model = IncomeStatement() # uses default_periods
93
+ ```
94
+
95
+ Access results with dot notation (primary) or bracket notation (useful when the name is in a variable):
96
+
97
+ ```python
98
+ model.net_income[2025] # 173_745.0 — dot notation
99
+ model["net_income"][2025] # same value — bracket notation
100
+
101
+ model.tax_rate.value # 0.21 — scalars have .value, not [t]
102
+
103
+ model.periods # [2024, 2025, 2026]
104
+ model.line_item_names # ["revenue", "cogs", "gross_profit", "net_income"]
105
+ model.scalar_names # ["tax_rate"]
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Line item types
111
+
112
+ | Type | Use |
113
+ |------|-----|
114
+ | `FixedLine(values, ...)` | Hardcoded values per period |
115
+ | `FormulaLine(formula, ...)` | Calculated from other items via a lambda |
116
+ | `ScalarLine(value, ...)` | A single value shared across all periods |
117
+ | `InputLine(default, ...)` | Period-indexed values supplied at instantiation |
118
+ | `ScalarInputLine(default, ...)` | A single value supplied at instantiation |
119
+
120
+ Formula lambdas receive `li` (the model namespace) and `t` (the current period). Period-indexed items use `li.name[t]`; scalars use `li.name` (no `[t]`). Reference prior periods with `li.name[t-1]`.
121
+
122
+ ---
123
+
124
+ ## Scenario inputs
125
+
126
+ `InputLine` and `ScalarInputLine` let callers supply values at instantiation without subclassing again — useful for scenario analysis.
127
+
128
+ ```python
129
+ from pyproforma import InputLine, ScalarInputLine
130
+
131
+ class FlexModel(ProformaModel):
132
+ default_periods = [2024, 2025, 2026]
133
+
134
+ margin = ScalarInputLine(default=0.45, label="Gross Margin")
135
+ revenue = FixedLine(
136
+ values={2024: 500_000, 2025: 550_000, 2026: 605_000},
137
+ label="Revenue",
138
+ value_format=Format.CURRENCY_NO_DECIMALS,
139
+ )
140
+ gross_profit = FormulaLine(
141
+ formula=lambda li, t: li.revenue[t] * li.margin,
142
+ label="Gross Profit",
143
+ value_format=Format.CURRENCY_NO_DECIMALS,
144
+ )
145
+
146
+ base = FlexModel() # uses default margin of 0.45
147
+ upside = FlexModel(margin=0.52) # override at instantiation
148
+ ```
149
+
150
+ Use `model.compare()` to diff two instances:
151
+
152
+ ```python
153
+ comparison = base.compare(upside, labels=["Base", "Upside"])
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Time-series formulas
159
+
160
+ Seed a value in the first period and let the formula compound from there — no `if t == first_year` guards needed:
161
+
162
+ ```python
163
+ revenue = FormulaLine(
164
+ formula=lambda li, t: li.revenue[t-1] * (1 + li.growth_rate),
165
+ values={2024: 500_000}, # engine uses this for 2024; formula runs from 2025 onward
166
+ label="Revenue",
167
+ )
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Tags
173
+
174
+ Tag line items to group them without fixed categories:
175
+
176
+ ```python
177
+ water_sales = FixedLine(values={...}, tags=["revenue"])
178
+ power_sales = FixedLine(values={...}, tags=["revenue"])
179
+
180
+ # Sum all "revenue"-tagged items in a formula
181
+ total_revenue = FormulaLine(formula=lambda li, t: li.tag["revenue"][t])
182
+ ```
183
+
184
+ Tags also work in table templates:
185
+
186
+ ```python
187
+ from pyproforma import TagTotalRow
188
+ TagTotalRow(tag="revenue", label="Total Revenue")
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Tables
194
+
195
+ Generate formatted tables for HTML, Excel, or pandas. `from_template` gives full control over layout:
196
+
197
+ ```python
198
+ from pyproforma import HeaderRow, LabelRow, ItemRow, BlankRow, LineItemsTotalRow
199
+
200
+ table = model.tables.from_template([
201
+ HeaderRow(),
202
+ LabelRow("Income Statement"),
203
+ ItemRow("revenue"),
204
+ ItemRow("cogs", reverse_sign=True), # display as positive deduction
205
+ ItemRow("gross_profit", bold=True, borders="top"),
206
+ BlankRow(),
207
+ ItemRow("net_income", bold=True),
208
+ ])
209
+
210
+ table.show() # inline in Jupyter
211
+ table.to_excel("output.xlsx") # Excel with formatting preserved
212
+ table.to_dataframe() # pandas DataFrame
213
+ ```
214
+
215
+ Convenience builders for common layouts:
216
+
217
+ ```python
218
+ model.tables.line_items().show() # all line items
219
+ model.tables.line_item("net_income", include_percent_change=True).show()
220
+ model.tables.precedents("net_income").show() # formula dependency tree
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Charts
226
+
227
+ ```python
228
+ model.charts.line_item("net_income", chart_type="bar").show()
229
+ model.charts.line_items(["revenue", "gross_profit", "net_income"]).show()
230
+ ```
231
+
232
+ Charts return a `ChartSpec` which can also render to a matplotlib `Figure`:
233
+
234
+ ```python
235
+ fig = model.charts.line_item("net_income").figure()
236
+ ```
237
+
238
+ Requires `pip install pyproforma[charts]`.
239
+
240
+ ---
241
+
242
+ ## Number formatting
243
+
244
+ Named format constants flow through to both HTML and Excel output:
245
+
246
+ ```python
247
+ from pyproforma import Format
248
+
249
+ ItemRow("revenue", value_format=Format.CURRENCY_NO_DECIMALS) # $500,000
250
+ ItemRow("revenue", value_format=Format.THOUSANDS_K) # 500.0K
251
+ ItemRow("margin", value_format=Format.PERCENT_ONE_DECIMAL) # 45.0%
252
+ ItemRow("net_income", value_format=Format.MILLIONS_M) # $0.2M
253
+ ```
254
+
255
+ Custom formats via `NumberFormatSpec`:
256
+
257
+ ```python
258
+ from pyproforma import NumberFormatSpec
259
+
260
+ fmt = NumberFormatSpec(decimals=1, scale="millions", prefix="$", suffix="M")
261
+ # 500_000 → "$0.5M"
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Explorer
267
+
268
+ A lightweight Flask web app for browsing any model interactively:
269
+
270
+ ```python
271
+ from pyproforma.explorer import create_app
272
+
273
+ app = create_app(model)
274
+ app.run(debug=True)
275
+ ```
276
+
277
+ Requires `pip install pyproforma[explorer]`. The app shows all line items, their values, formula sources, and lets you update `InputLine` / `ScalarInputLine` values live. You can also pass named tables, charts, and views to build a richer dashboard.
278
+
279
+ ---
280
+
281
+ ## Installation
282
+
283
+ ```bash
284
+ pip install pyproforma # core only
285
+ pip install pyproforma[charts] # + matplotlib
286
+ pip install pyproforma[excel] # + openpyxl
287
+ pip install pyproforma[explorer] # + Flask
288
+ pip install pyproforma[pandas] # + pandas
289
+ ```
290
+
291
+ Requires Python 3.9+.
292
+
293
+ ---
294
+
295
+ ## Status
296
+
297
+ Active development. Core modeling, table export, charts, and the Flask explorer are all stable. Feedback welcome — open an issue on GitHub.
298
+
299
+ ## License
300
+
301
+ MIT
@@ -0,0 +1,260 @@
1
+ # pyproforma
2
+
3
+ A Python library for building financial models — a code-first alternative to Excel for pro formas, projections, and structured financial tables.
4
+
5
+ ```bash
6
+ pip install pyproforma
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Why
12
+
13
+ Spreadsheets are the default tool for financial modeling, but they have real problems: no version control, no testing, formulas hidden inside cells, and no easy way to generate the same model for multiple scenarios. pyproforma is designed for analysts who want the benefits of code — reproducibility, testability, version history — without giving up the tabular output that finance people actually use.
14
+
15
+ ---
16
+
17
+ ## How it works
18
+
19
+ Define a model by subclassing `ProformaModel` and declaring line items as class attributes. Instantiate it with a list of periods and the library calculates everything.
20
+
21
+ ```python
22
+ from pyproforma import ProformaModel, FixedLine, FormulaLine, ScalarLine, Format
23
+
24
+ class IncomeStatement(ProformaModel):
25
+ default_periods = [2024, 2025, 2026]
26
+
27
+ tax_rate = ScalarLine(value=0.21, label="Tax Rate")
28
+
29
+ revenue = FixedLine(
30
+ values={2024: 500_000, 2025: 550_000, 2026: 605_000},
31
+ label="Revenue",
32
+ tags=["operating"],
33
+ value_format=Format.CURRENCY_NO_DECIMALS,
34
+ )
35
+ cogs = FormulaLine(
36
+ formula=lambda li, t: li.revenue[t] * 0.55,
37
+ label="Cost of Goods Sold",
38
+ value_format=Format.CURRENCY_NO_DECIMALS,
39
+ )
40
+ gross_profit = FormulaLine(
41
+ formula=lambda li, t: li.revenue[t] - li.cogs[t],
42
+ label="Gross Profit",
43
+ value_format=Format.CURRENCY_NO_DECIMALS,
44
+ )
45
+ net_income = FormulaLine(
46
+ formula=lambda li, t: li.gross_profit[t] * (1 - li.tax_rate),
47
+ label="Net Income",
48
+ value_format=Format.CURRENCY_NO_DECIMALS,
49
+ )
50
+
51
+ model = IncomeStatement() # uses default_periods
52
+ ```
53
+
54
+ Access results with dot notation (primary) or bracket notation (useful when the name is in a variable):
55
+
56
+ ```python
57
+ model.net_income[2025] # 173_745.0 — dot notation
58
+ model["net_income"][2025] # same value — bracket notation
59
+
60
+ model.tax_rate.value # 0.21 — scalars have .value, not [t]
61
+
62
+ model.periods # [2024, 2025, 2026]
63
+ model.line_item_names # ["revenue", "cogs", "gross_profit", "net_income"]
64
+ model.scalar_names # ["tax_rate"]
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Line item types
70
+
71
+ | Type | Use |
72
+ |------|-----|
73
+ | `FixedLine(values, ...)` | Hardcoded values per period |
74
+ | `FormulaLine(formula, ...)` | Calculated from other items via a lambda |
75
+ | `ScalarLine(value, ...)` | A single value shared across all periods |
76
+ | `InputLine(default, ...)` | Period-indexed values supplied at instantiation |
77
+ | `ScalarInputLine(default, ...)` | A single value supplied at instantiation |
78
+
79
+ Formula lambdas receive `li` (the model namespace) and `t` (the current period). Period-indexed items use `li.name[t]`; scalars use `li.name` (no `[t]`). Reference prior periods with `li.name[t-1]`.
80
+
81
+ ---
82
+
83
+ ## Scenario inputs
84
+
85
+ `InputLine` and `ScalarInputLine` let callers supply values at instantiation without subclassing again — useful for scenario analysis.
86
+
87
+ ```python
88
+ from pyproforma import InputLine, ScalarInputLine
89
+
90
+ class FlexModel(ProformaModel):
91
+ default_periods = [2024, 2025, 2026]
92
+
93
+ margin = ScalarInputLine(default=0.45, label="Gross Margin")
94
+ revenue = FixedLine(
95
+ values={2024: 500_000, 2025: 550_000, 2026: 605_000},
96
+ label="Revenue",
97
+ value_format=Format.CURRENCY_NO_DECIMALS,
98
+ )
99
+ gross_profit = FormulaLine(
100
+ formula=lambda li, t: li.revenue[t] * li.margin,
101
+ label="Gross Profit",
102
+ value_format=Format.CURRENCY_NO_DECIMALS,
103
+ )
104
+
105
+ base = FlexModel() # uses default margin of 0.45
106
+ upside = FlexModel(margin=0.52) # override at instantiation
107
+ ```
108
+
109
+ Use `model.compare()` to diff two instances:
110
+
111
+ ```python
112
+ comparison = base.compare(upside, labels=["Base", "Upside"])
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Time-series formulas
118
+
119
+ Seed a value in the first period and let the formula compound from there — no `if t == first_year` guards needed:
120
+
121
+ ```python
122
+ revenue = FormulaLine(
123
+ formula=lambda li, t: li.revenue[t-1] * (1 + li.growth_rate),
124
+ values={2024: 500_000}, # engine uses this for 2024; formula runs from 2025 onward
125
+ label="Revenue",
126
+ )
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Tags
132
+
133
+ Tag line items to group them without fixed categories:
134
+
135
+ ```python
136
+ water_sales = FixedLine(values={...}, tags=["revenue"])
137
+ power_sales = FixedLine(values={...}, tags=["revenue"])
138
+
139
+ # Sum all "revenue"-tagged items in a formula
140
+ total_revenue = FormulaLine(formula=lambda li, t: li.tag["revenue"][t])
141
+ ```
142
+
143
+ Tags also work in table templates:
144
+
145
+ ```python
146
+ from pyproforma import TagTotalRow
147
+ TagTotalRow(tag="revenue", label="Total Revenue")
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Tables
153
+
154
+ Generate formatted tables for HTML, Excel, or pandas. `from_template` gives full control over layout:
155
+
156
+ ```python
157
+ from pyproforma import HeaderRow, LabelRow, ItemRow, BlankRow, LineItemsTotalRow
158
+
159
+ table = model.tables.from_template([
160
+ HeaderRow(),
161
+ LabelRow("Income Statement"),
162
+ ItemRow("revenue"),
163
+ ItemRow("cogs", reverse_sign=True), # display as positive deduction
164
+ ItemRow("gross_profit", bold=True, borders="top"),
165
+ BlankRow(),
166
+ ItemRow("net_income", bold=True),
167
+ ])
168
+
169
+ table.show() # inline in Jupyter
170
+ table.to_excel("output.xlsx") # Excel with formatting preserved
171
+ table.to_dataframe() # pandas DataFrame
172
+ ```
173
+
174
+ Convenience builders for common layouts:
175
+
176
+ ```python
177
+ model.tables.line_items().show() # all line items
178
+ model.tables.line_item("net_income", include_percent_change=True).show()
179
+ model.tables.precedents("net_income").show() # formula dependency tree
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Charts
185
+
186
+ ```python
187
+ model.charts.line_item("net_income", chart_type="bar").show()
188
+ model.charts.line_items(["revenue", "gross_profit", "net_income"]).show()
189
+ ```
190
+
191
+ Charts return a `ChartSpec` which can also render to a matplotlib `Figure`:
192
+
193
+ ```python
194
+ fig = model.charts.line_item("net_income").figure()
195
+ ```
196
+
197
+ Requires `pip install pyproforma[charts]`.
198
+
199
+ ---
200
+
201
+ ## Number formatting
202
+
203
+ Named format constants flow through to both HTML and Excel output:
204
+
205
+ ```python
206
+ from pyproforma import Format
207
+
208
+ ItemRow("revenue", value_format=Format.CURRENCY_NO_DECIMALS) # $500,000
209
+ ItemRow("revenue", value_format=Format.THOUSANDS_K) # 500.0K
210
+ ItemRow("margin", value_format=Format.PERCENT_ONE_DECIMAL) # 45.0%
211
+ ItemRow("net_income", value_format=Format.MILLIONS_M) # $0.2M
212
+ ```
213
+
214
+ Custom formats via `NumberFormatSpec`:
215
+
216
+ ```python
217
+ from pyproforma import NumberFormatSpec
218
+
219
+ fmt = NumberFormatSpec(decimals=1, scale="millions", prefix="$", suffix="M")
220
+ # 500_000 → "$0.5M"
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Explorer
226
+
227
+ A lightweight Flask web app for browsing any model interactively:
228
+
229
+ ```python
230
+ from pyproforma.explorer import create_app
231
+
232
+ app = create_app(model)
233
+ app.run(debug=True)
234
+ ```
235
+
236
+ Requires `pip install pyproforma[explorer]`. The app shows all line items, their values, formula sources, and lets you update `InputLine` / `ScalarInputLine` values live. You can also pass named tables, charts, and views to build a richer dashboard.
237
+
238
+ ---
239
+
240
+ ## Installation
241
+
242
+ ```bash
243
+ pip install pyproforma # core only
244
+ pip install pyproforma[charts] # + matplotlib
245
+ pip install pyproforma[excel] # + openpyxl
246
+ pip install pyproforma[explorer] # + Flask
247
+ pip install pyproforma[pandas] # + pandas
248
+ ```
249
+
250
+ Requires Python 3.9+.
251
+
252
+ ---
253
+
254
+ ## Status
255
+
256
+ Active development. Core modeling, table export, charts, and the Flask explorer are all stable. Feedback welcome — open an issue on GitHub.
257
+
258
+ ## License
259
+
260
+ MIT
@@ -3,7 +3,26 @@ PyProforma - A lightweight financial modeling framework.
3
3
  """
4
4
 
5
5
  from .charts.chart_def import ChartDef
6
- from .tables.table_def import TableDef
6
+ from .compare import ModelComparison
7
+ from .engine import LineItemValue, LineItemValues
8
+ from .proforma_model import ProformaModel
9
+ from .results import LineItemResult, LineItemSelection, ScalarResult
10
+ from .results.tags_namespace import TagNamespace
11
+ from .specs import (
12
+ DebtCalculator,
13
+ DebtConfig,
14
+ DebtInterestLine,
15
+ DebtPrincipalLine,
16
+ FixedLine,
17
+ FormulaLine,
18
+ InputLine,
19
+ LineItem,
20
+ ScalarInputLine,
21
+ ScalarLine,
22
+ create_debt_lines,
23
+ )
24
+ from .table import Format, NumberFormatSpec
25
+ from .tables import Tables
7
26
  from .tables.row_types import (
8
27
  BlankRow,
9
28
  CumulativeChangeRow,
@@ -16,25 +35,7 @@ from .tables.row_types import (
16
35
  TagItemsRow,
17
36
  TagTotalRow,
18
37
  )
19
- from .line_items import (
20
- DebtCalculator,
21
- DebtInterestLine,
22
- DebtPrincipalLine,
23
- FixedLine,
24
- FormulaLine,
25
- InputLine,
26
- LineItem,
27
- LineItemResult,
28
- LineItemSelection,
29
- LineItemValue,
30
- LineItemValues,
31
- create_debt_lines,
32
- )
33
- from .table import Format, NumberFormatSpec
34
- from .compare import ModelComparison
35
- from .proforma_model import ProformaModel
36
- from .tables import Tables
37
- from .tags_namespace import ModelTagNamespace
38
+ from .tables.table_def import TableDef
38
39
 
39
40
  __all__ = [
40
41
  "ProformaModel",
@@ -53,16 +54,20 @@ __all__ = [
53
54
  "FixedLine",
54
55
  "FormulaLine",
55
56
  "InputLine",
57
+ "ScalarLine",
58
+ "ScalarInputLine",
59
+ "ScalarResult",
56
60
  "DebtPrincipalLine",
57
61
  "DebtInterestLine",
58
62
  "DebtCalculator",
63
+ "DebtConfig",
59
64
  "create_debt_lines",
60
65
  "LineItem",
61
66
  "LineItemValues",
62
67
  "LineItemValue",
63
68
  "LineItemResult",
64
69
  "LineItemSelection",
65
- "ModelTagNamespace",
70
+ "TagNamespace",
66
71
  "Tables",
67
72
  "ModelComparison",
68
73
  "Format",
@@ -7,8 +7,8 @@ chart types. Applies NumberFormatSpec to y-axis tick labels when set.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from pyproforma.chart.renderers.base import ChartRenderer
11
10
  from pyproforma.chart.chart import Chart as ChartSpec
11
+ from pyproforma.chart.renderers.base import ChartRenderer
12
12
 
13
13
 
14
14
  class MatplotlibRenderer(ChartRenderer):
@@ -26,7 +26,6 @@ class MatplotlibRenderer(ChartRenderer):
26
26
  matplotlib.figure.Figure
27
27
  """
28
28
  import matplotlib.pyplot as plt
29
- import numpy as np
30
29
 
31
30
  fig, ax = plt.subplots(figsize=figsize)
32
31
 
@@ -109,6 +108,7 @@ class MatplotlibRenderer(ChartRenderer):
109
108
  return
110
109
 
111
110
  from matplotlib.ticker import FuncFormatter
111
+
112
112
  from pyproforma.table.format_value import format_value
113
113
 
114
114
  fmt = spec.value_format
@@ -6,8 +6,7 @@ chart type, title, and optional per-series colors. Accepts either the dataclass
6
6
  form (IDE autocomplete, validation) or a plain dict (JSON-serializable configs).
7
7
  """
8
8
 
9
- from dataclasses import dataclass, field
10
- from typing import Union
9
+ from dataclasses import dataclass
11
10
 
12
11
  from pyproforma.chart.chart import ChartType
13
12
 
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING
15
15
  from pyproforma.chart.chart import Chart, ChartSeries, ChartType
16
16
 
17
17
  if TYPE_CHECKING:
18
+ from pyproforma.charts.chart_def import ChartDef
18
19
  from pyproforma.proforma_model import ProformaModel
19
20
 
20
21
 
@@ -113,7 +114,9 @@ class Charts:
113
114
  Examples:
114
115
  >>> model.charts.line_items(["revenue", "expenses"]).show()
115
116
  >>> model.charts.line_items(["revenue", "cogs"], chart_type="stacked_bar").show()
116
- >>> model.charts.line_items(["revenue", "expenses"], value_format=Format.MILLIONS_M).show()
117
+ >>> model.charts.line_items( # noqa: E501
118
+ ... ["revenue", "expenses"], value_format=Format.MILLIONS_M
119
+ ... ).show()
117
120
  """
118
121
  for name in names:
119
122
  self._validate_line_item(name)
@@ -293,7 +293,9 @@ class ModelComparison:
293
293
  for model, label in zip(self.models, self.labels):
294
294
  row = [Cell(value=label, align="left")]
295
295
  for period in self.common_periods:
296
- row.append(Cell(value=model.get_value(item_name, period), value_format=value_format))
296
+ row.append(
297
+ Cell(value=model.get_value(item_name, period), value_format=value_format)
298
+ )
297
299
  all_rows.append(row)
298
300
 
299
301
  # Difference row(s)