pyproforma 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {pyproforma-0.3.1/pyproforma.egg-info → pyproforma-0.3.2}/PKG-INFO +9 -1
  2. {pyproforma-0.3.1 → pyproforma-0.3.2}/README.md +6 -0
  3. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/charts/chart_def.py +9 -2
  4. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/charts/charts.py +114 -5
  5. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/components.py +2 -1
  6. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/excel.py +2 -0
  7. {pyproforma-0.3.1 → pyproforma-0.3.2/pyproforma.egg-info}/PKG-INFO +9 -1
  8. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma.egg-info/requires.txt +3 -0
  9. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproject.toml +4 -1
  10. {pyproforma-0.3.1 → pyproforma-0.3.2}/LICENSE +0 -0
  11. {pyproforma-0.3.1 → pyproforma-0.3.2}/MANIFEST.in +0 -0
  12. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/__init__.py +0 -0
  13. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/chart/__init__.py +0 -0
  14. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/chart/chart.py +0 -0
  15. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/chart/renderers/__init__.py +0 -0
  16. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/chart/renderers/base.py +0 -0
  17. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/chart/renderers/matplotlib_renderer.py +0 -0
  18. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/charts/__init__.py +0 -0
  19. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/cli.py +0 -0
  20. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/compare/__init__.py +0 -0
  21. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/compare/model_comparison.py +0 -0
  22. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/engine/__init__.py +0 -0
  23. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/engine/calculation_engine.py +0 -0
  24. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/engine/line_item_values.py +0 -0
  25. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/engine/model_namespace.py +0 -0
  26. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/__init__.py +0 -0
  27. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/app.py +0 -0
  28. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/compare_defs.py +0 -0
  29. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/config.py +0 -0
  30. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/scenario_app.py +0 -0
  31. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/base.html +0 -0
  32. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/chart_view.html +0 -0
  33. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/compare_item.html +0 -0
  34. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/compare_items.html +0 -0
  35. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/compare_overview.html +0 -0
  36. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/index.html +0 -0
  37. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/line_item.html +0 -0
  38. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/table_view.html +0 -0
  39. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/explorer/templates/view.html +0 -0
  40. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/proforma_model.py +0 -0
  41. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/reserved_words.py +0 -0
  42. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/__init__.py +0 -0
  43. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/line_item_result.py +0 -0
  44. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/line_item_selection.py +0 -0
  45. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/line_item_stat.py +0 -0
  46. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/scalar_result.py +0 -0
  47. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/results/tags_namespace.py +0 -0
  48. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/__init__.py +0 -0
  49. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/debt_line.py +0 -0
  50. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/fixed_line.py +0 -0
  51. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/formula_line.py +0 -0
  52. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/input_line.py +0 -0
  53. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/line_item.py +0 -0
  54. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/scalar_input_line.py +0 -0
  55. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/specs/scalar_line.py +0 -0
  56. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/__init__.py +0 -0
  57. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/bootstrap_html_renderer.py +0 -0
  58. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/col_widths.py +0 -0
  59. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/colors.py +0 -0
  60. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/format_value.py +0 -0
  61. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/html_renderer.py +0 -0
  62. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/table/table_class.py +0 -0
  63. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/tables/__init__.py +0 -0
  64. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/tables/row_types.py +0 -0
  65. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/tables/table_def.py +0 -0
  66. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma/tables/tables.py +0 -0
  67. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma.egg-info/SOURCES.txt +0 -0
  68. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma.egg-info/dependency_links.txt +0 -0
  69. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma.egg-info/entry_points.txt +0 -0
  70. {pyproforma-0.3.1 → pyproforma-0.3.2}/pyproforma.egg-info/top_level.txt +0 -0
  71. {pyproforma-0.3.1 → pyproforma-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproforma
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A Python package for financial modeling and reporting
5
5
  Author-email: Robert Hannay <rhannay@gmail.com>
6
6
  Maintainer-email: Robert Hannay <rhannay@gmail.com>
@@ -33,6 +33,8 @@ Requires-Dist: ipython>=7.0.0; extra == "notebook"
33
33
  Provides-Extra: explorer
34
34
  Requires-Dist: flask>=2.0.0; extra == "explorer"
35
35
  Requires-Dist: PyYAML>=6.0.0; extra == "explorer"
36
+ Provides-Extra: all
37
+ Requires-Dist: pyproforma[charts,excel,explorer,notebook,pandas]; extra == "all"
36
38
  Provides-Extra: dev
37
39
  Requires-Dist: pytest>=6.0; extra == "dev"
38
40
  Requires-Dist: pytest-cov; extra == "dev"
@@ -230,6 +232,12 @@ model.charts.line_item("net_income", chart_type="bar").show()
230
232
  model.charts.line_items(["revenue", "gross_profit", "net_income"]).show()
231
233
  ```
232
234
 
235
+ Compare items with different units or scales by rebasing them all to 100 at a common period:
236
+
237
+ ```python
238
+ model.charts.indexed_line_items(["revenue", "headcount"]).show()
239
+ ```
240
+
233
241
  Charts return a `ChartSpec` which can also render to a matplotlib `Figure`:
234
242
 
235
243
  ```python
@@ -188,6 +188,12 @@ model.charts.line_item("net_income", chart_type="bar").show()
188
188
  model.charts.line_items(["revenue", "gross_profit", "net_income"]).show()
189
189
  ```
190
190
 
191
+ Compare items with different units or scales by rebasing them all to 100 at a common period:
192
+
193
+ ```python
194
+ model.charts.indexed_line_items(["revenue", "headcount"]).show()
195
+ ```
196
+
191
197
  Charts return a `ChartSpec` which can also render to a matplotlib `Figure`:
192
198
 
193
199
  ```python
@@ -2,8 +2,9 @@
2
2
  ChartDef — declarative definition of a chart to build from a model.
3
3
 
4
4
  Used with model.charts.build() to produce a Chart. Holds the line item names,
5
- chart type, title, and optional per-series colors. Accepts either the dataclass
6
- form (IDE autocomplete, validation) or a plain dict (JSON-serializable configs).
5
+ chart type, title, optional per-series colors, and an optional value transform
6
+ ("indexed"). Accepts either the dataclass form (IDE autocomplete, validation)
7
+ or a plain dict (JSON-serializable configs).
7
8
  """
8
9
 
9
10
  from dataclasses import dataclass
@@ -24,15 +25,19 @@ class ChartDef:
24
25
  >>> ChartDef(names=["revenue", "expenses"])
25
26
  >>> ChartDef(names=["revenue"], chart_type="bar", title="Revenue")
26
27
  >>> ChartDef(names=["revenue", "expenses"], colors=["#206bc4", "#d63939"])
28
+ >>> ChartDef(names=["revenue", "headcount"], transform="indexed")
27
29
 
28
30
  The dict form is equivalent:
29
31
  >>> {"names": ["revenue"], "chart_type": "bar", "colors": ["#206bc4"]}
32
+ >>> {"names": ["revenue", "headcount"], "transform": "indexed"}
30
33
  """
31
34
 
32
35
  names: list[str]
33
36
  chart_type: ChartType = "line"
34
37
  title: str | None = None
35
38
  colors: list[str] | None = None
39
+ transform: str | None = None
40
+ base_period: int | None = None
36
41
 
37
42
  @classmethod
38
43
  def from_dict(cls, data: dict) -> "ChartDef":
@@ -41,4 +46,6 @@ class ChartDef:
41
46
  chart_type=data.get("chart_type", "line"),
42
47
  title=data.get("title"),
43
48
  colors=data.get("colors"),
49
+ transform=data.get("transform"),
50
+ base_period=data.get("base_period"),
44
51
  )
