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,241 @@
|
|
|
1
|
+
"""Validation commands for microlens-submit CLI."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from microlens_submit.error_messages import enhance_validation_messages
|
|
13
|
+
from microlens_submit.utils import load
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_solution(
|
|
19
|
+
solution_id: str,
|
|
20
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Validate a specific solution's parameters and configuration."""
|
|
23
|
+
sub = load(str(project_path))
|
|
24
|
+
|
|
25
|
+
# Find the solution
|
|
26
|
+
target_solution = None
|
|
27
|
+
target_event_id = None
|
|
28
|
+
for event_id, event in sub.events.items():
|
|
29
|
+
if solution_id in event.solutions:
|
|
30
|
+
target_solution = event.solutions[solution_id]
|
|
31
|
+
target_event_id = event_id
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
if target_solution is None:
|
|
35
|
+
console.print(f"Solution {solution_id} not found", style="bold red")
|
|
36
|
+
raise typer.Exit(code=1)
|
|
37
|
+
|
|
38
|
+
# Run validation
|
|
39
|
+
messages = target_solution.run_validation()
|
|
40
|
+
enhanced_messages = enhance_validation_messages(messages, target_solution.model_type, target_solution.parameters)
|
|
41
|
+
|
|
42
|
+
if not enhanced_messages:
|
|
43
|
+
console.print(
|
|
44
|
+
Panel(
|
|
45
|
+
f"✅ All validations passed for {solution_id} (event {target_event_id})",
|
|
46
|
+
style="bold green",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
console.print(
|
|
51
|
+
Panel(
|
|
52
|
+
f"Validation Results for {solution_id} (event {target_event_id})",
|
|
53
|
+
style="yellow",
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
for msg in enhanced_messages:
|
|
57
|
+
console.print(f" • {msg}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_submission(
|
|
61
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Validate the entire submission for missing or incomplete information."""
|
|
64
|
+
sub = load(str(project_path))
|
|
65
|
+
warnings = sub.run_validation_warnings()
|
|
66
|
+
|
|
67
|
+
if not warnings:
|
|
68
|
+
console.print(Panel("\u2705 All validations passed!", style="bold green"))
|
|
69
|
+
else:
|
|
70
|
+
console.print(Panel("Validation Warnings", style="yellow"))
|
|
71
|
+
for warning in warnings:
|
|
72
|
+
console.print(f" \u2022 {warning}")
|
|
73
|
+
console.print(f"\nFound {len(warnings)} validation issue(s)", style="yellow")
|
|
74
|
+
|
|
75
|
+
# Provide helpful guidance for common issues
|
|
76
|
+
has_repo_issue = any("repo_url" in w.lower() or "github" in w.lower() for w in warnings)
|
|
77
|
+
has_hardware_issue = any("hardware" in w.lower() for w in warnings)
|
|
78
|
+
|
|
79
|
+
if has_repo_issue:
|
|
80
|
+
console.print(
|
|
81
|
+
"\n[blue]💡 To fix repository URL issues:[/blue]\n"
|
|
82
|
+
" microlens-submit set-repo-url <url> <project_dir>",
|
|
83
|
+
style="blue",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if has_hardware_issue:
|
|
87
|
+
console.print(
|
|
88
|
+
"\n[blue]💡 To fix hardware info issues:[/blue]\n"
|
|
89
|
+
" microlens-submit nexus-init --team-name <name> --tier <tier> <project_dir>\n"
|
|
90
|
+
" (or manually set hardware_info in submission.json)",
|
|
91
|
+
style="blue",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
console.print(
|
|
95
|
+
"\n[yellow]⚠️ Note: These warnings will become errors when saving or exporting.[/yellow]", style="yellow"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def validate_event(
|
|
100
|
+
event_id: str,
|
|
101
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Validate all solutions for a specific event."""
|
|
104
|
+
sub = load(str(project_path))
|
|
105
|
+
|
|
106
|
+
if event_id not in sub.events:
|
|
107
|
+
console.print(f"Event {event_id} not found", style="bold red")
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
event = sub.events[event_id]
|
|
111
|
+
all_messages = []
|
|
112
|
+
|
|
113
|
+
console.print(Panel(f"Validating Event: {event_id}", style="cyan"))
|
|
114
|
+
|
|
115
|
+
for solution in event.solutions.values():
|
|
116
|
+
messages = solution.run_validation()
|
|
117
|
+
enhanced_messages = enhance_validation_messages(messages, solution.model_type, solution.parameters)
|
|
118
|
+
if enhanced_messages:
|
|
119
|
+
console.print(f"\n[bold]Solution {solution.solution_id}:[/bold]")
|
|
120
|
+
for msg in enhanced_messages:
|
|
121
|
+
console.print(f" • {msg}")
|
|
122
|
+
all_messages.append(f"{solution.solution_id}: {msg}")
|
|
123
|
+
else:
|
|
124
|
+
console.print(f"✅ Solution {solution.solution_id}: All validations passed")
|
|
125
|
+
|
|
126
|
+
if not all_messages:
|
|
127
|
+
console.print(Panel("✅ All solutions passed validation!", style="bold green"))
|
|
128
|
+
else:
|
|
129
|
+
console.print(
|
|
130
|
+
f"\nFound {len(all_messages)} validation issue(s) across all solutions",
|
|
131
|
+
style="yellow",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def list_solutions(
|
|
136
|
+
event_id: str,
|
|
137
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Display a table of solutions for a specific event."""
|
|
140
|
+
sub = load(str(project_path))
|
|
141
|
+
if event_id not in sub.events:
|
|
142
|
+
console.print(f"Event {event_id} not found", style="bold red")
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
evt = sub.events[event_id]
|
|
145
|
+
table = Table(title=f"Solutions for {event_id}")
|
|
146
|
+
table.add_column("Solution ID")
|
|
147
|
+
table.add_column("Model Type")
|
|
148
|
+
table.add_column("Status")
|
|
149
|
+
table.add_column("Notes")
|
|
150
|
+
for sol in evt.solutions.values():
|
|
151
|
+
status = "[green]Active[/green]" if sol.is_active else "[red]Inactive[/red]"
|
|
152
|
+
table.add_row(sol.solution_id, sol.model_type, status, sol.notes)
|
|
153
|
+
console.print(table)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def compare_solutions(
|
|
157
|
+
event_id: str,
|
|
158
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Rank active solutions for an event using the Bayesian Information Criterion."""
|
|
161
|
+
sub = load(str(project_path))
|
|
162
|
+
if event_id not in sub.events:
|
|
163
|
+
console.print(f"Event {event_id} not found", style="bold red")
|
|
164
|
+
raise typer.Exit(code=1)
|
|
165
|
+
|
|
166
|
+
evt = sub.events[event_id]
|
|
167
|
+
solutions = []
|
|
168
|
+
for s in evt.get_active_solutions():
|
|
169
|
+
if s.log_likelihood is None or s.n_data_points is None:
|
|
170
|
+
continue
|
|
171
|
+
if s.n_data_points <= 0:
|
|
172
|
+
console.print(
|
|
173
|
+
f"Skipping {s.solution_id}: n_data_points <= 0",
|
|
174
|
+
style="bold red",
|
|
175
|
+
)
|
|
176
|
+
continue
|
|
177
|
+
solutions.append(s)
|
|
178
|
+
|
|
179
|
+
table = Table(title=f"Solution Comparison for {event_id}")
|
|
180
|
+
table.add_column("Solution ID")
|
|
181
|
+
table.add_column("Model Type")
|
|
182
|
+
table.add_column("Higher-Order Effects")
|
|
183
|
+
table.add_column("# Params (k)")
|
|
184
|
+
table.add_column("Log-Likelihood")
|
|
185
|
+
table.add_column("BIC")
|
|
186
|
+
table.add_column("Relative Prob")
|
|
187
|
+
|
|
188
|
+
rel_prob_map: Dict[str, float] = {}
|
|
189
|
+
note = None
|
|
190
|
+
if solutions:
|
|
191
|
+
provided_sum = sum(s.relative_probability or 0.0 for s in solutions if s.relative_probability is not None)
|
|
192
|
+
need_calc = [s for s in solutions if s.relative_probability is None]
|
|
193
|
+
if need_calc:
|
|
194
|
+
can_calc = all(
|
|
195
|
+
s.log_likelihood is not None and s.n_data_points and s.n_data_points > 0 and len(s.parameters) > 0
|
|
196
|
+
for s in need_calc
|
|
197
|
+
)
|
|
198
|
+
remaining = max(1.0 - provided_sum, 0.0)
|
|
199
|
+
if can_calc:
|
|
200
|
+
bic_vals = {
|
|
201
|
+
s.solution_id: len(s.parameters) * math.log(s.n_data_points) - 2 * s.log_likelihood
|
|
202
|
+
for s in need_calc
|
|
203
|
+
}
|
|
204
|
+
bic_min = min(bic_vals.values())
|
|
205
|
+
weights = {sid: math.exp(-0.5 * (bic - bic_min)) for sid, bic in bic_vals.items()}
|
|
206
|
+
wsum = sum(weights.values())
|
|
207
|
+
for sid, w in weights.items():
|
|
208
|
+
rel_prob_map[sid] = remaining * w / wsum if wsum > 0 else remaining / len(weights)
|
|
209
|
+
note = "Relative probabilities calculated using BIC"
|
|
210
|
+
else:
|
|
211
|
+
eq = remaining / len(need_calc) if need_calc else 0.0
|
|
212
|
+
for s in need_calc:
|
|
213
|
+
rel_prob_map[s.solution_id] = eq
|
|
214
|
+
note = "Relative probabilities set equal due to missing data"
|
|
215
|
+
|
|
216
|
+
rows = []
|
|
217
|
+
for sol in solutions:
|
|
218
|
+
k = len(sol.parameters)
|
|
219
|
+
bic = k * math.log(sol.n_data_points) - 2 * sol.log_likelihood
|
|
220
|
+
rp = sol.relative_probability if sol.relative_probability is not None else rel_prob_map.get(sol.solution_id)
|
|
221
|
+
rows.append(
|
|
222
|
+
(
|
|
223
|
+
bic,
|
|
224
|
+
[
|
|
225
|
+
sol.solution_id,
|
|
226
|
+
sol.model_type,
|
|
227
|
+
(",".join(sol.higher_order_effects) if sol.higher_order_effects else "-"),
|
|
228
|
+
str(k),
|
|
229
|
+
f"{sol.log_likelihood:.2f}",
|
|
230
|
+
f"{bic:.2f}",
|
|
231
|
+
f"{rp:.3f}" if rp is not None else "N/A",
|
|
232
|
+
],
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
for _, cols in sorted(rows, key=lambda x: x[0]):
|
|
237
|
+
table.add_row(*cols)
|
|
238
|
+
|
|
239
|
+
console.print(table)
|
|
240
|
+
if note:
|
|
241
|
+
console.print(note, style="yellow")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Command line interface for microlens-submit.
|
|
2
|
+
|
|
3
|
+
This module provides a comprehensive CLI for managing microlensing challenge
|
|
4
|
+
submissions. It includes commands for project initialization, solution management,
|
|
5
|
+
validation, dossier generation, and export functionality.
|
|
6
|
+
|
|
7
|
+
The CLI is built using Typer and provides rich, colored output with helpful
|
|
8
|
+
error messages and validation feedback. All commands support both interactive
|
|
9
|
+
and scripted usage patterns.
|
|
10
|
+
|
|
11
|
+
**Key Commands:**
|
|
12
|
+
- init: Create new submission projects
|
|
13
|
+
- add-solution: Add microlensing solutions with parameters
|
|
14
|
+
- validate-submission: Check submission completeness
|
|
15
|
+
- generate-dossier: Create HTML documentation
|
|
16
|
+
- export: Create submission archives
|
|
17
|
+
|
|
18
|
+
**Example Workflow:**
|
|
19
|
+
# Initialize a new project
|
|
20
|
+
microlens-submit init --team-name "Team Alpha" --tier "advanced" ./my_project
|
|
21
|
+
|
|
22
|
+
# Add a solution
|
|
23
|
+
microlens-submit add-solution EVENT001 1S1L ./my_project \
|
|
24
|
+
--param t0=2459123.5 --param u0=0.1 --param tE=20.0 \
|
|
25
|
+
--log-likelihood -1234.56 --cpu-hours 2.5
|
|
26
|
+
|
|
27
|
+
# Validate and generate dossier
|
|
28
|
+
microlens-submit validate-submission ./my_project
|
|
29
|
+
microlens-submit generate-dossier ./my_project
|
|
30
|
+
|
|
31
|
+
# Export for submission
|
|
32
|
+
microlens-submit export submission.zip ./my_project
|
|
33
|
+
|
|
34
|
+
**Note:**
|
|
35
|
+
All commands that modify data automatically save changes to disk.
|
|
36
|
+
Use --dry-run flags to preview changes without saving.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import typer
|
|
42
|
+
from rich.console import Console
|
|
43
|
+
|
|
44
|
+
from .. import __version__
|
|
45
|
+
|
|
46
|
+
# Import command modules
|
|
47
|
+
from .commands import dossier, export, init, solutions, validation
|
|
48
|
+
|
|
49
|
+
console = Console()
|
|
50
|
+
app = typer.Typer()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("version")
|
|
54
|
+
def version() -> None:
|
|
55
|
+
"""Show the version of microlens-submit.
|
|
56
|
+
|
|
57
|
+
Displays the current version of the microlens-submit package.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> microlens-submit version
|
|
61
|
+
microlens-submit version 0.12.0-dev
|
|
62
|
+
|
|
63
|
+
Note:
|
|
64
|
+
This command is useful for verifying the installed version
|
|
65
|
+
and for debugging purposes.
|
|
66
|
+
"""
|
|
67
|
+
console.print(f"microlens-submit version {__version__}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.callback()
|
|
71
|
+
def main(
|
|
72
|
+
ctx: typer.Context,
|
|
73
|
+
no_color: bool = typer.Option(False, "--no-color", help="Disable colored output"),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Handle global CLI options.
|
|
76
|
+
|
|
77
|
+
Sets up global configuration for the CLI, including color output
|
|
78
|
+
preferences that apply to all commands.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
ctx: Typer context for command execution.
|
|
82
|
+
no_color: If True, disable colored output for all commands.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
# Disable colors for all commands
|
|
86
|
+
microlens-submit --no-color init --team-name "Team" --tier "basic" ./project
|
|
87
|
+
|
|
88
|
+
Note:
|
|
89
|
+
This is a Typer callback that runs before any command execution.
|
|
90
|
+
It's used to configure global settings like color output.
|
|
91
|
+
"""
|
|
92
|
+
if no_color:
|
|
93
|
+
global console
|
|
94
|
+
console = Console(color_system=None)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Register all commands from modules
|
|
98
|
+
app.command("init")(init.init)
|
|
99
|
+
app.command("nexus-init")(init.nexus_init)
|
|
100
|
+
|
|
101
|
+
app.command("add-solution")(solutions.add_solution)
|
|
102
|
+
app.command("deactivate")(solutions.deactivate)
|
|
103
|
+
app.command("activate")(solutions.activate)
|
|
104
|
+
app.command("remove-solution")(solutions.remove_solution)
|
|
105
|
+
app.command("edit-solution")(solutions.edit_solution)
|
|
106
|
+
app.command("notes")(solutions.edit_notes)
|
|
107
|
+
app.command("import-solutions")(solutions.import_solutions)
|
|
108
|
+
|
|
109
|
+
app.command("validate-solution")(validation.validate_solution)
|
|
110
|
+
app.command("validate-submission")(validation.validate_submission)
|
|
111
|
+
app.command("validate-event")(validation.validate_event)
|
|
112
|
+
app.command("list-solutions")(validation.list_solutions)
|
|
113
|
+
app.command("compare-solutions")(validation.compare_solutions)
|
|
114
|
+
|
|
115
|
+
app.command("generate-dossier")(dossier.generate_dossier)
|
|
116
|
+
|
|
117
|
+
app.command("export")(export.export)
|
|
118
|
+
app.command("remove-event")(export.remove_event)
|
|
119
|
+
app.command("set-repo-url")(export.set_repo_url)
|
|
120
|
+
app.command("set-hardware-info")(export.set_hardware_info)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dossier generation package for microlens-submit.
|
|
3
|
+
|
|
4
|
+
This package provides functionality to generate HTML dossiers and dashboards
|
|
5
|
+
for submission review and documentation. It creates comprehensive, printable
|
|
6
|
+
HTML reports that showcase microlensing challenge submissions with detailed
|
|
7
|
+
statistics, visualizations, and participant notes.
|
|
8
|
+
|
|
9
|
+
The package generates three types of HTML pages:
|
|
10
|
+
1. Dashboard (index.html) - Overview of all events and solutions
|
|
11
|
+
2. Event pages - Detailed view of each event with its solutions
|
|
12
|
+
3. Solution pages - Individual solution details with parameters and notes
|
|
13
|
+
|
|
14
|
+
All pages use Tailwind CSS for styling and include syntax highlighting for
|
|
15
|
+
code blocks in participant notes.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from microlens_submit import load
|
|
19
|
+
>>> from microlens_submit.dossier import generate_dashboard_html
|
|
20
|
+
>>> from pathlib import Path
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Load a submission project
|
|
23
|
+
>>> submission = load("./my_project")
|
|
24
|
+
>>>
|
|
25
|
+
>>> # Generate the complete dossier
|
|
26
|
+
>>> generate_dashboard_html(submission, Path("./dossier_output"))
|
|
27
|
+
>>>
|
|
28
|
+
>>> # Files created:
|
|
29
|
+
>>> # - ./dossier_output/index.html (main dashboard)
|
|
30
|
+
>>> # - ./dossier_output/EVENT001.html (event page)
|
|
31
|
+
>>> # - ./dossier_output/solution_id.html (solution pages)
|
|
32
|
+
>>> # - ./dossier_output/full_dossier_report.html (printable version)
|
|
33
|
+
>>> # - ./dossier_output/assets/ (logos and icons)
|
|
34
|
+
|
|
35
|
+
Note:
|
|
36
|
+
This package is designed to be used primarily through the CLI command
|
|
37
|
+
`microlens-submit generate-dossier`, but can also be used programmatically
|
|
38
|
+
for custom dossier generation workflows.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from .dashboard import generate_dashboard_html
|
|
42
|
+
from .event_page import generate_event_page
|
|
43
|
+
from .full_report import generate_full_dossier_report_html
|
|
44
|
+
from .solution_page import generate_solution_page
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"generate_dashboard_html",
|
|
48
|
+
"generate_event_page",
|
|
49
|
+
"generate_solution_page",
|
|
50
|
+
"generate_full_dossier_report_html",
|
|
51
|
+
]
|