finflow-sankey 0.1.10__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 (50) hide show
  1. finflow_sankey-0.1.10/PKG-INFO +239 -0
  2. finflow_sankey-0.1.10/README.md +210 -0
  3. finflow_sankey-0.1.10/finflow_sankey/__init__.py +100 -0
  4. finflow_sankey-0.1.10/finflow_sankey/core/__init__.py +1 -0
  5. finflow_sankey-0.1.10/finflow_sankey/core/exceptions.py +107 -0
  6. finflow_sankey-0.1.10/finflow_sankey/core/graph.py +58 -0
  7. finflow_sankey-0.1.10/finflow_sankey/core/mapper.py +78 -0
  8. finflow_sankey-0.1.10/finflow_sankey/core/normalizer.py +86 -0
  9. finflow_sankey-0.1.10/finflow_sankey/core/palette.py +202 -0
  10. finflow_sankey-0.1.10/finflow_sankey/core/pipeline.py +295 -0
  11. finflow_sankey-0.1.10/finflow_sankey/core/schema.py +79 -0
  12. finflow_sankey-0.1.10/finflow_sankey/core/validator.py +184 -0
  13. finflow_sankey-0.1.10/finflow_sankey/examples/cash_flow_example.py +46 -0
  14. finflow_sankey-0.1.10/finflow_sankey/examples/income_statement_example.py +70 -0
  15. finflow_sankey-0.1.10/finflow_sankey/mappings/default_balance_sheet.yaml +24 -0
  16. finflow_sankey-0.1.10/finflow_sankey/mappings/default_cash_flow.yaml +27 -0
  17. finflow_sankey-0.1.10/finflow_sankey/mappings/default_income_statement.yaml +27 -0
  18. finflow_sankey-0.1.10/finflow_sankey/palettes/colorblind_safe.yaml +31 -0
  19. finflow_sankey-0.1.10/finflow_sankey/palettes/dark.yaml +31 -0
  20. finflow_sankey-0.1.10/finflow_sankey/palettes/default.yaml +31 -0
  21. finflow_sankey-0.1.10/finflow_sankey/palettes/minimal.yaml +31 -0
  22. finflow_sankey-0.1.10/finflow_sankey/palettes/monochrome.yaml +31 -0
  23. finflow_sankey-0.1.10/finflow_sankey/py.typed +0 -0
  24. finflow_sankey-0.1.10/finflow_sankey/renderers/__init__.py +1 -0
  25. finflow_sankey-0.1.10/finflow_sankey/renderers/base.py +17 -0
  26. finflow_sankey-0.1.10/finflow_sankey/renderers/plotly_renderer.py +203 -0
  27. finflow_sankey-0.1.10/finflow_sankey/templates/__init__.py +1 -0
  28. finflow_sankey-0.1.10/finflow_sankey/templates/balance_sheet.py +216 -0
  29. finflow_sankey-0.1.10/finflow_sankey/templates/base.py +25 -0
  30. finflow_sankey-0.1.10/finflow_sankey/templates/cash_flow.py +147 -0
  31. finflow_sankey-0.1.10/finflow_sankey/templates/income_statement.py +157 -0
  32. finflow_sankey-0.1.10/finflow_sankey/templates/multi_period.py +129 -0
  33. finflow_sankey-0.1.10/finflow_sankey.egg-info/PKG-INFO +239 -0
  34. finflow_sankey-0.1.10/finflow_sankey.egg-info/SOURCES.txt +48 -0
  35. finflow_sankey-0.1.10/finflow_sankey.egg-info/dependency_links.txt +1 -0
  36. finflow_sankey-0.1.10/finflow_sankey.egg-info/requires.txt +11 -0
  37. finflow_sankey-0.1.10/finflow_sankey.egg-info/top_level.txt +1 -0
  38. finflow_sankey-0.1.10/pyproject.toml +49 -0
  39. finflow_sankey-0.1.10/setup.cfg +4 -0
  40. finflow_sankey-0.1.10/tests/test_balance_sheet.py +63 -0
  41. finflow_sankey-0.1.10/tests/test_basic.py +82 -0
  42. finflow_sankey-0.1.10/tests/test_cash_flow.py +95 -0
  43. finflow_sankey-0.1.10/tests/test_dark_theme.py +27 -0
  44. finflow_sankey-0.1.10/tests/test_edge_cases.py +112 -0
  45. finflow_sankey-0.1.10/tests/test_export.py +53 -0
  46. finflow_sankey-0.1.10/tests/test_group_minor.py +69 -0
  47. finflow_sankey-0.1.10/tests/test_hover.py +67 -0
  48. finflow_sankey-0.1.10/tests/test_layout.py +62 -0
  49. finflow_sankey-0.1.10/tests/test_mapping.py +64 -0
  50. finflow_sankey-0.1.10/tests/test_multi_period.py +43 -0
