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,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
+ ]