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.
- panelbox/__init__.py +67 -0
- panelbox/__version__.py +14 -0
- panelbox/cli/__init__.py +0 -0
- panelbox/cli/{commands}/__init__.py +0 -0
- panelbox/core/__init__.py +0 -0
- panelbox/core/base_model.py +164 -0
- panelbox/core/formula_parser.py +318 -0
- panelbox/core/panel_data.py +387 -0
- panelbox/core/results.py +366 -0
- panelbox/datasets/__init__.py +0 -0
- panelbox/datasets/{data}/__init__.py +0 -0
- panelbox/gmm/__init__.py +65 -0
- panelbox/gmm/difference_gmm.py +645 -0
- panelbox/gmm/estimator.py +562 -0
- panelbox/gmm/instruments.py +580 -0
- panelbox/gmm/results.py +550 -0
- panelbox/gmm/system_gmm.py +621 -0
- panelbox/gmm/tests.py +535 -0
- panelbox/models/__init__.py +11 -0
- panelbox/models/dynamic/__init__.py +0 -0
- panelbox/models/iv/__init__.py +0 -0
- panelbox/models/static/__init__.py +13 -0
- panelbox/models/static/fixed_effects.py +516 -0
- panelbox/models/static/pooled_ols.py +298 -0
- panelbox/models/static/random_effects.py +512 -0
- panelbox/report/__init__.py +61 -0
- panelbox/report/asset_manager.py +410 -0
- panelbox/report/css_manager.py +472 -0
- panelbox/report/exporters/__init__.py +15 -0
- panelbox/report/exporters/html_exporter.py +440 -0
- panelbox/report/exporters/latex_exporter.py +510 -0
- panelbox/report/exporters/markdown_exporter.py +446 -0
- panelbox/report/renderers/__init__.py +11 -0
- panelbox/report/renderers/static/__init__.py +0 -0
- panelbox/report/renderers/static_validation_renderer.py +341 -0
- panelbox/report/report_manager.py +502 -0
- panelbox/report/template_manager.py +337 -0
- panelbox/report/transformers/__init__.py +0 -0
- panelbox/report/transformers/static/__init__.py +0 -0
- panelbox/report/validation_transformer.py +449 -0
- panelbox/standard_errors/__init__.py +0 -0
- panelbox/templates/__init__.py +0 -0
- panelbox/templates/assets/css/base_styles.css +382 -0
- panelbox/templates/assets/css/report_components.css +747 -0
- panelbox/templates/assets/js/tab-navigation.js +161 -0
- panelbox/templates/assets/js/utils.js +276 -0
- panelbox/templates/common/footer.html +24 -0
- panelbox/templates/common/header.html +44 -0
- panelbox/templates/common/meta.html +5 -0
- panelbox/templates/validation/interactive/index.html +272 -0
- panelbox/templates/validation/interactive/partials/charts.html +58 -0
- panelbox/templates/validation/interactive/partials/methodology.html +201 -0
- panelbox/templates/validation/interactive/partials/overview.html +146 -0
- panelbox/templates/validation/interactive/partials/recommendations.html +101 -0
- panelbox/templates/validation/interactive/partials/test_results.html +231 -0
- panelbox/utils/__init__.py +0 -0
- panelbox/utils/formatting.py +172 -0
- panelbox/utils/matrix_ops.py +233 -0
- panelbox/utils/statistical.py +173 -0
- panelbox/validation/__init__.py +58 -0
- panelbox/validation/base.py +175 -0
- panelbox/validation/cointegration/__init__.py +0 -0
- panelbox/validation/cross_sectional_dependence/__init__.py +13 -0
- panelbox/validation/cross_sectional_dependence/breusch_pagan_lm.py +222 -0
- panelbox/validation/cross_sectional_dependence/frees.py +297 -0
- panelbox/validation/cross_sectional_dependence/pesaran_cd.py +188 -0
- panelbox/validation/heteroskedasticity/__init__.py +13 -0
- panelbox/validation/heteroskedasticity/breusch_pagan.py +222 -0
- panelbox/validation/heteroskedasticity/modified_wald.py +172 -0
- panelbox/validation/heteroskedasticity/white.py +208 -0
- panelbox/validation/instruments/__init__.py +0 -0
- panelbox/validation/robustness/__init__.py +0 -0
- panelbox/validation/serial_correlation/__init__.py +13 -0
- panelbox/validation/serial_correlation/baltagi_wu.py +220 -0
- panelbox/validation/serial_correlation/breusch_godfrey.py +260 -0
- panelbox/validation/serial_correlation/wooldridge_ar.py +200 -0
- panelbox/validation/specification/__init__.py +16 -0
- panelbox/validation/specification/chow.py +273 -0
- panelbox/validation/specification/hausman.py +264 -0
- panelbox/validation/specification/mundlak.py +331 -0
- panelbox/validation/specification/reset.py +273 -0
- panelbox/validation/unit_root/__init__.py +0 -0
- panelbox/validation/validation_report.py +257 -0
- panelbox/validation/validation_suite.py +401 -0
- panelbox-0.2.0.dist-info/METADATA +337 -0
- panelbox-0.2.0.dist-info/RECORD +90 -0
- panelbox-0.2.0.dist-info/WHEEL +5 -0
- panelbox-0.2.0.dist-info/entry_points.txt +2 -0
- panelbox-0.2.0.dist-info/licenses/LICENSE +21 -0
- 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
|