panelbox 0.2.0__py3-none-any.whl

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 (90) hide show
  1. panelbox/__init__.py +67 -0
  2. panelbox/__version__.py +14 -0
  3. panelbox/cli/__init__.py +0 -0
  4. panelbox/cli/{commands}/__init__.py +0 -0
  5. panelbox/core/__init__.py +0 -0
  6. panelbox/core/base_model.py +164 -0
  7. panelbox/core/formula_parser.py +318 -0
  8. panelbox/core/panel_data.py +387 -0
  9. panelbox/core/results.py +366 -0
  10. panelbox/datasets/__init__.py +0 -0
  11. panelbox/datasets/{data}/__init__.py +0 -0
  12. panelbox/gmm/__init__.py +65 -0
  13. panelbox/gmm/difference_gmm.py +645 -0
  14. panelbox/gmm/estimator.py +562 -0
  15. panelbox/gmm/instruments.py +580 -0
  16. panelbox/gmm/results.py +550 -0
  17. panelbox/gmm/system_gmm.py +621 -0
  18. panelbox/gmm/tests.py +535 -0
  19. panelbox/models/__init__.py +11 -0
  20. panelbox/models/dynamic/__init__.py +0 -0
  21. panelbox/models/iv/__init__.py +0 -0
  22. panelbox/models/static/__init__.py +13 -0
  23. panelbox/models/static/fixed_effects.py +516 -0
  24. panelbox/models/static/pooled_ols.py +298 -0
  25. panelbox/models/static/random_effects.py +512 -0
  26. panelbox/report/__init__.py +61 -0
  27. panelbox/report/asset_manager.py +410 -0
  28. panelbox/report/css_manager.py +472 -0
  29. panelbox/report/exporters/__init__.py +15 -0
  30. panelbox/report/exporters/html_exporter.py +440 -0
  31. panelbox/report/exporters/latex_exporter.py +510 -0
  32. panelbox/report/exporters/markdown_exporter.py +446 -0
  33. panelbox/report/renderers/__init__.py +11 -0
  34. panelbox/report/renderers/static/__init__.py +0 -0
  35. panelbox/report/renderers/static_validation_renderer.py +341 -0
  36. panelbox/report/report_manager.py +502 -0
  37. panelbox/report/template_manager.py +337 -0
  38. panelbox/report/transformers/__init__.py +0 -0
  39. panelbox/report/transformers/static/__init__.py +0 -0
  40. panelbox/report/validation_transformer.py +449 -0
  41. panelbox/standard_errors/__init__.py +0 -0
  42. panelbox/templates/__init__.py +0 -0
  43. panelbox/templates/assets/css/base_styles.css +382 -0
  44. panelbox/templates/assets/css/report_components.css +747 -0
  45. panelbox/templates/assets/js/tab-navigation.js +161 -0
  46. panelbox/templates/assets/js/utils.js +276 -0
  47. panelbox/templates/common/footer.html +24 -0
  48. panelbox/templates/common/header.html +44 -0
  49. panelbox/templates/common/meta.html +5 -0
  50. panelbox/templates/validation/interactive/index.html +272 -0
  51. panelbox/templates/validation/interactive/partials/charts.html +58 -0
  52. panelbox/templates/validation/interactive/partials/methodology.html +201 -0
  53. panelbox/templates/validation/interactive/partials/overview.html +146 -0
  54. panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
  55. panelbox/templates/validation/interactive/partials/test_results.html +231 -0
  56. panelbox/utils/__init__.py +0 -0
  57. panelbox/utils/formatting.py +172 -0
  58. panelbox/utils/matrix_ops.py +233 -0
  59. panelbox/utils/statistical.py +173 -0
  60. panelbox/validation/__init__.py +58 -0
  61. panelbox/validation/base.py +175 -0
  62. panelbox/validation/cointegration/__init__.py +0 -0
  63. panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
  64. panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
  65. panelbox/validation/cross_sectional_dependence/frees.py +297 -0
  66. panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
  67. panelbox/validation/heteroskedasticity/__init__.py +13 -0
  68. panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
  69. panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
  70. panelbox/validation/heteroskedasticity/white.py +208 -0
  71. panelbox/validation/instruments/__init__.py +0 -0
  72. panelbox/validation/robustness/__init__.py +0 -0
  73. panelbox/validation/serial_correlation/__init__.py +13 -0
  74. panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
  75. panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
  76. panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
  77. panelbox/validation/specification/__init__.py +16 -0
  78. panelbox/validation/specification/chow.py +273 -0
  79. panelbox/validation/specification/hausman.py +264 -0
  80. panelbox/validation/specification/mundlak.py +331 -0
  81. panelbox/validation/specification/reset.py +273 -0
  82. panelbox/validation/unit_root/__init__.py +0 -0
  83. panelbox/validation/validation_report.py +257 -0
  84. panelbox/validation/validation_suite.py +401 -0
  85. panelbox-0.2.0.dist-info/METADATA +337 -0
  86. panelbox-0.2.0.dist-info/RECORD +90 -0
  87. panelbox-0.2.0.dist-info/WHEEL +5 -0
  88. panelbox-0.2.0.dist-info/entry_points.txt +2 -0
  89. panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
  90. panelbox-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,337 @@