@@ -10,14 +10,17 @@ This layer knows about ProformaModel; the Chart class beneath it does not.
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
- from typing import TYPE_CHECKING
13
+ from typing import TYPE_CHECKING, Literal
14
14
 
15
15
  from pyproforma.chart.chart import Chart, ChartSeries, ChartType
16
+ from pyproforma.table import Format
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from pyproforma.charts.chart_def import ChartDef
19
20
  from pyproforma.proforma_model import ProformaModel
20
21
 
22
+ Transform = Literal["indexed"]
23
+
21
24
 
22
25
  class Charts:
23
26
  """
@@ -92,6 +95,8 @@ class Charts:
92
95
  chart_type: ChartType = "line",
93
96
  title: str | None = None,
94
97
  value_format=None,
98
+ transform: Transform | None = None,
99
+ base_period: int | None = None,
95
100
  ) -> Chart:
96
101
  """
97
102
  Build a chart with one series per line item.
@@ -104,12 +109,25 @@ class Charts:
104
109
  chart_type: One of "line", "bar", "stacked_bar". Defaults to "line".
105
110
  title: Chart title. Defaults to None (no title).
106
111
  value_format: Override the auto-detected format for the y-axis.
112
+ Defaults to Format.NO_DECIMALS when transform="indexed".
113
+ transform: Optional value transform applied to every series before
114
+ charting. "indexed" rebases each series to 100 at base_period:
115
+ value[t] / value[base_period] * 100 — useful for comparing
116
+ items with different units or scales (e.g. revenue vs.
117
+ headcount) on a common axis. Defaults to None (raw values).
118
+ base_period: Reference period for transform="indexed". Defaults to
119
+ the model's first period. Raising if set without a transform
120
+ catches the likely mistake of forgetting transform="indexed".
107
121
 
