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,410 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asset Manager for PanelBox Reports.
|
|
3
|
+
|
|
4
|
+
Manages collection, minification, and embedding of assets (CSS, JS, images).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, List, Optional
|
|
10
|
+
import mimetypes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AssetManager:
|
|
14
|
+
"""
|
|
15
|
+
Manages assets for report generation.
|
|
16
|
+
|
|
17
|
+
Handles CSS, JavaScript, and image files. Can embed assets
|
|
18
|
+
inline for self-contained HTML reports.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
asset_dir : str or Path, optional
|
|
23
|
+
Directory containing assets. If None, uses package assets.
|
|
24
|
+
minify : bool, default=False
|
|
25
|
+
Enable minification of CSS/JS (requires additional packages)
|
|
26
|
+
|
|
27
|
+
Attributes
|
|
28
|
+
----------
|
|
29
|
+
asset_dir : Path
|
|
30
|
+
Directory containing assets
|
|
31
|
+
minify : bool
|
|
32
|
+
Whether to minify assets
|
|
33
|
+
asset_cache : dict
|
|
34
|
+
Cache for loaded assets
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> manager = AssetManager()
|
|
39
|
+
>>> css = manager.get_css('base_styles.css')
|
|
40
|
+
>>> js = manager.get_js('tab-navigation.js')
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
asset_dir: Optional[Path] = None,
|
|
46
|
+
minify: bool = False
|
|
47
|
+
):
|
|
48
|
+
"""Initialize Asset Manager."""
|
|
49
|
+
# Determine asset directory
|
|
50
|
+
if asset_dir is None:
|
|
51
|
+
# Use package assets
|
|
52
|
+
package_dir = Path(__file__).parent.parent
|
|
53
|
+
asset_dir = package_dir / 'templates' / 'assets'
|
|
54
|
+
else:
|
|
55
|
+
asset_dir = Path(asset_dir)
|
|
56
|
+
|
|
57
|
+
if not asset_dir.exists():
|
|
58
|
+
raise ValueError(f"Asset directory does not exist: {asset_dir}")
|
|
59
|
+
|
|
60
|
+
self.asset_dir = asset_dir
|
|
61
|
+
self.minify = minify
|
|
62
|
+
self.asset_cache: Dict[str, str] = {}
|
|
63
|
+
|
|
64
|
+
def get_css(self, css_path: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Load CSS file content.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
css_path : str
|
|
71
|
+
Relative path to CSS file from assets/css directory.
|
|
72
|
+
Example: 'base_styles.css' or 'components/buttons.css'
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
str
|
|
77
|
+
CSS content
|
|
78
|
+
|
|
79
|
+
Examples
|
|
80
|
+
--------
|
|
81
|
+
>>> css = manager.get_css('base_styles.css')
|
|
82
|
+
>>> css = manager.get_css('components/buttons.css')
|
|
83
|
+
"""
|
|
84
|
+
cache_key = f"css:{css_path}"
|
|
85
|
+
|
|
86
|
+
if cache_key in self.asset_cache:
|
|
87
|
+
return self.asset_cache[cache_key]
|
|
88
|
+
|
|
89
|
+
css_file = self.asset_dir / 'css' / css_path
|
|
90
|
+
|
|
91
|
+
if not css_file.exists():
|
|
92
|
+
raise FileNotFoundError(f"CSS file not found: {css_file}")
|
|
93
|
+
|
|
94
|
+
content = css_file.read_text(encoding='utf-8')
|
|
95
|
+
|
|
96
|
+
if self.minify:
|
|
97
|
+
content = self._minify_css(content)
|
|
98
|
+
|
|
99
|
+
self.asset_cache[cache_key] = content
|
|
100
|
+
return content
|
|
101
|
+
|
|
102
|
+
def get_js(self, js_path: str) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Load JavaScript file content.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
js_path : str
|
|
109
|
+
Relative path to JS file from assets/js directory.
|
|
110
|
+
Example: 'tab-navigation.js' or 'components/charts.js'
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
str
|
|
115
|
+
JavaScript content
|
|
116
|
+
|
|
117
|
+
Examples
|
|
118
|
+
--------
|
|
119
|
+
>>> js = manager.get_js('tab-navigation.js')
|
|
120
|
+
>>> js = manager.get_js('components/charts.js')
|
|
121
|
+
"""
|
|
122
|
+
cache_key = f"js:{js_path}"
|
|
123
|
+
|
|
124
|
+
if cache_key in self.asset_cache:
|
|
125
|
+
return self.asset_cache[cache_key]
|
|
126
|
+
|
|
127
|
+
js_file = self.asset_dir / 'js' / js_path
|
|
128
|
+
|
|
129
|
+
if not js_file.exists():
|
|
130
|
+
raise FileNotFoundError(f"JavaScript file not found: {js_file}")
|
|
131
|
+
|
|
132
|
+
content = js_file.read_text(encoding='utf-8')
|
|
133
|
+
|
|
134
|
+
if self.minify:
|
|
135
|
+
content = self._minify_js(content)
|
|
136
|
+
|
|
137
|
+
self.asset_cache[cache_key] = content
|
|
138
|
+
return content
|
|
139
|
+
|
|
140
|
+
def get_image_base64(self, image_path: str) -> str:
|
|
141
|
+
"""
|
|
142
|
+
Load image and encode as base64 data URI.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
image_path : str
|
|
147
|
+
Relative path to image from assets directory
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
str
|
|
152
|
+
Base64-encoded data URI
|
|
153
|
+
|
|
154
|
+
Examples
|
|
155
|
+
--------
|
|
156
|
+
>>> data_uri = manager.get_image_base64('images/logo.png')
|
|
157
|
+
>>> # Returns: 'data:image/png;base64,iVBORw0KG...'
|
|
158
|
+
"""
|
|
159
|
+
cache_key = f"img:{image_path}"
|
|
160
|
+
|
|
161
|
+
if cache_key in self.asset_cache:
|
|
162
|
+
return self.asset_cache[cache_key]
|
|
163
|
+
|
|
164
|
+
img_file = self.asset_dir / image_path
|
|
165
|
+
|
|
166
|
+
if not img_file.exists():
|
|
167
|
+
raise FileNotFoundError(f"Image file not found: {img_file}")
|
|
168
|
+
|
|
169
|
+
# Read binary data
|
|
170
|
+
img_data = img_file.read_bytes()
|
|
171
|
+
|
|
172
|
+
# Encode as base64
|
|
173
|
+
b64_data = base64.b64encode(img_data).decode('utf-8')
|
|
174
|
+
|
|
175
|
+
# Determine MIME type
|
|
176
|
+
mime_type, _ = mimetypes.guess_type(str(img_file))
|
|
177
|
+
if mime_type is None:
|
|
178
|
+
mime_type = 'application/octet-stream'
|
|
179
|
+
|
|
180
|
+
# Create data URI
|
|
181
|
+
data_uri = f"data:{mime_type};base64,{b64_data}"
|
|
182
|
+
|
|
183
|
+
self.asset_cache[cache_key] = data_uri
|
|
184
|
+
return data_uri
|
|
185
|
+
|
|
186
|
+
def collect_css(self, css_files: List[str]) -> str:
|
|
187
|
+
"""
|
|
188
|
+
Collect multiple CSS files into one string.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
css_files : list of str
|
|
193
|
+
List of CSS file paths to collect
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
str
|
|
198
|
+
Combined CSS content
|
|
199
|
+
|
|
200
|
+
Examples
|
|
201
|
+
--------
|
|
202
|
+
>>> css = manager.collect_css([
|
|
203
|
+
... 'base_styles.css',
|
|
204
|
+
... 'report_components.css',
|
|
205
|
+
... 'components/tables.css'
|
|
206
|
+
... ])
|
|
207
|
+
"""
|
|
208
|
+
css_parts = []
|
|
209
|
+
|
|
210
|
+
for css_file in css_files:
|
|
211
|
+
try:
|
|
212
|
+
content = self.get_css(css_file)
|
|
213
|
+
css_parts.append(f"/* ========== {css_file} ========== */\n")
|
|
214
|
+
css_parts.append(content)
|
|
215
|
+
css_parts.append("\n\n")
|
|
216
|
+
except FileNotFoundError as e:
|
|
217
|
+
print(f"Warning: {e}")
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
return "".join(css_parts)
|
|
221
|
+
|
|
222
|
+
def collect_js(self, js_files: List[str]) -> str:
|
|
223
|
+
"""
|
|
224
|
+
Collect multiple JavaScript files into one string.
|
|
225
|
+
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
js_files : list of str
|
|
229
|
+
List of JS file paths to collect
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
str
|
|
234
|
+
Combined JavaScript content
|
|
235
|
+
|
|
236
|
+
Examples
|
|
237
|
+
--------
|
|
238
|
+
>>> js = manager.collect_js([
|
|
239
|
+
... 'utils.js',
|
|
240
|
+
... 'tab-navigation.js'
|
|
241
|
+
... ])
|
|
242
|
+
"""
|
|
243
|
+
js_parts = []
|
|
244
|
+
|
|
245
|
+
for js_file in js_files:
|
|
246
|
+
try:
|
|
247
|
+
content = self.get_js(js_file)
|
|
248
|
+
js_parts.append(f"/* ========== {js_file} ========== */\n")
|
|
249
|
+
js_parts.append(content)
|
|
250
|
+
js_parts.append("\n\n")
|
|
251
|
+
except FileNotFoundError as e:
|
|
252
|
+
print(f"Warning: {e}")
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
return "".join(js_parts)
|
|
256
|
+
|
|
257
|
+
def embed_plotly(self, include_plotly: bool = True) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Get Plotly.js library for embedding.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
include_plotly : bool, default=True
|
|
264
|
+
Whether to include full Plotly library
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
str
|
|
269
|
+
Plotly.js library content or CDN link
|
|
270
|
+
|
|
271
|
+
Examples
|
|
272
|
+
--------
|
|
273
|
+
>>> plotly_js = manager.embed_plotly()
|
|
274
|
+
"""
|
|
275
|
+
if not include_plotly:
|
|
276
|
+
return ""
|
|
277
|
+
|
|
278
|
+
# Use CDN link for now
|
|
279
|
+
# In production, you might want to embed the full library
|
|
280
|
+
return (
|
|
281
|
+
'<script src="https://cdn.plot.ly/plotly-2.27.0.min.js" '
|
|
282
|
+
'charset="utf-8"></script>'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
def _minify_css(self, css: str) -> str:
|
|
286
|
+
"""
|
|
287
|
+
Minify CSS content.
|
|
288
|
+
|
|
289
|
+
Basic minification without external dependencies.
|
|
290
|
+
For production, consider using cssmin package.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
css : str
|
|
295
|
+
CSS content to minify
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
str
|
|
300
|
+
Minified CSS
|
|
301
|
+
"""
|
|
302
|
+
if not self.minify:
|
|
303
|
+
return css
|
|
304
|
+
|
|
305
|
+
# Basic minification
|
|
306
|
+
# Remove comments
|
|
307
|
+
import re
|
|
308
|
+
css = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL)
|
|
309
|
+
|
|
310
|
+
# Remove extra whitespace
|
|
311
|
+
css = re.sub(r'\s+', ' ', css)
|
|
312
|
+
css = re.sub(r'\s*([{}:;,])\s*', r'\1', css)
|
|
313
|
+
|
|
314
|
+
return css.strip()
|
|
315
|
+
|
|
316
|
+
def _minify_js(self, js: str) -> str:
|
|
317
|
+
"""
|
|
318
|
+
Minify JavaScript content.
|
|
319
|
+
|
|
320
|
+
Basic minification without external dependencies.
|
|
321
|
+
For production, consider using jsmin package.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
js : str
|
|
326
|
+
JavaScript content to minify
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
str
|
|
331
|
+
Minified JavaScript
|
|
332
|
+
"""
|
|
333
|
+
if not self.minify:
|
|
334
|
+
return js
|
|
335
|
+
|
|
336
|
+
# Basic minification
|
|
337
|
+
# Remove comments (simple approach)
|
|
338
|
+
import re
|
|
339
|
+
js = re.sub(r'//.*?$', '', js, flags=re.MULTILINE)
|
|
340
|
+
js = re.sub(r'/\*.*?\*/', '', js, flags=re.DOTALL)
|
|
341
|
+
|
|
342
|
+
# Remove extra whitespace (but preserve newlines in strings)
|
|
343
|
+
# This is a very basic approach
|
|
344
|
+
js = re.sub(r'\s+', ' ', js)
|
|
345
|
+
|
|
346
|
+
return js.strip()
|
|
347
|
+
|
|
348
|
+
def clear_cache(self) -> None:
|
|
349
|
+
"""Clear asset cache."""
|
|
350
|
+
self.asset_cache.clear()
|
|
351
|
+
|
|
352
|
+
def list_assets(self, asset_type: str = "all") -> Dict[str, List[str]]:
|
|
353
|
+
"""
|
|
354
|
+
List available assets.
|
|
355
|
+
|
|
356
|
+
Parameters
|
|
357
|
+
----------
|
|
358
|
+
asset_type : str, default="all"
|
|
359
|
+
Type of assets to list: "css", "js", "images", or "all"
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
dict
|
|
364
|
+
Dictionary mapping asset type to list of paths
|
|
365
|
+
|
|
366
|
+
Examples
|
|
367
|
+
--------
|
|
368
|
+
>>> assets = manager.list_assets()
|
|
369
|
+
>>> print(assets['css'])
|
|
370
|
+
['base_styles.css', 'report_components.css', ...]
|
|
371
|
+
"""
|
|
372
|
+
assets = {}
|
|
373
|
+
|
|
374
|
+
if asset_type in ("css", "all"):
|
|
375
|
+
css_dir = self.asset_dir / 'css'
|
|
376
|
+
if css_dir.exists():
|
|
377
|
+
css_files = [
|
|
378
|
+
str(p.relative_to(css_dir))
|
|
379
|
+
for p in css_dir.rglob("*.css")
|
|
380
|
+
]
|
|
381
|
+
assets['css'] = sorted(css_files)
|
|
382
|
+
|
|
383
|
+
if asset_type in ("js", "all"):
|
|
384
|
+
js_dir = self.asset_dir / 'js'
|
|
385
|
+
if js_dir.exists():
|
|
386
|
+
js_files = [
|
|
387
|
+
str(p.relative_to(js_dir))
|
|
388
|
+
for p in js_dir.rglob("*.js")
|
|
389
|
+
]
|
|
390
|
+
assets['js'] = sorted(js_files)
|
|
391
|
+
|
|
392
|
+
if asset_type in ("images", "all"):
|
|
393
|
+
img_dir = self.asset_dir / 'images'
|
|
394
|
+
if img_dir.exists():
|
|
395
|
+
img_files = [
|
|
396
|
+
str(p.relative_to(self.asset_dir))
|
|
397
|
+
for p in img_dir.rglob("*")
|
|
398
|
+
if p.is_file()
|
|
399
|
+
]
|
|
400
|
+
assets['images'] = sorted(img_files)
|
|
401
|
+
|
|
402
|
+
return assets
|
|
403
|
+
|
|
404
|
+
def __repr__(self) -> str:
|
|
405
|
+
"""String representation."""
|
|
406
|
+
return (
|
|
407
|
+
f"AssetManager(asset_dir={self.asset_dir}, "
|
|
408
|
+
f"minify={self.minify}, "
|
|
409
|
+
f"cached_assets={len(self.asset_cache)})"
|
|
410
|
+
)
|