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