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.
- 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.2.dist-info → microlens_submit-0.16.0.dist-info}/METADATA +42 -27
- microlens_submit-0.16.0.dist-info/RECORD +32 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.0.dist-info}/WHEEL +1 -1
- microlens_submit/api.py +0 -1257
- microlens_submit/cli.py +0 -1803
- microlens_submit/dossier.py +0 -1443
- microlens_submit-0.12.2.dist-info/RECORD +0 -13
- {microlens_submit-0.12.2.dist-info/licenses → microlens_submit-0.16.0.dist-info}/LICENSE +0 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.0.dist-info}/entry_points.txt +0 -0
- {microlens_submit-0.12.2.dist-info → microlens_submit-0.16.0.dist-info}/top_level.txt +0 -0
microlens_submit/__init__.py
CHANGED
|
@@ -1,163 +1,13 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Stateful tools for Microlensing Data Challenge submissions.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Core Features:**
|
|
8
|
-
- Project initialization and management
|
|
9
|
-
- Solution creation and parameter validation
|
|
10
|
-
- Submission validation and completeness checking
|
|
11
|
-
- HTML dossier generation with rich visualizations
|
|
12
|
-
- Export functionality for challenge submission
|
|
13
|
-
- Command-line interface for all operations
|
|
14
|
-
|
|
15
|
-
**Main Components:**
|
|
16
|
-
- Submission: Top-level container for all submission data
|
|
17
|
-
- Event: Container for microlensing events and their solutions
|
|
18
|
-
- Solution: Individual microlensing solutions with parameters and metadata
|
|
19
|
-
- Validation: Comprehensive parameter and submission validation
|
|
20
|
-
- Dossier: HTML report generation with Tailwind CSS styling
|
|
21
|
-
|
|
22
|
-
**Quick Start:**
|
|
23
|
-
>>> from microlens_submit import load, Submission
|
|
24
|
-
>>>
|
|
25
|
-
>>> # Load an existing project
|
|
26
|
-
>>> submission = load("./my_project")
|
|
27
|
-
>>>
|
|
28
|
-
>>> # Or create a new submission
|
|
29
|
-
>>> submission = Submission()
|
|
30
|
-
>>> submission.team_name = "Team Alpha"
|
|
31
|
-
>>> submission.tier = "advanced"
|
|
32
|
-
>>> submission.save("./my_project")
|
|
33
|
-
>>>
|
|
34
|
-
>>> # Add a solution to an event
|
|
35
|
-
>>> event = submission.get_event("EVENT001")
|
|
36
|
-
>>> solution = event.add_solution(
|
|
37
|
-
... model_type="1S1L",
|
|
38
|
-
... parameters={"t0": 2459123.5, "u0": 0.1, "tE": 20.0}
|
|
39
|
-
... )
|
|
40
|
-
>>> solution.log_likelihood = -1234.56
|
|
41
|
-
>>> solution.cpu_hours = 2.5
|
|
42
|
-
>>> submission.save()
|
|
43
|
-
|
|
44
|
-
**Command Line Usage:**
|
|
45
|
-
# Initialize a new project
|
|
46
|
-
microlens-submit init --team-name "Team Alpha" --tier "advanced" ./project
|
|
47
|
-
|
|
48
|
-
# Add a solution
|
|
49
|
-
microlens-submit add-solution EVENT001 1S1L ./project \
|
|
50
|
-
--param t0=2459123.5 --param u0=0.1 --param tE=20.0 \
|
|
51
|
-
--log-likelihood -1234.56 --cpu-hours 2.5
|
|
52
|
-
|
|
53
|
-
# Validate and generate dossier
|
|
54
|
-
microlens-submit validate-submission ./project
|
|
55
|
-
microlens-submit generate-dossier ./project
|
|
56
|
-
|
|
57
|
-
# Export for submission
|
|
58
|
-
microlens-submit export submission.zip ./project
|
|
59
|
-
|
|
60
|
-
**Supported Model Types:**
|
|
61
|
-
- 1S1L: Point Source, Single Point Lens (standard microlensing)
|
|
62
|
-
- 1S2L: Point Source, Binary Point Lens
|
|
63
|
-
- 2S1L: Binary Source, Single Point Lens
|
|
64
|
-
- 2S2L: Binary Source, Binary Point Lens
|
|
65
|
-
- 1S3L: Point Source, Triple Point Lens
|
|
66
|
-
- 2S3L: Binary Source, Triple Point Lens
|
|
67
|
-
|
|
68
|
-
**Higher-Order Effects:**
|
|
69
|
-
- parallax: Microlens parallax effect
|
|
70
|
-
- finite-source: Finite source size effect
|
|
71
|
-
- lens-orbital-motion: Orbital motion of lens components
|
|
72
|
-
- xallarap: Source orbital motion
|
|
73
|
-
- gaussian-process: Gaussian process noise modeling
|
|
74
|
-
- stellar-rotation: Stellar rotation effects
|
|
75
|
-
- fitted-limb-darkening: Fitted limb darkening coefficients
|
|
76
|
-
|
|
77
|
-
Example:
|
|
78
|
-
>>> from microlens_submit import load, Submission
|
|
79
|
-
>>> from pathlib import Path
|
|
80
|
-
>>>
|
|
81
|
-
>>> # Create a new submission project
|
|
82
|
-
>>> submission = Submission()
|
|
83
|
-
>>> submission.team_name = "Team Alpha"
|
|
84
|
-
>>> submission.tier = "advanced"
|
|
85
|
-
>>> submission.repo_url = "https://github.com/team-alpha/microlens-analysis"
|
|
86
|
-
>>>
|
|
87
|
-
>>> # Add hardware information
|
|
88
|
-
>>> submission.hardware_info = {
|
|
89
|
-
... "cpu_details": "Intel Xeon E5-2680 v4",
|
|
90
|
-
... "memory_gb": 64,
|
|
91
|
-
... "nexus_image": "roman-science-platform:latest"
|
|
92
|
-
... }
|
|
93
|
-
>>>
|
|
94
|
-
>>> # Create an event and add solutions
|
|
95
|
-
>>> event = submission.get_event("EVENT001")
|
|
96
|
-
>>>
|
|
97
|
-
>>> # Simple 1S1L solution
|
|
98
|
-
>>> solution1 = event.add_solution(
|
|
99
|
-
... model_type="1S1L",
|
|
100
|
-
... parameters={
|
|
101
|
-
... "t0": 2459123.5,
|
|
102
|
-
... "u0": 0.1,
|
|
103
|
-
... "tE": 20.0,
|
|
104
|
-
... "F0_S": 1000.0,
|
|
105
|
-
... "F0_B": 500.0
|
|
106
|
-
... }
|
|
107
|
-
... )
|
|
108
|
-
>>> solution1.log_likelihood = -1234.56
|
|
109
|
-
>>> solution1.n_data_points = 1250
|
|
110
|
-
>>> solution1.cpu_hours = 2.5
|
|
111
|
-
>>> solution1.relative_probability = 0.8
|
|
112
|
-
>>> solution1.notes = "# Simple Point Lens Fit\n\nThis is a basic 1S1L solution."
|
|
113
|
-
>>>
|
|
114
|
-
>>> # Binary lens solution with parallax
|
|
115
|
-
>>> solution2 = event.add_solution(
|
|
116
|
-
... model_type="1S2L",
|
|
117
|
-
... parameters={
|
|
118
|
-
... "t0": 2459123.5,
|
|
119
|
-
... "u0": 0.08,
|
|
120
|
-
... "tE": 22.1,
|
|
121
|
-
... "s": 1.15,
|
|
122
|
-
... "q": 0.001,
|
|
123
|
-
... "alpha": 45.2,
|
|
124
|
-
... "piEN": 0.1,
|
|
125
|
-
... "piEE": 0.05,
|
|
126
|
-
... "F0_S": 1000.0,
|
|
127
|
-
... "F0_B": 500.0
|
|
128
|
-
... }
|
|
129
|
-
... )
|
|
130
|
-
>>> solution2.higher_order_effects = ["parallax"]
|
|
131
|
-
>>> solution2.t_ref = 2459123.0
|
|
132
|
-
>>> solution2.log_likelihood = -1189.34
|
|
133
|
-
>>> solution2.cpu_hours = 15.2
|
|
134
|
-
>>> solution2.relative_probability = 0.2
|
|
135
|
-
>>>
|
|
136
|
-
>>> # Save the submission
|
|
137
|
-
>>> submission.save("./my_submission")
|
|
138
|
-
>>>
|
|
139
|
-
>>> # Validate the submission
|
|
140
|
-
>>> warnings = submission.validate()
|
|
141
|
-
>>> if warnings:
|
|
142
|
-
... print("Validation warnings:", warnings)
|
|
143
|
-
>>> else:
|
|
144
|
-
... print("Submission is valid!")
|
|
145
|
-
>>>
|
|
146
|
-
>>> # Generate dossier
|
|
147
|
-
>>> from microlens_submit.dossier import generate_dashboard_html
|
|
148
|
-
>>> generate_dashboard_html(submission, Path("./my_submission/dossier"))
|
|
149
|
-
|
|
150
|
-
Note:
|
|
151
|
-
This package is designed for the Microlensing Data Challenge and provides
|
|
152
|
-
comprehensive tools for managing submission data. All data is stored in
|
|
153
|
-
JSON format for portability and human readability. The package includes
|
|
154
|
-
extensive validation to ensure submission completeness and correctness.
|
|
155
|
-
The HTML dossier generation creates professional, printable reports with
|
|
156
|
-
Tailwind CSS styling and syntax-highlighted markdown notes.
|
|
3
|
+
``microlens-submit`` manages events and solutions on disk so you can build,
|
|
4
|
+
validate, and export a challenge submission using either the Python API or
|
|
5
|
+
the command line interface.
|
|
157
6
|
"""
|
|
158
7
|
|
|
159
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.16.0"
|
|
160
9
|
|
|
161
|
-
from .
|
|
10
|
+
from .models import Event, Solution, Submission
|
|
11
|
+
from .utils import load
|
|
162
12
|
|
|
163
13
|
__all__ = ["Event", "Solution", "Submission", "load"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI commands package for microlens-submit."""
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Dossier generation commands for microlens-submit CLI."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from microlens_submit.dossier import generate_dashboard_html, generate_event_page, generate_solution_page
|
|
11
|
+
from microlens_submit.dossier.full_report import generate_full_dossier_report_html
|
|
12
|
+
from microlens_submit.utils import load
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def generate_dossier(
|
|
18
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
19
|
+
event_id: Optional[str] = typer.Option(
|
|
20
|
+
None,
|
|
21
|
+
"--event-id",
|
|
22
|
+
help="Generate dossier for a specific event only (omit for full dossier)",
|
|
23
|
+
),
|
|
24
|
+
solution_id: Optional[str] = typer.Option(
|
|
25
|
+
None,
|
|
26
|
+
"--solution-id",
|
|
27
|
+
help="Generate dossier for a specific solution only (omit for full dossier)",
|
|
28
|
+
),
|
|
29
|
+
open: bool = typer.Option(
|
|
30
|
+
False,
|
|
31
|
+
"--open",
|
|
32
|
+
help="Open the generated dossier in your web browser after generation.",
|
|
33
|
+
),
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Generate an HTML dossier for the submission.
|
|
36
|
+
|
|
37
|
+
Use --open to automatically open the main dossier page in your browser after generation.
|
|
38
|
+
"""
|
|
39
|
+
sub = load(str(project_path))
|
|
40
|
+
output_dir = Path(project_path) / "dossier"
|
|
41
|
+
|
|
42
|
+
if solution_id:
|
|
43
|
+
# Find the solution across all events (same pattern as other CLI commands)
|
|
44
|
+
solution = None
|
|
45
|
+
containing_event_id = None
|
|
46
|
+
for eid, event in sub.events.items():
|
|
47
|
+
if solution_id in event.solutions:
|
|
48
|
+
solution = event.solutions[solution_id]
|
|
49
|
+
containing_event_id = eid
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
if solution is None:
|
|
53
|
+
console.print(f"Solution {solution_id} not found", style="bold red")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
# Create output directory and assets subdirectory
|
|
57
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
(output_dir / "assets").mkdir(exist_ok=True)
|
|
59
|
+
|
|
60
|
+
# Generate only the specific solution page
|
|
61
|
+
event = sub.events[containing_event_id]
|
|
62
|
+
console.print(
|
|
63
|
+
Panel(
|
|
64
|
+
f"Generating dossier for solution {solution_id} in event {containing_event_id}...",
|
|
65
|
+
style="cyan",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
generate_solution_page(solution, event, sub, output_dir)
|
|
69
|
+
if open:
|
|
70
|
+
import webbrowser
|
|
71
|
+
|
|
72
|
+
solution_path = output_dir / f"{solution_id}.html"
|
|
73
|
+
if solution_path.exists():
|
|
74
|
+
webbrowser.open(solution_path.resolve().as_uri())
|
|
75
|
+
|
|
76
|
+
elif event_id:
|
|
77
|
+
# Generate only the specific event page
|
|
78
|
+
if event_id not in sub.events:
|
|
79
|
+
console.print(f"Event {event_id} not found", style="bold red")
|
|
80
|
+
raise typer.Exit(1)
|
|
81
|
+
|
|
82
|
+
# Create output directory and assets subdirectory
|
|
83
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
(output_dir / "assets").mkdir(exist_ok=True)
|
|
85
|
+
|
|
86
|
+
event = sub.events[event_id]
|
|
87
|
+
console.print(Panel(f"Generating dossier for event {event_id}...", style="cyan"))
|
|
88
|
+
generate_event_page(event, sub, output_dir)
|
|
89
|
+
if open:
|
|
90
|
+
import webbrowser
|
|
91
|
+
|
|
92
|
+
event_path = output_dir / f"{event_id}.html"
|
|
93
|
+
if event_path.exists():
|
|
94
|
+
webbrowser.open(event_path.resolve().as_uri())
|
|
95
|
+
|
|
96
|
+
else:
|
|
97
|
+
# Generate full dossier (all events and solutions)
|
|
98
|
+
console.print(
|
|
99
|
+
Panel(
|
|
100
|
+
"Generating comprehensive dossier for all events and solutions...",
|
|
101
|
+
style="cyan",
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
generate_dashboard_html(sub, output_dir)
|
|
105
|
+
|
|
106
|
+
# Generate comprehensive printable dossier
|
|
107
|
+
console.print(Panel("Generating comprehensive printable dossier...", style="cyan"))
|
|
108
|
+
generate_full_dossier_report_html(sub, output_dir)
|
|
109
|
+
|
|
110
|
+
# Replace placeholder in index.html with the real link
|
|
111
|
+
dashboard_path = output_dir / "index.html"
|
|
112
|
+
if dashboard_path.exists():
|
|
113
|
+
with dashboard_path.open("r", encoding="utf-8") as f:
|
|
114
|
+
dashboard_html = f.read()
|
|
115
|
+
dashboard_html = dashboard_html.replace(
|
|
116
|
+
"<!--FULL_DOSSIER_LINK_PLACEHOLDER-->",
|
|
117
|
+
'<div class="text-center">'
|
|
118
|
+
'<a href="./full_dossier_report.html" '
|
|
119
|
+
'class="inline-block bg-rtd-accent text-white py-3 px-6 '
|
|
120
|
+
"rounded-lg shadow-md hover:bg-rtd-secondary "
|
|
121
|
+
'transition-colors duration-200 text-lg font-semibold mt-8">'
|
|
122
|
+
"View Full Comprehensive Dossier (Printable)</a></div>",
|
|
123
|
+
)
|
|
124
|
+
with dashboard_path.open("w", encoding="utf-8") as f:
|
|
125
|
+
f.write(dashboard_html)
|
|
126
|
+
console.print(Panel("Comprehensive dossier generated!", style="bold green"))
|
|
127
|
+
|
|
128
|
+
# Open the main dashboard if requested
|
|
129
|
+
if open:
|
|
130
|
+
import webbrowser
|
|
131
|
+
|
|
132
|
+
webbrowser.open(dashboard_path.resolve().as_uri())
|
|
133
|
+
|
|
134
|
+
console.print(
|
|
135
|
+
Panel(
|
|
136
|
+
f"Dossier generated successfully at {output_dir / 'index.html'}",
|
|
137
|
+
style="bold green",
|
|
138
|
+
)
|
|
139
|
+
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Export commands for microlens-submit CLI."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from microlens_submit.utils import load
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def export(
|
|
16
|
+
output_path: Path,
|
|
17
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Generate a zip archive containing all active solutions.
|
|
20
|
+
|
|
21
|
+
This command creates a submission-ready archive. Unlike save operations,
|
|
22
|
+
export requires all validation checks to pass (complete team info,
|
|
23
|
+
hardware info, valid parameters, etc.) since this is for actual submission.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
output_path: Path for the output zip file
|
|
27
|
+
project_path: Directory containing the submission project
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
# Export for submission (requires complete submission)
|
|
31
|
+
microlens-submit export submission.zip ./my_project
|
|
32
|
+
|
|
33
|
+
Note:
|
|
34
|
+
Export is strict and requires complete submissions. Use save operations
|
|
35
|
+
for saving incomplete work during development.
|
|
36
|
+
"""
|
|
37
|
+
sub = load(str(project_path))
|
|
38
|
+
sub.export(str(output_path))
|
|
39
|
+
console.print(Panel(f"Exported submission to {output_path}", style="bold green"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def remove_event(
|
|
43
|
+
event_id: str,
|
|
44
|
+
force: bool = typer.Option(False, "--force", help="Force removal even if event has saved solutions"),
|
|
45
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Remove an entire event and all its solutions from the submission."""
|
|
48
|
+
submission = load(str(project_path))
|
|
49
|
+
|
|
50
|
+
if event_id not in submission.events:
|
|
51
|
+
typer.echo(f"❌ Event '{event_id}' not found in submission")
|
|
52
|
+
raise typer.Exit(1)
|
|
53
|
+
|
|
54
|
+
event = submission.events[event_id]
|
|
55
|
+
solution_count = len(event.solutions)
|
|
56
|
+
|
|
57
|
+
if not force:
|
|
58
|
+
typer.echo(f"⚠️ This will permanently remove event '{event_id}' " f"and all {solution_count} solutions.")
|
|
59
|
+
typer.echo(" This action cannot be undone.")
|
|
60
|
+
confirm = typer.confirm("Are you sure you want to continue?")
|
|
61
|
+
if not confirm:
|
|
62
|
+
typer.echo("❌ Operation cancelled")
|
|
63
|
+
raise typer.Exit(0)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
removed = submission.remove_event(event_id, force=force)
|
|
67
|
+
if removed:
|
|
68
|
+
typer.echo(f"✅ Removed event '{event_id}' and all {solution_count} solutions")
|
|
69
|
+
submission.save()
|
|
70
|
+
else:
|
|
71
|
+
typer.echo(f"❌ Failed to remove event '{event_id}'")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
except ValueError as e:
|
|
74
|
+
typer.echo(f"❌ Cannot remove event: {e}")
|
|
75
|
+
raise typer.Exit(1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def set_repo_url(
|
|
79
|
+
repo_url: str = typer.Argument(..., help="GitHub repository URL (e.g. https://github.com/owner/repo)"),
|
|
80
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Set or update the GitHub repository URL in the submission metadata."""
|
|
83
|
+
sub = load(str(project_path))
|
|
84
|
+
sub.repo_url = repo_url
|
|
85
|
+
sub.save()
|
|
86
|
+
console.print(
|
|
87
|
+
Panel(
|
|
88
|
+
f"Set repo_url to {repo_url} in {project_path}/submission.json",
|
|
89
|
+
style="bold green",
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def set_hardware_info(
|
|
95
|
+
cpu: Optional[str] = typer.Option(None, "--cpu", help="CPU model/description"),
|
|
96
|
+
cpu_details: Optional[str] = typer.Option(None, "--cpu-details", help="Detailed CPU information"),
|
|
97
|
+
memory_gb: Optional[float] = typer.Option(None, "--memory-gb", help="Memory in GB"),
|
|
98
|
+
ram_gb: Optional[float] = typer.Option(None, "--ram-gb", help="RAM in GB (alternative to --memory-gb)"),
|
|
99
|
+
platform: Optional[str] = typer.Option(
|
|
100
|
+
None,
|
|
101
|
+
"--platform",
|
|
102
|
+
help="Platform description (e.g., 'Local Analysis', 'Roman Nexus')",
|
|
103
|
+
),
|
|
104
|
+
nexus_image: Optional[str] = typer.Option(None, "--nexus-image", help="Roman Nexus image identifier"),
|
|
105
|
+
clear: bool = typer.Option(False, "--clear", help="Clear all existing hardware info"),
|
|
106
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be changed without saving"),
|
|
107
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Set or update hardware information in the submission metadata."""
|
|
110
|
+
sub = load(str(project_path))
|
|
111
|
+
|
|
112
|
+
# Initialize hardware_info if it doesn't exist
|
|
113
|
+
if sub.hardware_info is None:
|
|
114
|
+
sub.hardware_info = {}
|
|
115
|
+
|
|
116
|
+
changes = []
|
|
117
|
+
|
|
118
|
+
# Clear existing info if requested
|
|
119
|
+
if clear:
|
|
120
|
+
if sub.hardware_info:
|
|
121
|
+
changes.append("Clear all existing hardware info")
|
|
122
|
+
sub.hardware_info = {}
|
|
123
|
+
|
|
124
|
+
# Set new values
|
|
125
|
+
if cpu_details is not None:
|
|
126
|
+
if sub.hardware_info.get("cpu_details") != cpu_details:
|
|
127
|
+
changes.append(f"Set cpu_details: {cpu_details}")
|
|
128
|
+
sub.hardware_info["cpu_details"] = cpu_details
|
|
129
|
+
elif cpu is not None:
|
|
130
|
+
if sub.hardware_info.get("cpu") != cpu:
|
|
131
|
+
changes.append(f"Set cpu: {cpu}")
|
|
132
|
+
sub.hardware_info["cpu"] = cpu
|
|
133
|
+
|
|
134
|
+
if memory_gb is not None:
|
|
135
|
+
if sub.hardware_info.get("memory_gb") != memory_gb:
|
|
136
|
+
changes.append(f"Set memory_gb: {memory_gb}")
|
|
137
|
+
sub.hardware_info["memory_gb"] = memory_gb
|
|
138
|
+
elif ram_gb is not None:
|
|
139
|
+
if sub.hardware_info.get("ram_gb") != ram_gb:
|
|
140
|
+
changes.append(f"Set ram_gb: {ram_gb}")
|
|
141
|
+
sub.hardware_info["ram_gb"] = ram_gb
|
|
142
|
+
|
|
143
|
+
if platform is not None:
|
|
144
|
+
if sub.hardware_info.get("platform") != platform:
|
|
145
|
+
changes.append(f"Set platform: {platform}")
|
|
146
|
+
sub.hardware_info["platform"] = platform
|
|
147
|
+
|
|
148
|
+
if nexus_image is not None:
|
|
149
|
+
if sub.hardware_info.get("nexus_image") != nexus_image:
|
|
150
|
+
changes.append(f"Set nexus_image: {nexus_image}")
|
|
151
|
+
sub.hardware_info["nexus_image"] = nexus_image
|
|
152
|
+
|
|
153
|
+
# Show dry run results
|
|
154
|
+
if dry_run:
|
|
155
|
+
if changes:
|
|
156
|
+
console.print(Panel("Hardware info changes (dry run):", style="cyan"))
|
|
157
|
+
for change in changes:
|
|
158
|
+
console.print(f" • {change}")
|
|
159
|
+
console.print(f"\nNew hardware_info: {sub.hardware_info}")
|
|
160
|
+
else:
|
|
161
|
+
console.print(Panel("No changes would be made", style="yellow"))
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
# Apply changes
|
|
165
|
+
if changes:
|
|
166
|
+
sub.save()
|
|
167
|
+
console.print(
|
|
168
|
+
Panel(
|
|
169
|
+
f"Updated hardware info in {project_path}/submission.json",
|
|
170
|
+
style="bold green",
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
for change in changes:
|
|
174
|
+
console.print(f" • {change}")
|
|
175
|
+
console.print(f"\nCurrent hardware_info: {sub.hardware_info}")
|
|
176
|
+
else:
|
|
177
|
+
console.print(Panel("No changes made", style="yellow"))
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""Initialization commands for microlens-submit CLI."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from microlens_submit.utils import load
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def init(
|
|
16
|
+
team_name: str = typer.Option(..., help="Team name"),
|
|
17
|
+
tier: str = typer.Option(..., help="Challenge tier"),
|
|
18
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Create a new submission project in the specified directory.
|
|
21
|
+
|
|
22
|
+
Initializes a new microlensing submission project with the given team name
|
|
23
|
+
and tier. The project directory structure is created automatically, and
|
|
24
|
+
the submission.json file is initialized with basic metadata.
|
|
25
|
+
|
|
26
|
+
This command also attempts to auto-detect the GitHub repository URL
|
|
27
|
+
from the current git configuration and provides helpful feedback.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
team_name: Name of the participating team (e.g., "Team Alpha").
|
|
31
|
+
tier: Challenge tier level (e.g., "basic", "advanced").
|
|
32
|
+
project_path: Directory where the project will be created.
|
|
33
|
+
Defaults to current directory if not specified.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
OSError: If unable to create the project directory or write files.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
# Create project in current directory
|
|
40
|
+
microlens-submit init --team-name "Team Alpha" --tier "advanced"
|
|
41
|
+
|
|
42
|
+
# Create project in specific directory
|
|
43
|
+
microlens-submit init --team-name "Team Beta" --tier "basic" ./my_submission
|
|
44
|
+
|
|
45
|
+
# Project structure created:
|
|
46
|
+
# ./my_submission/
|
|
47
|
+
# ├── submission.json
|
|
48
|
+
# └── events/
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
If the project directory already exists, it will be used as-is.
|
|
52
|
+
If a git repository is detected, the GitHub URL will be automatically
|
|
53
|
+
set. Otherwise, a warning is shown and you can set it later with
|
|
54
|
+
set-repo-url command.
|
|
55
|
+
"""
|
|
56
|
+
# Validate tier
|
|
57
|
+
try:
|
|
58
|
+
from microlens_submit.tier_validation import get_available_tiers, get_tier_description
|
|
59
|
+
|
|
60
|
+
available_tiers = get_available_tiers()
|
|
61
|
+
if tier not in available_tiers:
|
|
62
|
+
console.print(f"[yellow]Warning: Invalid tier '{tier}'[/yellow]")
|
|
63
|
+
console.print(f"Available tiers: {', '.join(available_tiers)}")
|
|
64
|
+
console.print("[yellow]Setting tier to 'None' (no validation)[/yellow]")
|
|
65
|
+
tier = "None"
|
|
66
|
+
tier_desc = get_tier_description(tier)
|
|
67
|
+
console.print(f"[green]Using tier:[/green] {tier} - {tier_desc}")
|
|
68
|
+
except ImportError:
|
|
69
|
+
# Tier validation module not available, skip validation
|
|
70
|
+
console.print(f"[yellow]Warning: Tier validation not available, using tier '{tier}'[/yellow]")
|
|
71
|
+
except ValueError as e:
|
|
72
|
+
console.print(f"[yellow]Warning: {e}[/yellow]")
|
|
73
|
+
console.print("[yellow]Setting tier to 'None' (no validation)[/yellow]")
|
|
74
|
+
tier = "None"
|
|
75
|
+
|
|
76
|
+
sub = load(str(project_path))
|
|
77
|
+
sub.team_name = team_name
|
|
78
|
+
sub.tier = tier
|
|
79
|
+
# Try to auto-detect repo_url
|
|
80
|
+
try:
|
|
81
|
+
repo_url = (
|
|
82
|
+
subprocess.check_output(
|
|
83
|
+
["git", "config", "--get", "remote.origin.url"],
|
|
84
|
+
stderr=subprocess.DEVNULL,
|
|
85
|
+
)
|
|
86
|
+
.decode()
|
|
87
|
+
.strip()
|
|
88
|
+
)
|
|
89
|
+
except Exception:
|
|
90
|
+
repo_url = None
|
|
91
|
+
if repo_url:
|
|
92
|
+
sub.repo_url = repo_url
|
|
93
|
+
console.print(f"[green]Auto-detected GitHub repo URL:[/green] {repo_url}")
|
|
94
|
+
else:
|
|
95
|
+
console.print(
|
|
96
|
+
"[yellow]Could not auto-detect a GitHub repository URL. "
|
|
97
|
+
"Please add it using 'microlens-submit set-repo-url <url> "
|
|
98
|
+
"<project_dir>'.[/yellow]"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Run warnings-only validation
|
|
102
|
+
warnings = sub.run_validation_warnings()
|
|
103
|
+
if warnings:
|
|
104
|
+
console.print("[yellow]⚠️ Project initialized with warnings:[/yellow]")
|
|
105
|
+
for warning in warnings:
|
|
106
|
+
console.print(f" [yellow]• {warning}[/yellow]")
|
|
107
|
+
console.print("[yellow]💡 These warnings will become errors when saving or exporting.[/yellow]")
|
|
108
|
+
|
|
109
|
+
# Try to save, but don't fail if there are validation errors
|
|
110
|
+
try:
|
|
111
|
+
sub.save()
|
|
112
|
+
console.print(Panel(f"Initialized project at {project_path}", style="bold green"))
|
|
113
|
+
except ValueError as e:
|
|
114
|
+
console.print("[yellow]⚠️ Project initialized but could not save due to validation errors:[/yellow]")
|
|
115
|
+
console.print(f"[yellow]{str(e)}[/yellow]")
|
|
116
|
+
console.print("[yellow]💡 Fix validation errors before saving or exporting.[/yellow]")
|
|
117
|
+
console.print(Panel(f"Initialized project at {project_path} (unsaved)", style="bold yellow"))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def nexus_init(
|
|
121
|
+
team_name: str = typer.Option(..., help="Team name"),
|
|
122
|
+
tier: str = typer.Option(..., help="Challenge tier"),
|
|
123
|
+
project_path: Path = typer.Argument(Path("."), help="Project directory"),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Create a project and record Roman Nexus environment details.
|
|
126
|
+
|
|
127
|
+
This command combines the functionality of init() with automatic
|
|
128
|
+
detection of Roman Science Platform environment information. It
|
|
129
|
+
populates hardware_info with CPU details, memory information, and
|
|
130
|
+
the Nexus image identifier.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
team_name: Name of the participating team (e.g., "Team Alpha").
|
|
134
|
+
tier: Challenge tier level (e.g., "basic", "advanced").
|
|
135
|
+
project_path: Directory where the project will be created.
|
|
136
|
+
Defaults to current directory if not specified.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
# Initialize project with Nexus platform info
|
|
140
|
+
microlens-submit nexus-init --team-name "Team Alpha" --tier "advanced" ./project
|
|
141
|
+
|
|
142
|
+
# This will automatically detect:
|
|
143
|
+
# - CPU model from /proc/cpuinfo
|
|
144
|
+
# - Memory from /proc/meminfo
|
|
145
|
+
# - Nexus image from JUPYTER_IMAGE_SPEC
|
|
146
|
+
|
|
147
|
+
Note:
|
|
148
|
+
This command is specifically designed for the Roman Science Platform
|
|
149
|
+
environment. It will silently skip any environment information that
|
|
150
|
+
cannot be detected (e.g., if running outside of Nexus).
|
|
151
|
+
"""
|
|
152
|
+
init(team_name=team_name, tier=tier, project_path=project_path)
|
|
153
|
+
sub = load(str(project_path))
|
|
154
|
+
sub.autofill_nexus_info()
|
|
155
|
+
|
|
156
|
+
# Run warnings-only validation after adding hardware info
|
|
157
|
+
warnings = sub.run_validation_warnings()
|
|
158
|
+
if warnings:
|
|
159
|
+
console.print("[yellow]⚠️ Project updated with warnings:[/yellow]")
|
|
160
|
+
for warning in warnings:
|
|
161
|
+
console.print(f" [yellow]• {warning}[/yellow]")
|
|
162
|
+
console.print("[yellow]💡 These warnings will become errors when saving or exporting.[/yellow]")
|
|
163
|
+
|
|
164
|
+
# Try to save, but don't fail if there are validation errors
|
|
165
|
+
try:
|
|
166
|
+
sub.save()
|
|
167
|
+
console.print("Nexus platform info captured.", style="bold green")
|
|
168
|
+
except ValueError as e:
|
|
169
|
+
console.print("[yellow]⚠️ Project updated but could not save due to validation errors:[/yellow]")
|
|
170
|
+
console.print(f"[yellow]{str(e)}[/yellow]")
|
|
171
|
+
console.print("[yellow]💡 Fix validation errors before saving or exporting.[/yellow]")
|
|
172
|
+
console.print("Nexus platform info captured (unsaved).", style="bold yellow")
|