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,370 @@
1
+ """
2
+ Event page generation module for microlens-submit.
3
+
4
+ This module provides functionality to generate HTML pages for individual
5
+ microlensing events, including event overview, solutions tables, and
6
+ evaluator-only visualizations.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+ from .. import __version__
13
+ from ..models import Event, Submission
14
+ from .solution_page import generate_solution_page
15
+
16
+
17
+ def generate_event_page(event: Event, submission: Submission, output_dir: Path) -> None:
18
+ """Generate an HTML dossier page for a single event.
19
+
20
+ Creates a detailed HTML page for a specific microlensing event, following
21
+ the Event_Page_Design.md specification. The page includes event overview,
22
+ solutions table, and evaluator-only visualizations.
23
+
24
+ Args:
25
+ event: The Event object containing solutions and metadata.
26
+ submission: The parent Submission object for context and metadata.
27
+ output_dir: The dossier directory where the HTML file will be saved.
28
+ The file will be named {event.event_id}.html.
29
+
30
+ Raises:
31
+ OSError: If unable to write the HTML file.
32
+ ValueError: If event data is invalid.
33
+
34
+ Example:
35
+ >>> from microlens_submit import load
36
+ >>> from microlens_submit.dossier import generate_event_page
37
+ >>> from pathlib import Path
38
+ >>>
39
+ >>> submission = load("./my_project")
40
+ >>> event = submission.get_event("EVENT001")
41
+ >>>
42
+ >>> # Generate event page
43
+ >>> generate_event_page(event, submission, Path("./dossier_output"))
44
+ >>>
45
+ >>> # Creates: ./dossier_output/EVENT001.html
46
+
47
+ Note:
48
+ This function also triggers generation of solution pages for all
49
+ solutions in the event. The event page includes navigation links
50
+ to individual solution pages.
51
+ """
52
+ # Prepare output directory (already created)
53
+ html = _generate_event_page_content(event, submission)
54
+ with (output_dir / f"{event.event_id}.html").open("w", encoding="utf-8") as f:
55
+ f.write(html)
56
+
57
+ # After generating the event page, generate solution pages
58
+ for sol in event.solutions.values():
59
+ generate_solution_page(sol, event, submission, output_dir)
60
+
61
+
62
+ def _generate_event_page_content(event: Event, submission: Submission) -> str:
63
+ """Generate the HTML content for an event dossier page.
64
+
65
+ Creates the complete HTML content for a single event page, including
66
+ event overview, solutions table with sorting, and evaluator-only
67
+ visualization placeholders.
68
+
69
+ Args:
70
+ event: The Event object containing solutions and metadata.
71
+ submission: The parent Submission object for context and metadata.
72
+
73
+ Returns:
74
+ str: Complete HTML content as a string for the event page.
75
+
76
+ Example:
77
+ >>> from microlens_submit import load
78
+ >>> from microlens_submit.dossier import _generate_event_page_content
79
+ >>>
80
+ >>> submission = load("./my_project")
81
+ >>> event = submission.get_event("EVENT001")
82
+ >>> html_content = _generate_event_page_content(event, submission)
83
+ >>>
84
+ >>> # Write to file
85
+ >>> with open("event_page.html", "w", encoding="utf-8") as f:
86
+ ... f.write(html_content)
87
+
88
+ Note:
89
+ Solutions are sorted by: active status (active first), relative
90
+ probability (descending), then solution ID. The page includes
91
+ navigation back to the dashboard and links to individual solution pages.
92
+ """
93
+
94
+ # Sort solutions: active first, then by relative_probability (desc, None last), then by solution_id
95
+ def sort_key(sol):
96
+ return (
97
+ not sol.is_active, # active first
98
+ -(sol.relative_probability if sol.relative_probability is not None else float("-inf")),
99
+ sol.solution_id,
100
+ )
101
+
102
+ solutions = sorted(event.solutions.values(), key=sort_key)
103
+ # Table rows
104
+ rows = []
105
+ for sol in solutions:
106
+ status = (
107
+ '<span class="text-green-600">Active</span>'
108
+ if sol.is_active
109
+ else '<span class="text-red-600">Inactive</span>'
110
+ )
111
+ logl = f"{sol.log_likelihood:.2f}" if sol.log_likelihood is not None else "N/A"
112
+ ndp = str(sol.n_data_points) if sol.n_data_points is not None else "N/A"
113
+ relprob = f"{sol.relative_probability:.3f}" if sol.relative_probability is not None else "N/A"
114
+ # Read notes snippet from file
115
+ notes_snip = (
116
+ (
117
+ sol.get_notes(project_root=Path(submission.project_path))[:50]
118
+ + ("..." if len(sol.get_notes(project_root=Path(submission.project_path))) > 50 else "")
119
+ )
120
+ if sol.notes_path
121
+ else ""
122
+ )
123
+
124
+ # Display alias as primary identifier, UUID as secondary
125
+ if sol.alias:
126
+ solution_display = f"""
127
+ <div>
128
+ <a href="{sol.solution_id}.html"
129
+ class="font-medium text-rtd-accent hover:underline">{sol.alias}</a>
130
+ <div class="text-xs text-gray-500 font-mono">{sol.solution_id[:8]}...</div>
131
+ </div>
132
+ """
133
+ else:
134
+ solution_display = (
135
+ f'<a href="{sol.solution_id}.html" '
136
+ f'class="font-medium text-rtd-accent hover:underline">'
137
+ f"{sol.solution_id[:8]}...</a>"
138
+ )
139
+
140
+ rows.append(
141
+ f"""
142
+ <tr class='border-b border-gray-200 hover:bg-gray-50'>
143
+ <td class='py-3 px-4'>{solution_display}</td>
144
+ <td class='py-3 px-4'>{sol.model_type}</td>
145
+ <td class='py-3 px-4'>{status}</td>
146
+ <td class='py-3 px-4'>{logl}</td>
147
+ <td class='py-3 px-4'>{ndp}</td>
148
+ <td class='py-3 px-4'>{relprob}</td>
149
+ <td class='py-3 px-4 text-gray-600 italic'>{notes_snip}</td>
150
+ </tr>
151
+ """
152
+ )
153
+ table_body = (
154
+ "\n".join(rows)
155
+ if rows
156
+ else """
157
+ <tr class='border-b border-gray-200'>
158
+ <td colspan='7' class='py-3 px-4 text-center text-gray-500'>
159
+ No solutions found
160
+ </td>
161
+ </tr>
162
+ """
163
+ )
164
+ # Optional raw data link
165
+ raw_data_html = ""
166
+ if hasattr(event, "event_data_path") and event.event_data_path:
167
+ raw_data_html = (
168
+ f'<p class="text-rtd-text">Raw Event Data: '
169
+ f'<a href="{event.event_data_path}" '
170
+ f'class="text-rtd-accent hover:underline">Download Data</a></p>'
171
+ )
172
+ # HTML content
173
+ html = f"""<!DOCTYPE html>
174
+ <html lang='en'>
175
+ <head>
176
+ <meta charset='UTF-8'>
177
+ <meta name='viewport' content='width=device-width, initial-scale=1.0'>
178
+ <title>Event Dossier: {event.event_id} - {submission.team_name}</title>
179
+ <script src='https://cdn.tailwindcss.com'></script>
180
+ <script>
181
+ tailwind.config = {{
182
+ theme: {{
183
+ extend: {{
184
+ colors: {{
185
+ 'rtd-primary': '#dfc5fa',
186
+ 'rtd-secondary': '#361d49',
187
+ 'rtd-accent': '#a859e4',
188
+ 'rtd-background': '#faf7fd',
189
+ 'rtd-text': '#000',
190
+ }},
191
+ fontFamily: {{
192
+ inter: ['Inter', 'sans-serif'],
193
+ }},
194
+ }},
195
+ }},
196
+ }};
197
+ </script>
198
+ <link href='https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap' \
199
+ rel='stylesheet'>
200
+ <!-- Highlight.js for code syntax highlighting -->
201
+ <link rel="stylesheet" \
202
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
203
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
204
+ <script>hljs.highlightAll();</script>
205
+ <style>
206
+ .prose {{
207
+ color: #000;
208
+ line-height: 1.6;
209
+ }}
210
+ .prose h1 {{
211
+ font-size: 1.5rem;
212
+ font-weight: 700;
213
+ color: #361d49;
214
+ margin-top: 1.5rem;
215
+ margin-bottom: 0.75rem;
216
+ }}
217
+ .prose h2 {{
218
+ font-size: 1.25rem;
219
+ font-weight: 600;
220
+ color: #361d49;
221
+ margin-top: 1.25rem;
222
+ margin-bottom: 0.5rem;
223
+ }}
224
+ .prose h3 {{
225
+ font-size: 1.125rem;
226
+ font-weight: 600;
227
+ color: #a859e4;
228
+ margin-top: 1rem;
229
+ margin-bottom: 0.5rem;
230
+ }}
231
+ .prose p {{
232
+ margin-bottom: 0.75rem;
233
+ }}
234
+ .prose ul, .prose ol {{
235
+ margin-left: 1.5rem;
236
+ margin-bottom: 0.75rem;
237
+ }}
238
+ .prose ul {{ list-style-type: disc; }}
239
+ .prose ol {{ list-style-type: decimal; }}
240
+ .prose li {{
241
+ margin-bottom: 0.25rem;
242
+ }}
243
+ .prose code {{
244
+ background: #f3f3f3;
245
+ padding: 2px 4px;
246
+ border-radius: 4px;
247
+ font-family: 'Courier New', monospace;
248
+ font-size: 0.875rem;
249
+ }}
250
+ .prose pre {{
251
+ background: #f8f8f8;
252
+ padding: 1rem;
253
+ border-radius: 8px;
254
+ overflow-x: auto;
255
+ margin: 1rem 0;
256
+ border: 1px solid #e5e5e5;
257
+ }}
258
+ .prose pre code {{
259
+ background: none;
260
+ padding: 0;
261
+ }}
262
+ .prose blockquote {{
263
+ border-left: 4px solid #a859e4;
264
+ padding-left: 1rem;
265
+ margin: 1rem 0;
266
+ font-style: italic;
267
+ color: #666;
268
+ }}
269
+ </style>
270
+ </head>
271
+ <body class='font-inter bg-rtd-background'>
272
+ <div class='max-w-7xl mx-auto p-6 lg:p-8'>
273
+ <div class='bg-white shadow-xl rounded-lg'>
274
+ <!-- Header & Navigation -->
275
+ <div class='text-center py-8'>
276
+ <img src='assets/rges-pit_logo.png' alt='RGES-PIT Logo' class='w-48 mx-auto mb-6'>
277
+ <h1 class='text-4xl font-bold text-rtd-secondary text-center mb-2'>
278
+ Event Dossier: {event.event_id}
279
+ </h1>
280
+ <p class='text-xl text-rtd-accent text-center mb-4'>
281
+ Team: {submission.team_name or 'Not specified'} | Tier: {submission.tier or 'Not specified'}
282
+ </p>
283
+ <nav class='flex justify-center space-x-4 mb-8'>
284
+ <a href='index.html' class='text-rtd-accent hover:underline'>&larr; Back to Dashboard</a>
285
+ </nav>
286
+ </div>
287
+
288
+ <hr class="border-t-4 border-rtd-accent my-8 mx-8">
289
+
290
+ <!-- Regex Start -->
291
+
292
+ <!-- Event Summary -->
293
+ <section class='mb-10 px-8'>
294
+ <h2 class='text-2xl font-semibold text-rtd-secondary mb-4'>Event Overview</h2>
295
+ <p class='text-rtd-text'>
296
+ This page provides details for microlensing event {event.event_id}.
297
+ </p>
298
+ {raw_data_html}
299
+ </section>
300
+ <!-- Solutions Table -->
301
+ <section class='mb-10 px-8'>
302
+ <h2 class='text-2xl font-semibold text-rtd-secondary mb-4'>
303
+ Solutions for Event {event.event_id}
304
+ </h2>
305
+ <table class='w-full text-left table-auto border-collapse'>
306
+ <thead class='bg-rtd-primary text-rtd-secondary uppercase text-sm'>
307
+ <tr>
308
+ <th class='py-3 px-4'>Solution ID</th>
309
+ <th class='py-3 px-4'>Model Type</th>
310
+ <th class='py-3 px-4'>Status</th>
311
+ <th class='py-3 px-4'>Log-Likelihood</th>
312
+ <th class='py-3 px-4'>N Data Points</th>
313
+ <th class='py-3 px-4'>Relative Probability</th>
314
+ <th class='py-3 px-4'>Notes Snippet</th>
315
+ </tr>
316
+ </thead>
317
+ <tbody class='text-rtd-text'>
318
+ {table_body}
319
+ </tbody>
320
+ </table>
321
+ </section>
322
+ <!-- Event-Specific Data Visualizations (Evaluator-Only Placeholders) -->
323
+ <section class='mb-10 px-8'>
324
+ <h2 class='text-2xl font-semibold text-rtd-secondary mb-4'>
325
+ Event Data Visualizations (Evaluator-Only)
326
+ </h2>
327
+ <p class='text-sm text-gray-500 italic mb-4'>
328
+ Note: These advanced plots, including comparisons to simulation truths and
329
+ other teams' results, are available in the Evaluator Dossier.
330
+ </p>
331
+ <div class='mb-6'>
332
+ <img
333
+ src='https://placehold.co/800x450/dfc5fa/361d49?text=Raw+Lightcurve+and+Astrometry+Data+
334
+ (Evaluator+Only)'
335
+ alt='Raw Data Plot'
336
+ class='w-full rounded-lg shadow-md'
337
+ >
338
+ <p class='text-sm text-gray-600 mt-2'>
339
+ Raw lightcurve and astrometry data for Event
340
+ {event.event_id}, with true model overlaid (Evaluator View).
341
+ </p>
342
+ </div>
343
+ <div class='mb-6'>
344
+ <img src='https://placehold.co/600x400/dfc5fa/361d49?text=Mass+vs+Distance+Scatter+Plot+
345
+ (Evaluator+Only)'
346
+ alt='Mass vs Distance Plot' class='w-full rounded-lg shadow-md'>
347
+ <p class='text-sm text-gray-600 mt-2'>Derived Lens Mass vs. Lens Distance for solutions of \
348
+ Event {event.event_id}. Points colored by Relative Probability (Evaluator View).</p>
349
+ </div>
350
+ <div class='mb-6'>
351
+ <img src='https://placehold.co/600x400/dfc5fa/361d49?text=Proper+Motion+N+vs+E+Plot+
352
+ (Evaluator+Only)'
353
+ alt='Proper Motion Plot' class='w-full rounded-lg shadow-md'>
354
+ <p class='text-sm text-gray-600 mt-2'>Proper Motion North vs. East components for solutions of \
355
+ Event {event.event_id}. Points colored by Relative Probability (Evaluator View).</p>
356
+ </div>
357
+ </section>
358
+
359
+ <!-- Footer -->
360
+ <div class='text-sm text-gray-500 text-center pt-8 border-t border-gray-200 mt-10'>
361
+ Generated by microlens-submit v{__version__} on {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}
362
+ </div>
363
+
364
+ <!-- Regex Finish -->
365
+
366
+ </div>
367
+ </div>
368
+ </body>
369
+ </html>"""
370
+ return html