microlens-submit 0.12.2__py3-none-any.whl → 0.16.1__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 (34) hide show
  1. microlens_submit/__init__.py +7 -157
  2. microlens_submit/cli/__init__.py +5 -0
  3. microlens_submit/cli/__main__.py +6 -0
  4. microlens_submit/cli/commands/__init__.py +1 -0
  5. microlens_submit/cli/commands/dossier.py +139 -0
  6. microlens_submit/cli/commands/export.py +177 -0
  7. microlens_submit/cli/commands/init.py +172 -0
  8. microlens_submit/cli/commands/solutions.py +722 -0
  9. microlens_submit/cli/commands/validation.py +241 -0
  10. microlens_submit/cli/main.py +120 -0
  11. microlens_submit/dossier/__init__.py +51 -0
  12. microlens_submit/dossier/dashboard.py +503 -0
  13. microlens_submit/dossier/event_page.py +370 -0
  14. microlens_submit/dossier/full_report.py +330 -0
  15. microlens_submit/dossier/solution_page.py +534 -0
  16. microlens_submit/dossier/utils.py +111 -0
  17. microlens_submit/error_messages.py +283 -0
  18. microlens_submit/models/__init__.py +28 -0
  19. microlens_submit/models/event.py +406 -0
  20. microlens_submit/models/solution.py +569 -0
  21. microlens_submit/models/submission.py +569 -0
  22. microlens_submit/tier_validation.py +208 -0
  23. microlens_submit/utils.py +373 -0
  24. microlens_submit/validate_parameters.py +478 -180
  25. {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/METADATA +52 -14
  26. microlens_submit-0.16.1.dist-info/RECORD +32 -0
  27. microlens_submit/api.py +0 -1257
  28. microlens_submit/cli.py +0 -1803
  29. microlens_submit/dossier.py +0 -1443
  30. microlens_submit-0.12.2.dist-info/RECORD +0 -13
  31. {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/WHEEL +0 -0
  32. {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/entry_points.txt +0 -0
  33. {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/licenses/LICENSE +0 -0
  34. {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,503 @@
1
+ """
2
+ Dashboard generation module for microlens-submit.
3
+
4
+ This module provides functionality to generate the main dashboard HTML page
5
+ for submission review and documentation. The dashboard provides an overview
6
+ of the submission including event summaries, solution statistics, and metadata.
7
+ """
8
+
9
+ import shutil
10
+ import webbrowser
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+
14
+ try: # Prefer stdlib importlib.resources when available (Python >= 3.9)
15
+ import importlib.resources as importlib_resources
16
+ except ImportError: # pragma: no cover - fallback for Python < 3.9
17
+ import importlib_resources
18
+
19
+ from .. import __version__
20
+ from ..models.submission import Submission
21
+ from .utils import extract_github_repo_name, format_hardware_info
22
+
23
+
24
+ def generate_dashboard_html(submission: Submission, output_dir: Path, open: bool = False) -> None:
25
+ """Generate a complete HTML dossier for the submission.
26
+
27
+ Creates a comprehensive HTML dashboard that provides an overview of the submission,
28
+ including event summaries, solution statistics, and metadata. The dossier includes:
29
+ - Main dashboard (index.html) with submission overview
30
+ - Individual event pages for each event
31
+ - Individual solution pages for each solution
32
+ - Full comprehensive dossier (full_dossier_report.html) for printing
33
+
34
+ The function creates the output directory structure and copies necessary assets
35
+ like logos and GitHub icons.
36
+
37
+ Args:
38
+ submission: The submission object containing events and solutions.
39
+ output_dir: Directory where the HTML files will be saved. Will be created
40
+ if it doesn't exist.
41
+ open: If True, open the generated index.html in the default web browser after generation.
42
+
43
+ Raises:
44
+ OSError: If unable to create output directory or write files.
45
+ ValueError: If submission data is invalid or missing required fields.
46
+
47
+ Example:
48
+ >>> from microlens_submit import load
49
+ >>> from microlens_submit.dossier import generate_dashboard_html
50
+ >>> from pathlib import Path
51
+ >>>
52
+ >>> # Load a submission project
53
+ >>> submission = load("./my_project")
54
+ >>>
55
+ >>> # Generate the complete dossier and open in browser
56
+ >>> generate_dashboard_html(submission, Path("./dossier_output"), open=True)
57
+ >>>
58
+ >>> # Files created:
59
+ >>> # - ./dossier_output/index.html (main dashboard)
60
+ >>> # - ./dossier_output/EVENT001.html (event page)
61
+ >>> # - ./dossier_output/solution_id.html (solution pages)
62
+ >>> # - ./dossier_output/full_dossier_report.html (printable version)
63
+ >>> # - ./dossier_output/assets/ (logos and icons)
64
+
65
+ Note:
66
+ This function generates all dossier components. For partial generation
67
+ (e.g., only specific events), use the CLI command with --event-id or
68
+ --solution-id flags instead.
69
+ """
70
+ # Create output directory structure
71
+ output_dir.mkdir(parents=True, exist_ok=True)
72
+ (output_dir / "assets").mkdir(exist_ok=True)
73
+ # (No events or solutions subfolders)
74
+
75
+ # Check if full dossier report exists
76
+ full_dossier_exists = (output_dir / "full_dossier_report.html").exists()
77
+ # Generate the main dashboard HTML
78
+ html_content = _generate_dashboard_content(submission, full_dossier_exists=full_dossier_exists)
79
+
80
+ # Write the HTML file
81
+ index_path = output_dir / "index.html"
82
+ with index_path.open("w", encoding="utf-8") as f:
83
+ f.write(html_content)
84
+
85
+ # Copy logos using importlib_resources for robust package data access
86
+ def _get_asset_path(package, filename):
87
+ try:
88
+ # Python 3.9+ or importlib_resources >= 3.1
89
+ return importlib_resources.files(package).joinpath(filename)
90
+ except AttributeError:
91
+ # Python 3.8 fallback
92
+ with importlib_resources.path(package, filename) as p:
93
+ return p
94
+
95
+ try:
96
+ logo_path = _get_asset_path("microlens_submit.assets", "rges-pit_logo.png")
97
+ shutil.copy2(logo_path, output_dir / "assets" / "rges-pit_logo.png")
98
+ except (FileNotFoundError, ModuleNotFoundError, AttributeError):
99
+ pass
100
+ try:
101
+ github_logo_path = _get_asset_path("microlens_submit.assets", "github-desktop_logo.png")
102
+ shutil.copy2(github_logo_path, output_dir / "assets" / "github-desktop_logo.png")
103
+ except (FileNotFoundError, ModuleNotFoundError, AttributeError):
104
+ pass
105
+
106
+ # After generating index.html, generate event pages
107
+ # Import here to avoid circular imports
108
+ from .event_page import generate_event_page
109
+
110
+ for event in submission.events.values():
111
+ generate_event_page(event, submission, output_dir)
112
+
113
+ # Optionally open the dashboard in the browser
114
+ if open:
115
+ webbrowser.open(index_path.resolve().as_uri())
116
+
117
+
118
+ def _generate_dashboard_content(submission: Submission, full_dossier_exists: bool = False) -> str:
119
+ """Generate the HTML content for the submission dashboard.
120
+
121
+ Creates the main dashboard HTML following the Dashboard_Design.md specification.
122
+ The dashboard includes submission statistics, progress tracking, event tables,
123
+ and aggregate parameter distributions.
124
+
125
+ Args:
126
+ submission: The submission object containing events and solutions.
127
+ full_dossier_exists: Whether the full dossier report exists. Currently
128
+ ignored but kept for future use.
129
+
130
+ Returns:
131
+ str: Complete HTML content as a string, ready to be written to index.html.
132
+
133
+ Example:
134
+ >>> from microlens_submit import load
135
+ >>> from microlens_submit.dossier import _generate_dashboard_content
136
+ >>>
137
+ >>> submission = load("./my_project")
138
+ >>> html_content = _generate_dashboard_content(submission)
139
+ >>>
140
+ >>> # Write to file
141
+ >>> with open("dashboard.html", "w", encoding="utf-8") as f:
142
+ ... f.write(html_content)
143
+
144
+ Note:
145
+ This is an internal function. Use generate_dashboard_html() for the
146
+ complete dossier generation workflow.
147
+ """
148
+ # Calculate statistics
149
+ total_events = len(submission.events)
150
+ total_active_solutions = sum(len(event.get_active_solutions()) for event in submission.events.values())
151
+ total_cpu_hours = 0
152
+ total_wall_time_hours = 0
153
+
154
+ # Calculate compute time
155
+ for event in submission.events.values():
156
+ for solution in event.solutions.values():
157
+ if solution.compute_info:
158
+ total_cpu_hours += solution.compute_info.get("cpu_hours", 0)
159
+ total_wall_time_hours += solution.compute_info.get("wall_time_hours", 0)
160
+
161
+ # Format hardware info
162
+ hardware_info_str = format_hardware_info(submission.hardware_info)
163
+
164
+ # Calculate progress (hardcoded total from design spec)
165
+ TOTAL_CHALLENGE_EVENTS = 293
166
+ progress_percentage = (total_events / TOTAL_CHALLENGE_EVENTS) * 100 if TOTAL_CHALLENGE_EVENTS > 0 else 0
167
+
168
+ # Generate event table
169
+ event_rows = []
170
+ for event in sorted(submission.events.values(), key=lambda e: e.event_id):
171
+ active_solutions = event.get_active_solutions()
172
+ model_types = set(sol.model_type for sol in active_solutions)
173
+ model_types_str = ", ".join(sorted(model_types)) if model_types else "None"
174
+
175
+ event_rows.append(
176
+ f"""
177
+ <tr class="border-b border-gray-200 hover:bg-gray-50">
178
+ <td class="py-3 px-4">
179
+ <a href="{event.event_id}.html"
180
+ class="font-medium text-rtd-accent hover:underline">
181
+ {event.event_id}
182
+ </a>
183
+ </td>
184
+ <td class="py-3 px-4">{len(active_solutions)}</td>
185
+ <td class="py-3 px-4">{model_types_str}</td>
186
+ </tr>
187
+ """
188
+ )
189
+
190
+ event_table = (
191
+ "\n".join(event_rows)
192
+ if event_rows
193
+ else """
194
+ <tr class="border-b border-gray-200">
195
+ <td colspan="3" class="py-3 px-4 text-center text-gray-500">
196
+ No events found
197
+ </td>
198
+ </tr>
199
+ """
200
+ )
201
+
202
+ # Insert Print Full Dossier placeholder before the footer
203
+ print_link_html = "<!--FULL_DOSSIER_LINK_PLACEHOLDER-->"
204
+
205
+ # GitHub repo link (if present)
206
+ github_html = ""
207
+ repo_url = getattr(submission, "repo_url", None) or (
208
+ submission.repo_url if hasattr(submission, "repo_url") else None
209
+ )
210
+ if repo_url:
211
+ repo_name = extract_github_repo_name(repo_url)
212
+ github_html = f"""
213
+ <div class="flex items-center justify-center mb-4">
214
+ <a href="{repo_url}" target="_blank" rel="noopener"
215
+ class="flex items-center space-x-2 group">
216
+ <img src="assets/github-desktop_logo.png" alt="GitHub"
217
+ class="w-6 h-6 inline-block align-middle mr-2 group-hover:opacity-80"
218
+ style="display:inline;vertical-align:middle;">
219
+ <span class="text-base text-rtd-accent font-semibold group-hover:underline">
220
+ {repo_name}
221
+ </span>
222
+ </a>
223
+ </div>
224
+ """
225
+
226
+ # Generate the complete HTML following the design spec
227
+ html = f"""<!DOCTYPE html>
228
+ <html lang="en">
229
+ <head>
230
+ <meta charset="UTF-8">
231
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
232
+ <title>Microlensing Data Challenge Submission Dossier - \
233
+ {submission.team_name}</title>
234
+ <script src="https://cdn.tailwindcss.com"></script>
235
+ <script>
236
+ tailwind.config = {{
237
+ theme: {{
238
+ extend: {{
239
+ colors: {{
240
+ 'rtd-primary': '#dfc5fa',
241
+ 'rtd-secondary': '#361d49',
242
+ 'rtd-accent': '#a859e4',
243
+ 'rtd-background': '#faf7fd',
244
+ 'rtd-text': '#000',
245
+ }},
246
+ fontFamily: {{
247
+ inter: ['Inter', 'sans-serif'],
248
+ }},
249
+ }},
250
+ }},
251
+ }};
252
+ </script>
253
+ <link
254
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap"
255
+ rel="stylesheet"
256
+ >
257
+ <!-- Highlight.js for code syntax highlighting -->
258
+ <link
259
+ rel="stylesheet"
260
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
261
+ >
262
+ <script
263
+ src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"
264
+ >
265
+ </script>
266
+ <script>hljs.highlightAll();</script>
267
+ <style>
268
+ .prose {{
269
+ color: #000;
270
+ line-height: 1.6;
271
+ }}
272
+ .prose h1 {{
273
+ font-size: 1.5rem;
274
+ font-weight: 700;
275
+ color: #361d49;
276
+ margin-top: 1.5rem;
277
+ margin-bottom: 0.75rem;
278
+ }}
279
+ .prose h2 {{
280
+ font-size: 1.25rem;
281
+ font-weight: 600;
282
+ color: #361d49;
283
+ margin-top: 1.25rem;
284
+ margin-bottom: 0.5rem;
285
+ }}
286
+ .prose h3 {{
287
+ font-size: 1.125rem;
288
+ font-weight: 600;
289
+ color: #a859e4;
290
+ margin-top: 1rem;
291
+ margin-bottom: 0.5rem;
292
+ }}
293
+ .prose p {{
294
+ margin-bottom: 0.75rem;
295
+ }}
296
+ .prose ul, .prose ol {{
297
+ margin-left: 1.5rem;
298
+ margin-bottom: 0.75rem;
299
+ }}
300
+ .prose ul {{ list-style-type: disc; }}
301
+ .prose ol {{ list-style-type: decimal; }}
302
+ .prose li {{
303
+ margin-bottom: 0.25rem;
304
+ }}
305
+ .prose code {{
306
+ background: #f3f3f3;
307
+ padding: 2px 4px;
308
+ border-radius: 4px;
309
+ font-family: 'Courier New', monospace;
310
+ font-size: 0.875rem;
311
+ }}
312
+ .prose pre {{
313
+ background: #f8f8f8;
314
+ padding: 1rem;
315
+ border-radius: 8px;
316
+ overflow-x: auto;
317
+ margin: 1rem 0;
318
+ border: 1px solid #e5e5e5;
319
+ }}
320
+ .prose pre code {{
321
+ background: none;
322
+ padding: 0;
323
+ }}
324
+ .prose blockquote {{
325
+ border-left: 4px solid #a859e4;
326
+ padding-left: 1rem;
327
+ margin: 1rem 0;
328
+ font-style: italic;
329
+ color: #666;
330
+ }}
331
+ </style>
332
+ </head>
333
+ <body class="font-inter bg-rtd-background">
334
+ <div class="max-w-7xl mx-auto p-6 lg:p-8">
335
+ <div class="bg-white shadow-xl rounded-lg">
336
+ <!-- Header Section -->
337
+ <div class="text-center py-8">
338
+ <img src="./assets/rges-pit_logo.png" alt="RGES-PIT Logo" \
339
+ class="w-48 mx-auto mb-6">
340
+ <h1 class="text-4xl font-bold text-rtd-secondary text-center mb-2">
341
+ Microlensing Data Challenge Submission Dossier
342
+ </h1>
343
+ <p class="text-xl text-rtd-accent text-center mb-8">
344
+ Team: {submission.team_name or 'Not specified'} |
345
+ Tier: {submission.tier or 'Not specified'}
346
+ </p>
347
+ {github_html}
348
+ </div>
349
+
350
+ <hr class="border-t-4 border-rtd-accent my-8 mx-8">
351
+
352
+ <!-- Regex Start -->
353
+
354
+ <!-- Submission Summary Section -->
355
+ <section class="mb-10 px-8">
356
+ <h2 class="text-2xl font-semibold text-rtd-secondary mb-4">
357
+ Submission Overview
358
+ </h2>
359
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
360
+ <div class="bg-rtd-primary p-6 rounded-lg shadow-md text-center">
361
+ <p class="text-sm font-medium text-rtd-secondary">
362
+ Total Events Submitted
363
+ </p>
364
+ <p class="text-4xl font-bold text-rtd-accent mt-2">
365
+ {total_events}
366
+ </p>
367
+ </div>
368
+ <div class="bg-rtd-primary p-6 rounded-lg shadow-md text-center">
369
+ <p class="text-sm font-medium text-rtd-secondary">
370
+ Total Active Solutions
371
+ </p>
372
+ <p class="text-4xl font-bold text-rtd-accent mt-2">
373
+ {total_active_solutions}
374
+ </p>
375
+ </div>
376
+ <div class="bg-rtd-primary p-6 rounded-lg shadow-md text-center">
377
+ <p class="text-sm font-medium text-rtd-secondary">
378
+ Hardware Information
379
+ </p>
380
+ <p class="text-lg text-rtd-text mt-2">
381
+ {hardware_info_str}
382
+ </p>
383
+ </div>
384
+ </div>
385
+ </section>
386
+
387
+ <!-- Overall Progress & Compute Time -->
388
+ <section class="mb-10 px-8">
389
+ <h2 class="text-2xl font-semibold text-rtd-secondary mb-4">
390
+ Challenge Progress & Compute Summary
391
+ </h2>
392
+
393
+ <!-- Progress Bar -->
394
+ <div class="w-full bg-gray-200 rounded-full h-4 mb-4">
395
+ <div class="bg-rtd-accent h-4 rounded-full" \
396
+ style="width: {progress_percentage}%"></div>
397
+ </div>
398
+ <p class="text-sm text-rtd-text text-center mb-6">
399
+ {total_events} / {TOTAL_CHALLENGE_EVENTS} Events Processed
400
+ ({progress_percentage:.1f}%)
401
+ </p>
402
+
403
+ <!-- Compute Time Summary -->
404
+ <div class="text-lg text-rtd-text mb-2">
405
+ <p><strong>Total CPU Hours:</strong> {total_cpu_hours:.2f}</p>
406
+ <p><strong>Total Wall Time Hours:</strong> \
407
+ {total_wall_time_hours:.2f}</p>
408
+ </div>
409
+ <p class="text-sm text-gray-500 italic">
410
+ Note: Comparison to other teams' compute times is available in the
411
+ Evaluator Dossier.
412
+ </p>
413
+ </section>
414
+
415
+ <!-- Event List -->
416
+ <section class="mb-10 px-8">
417
+ <h2 class="text-2xl font-semibold text-rtd-secondary mb-4">
418
+ Submitted Events
419
+ </h2>
420
+ <table class="w-full text-left table-auto border-collapse">
421
+ <thead class="bg-rtd-primary text-rtd-secondary uppercase text-sm">
422
+ <tr>
423
+ <th class="py-3 px-4">Event ID</th>
424
+ <th class="py-3 px-4">Active Solutions</th>
425
+ <th class="py-3 px-4">Model Types Submitted</th>
426
+ </tr>
427
+ </thead>
428
+ <tbody class="text-rtd-text">
429
+ {event_table}
430
+ </tbody>
431
+ </table>
432
+ </section>
433
+
434
+ <!-- Aggregate Parameter Distributions (Placeholders) -->
435
+ <section class="mb-10 px-8">
436
+ <h2 class="text-2xl font-semibold text-rtd-secondary mb-4">
437
+ Aggregate Parameter Distributions
438
+ </h2>
439
+ <p class="text-sm text-gray-500 italic mb-4">
440
+ Note: These plots show distributions from <em>your</em> submitted
441
+ solutions. Comparisons to simulation truths and other teams' results
442
+ are available in the Evaluator Dossier.
443
+ </p>
444
+
445
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
446
+ <div class="text-center">
447
+ <img src="https://placehold.co/600x300/dfc5fa/361d49?text=tE+Distribution"
448
+ alt="tE Distribution"
449
+ class="w-full rounded-lg shadow-md"
450
+ >
451
+ <p class="text-sm text-gray-600 mt-2">
452
+ Histogram of Einstein Crossing Times (tE) from your
453
+ active solutions.
454
+ </p>
455
+ </div>
456
+ <div class="text-center">
457
+ <img src="https://placehold.co/600x300/dfc5fa/361d49?text=u0+Distribution"
458
+ alt="u0 Distribution"
459
+ class="w-full rounded-lg shadow-md"
460
+ >
461
+ <p class="text-sm text-gray-600 mt-2">
462
+ Histogram of Impact Parameters (u0) from your active
463
+ solutions.
464
+ </p>
465
+ </div>
466
+ <div class="text-center">
467
+ <img src="https://placehold.co/600x300/dfc5fa/361d49?text=Lens+Mass+Distribution"
468
+ alt="M_L Distribution"
469
+ class="w-full rounded-lg shadow-md"
470
+ >
471
+ <p class="text-sm text-gray-600 mt-2">
472
+ Histogram of derived Lens Masses (M_L) from your
473
+ active solutions.
474
+ </p>
475
+ </div>
476
+ <div class="text-center">
477
+ <img src="https://placehold.co/600x300/dfc5fa/361d49?text=Lens+Distance+Distribution"
478
+ alt="D_L Distribution"
479
+ class="w-full rounded-lg shadow-md"
480
+ >
481
+ <p class="text-sm text-gray-600 mt-2">
482
+ Histogram of derived Lens Distances (D_L) from your
483
+ active solutions.
484
+ </p>
485
+ </div>
486
+ </div>
487
+ </section>
488
+ {print_link_html}
489
+
490
+ <!-- Footer -->
491
+ <div class="text-sm text-gray-500 text-center pt-8 pb-6">
492
+ Generated by microlens-submit v{__version__} on
493
+ {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
494
+ </div>
495
+
496
+ <!-- Regex Finish -->
497
+
498
+ </div>
499
+ </div>
500
+ </body>
501
+ </html>"""
502
+
503
+ return html