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,472 @@
1
+ """
2
+ CSS Manager for PanelBox Reports.
3
+
4
+ Manages compilation and layering of CSS styles with 3-layer architecture.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Set
9
+ from dataclasses import dataclass
10
+
11
+ from .asset_manager import AssetManager
12
+
13
+
14
+ @dataclass
15
+ class CSSLayer:
16
+ """
17
+ Represents a CSS layer in the compilation pipeline.
18
+
19
+ Attributes
20
+ ----------
21
+ name : str
22
+ Layer name (e.g., 'base', 'components', 'custom')
23
+ files : list of str
24
+ CSS files to include in this layer
25
+ priority : int
26
+ Layer priority (lower = earlier in output)
27
+ """
28
+ name: str
29
+ files: List[str]
30
+ priority: int = 0
31
+
32
+
33
+ class CSSManager:
34
+ """
35
+ Manages CSS compilation with 3-layer architecture.
36
+
37
+ Implements a layered CSS system inspired by modern design systems:
38
+ - Layer 1 (Base): Design tokens, reset, utilities
39
+ - Layer 2 (Components): Reusable UI components
40
+ - Layer 3 (Custom): Report-specific overrides
41
+
42
+ Parameters
43
+ ----------
44
+ asset_manager : AssetManager, optional
45
+ Asset manager for loading CSS files. If None, creates default.
46
+ minify : bool, default=False
47
+ Enable CSS minification
48
+
49
+ Attributes
50
+ ----------
51
+ asset_manager : AssetManager
52
+ Asset manager instance
53
+ layers : dict
54
+ Registered CSS layers
55
+ minify : bool
56
+ Whether to minify output
57
+
58
+ Examples
59
+ --------
60
+ >>> css_mgr = CSSManager(minify=False)
61
+ >>> css = css_mgr.compile()
62
+ >>> css_mgr.add_custom_css('custom-styles.css')
63
+ >>> css = css_mgr.compile()
64
+ """
65
+
66
+ # Default layer configuration
67
+ DEFAULT_LAYERS = {
68
+ 'base': CSSLayer(
69
+ name='base',
70
+ files=['base_styles.css'],
71
+ priority=0
72
+ ),
73
+ 'components': CSSLayer(
74
+ name='components',
75
+ files=['report_components.css'],
76
+ priority=10
77
+ ),
78
+ 'custom': CSSLayer(
79
+ name='custom',
80
+ files=[],
81
+ priority=20
82
+ )
83
+ }
84
+
85
+ def __init__(
86
+ self,
87
+ asset_manager: Optional[AssetManager] = None,
88
+ minify: bool = False
89
+ ):
90
+ """Initialize CSS Manager."""
91
+ if asset_manager is None:
92
+ asset_manager = AssetManager(minify=minify)
93
+
94
+ self.asset_manager = asset_manager
95
+ self.minify = minify
96
+
97
+ # Initialize layers with defaults
98
+ self.layers: Dict[str, CSSLayer] = {}
99
+ for name, layer in self.DEFAULT_LAYERS.items():
100
+ self.layers[name] = CSSLayer(
101
+ name=layer.name,
102
+ files=layer.files.copy(),
103
+ priority=layer.priority
104
+ )
105
+
106
+ # Track custom CSS snippets
107
+ self.custom_css: List[str] = []
108
+
109
+ # Compilation cache
110
+ self._compiled_css: Optional[str] = None
111
+ self._cache_valid = False
112
+
113
+ def add_layer(
114
+ self,
115
+ name: str,
116
+ files: List[str],
117
+ priority: int
118
+ ) -> None:
119
+ """
120
+ Add a new CSS layer.
121
+
122
+ Parameters
123
+ ----------
124
+ name : str
125
+ Layer name
126
+ files : list of str
127
+ CSS files to include
128
+ priority : int
129
+ Layer priority (lower = earlier in output)
130
+
131
+ Examples
132
+ --------
133
+ >>> css_mgr.add_layer('theme', ['dark-theme.css'], priority=5)
134
+ """
135
+ self.layers[name] = CSSLayer(
136
+ name=name,
137
+ files=files,
138
+ priority=priority
139
+ )
140
+ self._invalidate_cache()
141
+
142
+ def add_css_to_layer(self, layer_name: str, css_file: str) -> None:
143
+ """
144
+ Add a CSS file to an existing layer.
145
+
146
+ Parameters
147
+ ----------
148
+ layer_name : str
149
+ Name of the layer
150
+ css_file : str
151
+ CSS file to add
152
+
153
+ Examples
154
+ --------
155
+ >>> css_mgr.add_css_to_layer('custom', 'my-styles.css')
156
+ """
157
+ if layer_name not in self.layers:
158
+ raise ValueError(f"Layer '{layer_name}' does not exist")
159
+
160
+ if css_file not in self.layers[layer_name].files:
161
+ self.layers[layer_name].files.append(css_file)
162
+ self._invalidate_cache()
163
+
164
+ def remove_css_from_layer(self, layer_name: str, css_file: str) -> None:
165
+ """
166
+ Remove a CSS file from a layer.
167
+
168
+ Parameters
169
+ ----------
170
+ layer_name : str
171
+ Name of the layer
172
+ css_file : str
173
+ CSS file to remove
174
+
175
+ Examples
176
+ --------
177
+ >>> css_mgr.remove_css_from_layer('custom', 'old-styles.css')
178
+ """
179
+ if layer_name not in self.layers:
180
+ raise ValueError(f"Layer '{layer_name}' does not exist")
181
+
182
+ if css_file in self.layers[layer_name].files:
183
+ self.layers[layer_name].files.remove(css_file)
184
+ self._invalidate_cache()
185
+
186
+ def add_custom_css(self, css_file: str) -> None:
187
+ """
188
+ Add a custom CSS file to the custom layer.
189
+
190
+ Convenience method for adding files to the custom layer.
191
+
192
+ Parameters
193
+ ----------
194
+ css_file : str
195
+ CSS file to add
196
+
197
+ Examples
198
+ --------
199
+ >>> css_mgr.add_custom_css('validation-custom.css')
200
+ """
201
+ self.add_css_to_layer('custom', css_file)
202
+
203
+ def add_inline_css(self, css_content: str) -> None:
204
+ """
205
+ Add inline CSS snippet.
206
+
207
+ Parameters
208
+ ----------
209
+ css_content : str
210
+ CSS content to add
211
+
212
+ Examples
213
+ --------
214
+ >>> css_mgr.add_inline_css('.my-class { color: red; }')
215
+ """
216
+ self.custom_css.append(css_content)
217
+ self._invalidate_cache()
218
+
219
+ def compile(self, force: bool = False) -> str:
220
+ """
221
+ Compile all CSS layers into a single string.
222
+
223
+ Parameters
224
+ ----------
225
+ force : bool, default=False
226
+ Force recompilation even if cache is valid
227
+
228
+ Returns
229
+ -------
230
+ str
231
+ Compiled CSS content
232
+
233
+ Examples
234
+ --------
235
+ >>> css = css_mgr.compile()
236
+ >>> # Use in template
237
+ >>> '<style>{{ css }}</style>'
238
+ """
239
+ # Return cached version if valid
240
+ if self._cache_valid and not force and self._compiled_css is not None:
241
+ return self._compiled_css
242
+
243
+ css_parts = []
244
+
245
+ # Sort layers by priority
246
+ sorted_layers = sorted(
247
+ self.layers.values(),
248
+ key=lambda layer: layer.priority
249
+ )
250
+
251
+ # Compile each layer
252
+ for layer in sorted_layers:
253
+ if not layer.files:
254
+ continue
255
+
256
+ # Add layer header
257
+ css_parts.append(
258
+ f"/* ========================================\n"
259
+ f" * Layer: {layer.name.upper()} (Priority: {layer.priority})\n"
260
+ f" * ======================================== */\n"
261
+ )
262
+
263
+ # Collect CSS files for this layer
264
+ layer_css = self.asset_manager.collect_css(layer.files)
265
+ css_parts.append(layer_css)
266
+ css_parts.append("\n")
267
+
268
+ # Add custom inline CSS
269
+ if self.custom_css:
270
+ css_parts.append(
271
+ f"/* ========================================\n"
272
+ f" * INLINE CUSTOM CSS\n"
273
+ f" * ======================================== */\n"
274
+ )
275
+ for custom in self.custom_css:
276
+ css_parts.append(custom)
277
+ css_parts.append("\n")
278
+
279
+ # Join all parts
280
+ compiled = "".join(css_parts)
281
+
282
+ # Cache result
283
+ self._compiled_css = compiled
284
+ self._cache_valid = True
285
+
286
+ return compiled
287
+
288
+ def compile_for_report_type(self, report_type: str) -> str:
289
+ """
290
+ Compile CSS with report-type-specific styles.
291
+
292
+ Automatically adds report-type CSS if available.
293
+
294
+ Parameters
295
+ ----------
296
+ report_type : str
297
+ Report type (e.g., 'validation', 'regression', 'gmm')
298
+
299
+ Returns
300
+ -------
301
+ str
302
+ Compiled CSS content
303
+
304
+ Examples
305
+ --------
306
+ >>> css = css_mgr.compile_for_report_type('validation')
307
+ """
308
+ # Check if report-type CSS exists
309
+ report_css_file = f"{report_type}_report.css"
310
+
311
+ # Temporarily add to custom layer
312
+ original_custom_files = self.layers['custom'].files.copy()
313
+
314
+ try:
315
+ # Add report-type CSS if it exists
316
+ self.add_custom_css(report_css_file)
317
+ except FileNotFoundError:
318
+ # File doesn't exist, that's okay
319
+ pass
320
+
321
+ # Compile
322
+ css = self.compile(force=True)
323
+
324
+ # Restore original custom files
325
+ self.layers['custom'].files = original_custom_files
326
+ self._invalidate_cache()
327
+
328
+ return css
329
+
330
+ def get_layer_info(self) -> Dict[str, Dict]:
331
+ """
332
+ Get information about all layers.
333
+
334
+ Returns
335
+ -------
336
+ dict
337
+ Dictionary mapping layer name to layer info
338
+
339
+ Examples
340
+ --------
341
+ >>> info = css_mgr.get_layer_info()
342
+ >>> print(info['base']['files'])
343
+ ['base_styles.css']
344
+ """
345
+ return {
346
+ name: {
347
+ 'priority': layer.priority,
348
+ 'files': layer.files.copy(),
349
+ 'file_count': len(layer.files)
350
+ }
351
+ for name, layer in self.layers.items()
352
+ }
353
+
354
+ def reset_to_defaults(self) -> None:
355
+ """
356
+ Reset CSS layers to default configuration.
357
+
358
+ Examples
359
+ --------
360
+ >>> css_mgr.add_custom_css('temp.css')
361
+ >>> css_mgr.reset_to_defaults()
362
+ >>> # All custom CSS removed
363
+ """
364
+ self.layers.clear()
365
+ for name, layer in self.DEFAULT_LAYERS.items():
366
+ self.layers[name] = CSSLayer(
367
+ name=layer.name,
368
+ files=layer.files.copy(),
369
+ priority=layer.priority
370
+ )
371
+
372
+ self.custom_css.clear()
373
+ self._invalidate_cache()
374
+
375
+ def list_available_css(self) -> Dict[str, List[str]]:
376
+ """
377
+ List all available CSS files from asset manager.
378
+
379
+ Returns
380
+ -------
381
+ dict
382
+ Dictionary with 'css' key containing list of available files
383
+
384
+ Examples
385
+ --------
386
+ >>> files = css_mgr.list_available_css()
387
+ >>> print(files['css'])
388
+ ['base_styles.css', 'report_components.css', ...]
389
+ """
390
+ return self.asset_manager.list_assets(asset_type='css')
391
+
392
+ def get_size_estimate(self) -> Dict[str, int]:
393
+ """
394
+ Estimate size of compiled CSS.
395
+
396
+ Returns
397
+ -------
398
+ dict
399
+ Dictionary with size estimates in bytes
400
+
401
+ Examples
402
+ --------
403
+ >>> sizes = css_mgr.get_size_estimate()
404
+ >>> print(f"Total size: {sizes['total'] / 1024:.1f} KB")
405
+ """
406
+ css = self.compile()
407
+
408
+ sizes = {
409
+ 'total': len(css.encode('utf-8')),
410
+ 'total_kb': len(css.encode('utf-8')) / 1024
411
+ }
412
+
413
+ # Estimate per layer
414
+ for name, layer in self.layers.items():
415
+ if not layer.files:
416
+ continue
417
+
418
+ layer_css = self.asset_manager.collect_css(layer.files)
419
+ sizes[f'{name}_layer'] = len(layer_css.encode('utf-8'))
420
+
421
+ return sizes
422
+
423
+ def validate_layers(self) -> Dict[str, List[str]]:
424
+ """
425
+ Validate that all CSS files in layers exist.
426
+
427
+ Returns
428
+ -------
429
+ dict
430
+ Dictionary mapping layer name to list of missing files
431
+
432
+ Examples
433
+ --------
434
+ >>> missing = css_mgr.validate_layers()
435
+ >>> if missing['custom']:
436
+ ... print(f"Missing files: {missing['custom']}")
437
+ """
438
+ missing = {}
439
+
440
+ for name, layer in self.layers.items():
441
+ missing[name] = []
442
+ for css_file in layer.files:
443
+ try:
444
+ self.asset_manager.get_css(css_file)
445
+ except FileNotFoundError:
446
+ missing[name].append(css_file)
447
+
448
+ return missing
449
+
450
+ def clear_cache(self) -> None:
451
+ """Clear compilation cache."""
452
+ self._invalidate_cache()
453
+ self.asset_manager.clear_cache()
454
+
455
+ def _invalidate_cache(self) -> None:
456
+ """Invalidate compilation cache."""
457
+ self._cache_valid = False
458
+ self._compiled_css = None
459
+
460
+ def __repr__(self) -> str:
461
+ """String representation."""
462
+ layer_count = len(self.layers)
463
+ file_count = sum(len(layer.files) for layer in self.layers.values())
464
+ custom_count = len(self.custom_css)
465
+
466
+ return (
467
+ f"CSSManager("
468
+ f"layers={layer_count}, "
469
+ f"files={file_count}, "
470
+ f"custom_snippets={custom_count}, "
471
+ f"minify={self.minify})"
472
+ )
@@ -0,0 +1,15 @@
1
+ """
2
+ PanelBox Report Exporters.
3
+
4
+ Provides export functionality for various output formats.
5
+ """
6
+
7
+ from .html_exporter import HTMLExporter
8
+ from .latex_exporter import LaTeXExporter
9
+ from .markdown_exporter import MarkdownExporter
10
+
11
+ __all__ = [
12
+ 'HTMLExporter',
13
+ 'LaTeXExporter',
14
+ 'MarkdownExporter',
15
+ ]