108
122
  Returns:
109
123
  Chart ready for rendering.
110
124
 
111
125
  Raises:
112
- ValueError: If any line item doesn't exist in the model.
126
+ ValueError: If any line item doesn't exist in the model, transform
127
+ is not a recognized value, base_period is set without
128
+ transform, base_period is not one of the model's periods, or
129
+ (for transform="indexed") a series' base_period value is None
130
+ or 0.
113
131
 
114
132
  Examples:
115
133
  >>> model.charts.line_items(["revenue", "expenses"]).show()
@@ -117,32 +135,114 @@ class Charts:
117
135
  >>> model.charts.line_items( # noqa: E501
118
136
  ... ["revenue", "expenses"], value_format=Format.MILLIONS_M
119
137
  ... ).show()
138
+ >>> # Compare growth trajectories on a common scale
139
+ >>> model.charts.line_items(["revenue", "headcount"], transform="indexed").show()
120
140
  """
121
141
  for name in names:
122
142
  self._validate_line_item(name)
123
143
 
144
+ if transform is not None and transform != "indexed":
145
+ raise ValueError(f"Unrecognized transform {transform!r}. Valid values: 'indexed'.")
146
+ if base_period is not None and transform is None:
147
+ raise ValueError(
148
+ "base_period is only valid with transform='indexed'. "
149
+ "Did you forget to pass transform='indexed'?"
150
+ )
151
+
152
+ resolved_base_period = base_period
153
+ if transform == "indexed" and resolved_base_period is None:
154
+ resolved_base_period = self._model.periods[0]
155
+
124
156
  series = []
125
157
  for name in names:
126
158
  result = self._model[name]
159
+ y_values = [result[p] for p in self._model.periods]
160
+ if transform == "indexed":
161
+ y_values = self._index_values(name, y_values, resolved_base_period)
127
162
  series.append(
128
163
  ChartSeries(
129
164
  label=result.label or name,
130
165
  x_values=list(self._model.periods),
131
- y_values=[result[p] for p in self._model.periods],
166
+ y_values=y_values,
132
167
  )
133
168
  )
134
169
 
135
170
  if value_format is None:
136
- formats = [self._model[n].value_format for n in names]
137
- value_format = formats[0] if len(set(formats)) == 1 else None
171
+ if transform == "indexed":
172
+ value_format = Format.NO_DECIMALS
173
+ else:
174
+ formats = [self._model[n].value_format for n in names]
175
+ value_format = formats[0] if len(set(formats)) == 1 else None
176
+
177
+ y_label = f"Index (Base = 100, {resolved_base_period})" if transform == "indexed" else None
138
178
 
139
179
  return Chart(
140
180
  series=series,
141
181
  chart_type=chart_type,
142
182
  title=title,
143
183
  value_format=value_format,
184
+ y_label=y_label,
144
185
  )
145
186
 
187
+ def indexed_line_items(
188
+ self,
189
+ names: list[str],
190
+ base_period: int | None = None,
191
+ title: str | None = None,
192
+ value_format=None,
193
+ ) -> Chart:
194
+ """
195
+ Build a line chart with every series rebased to 100 at base_period.
196
+
197
+ Convenience wrapper for line_items(names, transform="indexed", ...).
198
+ Lets you compare line items with different units or scales (e.g.
199
+ revenue in dollars vs. headcount) on a common relative axis.
200
+
201
+ Args:
202
+ names: List of line item names to include as series.
203
+ base_period: Reference period; value[t] / value[base_period] * 100.
204
+ Defaults to the model's first period.
205
+ title: Chart title. Defaults to None (no title).
206
+ value_format: Override the default (Format.NO_DECIMALS) y-axis format.
207
+
208
+ Returns:
209
+ Chart ready for rendering.
210
+
211
+ Raises:
212
+ ValueError: If any line item doesn't exist in the model, base_period
213
+ is not one of the model's periods, or a series' base_period
214
+ value is None or 0.
215
+
216
+ Examples:
217
+ >>> model.charts.indexed_line_items(["revenue", "headcount"]).show()
218
+ >>> model.charts.indexed_line_items(["revenue"], base_period=2025).show()
219
+ """
220
+ return self.line_items(
221
+ names,
222
+ title=title,
223
+ value_format=value_format,
224
+ transform="indexed",
225
+ base_period=base_period,
226
+ )
227
+
228
+ def _index_values(
229
+ self, name: str, y_values: list[float | None], base_period: int
230
+ ) -> list[float | None]:
231
+ """Rebase a series to 100 at base_period. None stays None (a gap)."""
232
+ try:
233
+ base_index = self._model.periods.index(base_period)
234
+ except ValueError:
235
+ raise ValueError(
236
+ f"base_period {base_period} is not in model periods {self._model.periods}"
237
+ ) from None
238
+ base_value = y_values[base_index]
239
+ if not base_value:
240
+ raise ValueError(
241
+ f"Cannot index '{name}': base period {base_period} value is "
242
+ f"{base_value!r} (must be a non-zero number)."
243
+ )
244
+ return [v / base_value * 100 if v is not None else None for v in y_values]
245
+
146
246
  def build(self, template: "ChartDef | dict") -> Chart:
147
247
  """
