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