1
+ """
2
+ Template Manager for PanelBox Reports.
3
+
4
+ Manages loading, caching, and rendering of Jinja2 templates.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional
10
+ import datetime
11
+
12
+ from jinja2 import Environment, FileSystemLoader, select_autoescape, Template
13
+
14
+
15
+ class TemplateManager:
16
+ """
17
+ Manages Jinja2 templates for report generation.
18
+
19
+ Provides template loading, caching, and custom filters/functions.
20
+
21
+ Parameters
22
+ ----------
23
+ template_dir : str or Path, optional
24
+ Directory containing templates. If None, uses package templates.
25
+ enable_cache : bool, default=True
26
+ Enable template caching for better performance.
27
+
28
+ Attributes
29
+ ----------
30
+ env : jinja2.Environment
31
+ Jinja2 environment instance
32
+ template_cache : dict
33
+ Cache for loaded templates
34
+
35
+ Examples
36
+ --------
37
+ >>> manager = TemplateManager()
38
+ >>> template = manager.get_template('validation/interactive/index.html')
39
+ >>> html = template.render(context)
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ template_dir: Optional[Path] = None,
45
+ enable_cache: bool = True
46
+ ):
47
+ """Initialize Template Manager."""
48
+ # Determine template directory
49
+ if template_dir is None:
50
+ # Use package templates
51
+ package_dir = Path(__file__).parent.parent
52
+ template_dir = package_dir / 'templates'
53
+ else:
54
+ template_dir = Path(template_dir)
55
+
56
+ if not template_dir.exists():
57
+ raise ValueError(f"Template directory does not exist: {template_dir}")
58
+
59
+ self.template_dir = template_dir
60
+ self.enable_cache = enable_cache
61
+ self.template_cache: Dict[str, Template] = {}
62
+
63
+ # Create Jinja2 environment
64
+ self.env = Environment(
65
+ loader=FileSystemLoader(str(template_dir)),
66
+ autoescape=select_autoescape(['html', 'xml']),
67
+ trim_blocks=True,
68
+ lstrip_blocks=True,
69
+ enable_async=False
70
+ )
71
+
72
+ # Register custom filters
73
+ self._register_filters()
74
+
75
+ # Register custom globals
76
+ self._register_globals()
77
+
78
+ def _register_filters(self) -> None:
79
+ """Register custom Jinja2 filters."""
80
+ self.env.filters['number_format'] = self._filter_number_format
81
+ self.env.filters['pvalue_format'] = self._filter_pvalue_format
82
+ self.env.filters['percentage'] = self._filter_percentage
83
+ self.env.filters['significance_stars'] = self._filter_significance_stars
84
+ self.env.filters['round'] = self._filter_round
85
+
86
+ def _register_globals(self) -> None:
87
+ """Register custom Jinja2 global functions."""
88
+ self.env.globals['now'] = datetime.datetime.now
89
+ self.env.globals['range'] = range
90
+ self.env.globals['len'] = len
91
+ self.env.globals['enumerate'] = enumerate
92
+ self.env.globals['zip'] = zip
93
+
94
+ def get_template(self, template_path: str) -> Template:
95
+ """
96
+ Load a template by path.
97
+
98
+ Parameters
99
+ ----------
100
+ template_path : str
101
+ Relative path to template from template directory.
102
+ Example: 'validation/interactive/index.html'
103
+
104
+ Returns
105
+ -------
106
+ Template
107
+ Loaded Jinja2 template
108
+
109
+ Examples
110
+ --------
111
+ >>> template = manager.get_template('validation/interactive/index.html')
112
+ >>> html = template.render({'title': 'My Report'})
113
+ """
114
+ # Check cache first
115
+ if self.enable_cache and template_path in self.template_cache:
116
+ return self.template_cache[template_path]
117
+
118
+ # Load template
119
+ template = self.env.get_template(template_path)
120
+
121
+ # Cache if enabled
122
+ if self.enable_cache:
123
+ self.template_cache[template_path] = template
124
+
125
+ return template
126
+
127
+ def render_template(
128
+ self,
129
+ template_path: str,
130
+ context: Dict[str, Any]
131
+ ) -> str:
132
+ """
133
+ Load and render a template with context.
134
+
135
+ Parameters
136
+ ----------
137
+ template_path : str
138
+ Relative path to template
139
+ context : dict
140
+ Template context variables
141
+
142
+ Returns
143
+ -------
144
+ str
145
+ Rendered HTML
146
+
147
+ Examples
148
+ --------
149
+ >>> html = manager.render_template(
150
+ ... 'validation/interactive/index.html',
151
+ ... {'title': 'Report', 'data': {...}}
152
+ ... )
153
+ """
154
+ template = self.get_template(template_path)
155
+ return template.render(**context)
156
+
157
+ def render_string(self, template_string: str, context: Dict[str, Any]) -> str:
158
+ """
159
+ Render a template from string.
160
+
161
+ Parameters
162
+ ----------
163
+ template_string : str
164
+ Template content as string
165
+ context : dict
166
+ Template context variables
167
+
168
+ Returns
169
+ -------
170
+ str
171
+ Rendered HTML
172
+
173
+ Examples
174
+ --------
175
+ >>> html = manager.render_string(
176
+ ... '<h1>{{ title }}</h1>',
177
+ ... {'title': 'Hello'}
178
+ ... )
179
+ """
180
+ template = self.env.from_string(template_string)
181
+ return template.render(**context)
182
+
183
+ def clear_cache(self) -> None:
184
+ """Clear template cache."""
185
+ self.template_cache.clear()
186
+
187
+ # Custom Filters
188
+
189
+ @staticmethod
190
+ def _filter_number_format(value, decimals: int = 3) -> str:
191
+ """
192
+ Format a number with specified decimals.
193
+
194
+ Examples
195
+ --------
196
+ {{ value|number_format }} -> "123.456"
197
+ {{ value|number_format(2) }} -> "123.46"
198
+ """
199
+ if value is None or (isinstance(value, float) and value != value): # NaN check
200
+ return "N/A"
201
+ try:
202
+ return f"{float(value):,.{decimals}f}"
203
+ except (ValueError, TypeError):
204
+ return str(value)
205
+
206
+ @staticmethod
207
+ def _filter_pvalue_format(value) -> str:
208
+ """
209
+ Format a p-value with scientific notation if small.
210
+
211
+ Examples
212
+ --------
213
+ {{ 0.0432|pvalue_format }} -> "0.0432"
214
+ {{ 0.00001|pvalue_format }} -> "1.00e-05"
215
+ """
216
+ if value is None or (isinstance(value, float) and value != value):
217
+ return "N/A"
218
+ try:
219
+ value = float(value)
220
+ if value < 0.001:
221
+ return f"{value:.2e}"
222
+ return f"{value:.4f}"
223
+ except (ValueError, TypeError):
224
+ return str(value)
225
+
226
+ @staticmethod
227
+ def _filter_percentage(value, decimals: int = 2) -> str:
228
+ """
229
+ Format a value as percentage.
230
+
231
+ Examples
232
+ --------
233
+ {{ 0.1234|percentage }} -> "12.34%"
234
+ {{ 0.1234|percentage(1) }} -> "12.3%"
235
+ """
236
+ if value is None or (isinstance(value, float) and value != value):
237
+ return "N/A"
238
+ try:
239
+ return f"{float(value) * 100:.{decimals}f}%"
240
+ except (ValueError, TypeError):
241
+ return str(value)
242
+
243
+ @staticmethod
244
+ def _filter_significance_stars(pvalue) -> str:
245
+ """
246
+ Add significance stars based on p-value.
247
+
248
+ Examples
249
+ --------
250
+ {{ 0.001|significance_stars }} -> "***"
251
+ {{ 0.02|significance_stars }} -> "**"
252
+ {{ 0.04|significance_stars }} -> "*"
253
+ {{ 0.08|significance_stars }} -> "."
254
+ {{ 0.15|significance_stars }} -> ""
255
+ """
256
+ try:
257
+ pvalue = float(pvalue)
258
+ if pvalue < 0.001:
259
+ return '***'
260
+ elif pvalue < 0.01:
261
+ return '**'
262
+ elif pvalue < 0.05:
263
+ return '*'
264
+ elif pvalue < 0.1:
265
+ return '.'
266
+ return ''
267
+ except (ValueError, TypeError):
268
+ return ''
269
+
270
+ @staticmethod
271
+ def _filter_round(value, decimals: int = 0) -> float:
272
+ """
273
+ Round a number to specified decimals.
274
+
275
+ Examples
276
+ --------
277
+ {{ 3.14159|round(2) }} -> 3.14
278
+ """
279
+ try:
280
+ return round(float(value), decimals)
281
+ except (ValueError, TypeError):
282
+ return value
283
+
284
+ def list_templates(self, pattern: str = "*.html") -> list:
285
+ """
286
+ List available templates matching pattern.
287
+
288
+ Parameters
289
+ ----------
290
+ pattern : str, default="*.html"
291
+ Glob pattern to match templates
292
+
293
+ Returns
294
+ -------
295
+ list
296
+ List of template paths relative to template directory
297
+
298
+ Examples
299
+ --------
300
+ >>> manager.list_templates("validation/*.html")
301
+ ['validation/interactive/index.html', ...]
302
+ """
303
+ templates = []
304
+ for path in self.template_dir.rglob(pattern):
305
+ rel_path = path.relative_to(self.template_dir)
306
+ templates.append(str(rel_path))
307
+ return sorted(templates)
308
+
309
+ def template_exists(self, template_path: str) -> bool:
310
+ """
311
+ Check if a template exists.
312
+
313
+ Parameters
314
+ ----------
315
+ template_path : str
316
+ Relative path to template
317
+
318
+ Returns
319
+ -------
320
+ bool
321
+ True if template exists
322
+
323
+ Examples
324
+ --------
325
+ >>> manager.template_exists('validation/interactive/index.html')
326
+ True
327
+ """
328
+ full_path = self.template_dir / template_path
329
+ return full_path.exists()
330
+
331
+ def __repr__(self) -> str:
332
+ """String representation."""
333
+ return (
334
+ f"TemplateManager(template_dir={self.template_dir}, "
335
+ f"cache_enabled={self.enable_cache}, "
336
+ f"cached_templates={len(self.template_cache)})"
337
+ )
File without changes
File without changes