148
248
  Build a Chart from a ChartDef or equivalent dict.
@@ -155,6 +255,10 @@ class Charts:
155
255
  - names (list[str]): Line item names to include as series.
156
256
  - chart_type (str, optional): "line", "bar", or "stacked_bar". Defaults to "line".
157
257
  - title (str, optional): Chart title.
258
+ - transform (str, optional): "indexed" to rebase every series to
259
+ 100 at base_period. Defaults to None (raw values).
260
+ - base_period (int, optional): Reference period for
261
+ transform="indexed". Defaults to the model's first period.
158
262
 
159
263
  Returns:
160
264
  Chart ready for rendering.
@@ -162,6 +266,9 @@ class Charts:
162
266
  Examples:
163
267
  >>> model.charts.from_template(ChartDef(names=["revenue", "expenses"]))
164
268
  >>> model.charts.from_template({"names": ["revenue"], "chart_type": "bar"})
269
+ >>> model.charts.from_template(
270
+ ... {"names": ["revenue", "headcount"], "transform": "indexed"}
271
+ ... )
165
272
  """
166
273
  from pyproforma.charts.chart_def import ChartDef
167
274
  if isinstance(template, dict):
@@ -170,6 +277,8 @@ class Charts:
170
277
  names=template.names,
171
278
  chart_type=template.chart_type,
172
279
  title=template.title,
280
+ transform=template.transform,
281
+ base_period=template.base_period,
173
282
  )
174
283
  if template.colors:
175
284
  for series, color in zip(chart_spec.series, template.colors):
@@ -20,7 +20,8 @@ class StatCard:
20
20
  Args:
21
21
  name: Line item name.
22
22
  label: Display label. Defaults to the line item's own label.
23
- aggregation: One of "min", "max", "latest", "first", "sum", "avg", "cagr". Defaults to "latest".
23
+ aggregation: One of "min", "max", "latest", "first", "sum", "avg", "cagr".
24
+ Defaults to "latest".
24
25
  value_format: Optional format override. Uses the line item's format if not set.
25
26
  start: Optional start period for the aggregation range.
26
27
  end: Optional end period for the aggregation range.
@@ -1,6 +1,8 @@
1
1
  from typing import TYPE_CHECKING, Optional, Union
2
2
 
3
3
  if TYPE_CHECKING:
4
+ from io import BytesIO
5
+
4
6
  from .table_class import Table
5
7
 
6
8
  from .colors import color_to_rgb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyproforma
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A Python package for financial modeling and reporting
5
5
  Author-email: Robert Hannay <rhannay@gmail.com>
6
6
  Maintainer-email: Robert Hannay <rhannay@gmail.com>
@@ -33,6 +33,8 @@ Requires-Dist: ipython>=7.0.0; extra == "notebook"
33
33
  Provides-Extra: explorer
34
34
  Requires-Dist: flask>=2.0.0; extra == "explorer"
35
35
  Requires-Dist: PyYAML>=6.0.0; extra == "explorer"
36
+ Provides-Extra: all
37
+ Requires-Dist: pyproforma[charts,excel,explorer,notebook,pandas]; extra == "all"
36
38
  Provides-Extra: dev
37
39
  Requires-Dist: pytest>=6.0; extra == "dev"
38
40
  Requires-Dist: pytest-cov; extra == "dev"
@@ -230,6 +232,12 @@ model.charts.line_item("net_income", chart_type="bar").show()
230
232
  model.charts.line_items(["revenue", "gross_profit", "net_income"]).show()
231
233
  ```
232
234
 
235
+ Compare items with different units or scales by rebasing them all to 100 at a common period:
236
+
237
+ ```python
238
+ model.charts.indexed_line_items(["revenue", "headcount"]).show()
239
+ ```
240
+
233
241
  Charts return a `ChartSpec` which can also render to a matplotlib `Figure`:
234
242
 
235
243
  ```python
@@ -1,4 +1,7 @@
1
1
 
2
+ [all]
3
+ pyproforma[charts,excel,explorer,notebook,pandas]
4
+
2
5
  [charts]
3
6
  matplotlib>=3.5.0
4
7
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pyproforma"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "A Python package for financial modeling and reporting"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -55,6 +55,9 @@ explorer = [
55
55
  "flask>=2.0.0",
56
56
  "PyYAML>=6.0.0",
57
57
  ]
58
+ all = [
59
+ "pyproforma[pandas,excel,charts,notebook,explorer]",
60
+ ]
58
61
  dev = [
59
62
  "pytest>=6.0",
60
63
  "pytest-cov",
File without changes
File without changes
File without changes
File without changes