@@ -0,0 +1,239 @@
1
+ Metadata-Version: 2.4
2
+ Name: finflow-sankey
3
+ Version: 0.1.10
4
+ Summary: Polars-first financial statement Sankey visualization library
5
+ Author: FinFlow Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/finflow/finflow-sankey
8
+ Project-URL: Repository, https://github.com/finflow/finflow-sankey
9
+ Keywords: sankey,finance,visualization,polars,plotly
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Financial and Insurance Industry
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: polars>=0.20.0
21
+ Requires-Dist: plotly>=5.18.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
26
+ Provides-Extra: docs
27
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
28
+ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
29
+
30
+ # FinFlow Sankey
31
+
32
+ Polars-first financial statement Sankey visualization library.
33
+
34
+ ## Features
35
+
36
+ - **Polars-first**: Native support for `pl.DataFrame` and `pl.LazyFrame`
37
+ - **Accounting-aware validation**: Period/currency checks, reconciliation validation
38
+ - **Role-based color palette**: Revenue, costs, profit, cash flow each have distinct colors
39
+ - **Customizable themes**: default, monochrome, colorblind-safe, minimal, dark, plus custom YAML palettes
40
+ - **Runtime palette override**: Change colors via dict or YAML without modifying source
41
+ - **Account mapping**: Map raw account names to standard sections via dict or YAML
42
+ - **Consistent line styles**: Link widths proportional to values, but stroke widths uniform
43
+ - **Node layout**: Level-based horizontal positioning, including reconciliation side-by-side view
44
+ - **Rich hover metadata**: Original accounts, validation status, period, currency
45
+ - **HTML export**: One-line export helper
46
+ - **Multi-period comparison**: Compare two periods in a single Sankey
47
+ - **Plotly renderer**: Returns `plotly.graph_objects.Figure` for Jupyter, Streamlit, Dash, HTML export
48
+
49
+ ## Quickstart
50
+
51
+ ```python
52
+ import polars as pl
53
+ from finflow_sankey import FinancialSankey
54
+
55
+ df = pl.DataFrame({
56
+ "account": ["Revenue", "Cost of Revenue", "Operating Expenses", "Tax", "Net Income"],
57
+ "value": [100_000_000.0, -40_000_000.0, -30_000_000.0, -10_000_000.0, 20_000_000.0],
58
+ "period": ["FY2025"] * 5,
59
+ "currency": ["USD"] * 5,
60
+ "statement": ["income_statement"] * 5,
61
+ "section": ["revenue", "cost_of_revenue", "operating_expenses", "tax", "profit"],
62
+ })
63
+
64
+ fig = (
65
+ FinancialSankey
66
+ .income_statement(df, period="FY2025", currency="USD")
67
+ .validate()
68
+ .render(title="FY2025 Income Statement Flow")
69
+ )
70
+
71
+ fig.show()
72
+ ```
73
+
74
+ ### Cash Flow Statement
75
+
76
+ ```python
77
+ df = pl.DataFrame({
78
+ "account": [
79
+ "Beginning Cash",
80
+ "Operating Cash Flow",
81
+ "Investing Cash Flow",
82
+ "Financing Cash Flow",
83
+ "FX Effect",
84
+ "Ending Cash",
85
+ ],
86
+ "value": [50_000_000.0, 25_000_000.0, -10_000_000.0, -5_000_000.0, 2_000_000.0, 62_000_000.0],
87
+ "period": ["FY2025"] * 6,
88
+ "currency": ["USD"] * 6,
89
+ "statement": ["cash_flow_statement"] * 6,
90
+ "section": [
91
+ "beginning_cash",
92
+ "operating_cash_flow",
93
+ "investing_cash_flow",
94
+ "financing_cash_flow",
95
+ "fx_effect",
96
+ "ending_cash",
97
+ ],
98
+ })
99
+
100
+ fig = (
101
+ FinancialSankey
102
+ .cash_flow_statement(df, period="FY2025", currency="USD")
103
+ .validate()
104
+ .render(title="FY2025 Cash Flow Bridge")
105
+ )
106
+ ```
107
+
108
+ ### Balance Sheet Reconciliation
109
+
110
+ ```python
111
+ df = pl.DataFrame({
112
+ "account": [
113
+ "Current Assets",
114
+ "Non-current Assets",
115
+ "Current Liabilities",
116
+ "Non-current Liabilities",
117
+ "Equity",
118
+ ],
119
+ "value": [60_000_000.0, 40_000_000.0, 30_000_000.0, 20_000_000.0, 50_000_000.0],
120
+ "period": ["2025-12-31"] * 5,
121
+ "currency": ["USD"] * 5,
122
+ "statement": ["balance_sheet"] * 5,
123
+ "section": [
124
+ "current_asset",
125
+ "non_current_asset",
126
+ "current_liability",
127
+ "non_current_liability",
128
+ "equity",
129
+ ],
130
+ })
131
+
132
+ fig = (
133
+ FinancialSankey
134
+ .balance_sheet_reconciliation(df, as_of="2025-12-31", currency="USD")
135
+ .validate()
136
+ .render(title="Balance Sheet Reconciliation")
137
+ )
138
+ ```
139
+
140
+ ### Multi-Period Comparison
141
+
142
+ ```python
143
+ df = pl.DataFrame({
144
+ "account": ["Revenue", "Revenue", "Operating Expenses", "Operating Expenses"],
145
+ "value": [100_000_000.0, 120_000_000.0, -40_000_000.0, -50_000_000.0],
146
+ "period": ["FY2024", "FY2025", "FY2024", "FY2025"],
147
+ "currency": ["USD"] * 4,
148
+ "statement": ["income_statement"] * 4,
149
+ "section": ["revenue", "revenue", "expense", "expense"],
150
+ })
151
+
152
+ fig = (
153
+ FinancialSankey
154
+ .multi_period_compare(df, currency="USD")
155
+ .validate()
156
+ .render(title="FY2024 vs FY2025")
157
+ )
158
+ ```
159
+
160
+ ## Account Mapping
161
+
162
+ Map raw account names to standard sections via dict or YAML:
163
+
164
+ ```python
165
+ mapping = {
166
+ "revenue": ["Net Sales", "Sales Revenue"],
167
+ "cost_of_revenue": ["COGS", "Cost of Goods Sold"],
168
+ "operating_expenses": ["SG&A", "R&D"],
169
+ "tax": ["Income Tax Expense"],
170
+ "profit": ["Net Income"],
171
+ }
172
+
173
+ fig = FinancialSankey.income_statement(df, mapping=mapping).validate().render()
174
+ ```
175
+
176
+ ## Themes & Palettes
177
+
178
+ ```python
179
+ # Built-in theme
180
+ fig = FinancialSankey.income_statement(df).validate().render(theme="colorblind_safe")
181
+
182
+ # Dark mode
183
+ fig = FinancialSankey.income_statement(df).validate().render(theme="dark")
184
+
185
+ # Custom YAML palette
186
+ fig = FinancialSankey.income_statement(df).validate().render(palette="./my_palette.yaml")
187
+
188
+ # Runtime dict override
189
+ fig = FinancialSankey.income_statement(df).validate().render(
190
+ palette={"revenue": "#0055FF", "profit": "#00AA55"}
191
+ )
192
+ ```
193
+
194
+ ## HTML Export
195
+
196
+ ```python
197
+ (
198
+ FinancialSankey
199
+ .income_statement(df)
200
+ .validate()
201
+ .export_html("income_statement.html", title="FY2025 Income Statement")
202
+ )
203
+ ```
204
+
205
+ ## Installation
206
+
207
+ ```bash
208
+ pip install finflow-sankey
209
+ ```
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ pip install -e ".[dev]"
215
+ ```
216
+
217
+ ## Development
218
+
219
+ ```bash
220
+ python -m pytest tests/ -v
221
+ ruff check finflow_sankey tests
222
+ ```
223
+
224
+ ## Project Structure
225
+
226
+ ```
227
+ finflow_sankey/
228
+ core/ # schema, validation, normalization, graph, palette, mapper
229
+ templates/ # income_statement, cash_flow, balance_sheet, multi_period
230
+ renderers/ # plotly renderer
231
+ palettes/ # YAML color palettes
232
+ mappings/ # YAML account mappings
233
+ examples/ # usage examples
234
+ tests/ # pytest suite
235
+ ```
236
+
237
+ ## License
238
+
239
+ MIT
@@ -0,0 +1,210 @@
1
+ # FinFlow Sankey
2
+
3
+ Polars-first financial statement Sankey visualization library.
4
+
5
+ ## Features
6
+
7
+ - **Polars-first**: Native support for `pl.DataFrame` and `pl.LazyFrame`
8
+ - **Accounting-aware validation**: Period/currency checks, reconciliation validation
9
+ - **Role-based color palette**: Revenue, costs, profit, cash flow each have distinct colors
10
+ - **Customizable themes**: default, monochrome, colorblind-safe, minimal, dark, plus custom YAML palettes
11
+ - **Runtime palette override**: Change colors via dict or YAML without modifying source
12
+ - **Account mapping**: Map raw account names to standard sections via dict or YAML
13
+ - **Consistent line styles**: Link widths proportional to values, but stroke widths uniform
14
+ - **Node layout**: Level-based horizontal positioning, including reconciliation side-by-side view
15
+ - **Rich hover metadata**: Original accounts, validation status, period, currency
16
+ - **HTML export**: One-line export helper
17
+ - **Multi-period comparison**: Compare two periods in a single Sankey
18
+ - **Plotly renderer**: Returns `plotly.graph_objects.Figure` for Jupyter, Streamlit, Dash, HTML export
19
+
20
+ ## Quickstart
21
+
22
+ ```python
23
+ import polars as pl
24
+ from finflow_sankey import FinancialSankey
25
+
26
+ df = pl.DataFrame({
27
+ "account": ["Revenue", "Cost of Revenue", "Operating Expenses", "Tax", "Net Income"],
28
+ "value": [100_000_000.0, -40_000_000.0, -30_000_000.0, -10_000_000.0, 20_000_000.0],
29
+ "period": ["FY2025"] * 5,
30
+ "currency": ["USD"] * 5,
31
+ "statement": ["income_statement"] * 5,
32
+ "section": ["revenue", "cost_of_revenue", "operating_expenses", "tax", "profit"],
33
+ })
34
+
35
+ fig = (
36
+ FinancialSankey
37
+ .income_statement(df, period="FY2025", currency="USD")
38
+ .validate()
39
+ .render(title="FY2025 Income Statement Flow")
40
+ )
41
+
42
+ fig.show()
43
+ ```
44
+
45
+ ### Cash Flow Statement
46
+
47
+ ```python
48
+ df = pl.DataFrame({
49
+ "account": [
50
+ "Beginning Cash",
51
+ "Operating Cash Flow",
52
+ "Investing Cash Flow",
53
+ "Financing Cash Flow",
54
+ "FX Effect",
55
+ "Ending Cash",
56
+ ],
57
+ "value": [50_000_000.0, 25_000_000.0, -10_000_000.0, -5_000_000.0, 2_000_000.0, 62_000_000.0],
58
+ "period": ["FY2025"] * 6,
59
+ "currency": ["USD"] * 6,
60
+ "statement": ["cash_flow_statement"] * 6,
61
+ "section": [
62
+ "beginning_cash",
63
+ "operating_cash_flow",
64
+ "investing_cash_flow",
65
+ "financing_cash_flow",
66
+ "fx_effect",
67
+ "ending_cash",
68
+ ],
69
+ })
70
+
71
+ fig = (
72
+ FinancialSankey
73
+ .cash_flow_statement(df, period="FY2025", currency="USD")
74
+ .validate()
75
+ .render(title="FY2025 Cash Flow Bridge")
76
+ )
77
+ ```
78
+
79
+ ### Balance Sheet Reconciliation
80
+
81
+ ```python
82
+ df = pl.DataFrame({
83
+ "account": [
84
+ "Current Assets",
85
+ "Non-current Assets",
86
+ "Current Liabilities",
87
+ "Non-current Liabilities",
88
+ "Equity",
89
+ ],
90
+ "value": [60_000_000.0, 40_000_000.0, 30_000_000.0, 20_000_000.0, 50_000_000.0],
91
+ "period": ["2025-12-31"] * 5,
92
+ "currency": ["USD"] * 5,
93
+ "statement": ["balance_sheet"] * 5,
94
+ "section": [
95
+ "current_asset",
96
+ "non_current_asset",
97
+ "current_liability",
98
+ "non_current_liability",
99
+ "equity",
100
+ ],
101
+ })
102
+
103
+ fig = (
104
+ FinancialSankey
105
+ .balance_sheet_reconciliation(df, as_of="2025-12-31", currency="USD")
106
+ .validate()
107
+ .render(title="Balance Sheet Reconciliation")
108
+ )
109
+ ```
110
+
111
+ ### Multi-Period Comparison
112
+
113
+ ```python
114
+ df = pl.DataFrame({
115
+ "account": ["Revenue", "Revenue", "Operating Expenses", "Operating Expenses"],
116
+ "value": [100_000_000.0, 120_000_000.0, -40_000_000.0, -50_000_000.0],
117
+ "period": ["FY2024", "FY2025", "FY2024", "FY2025"],
118
+ "currency": ["USD"] * 4,
119
+ "statement": ["income_statement"] * 4,
120
+ "section": ["revenue", "revenue", "expense", "expense"],
121
+ })
122
+
123
+ fig = (
124
+ FinancialSankey
125
+ .multi_period_compare(df, currency="USD")
126
+ .validate()
127
+ .render(title="FY2024 vs FY2025")
128
+ )
129
+ ```
130
+
131
+ ## Account Mapping
132
+
133
+ Map raw account names to standard sections via dict or YAML:
134
+
135
+ ```python
136
+ mapping = {
137
+ "revenue": ["Net Sales", "Sales Revenue"],
138
+ "cost_of_revenue": ["COGS", "Cost of Goods Sold"],
139
+ "operating_expenses": ["SG&A", "R&D"],
140
+ "tax": ["Income Tax Expense"],
141
+ "profit": ["Net Income"],
142
+ }
143
+
144
+ fig = FinancialSankey.income_statement(df, mapping=mapping).validate().render()
145
+ ```
146
+
147
+ ## Themes & Palettes
148
+
149
+ ```python
150
+ # Built-in theme
151
+ fig = FinancialSankey.income_statement(df).validate().render(theme="colorblind_safe")
152
+
153
+ # Dark mode
154
+ fig = FinancialSankey.income_statement(df).validate().render(theme="dark")
155
+
156
+ # Custom YAML palette
157
+ fig = FinancialSankey.income_statement(df).validate().render(palette="./my_palette.yaml")
158
+
159
+ # Runtime dict override
160
+ fig = FinancialSankey.income_statement(df).validate().render(
161
+ palette={"revenue": "#0055FF", "profit": "#00AA55"}
162
+ )
163
+ ```
164
+
165
+ ## HTML Export
166
+
167
+ ```python
168
+ (
169
+ FinancialSankey
170
+ .income_statement(df)
171
+ .validate()
172
+ .export_html("income_statement.html", title="FY2025 Income Statement")
173
+ )
174
+ ```
175
+
176
+ ## Installation
177
+
178
+ ```bash
179
+ pip install finflow-sankey
180
+ ```
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ pip install -e ".[dev]"
186
+ ```
187
+
188
+ ## Development
189
+
190
+ ```bash
191
+ python -m pytest tests/ -v
192
+ ruff check finflow_sankey tests
193
+ ```
194
+
195
+ ## Project Structure
196
+
197
+ ```
198
+ finflow_sankey/
199
+ core/ # schema, validation, normalization, graph, palette, mapper
200
+ templates/ # income_statement, cash_flow, balance_sheet, multi_period
201
+ renderers/ # plotly renderer
202
+ palettes/ # YAML color palettes
203
+ mappings/ # YAML account mappings
204
+ examples/ # usage examples
205
+ tests/ # pytest suite
206
+ ```
207
+
208
+ ## License
209
+
210
+ MIT
@@ -0,0 +1,100 @@
1
+ """FinFlow Sankey: Polars-first financial statement Sankey visualization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import polars as pl
9
+
10
+ from finflow_sankey.core.mapper import AccountMapper
11
+ from finflow_sankey.core.pipeline import SankeyPipeline
12
+ from finflow_sankey.templates.income_statement import IncomeStatementTemplate
13
+
14
+
15
+ class FinancialSankey:
16
+ """Main entry point for FinFlow Sankey."""
17
+
18
+ @classmethod
19
+ def income_statement(
20
+ cls,
21
+ data: pl.DataFrame | pl.LazyFrame,
22
+ *,
23
+ period: str | None = None,
24
+ currency: str | None = None,
25
+ mapping: AccountMapper | dict[str, Any] | str | Path | None = None,
26
+ ) -> SankeyPipeline:
27
+ """Create an income statement Sankey pipeline."""
28
+ template = IncomeStatementTemplate()
29
+ return SankeyPipeline(
30
+ data=data,
31
+ template=template,
32
+ period=period,
33
+ currency=currency,
34
+ mapping=mapping,
35
+ )
36
+
37
+ @classmethod
38
+ def cash_flow_statement(
39
+ cls,
40
+ data: pl.DataFrame | pl.LazyFrame,
41
+ *,
42
+ period: str | None = None,
43
+ currency: str | None = None,
44
+ mapping: AccountMapper | dict[str, Any] | str | Path | None = None,
45
+ ) -> SankeyPipeline:
46
+ """Create a cash flow statement Sankey pipeline."""
47
+ from finflow_sankey.templates.cash_flow import CashFlowStatementTemplate
48
+
49
+ template = CashFlowStatementTemplate()
50
+ return SankeyPipeline(
51
+ data=data,
52
+ template=template,
53
+ period=period,
54
+ currency=currency,
55
+ mapping=mapping,
56
+ )
57
+
58
+ @classmethod
59
+ def balance_sheet_reconciliation(
60
+ cls,
61
+ data: pl.DataFrame | pl.LazyFrame,
62
+ *,
63
+ as_of: str | None = None,
64
+ currency: str | None = None,
65
+ mapping: AccountMapper | dict[str, Any] | str | Path | None = None,
66
+ ) -> SankeyPipeline:
67
+ """Create a balance sheet reconciliation Sankey pipeline."""
68
+ from finflow_sankey.templates.balance_sheet import BalanceSheetReconciliationTemplate
69
+
70
+ template = BalanceSheetReconciliationTemplate()
71
+ return SankeyPipeline(
72
+ data=data,
73
+ template=template,
74
+ period=as_of,
75
+ currency=currency,
76
+ mapping=mapping,
77
+ )
78
+
79
+ @classmethod
80
+ def multi_period_compare(
81
+ cls,
82
+ data: pl.DataFrame | pl.LazyFrame,
83
+ *,
84
+ currency: str | None = None,
85
+ mapping: AccountMapper | dict[str, Any] | str | Path | None = None,
86
+ ) -> SankeyPipeline:
87
+ """Create a multi-period comparison Sankey pipeline."""
88
+ from finflow_sankey.templates.multi_period import MultiPeriodComparisonTemplate
89
+
90
+ template = MultiPeriodComparisonTemplate()
91
+ return SankeyPipeline(
92
+ data=data,
93
+ template=template,
94
+ period=None,
95
+ currency=currency,
96
+ mapping=mapping,
97
+ )
98
+
99
+
100
+ __all__ = ["FinancialSankey", "SankeyPipeline"]
@@ -0,0 +1 @@
1
+ """Core modules for FinFlow Sankey."""
@@ -0,0 +1,107 @@
1
+ """FinFlow Sankey custom exceptions."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class FinFlowError(Exception):
7
+ """Base exception for FinFlow Sankey."""
8
+
9
+ pass
10
+
11
+
12
+ class SchemaError(FinFlowError):
13
+ """Raised when input schema is invalid."""
14
+
15
+ pass
16
+
17
+
18
+ class MissingColumnError(SchemaError):
19
+ """Raised when a required column is missing."""
20
+
21
+ def __init__(self, column: str):
22
+ self.column = column
23
+ super().__init__(f"Required column '{column}' is missing from input data.")
24
+
25
+
26
+ class PeriodMismatchError(FinFlowError):
27
+ """Raised when multiple periods are detected."""
28
+
29
+ pass
30
+
31
+
32
+ class CurrencyMismatchError(FinFlowError):
33
+ """Raised when multiple currencies are detected."""
34
+
35
+ pass
36
+
37
+
38
+ class MissingAccountError(FinFlowError):
39
+ """Raised when a required account role is missing."""
40
+
41
+ def __init__(self, role: str, statement: str, available: list[str] | None = None):
42
+ self.role = role
43
+ self.statement = statement
44
+ self.available = available or []
45
+ msg = f"Required role '{role}' is missing for statement '{statement}'."
46
+ if self.available:
47
+ msg += f" Available accounts: {', '.join(self.available)}"
48
+ super().__init__(msg)
49
+
50
+
51
+ class ReconciliationError(FinFlowError):
52
+ """Raised when financial reconciliation fails."""
53
+
54
+ def __init__(
55
+ self,
56
+ rule: str,
57
+ expected: float,
58
+ actual: float,
59
+ difference: float,
60
+ period: str | None = None,
61
+ currency: str | None = None,
62
+ tolerance: float = 0.01,
63
+ ):
64
+ self.rule = rule
65
+ self.expected = expected
66
+ self.actual = actual
67
+ self.difference = difference
68
+ self.period = period
69
+ self.currency = currency
70
+ self.tolerance = tolerance
71
+ super().__init__(
72
+ f"Reconciliation failed: {rule}\n"
73
+ f" Expected: {expected:,.2f}\n"
74
+ f" Actual: {actual:,.2f}\n"
75
+ f" Difference: {difference:,.2f}\n"
76
+ f" Tolerance: {tolerance}"
77
+ )
78
+
79
+
80
+ class NullValueError(FinFlowError):
81
+ """Raised when null values are found in required fields."""
82
+
83
+ pass
84
+
85
+
86
+ class DuplicateAccountError(FinFlowError):
87
+ """Raised when duplicate accounts are detected."""
88
+
89
+ pass
90
+
91
+
92
+ class InvalidColorError(FinFlowError):
93
+ """Raised when an invalid color is provided."""
94
+
95
+ pass
96
+
97
+
98
+ class MissingRoleColorError(FinFlowError):
99
+ """Raised when a required role color is missing from palette."""
100
+
101
+ pass
102
+
103
+
104
+ class InvalidOpacityError(FinFlowError):
105
+ """Raised when opacity is out of valid range."""
106
+
107
+ pass