microlens-submit 0.16.4__tar.gz → 0.17.0__tar.gz

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 (49) hide show
  1. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/CHANGELOG.md +36 -0
  2. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/CITATION.cff +2 -2
  3. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/PKG-INFO +8 -4
  4. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/README.md +7 -3
  5. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/__init__.py +1 -1
  6. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/solutions.py +29 -1
  7. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/validation.py +8 -3
  8. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/event_page.py +20 -4
  9. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/full_report.py +42 -3
  10. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/solution_page.py +44 -5
  11. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/utils.py +63 -0
  12. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/solution.py +17 -0
  13. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/submission.py +4 -2
  14. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/tier_validation.py +6 -9
  15. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/validate_parameters.py +522 -190
  16. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/PKG-INFO +8 -4
  17. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/SOURCES.txt +2 -0
  18. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/pyproject.toml +1 -1
  19. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/setup.py +1 -1
  20. microlens_submit-0.17.0/tests/test_bic_calculation.py +235 -0
  21. microlens_submit-0.17.0/tests/test_physical_validation.py +55 -0
  22. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_tier_validation.py +2 -2
  23. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/LICENSE +0 -0
  24. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/MANIFEST.in +0 -0
  25. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/assets/github-desktop_logo.png +0 -0
  26. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/assets/rges-pit_logo.png +0 -0
  27. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/__init__.py +0 -0
  28. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/__main__.py +0 -0
  29. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/__init__.py +0 -0
  30. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/dossier.py +0 -0
  31. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/export.py +0 -0
  32. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/commands/init.py +0 -0
  33. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/cli/main.py +0 -0
  34. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/__init__.py +0 -0
  35. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/dossier/dashboard.py +0 -0
  36. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/error_messages.py +0 -0
  37. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/__init__.py +0 -0
  38. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/models/event.py +0 -0
  39. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/text_symbols.py +0 -0
  40. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit/utils.py +0 -0
  41. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/dependency_links.txt +0 -0
  42. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/entry_points.txt +0 -0
  43. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/requires.txt +0 -0
  44. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/microlens_submit.egg-info/top_level.txt +0 -0
  45. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/setup.cfg +0 -0
  46. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_api.py +0 -0
  47. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_cli.py +0 -0
  48. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_dossier_generation.py +0 -0
  49. {microlens_submit-0.16.4 → microlens_submit-0.17.0}/tests/test_dossier_pages.py +0 -0
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.17.0] - 2026-02-04
9
+
10
+ ### Added
11
+ - **Physical Parameters**: Added 24 physical parameters (Mtot, M1-M4, D_L, D_S, thetaE, piE components, mu_rel components, phi) to parameter spec
12
+ - **Physical Parameter Validation**: Comprehensive validation including mass consistency checks, vector magnitude verification, distance constraints, and unit confusion warnings
13
+ - **Uncertainty Metadata**: Added three new Solution fields:
14
+ - `uncertainty_method`: Method used to derive uncertainties (mcmc_posterior, fisher_matrix, bootstrap, propagation, inference, literature, other)
15
+ - `confidence_level`: Confidence level for uncertainties (default: 0.68 for 1-sigma)
16
+ - `physical_parameter_uncertainties`: Uncertainties for physical parameters (symmetric or asymmetric)
17
+ - **CLI Options**: Added `--physical-param-uncertainty`, `--uncertainty-method`, and `--confidence-level` options to `add-solution` command
18
+ - **Tests**: Added 3 new comprehensive BIC calculation tests verifying parameter counting
19
+ - **Version Management**: Enhanced `bump_version.py` to update and validate `parameter_spec.yaml` (includes drift check)
20
+
21
+ ### Fixed
22
+ - **BIC Calculation Bug**: Fixed critical bug where BIC calculation counted ALL parameters including metadata (t_ref, limb_darkening_coeffs) and physical parameters (Mtot, D_L, etc.) as "free parameters". Now correctly counts only fitted model parameters using new `count_model_parameters()` function. This affects relative probability calculations during export and solution comparison.
23
+
24
+ ### Changed
25
+ - Physical parameters now validated with `validate_physical_parameters()` automatically when present
26
+ - Solution metadata validation now includes uncertainty metadata checks
27
+ - BIC calculation in `submission.py` and `validation.py` now uses `count_model_parameters()` instead of `len(s.parameters)`
28
+
29
+ ### Documentation
30
+ - Added comprehensive examples of physical parameters and uncertainty metadata to tutorial and usage examples
31
+ - Tutorial emphasizes uncertainties are **optional but strongly recommended** for evaluation readiness
32
+ - Created `PHYSICAL_PARAMS_SUMMARY.md` with complete implementation guide
33
+
34
+ ## [Unreleased]
35
+
36
+ ## [0.16.5] - 2026-02-04
37
+
38
+ ### Fixed
39
+ - Dossier assets are now copied into the dossier output and linked with URL-safe relative paths,
40
+ improving image loading in Roman Research Nexus and similar environments.
41
+ - Event data, plots, and posteriors now resolve reliably when paths include spaces or nested folders.
42
+
43
+
8
44
  ## [0.16.4] - 2026-01-14
9
45
 
10
46
  ### Added
@@ -1,9 +1,9 @@
1
1
  cff-version: 1.2.0
2
2
  message: "If you use microlens-submit, please cite it as below."
3
3
  title: "microlens-submit"
4
- version: "0.16.4"
4
+ version: "0.17.0"
5
5
  authors:
6
6
  - family-names: Malpas
7
7
  given-names: Amber
8
8
  url: "https://github.com/AmberLee2427/microlens-submit"
9
- doi: "10.5281/zenodo.18246117"
9
+ doi: "10.5281/zenodo.18488304"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microlens-submit
3
- Version: 0.16.4
3
+ Version: 0.17.0
4
4
  Summary: A tool for managing and submitting microlensing solutions
5
5
  Home-page: https://github.com/AmberLee2427/microlens-submit
6
6
  Author: Amber Malpas
@@ -95,6 +95,10 @@ The CLI is the recommended way to interact with your submission project.
95
95
 
96
96
  You can pass ``--no-color`` to any command if your terminal does not support ANSI colors.
97
97
 
98
+ **Windows note:** If ``microlens-submit`` is not found after ``pip install``, your Python Scripts folder is likely missing from PATH.
99
+ Try ``py -m pip install microlens-submit`` and run ``py -m microlens_submit.cli --help``, or add the Scripts path shown by
100
+ ``py -m pip show -f microlens-submit`` to PATH.
101
+
98
102
  1. Initialize your project:
99
103
 
100
104
  ```bash
@@ -206,15 +210,15 @@ import this file directly.
206
210
 
207
211
  Bibtex:
208
212
  ```
209
- @software{malpas_2025_18246117,
213
+ @software{malpas_2025_18488304,
210
214
  author = {Malpas, Amber},
211
215
  title = {microlens-submit},
212
216
  month = oct,
213
217
  year = 2025,
214
218
  publisher = {Zenodo},
215
219
  version = {v0.16.3},
216
- doi = {10.5281/zenodo.18246117},
217
- url = {https://doi.org/10.5281/zenodo.18246117},
220
+ doi = {10.5281/zenodo.18488304},
221
+ url = {https://doi.org/10.5281/zenodo.18488304},
218
222
  }
219
223
  ```
220
224
 
@@ -45,6 +45,10 @@ The CLI is the recommended way to interact with your submission project.
45
45
 
46
46
  You can pass ``--no-color`` to any command if your terminal does not support ANSI colors.
47
47
 
48
+ **Windows note:** If ``microlens-submit`` is not found after ``pip install``, your Python Scripts folder is likely missing from PATH.
49
+ Try ``py -m pip install microlens-submit`` and run ``py -m microlens_submit.cli --help``, or add the Scripts path shown by
50
+ ``py -m pip show -f microlens-submit`` to PATH.
51
+
48
52
  1. Initialize your project:
49
53
 
50
54
  ```bash
@@ -156,15 +160,15 @@ import this file directly.
156
160
 
157
161
  Bibtex:
158
162
  ```
159
- @software{malpas_2025_18246117,
163
+ @software{malpas_2025_18488304,
160
164
  author = {Malpas, Amber},
161
165
  title = {microlens-submit},
162
166
  month = oct,
163
167
  year = 2025,
164
168
  publisher = {Zenodo},
165
169
  version = {v0.16.3},
166
- doi = {10.5281/zenodo.18246117},
167
- url = {https://doi.org/10.5281/zenodo.18246117},
170
+ doi = {10.5281/zenodo.18488304},
171
+ url = {https://doi.org/10.5281/zenodo.18488304},
168
172
  }
169
173
  ```
170
174
 
@@ -5,7 +5,7 @@ validate, and export a challenge submission using either the Python API or
5
5
  the command line interface.
6
6
  """
7
7
 
8
- __version__ = "0.16.4"
8
+ __version__ = "0.17.0"
9
9
 
10
10
  from .models import Event, Solution, Submission
11
11
  from .utils import load
@@ -187,7 +187,31 @@ def add_solution(
187
187
  physical_param: Optional[List[str]] = typer.Option(
188
188
  None,
189
189
  "--physical-param",
190
- help=("Physical parameters (M_L, D_L, M_planet, a, etc.) " "derived from model parameters [ADVANCED]"),
190
+ help=("Physical parameters (Mtot, D_L, thetaE, etc.) " "derived from model parameters [ADVANCED]"),
191
+ ),
192
+ physical_param_uncertainty: Optional[List[str]] = typer.Option(
193
+ None,
194
+ "--physical-param-uncertainty",
195
+ help=(
196
+ "Physical parameter uncertainties as key=value. "
197
+ "Can be single value (symmetric) or [lower,upper] (asymmetric) [ADVANCED]"
198
+ ),
199
+ ),
200
+ uncertainty_method: Optional[str] = typer.Option(
201
+ None,
202
+ "--uncertainty-method",
203
+ help=(
204
+ "Method used to derive uncertainties [ADVANCED]. "
205
+ "Options: mcmc_posterior, fisher_matrix, bootstrap, propagation, inference, literature, other"
206
+ ),
207
+ ),
208
+ confidence_level: Optional[float] = typer.Option(
209
+ None,
210
+ "--confidence-level",
211
+ help=(
212
+ "Confidence level for uncertainties (default: 0.68 = 1σ) [ADVANCED]. "
213
+ "Standard values: 0.68 (1σ), 0.95 (2σ), 0.997 (3σ)"
214
+ ),
191
215
  ),
192
216
  relative_probability: Optional[float] = typer.Option(
193
217
  None,
@@ -281,6 +305,10 @@ def add_solution(
281
305
  sol.limb_darkening_coeffs = _parse_pairs(limb_darkening_coeff)
282
306
  sol.parameter_uncertainties = _parse_pairs(parameter_uncertainty) or uncertainties
283
307
  sol.physical_parameters = _parse_pairs(physical_param)
308
+ sol.physical_parameter_uncertainties = _parse_pairs(physical_param_uncertainty)
309
+ sol.uncertainty_method = uncertainty_method
310
+ if confidence_level is not None:
311
+ sol.confidence_level = confidence_level
284
312
  sol.log_likelihood = log_likelihood
285
313
  sol.relative_probability = relative_probability
286
314
  sol.n_data_points = n_data_points
@@ -12,6 +12,7 @@ from rich.table import Table
12
12
  from microlens_submit.error_messages import enhance_validation_messages
13
13
  from microlens_submit.text_symbols import symbol
14
14
  from microlens_submit.utils import load
15
+ from microlens_submit.validate_parameters import count_model_parameters
15
16
 
16
17
  console = Console()
17
18
 
@@ -196,13 +197,17 @@ def compare_solutions(
196
197
  need_calc = [s for s in solutions if s.relative_probability is None]
197
198
  if need_calc:
198
199
  can_calc = all(
199
- s.log_likelihood is not None and s.n_data_points and s.n_data_points > 0 and len(s.parameters) > 0
200
+ s.log_likelihood is not None
201
+ and s.n_data_points
202
+ and s.n_data_points > 0
203
+ and count_model_parameters(s.parameters) > 0
200
204
  for s in need_calc
201
205
  )
202
206
  remaining = max(1.0 - provided_sum, 0.0)
203
207
  if can_calc:
204
208
  bic_vals = {
205
- s.solution_id: len(s.parameters) * math.log(s.n_data_points) - 2 * s.log_likelihood
209
+ s.solution_id: count_model_parameters(s.parameters) * math.log(s.n_data_points)
210
+ - 2 * s.log_likelihood
206
211
  for s in need_calc
207
212
  }
208
213
  bic_min = min(bic_vals.values())
@@ -219,7 +224,7 @@ def compare_solutions(
219
224
 
220
225
  rows = []
221
226
  for sol in solutions:
222
- k = len(sol.parameters)
227
+ k = count_model_parameters(sol.parameters)
223
228
  bic = k * math.log(sol.n_data_points) - 2 * sol.log_likelihood
224
229
  rp = sol.relative_probability if sol.relative_probability is not None else rel_prob_map.get(sol.solution_id)
225
230
  rows.append(
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
  from .. import __version__
13
13
  from ..models import Event, Submission
14
14
  from .solution_page import generate_solution_page
15
+ from .utils import resolve_dossier_asset_path
15
16
 
16
17
 
17
18
  def generate_event_page(event: Event, submission: Submission, output_dir: Path) -> None:
@@ -50,7 +51,17 @@ def generate_event_page(event: Event, submission: Submission, output_dir: Path)
50
51
  to individual solution pages.
51
52
  """
52
53
  # Prepare output directory (already created)
53
- html = _generate_event_page_content(event, submission)
54
+ project_root = Path(submission.project_path)
55
+ event_data_link = ""
56
+ if hasattr(event, "event_data_path") and event.event_data_path:
57
+ event_data_link = resolve_dossier_asset_path(
58
+ event.event_data_path,
59
+ project_root,
60
+ output_dir,
61
+ subdir="event-data",
62
+ prefix=f"{event.event_id}_event_data",
63
+ )
64
+ html = _generate_event_page_content(event, submission, event_data_link=event_data_link)
54
65
  with (output_dir / f"{event.event_id}.html").open("w", encoding="utf-8") as f:
55
66
  f.write(html)
56
67
 
@@ -59,7 +70,12 @@ def generate_event_page(event: Event, submission: Submission, output_dir: Path)
59
70
  generate_solution_page(sol, event, submission, output_dir)
60
71
 
61
72
 
62
- def _generate_event_page_content(event: Event, submission: Submission) -> str:
73
+ def _generate_event_page_content(
74
+ event: Event,
75
+ submission: Submission,
76
+ *,
77
+ event_data_link: str = "",
78
+ ) -> str:
63
79
  """Generate the HTML content for an event dossier page.
64
80
 
65
81
  Creates the complete HTML content for a single event page, including
@@ -163,10 +179,10 @@ def _generate_event_page_content(event: Event, submission: Submission) -> str:
163
179
  )
164
180
  # Optional raw data link
165
181
  raw_data_html = ""
166
- if hasattr(event, "event_data_path") and event.event_data_path:
182
+ if event_data_link:
167
183
  raw_data_html = (
168
184
  f'<p class="text-rtd-text">Raw Event Data: '
169
- f'<a href="{event.event_data_path}" '
185
+ f'<a href="{event_data_link}" '
170
186
  f'class="text-rtd-accent hover:underline">Download Data</a></p>'
171
187
  )
172
188
  # HTML content
@@ -15,6 +15,7 @@ from ..models.solution import Solution
15
15
  from .dashboard import _generate_dashboard_content
16
16
  from .event_page import _generate_event_page_content
17
17
  from .solution_page import _generate_solution_page_content
18
+ from .utils import resolve_dossier_asset_path
18
19
 
19
20
 
20
21
  def generate_full_dossier_report_html(submission: Submission, output_dir: Path) -> None:
@@ -63,19 +64,57 @@ def generate_full_dossier_report_html(submission: Submission, output_dir: Path)
63
64
  all_html_sections.append('<hr class="my-8 border-t-2 border-rtd-accent">') # Divider after dashboard
64
65
 
65
66
  # Events and solutions
67
+ project_root = Path(submission.project_path)
66
68
  for event in submission.events.values():
67
- event_html = _generate_event_page_content(event, submission)
69
+ event_data_link = ""
70
+ if hasattr(event, "event_data_path") and event.event_data_path:
71
+ event_data_link = resolve_dossier_asset_path(
72
+ event.event_data_path,
73
+ project_root,
74
+ output_dir,
75
+ subdir="event-data",
76
+ prefix=f"{event.event_id}_event_data",
77
+ )
78
+ event_html = _generate_event_page_content(event, submission, event_data_link=event_data_link)
68
79
  event_body = extract_main_content_body(event_html, section_type="event", section_id=event.event_id)
69
80
  all_html_sections.append(event_body)
70
81
  all_html_sections.append('<hr class="my-8 border-t-2 border-rtd-accent">') # Divider after event
71
82
 
72
83
  for sol in event.get_active_solutions():
73
- sol_html = _generate_solution_page_content(sol, event, submission)
84
+ lc_plot = resolve_dossier_asset_path(
85
+ sol.lightcurve_plot_path,
86
+ project_root,
87
+ output_dir,
88
+ subdir="plots",
89
+ prefix=f"{event.event_id}_{sol.solution_id}_lightcurve",
90
+ )
91
+ lens_plot = resolve_dossier_asset_path(
92
+ sol.lens_plane_plot_path,
93
+ project_root,
94
+ output_dir,
95
+ subdir="plots",
96
+ prefix=f"{event.event_id}_{sol.solution_id}_lens",
97
+ )
98
+ posterior = resolve_dossier_asset_path(
99
+ sol.posterior_path,
100
+ project_root,
101
+ output_dir,
102
+ subdir="posteriors",
103
+ prefix=f"{event.event_id}_{sol.solution_id}_posterior",
104
+ )
105
+ sol_html = _generate_solution_page_content(
106
+ sol,
107
+ event,
108
+ submission,
109
+ lc_plot=lc_plot,
110
+ lens_plot=lens_plot,
111
+ posterior=posterior,
112
+ )
74
113
  sol_body = extract_main_content_body(
75
114
  sol_html,
76
115
  section_type="solution",
77
116
  section_id=sol.solution_id,
78
- project_root=Path(submission.project_path),
117
+ project_root=project_root,
79
118
  solution=sol,
80
119
  )
81
120
  all_html_sections.append(sol_body)
@@ -8,6 +8,7 @@ notes rendering, and evaluator-only sections.
8
8
 
9
9
  from datetime import datetime
10
10
  from pathlib import Path
11
+ from typing import Optional
11
12
 
12
13
  import markdown
13
14
 
@@ -15,6 +16,7 @@ from .. import __version__
15
16
  from ..models.event import Event
16
17
  from ..models.solution import Solution
17
18
  from ..models.submission import Submission
19
+ from .utils import resolve_dossier_asset_path
18
20
 
19
21
 
20
22
  def generate_solution_page(solution: Solution, event: Event, submission: Submission, output_dir: Path) -> None:
@@ -55,12 +57,49 @@ def generate_solution_page(solution: Solution, event: Event, submission: Submiss
55
57
  Notes are rendered with syntax highlighting for code blocks.
56
58
  """
57
59
  # Prepare output directory (already created)
58
- html = _generate_solution_page_content(solution, event, submission)
60
+ project_root = Path(submission.project_path)
61
+ lc_plot = resolve_dossier_asset_path(
62
+ solution.lightcurve_plot_path,
63
+ project_root,
64
+ output_dir,
65
+ subdir="plots",
66
+ prefix=f"{event.event_id}_{solution.solution_id}_lightcurve",
67
+ )
68
+ lens_plot = resolve_dossier_asset_path(
69
+ solution.lens_plane_plot_path,
70
+ project_root,
71
+ output_dir,
72
+ subdir="plots",
73
+ prefix=f"{event.event_id}_{solution.solution_id}_lens",
74
+ )
75
+ posterior = resolve_dossier_asset_path(
76
+ solution.posterior_path,
77
+ project_root,
78
+ output_dir,
79
+ subdir="posteriors",
80
+ prefix=f"{event.event_id}_{solution.solution_id}_posterior",
81
+ )
82
+ html = _generate_solution_page_content(
83
+ solution,
84
+ event,
85
+ submission,
86
+ lc_plot=lc_plot,
87
+ lens_plot=lens_plot,
88
+ posterior=posterior,
89
+ )
59
90
  with (output_dir / f"{solution.solution_id}.html").open("w", encoding="utf-8") as f:
60
91
  f.write(html)
61
92
 
62
93
 
63
- def _generate_solution_page_content(solution: Solution, event: Event, submission: Submission) -> str:
94
+ def _generate_solution_page_content(
95
+ solution: Solution,
96
+ event: Event,
97
+ submission: Submission,
98
+ *,
99
+ lc_plot: Optional[str] = None,
100
+ lens_plot: Optional[str] = None,
101
+ posterior: Optional[str] = None,
102
+ ) -> str:
64
103
  """Generate the HTML content for a solution dossier page.
65
104
 
66
105
  Creates the complete HTML content for a single solution page, including
@@ -138,9 +177,9 @@ def _generate_solution_page_content(solution: Solution, event: Event, submission
138
177
  # Higher-order effects
139
178
  hoe_str = ", ".join(solution.higher_order_effects) if solution.higher_order_effects else "None"
140
179
  # Plot paths (relative to solution page)
141
- lc_plot = solution.lightcurve_plot_path or ""
142
- lens_plot = solution.lens_plane_plot_path or ""
143
- posterior = solution.posterior_path or ""
180
+ lc_plot = lc_plot if lc_plot is not None else (solution.lightcurve_plot_path or "")
181
+ lens_plot = lens_plot if lens_plot is not None else (solution.lens_plane_plot_path or "")
182
+ posterior = posterior if posterior is not None else (solution.posterior_path or "")
144
183
  # Physical parameters table
145
184
  phys_rows = []
146
185
  phys = solution.physical_parameters or {}
@@ -6,7 +6,11 @@ generation package, including hardware formatting, GitHub URL parsing,
6
6
  and other helper functions.
7
7
  """
8
8
 
9
+ import hashlib
10
+ import shutil
11
+ from pathlib import Path
9
12
  from typing import Any, Dict, Optional
13
+ from urllib.parse import quote, urlparse
10
14
 
11
15
 
12
16
  def format_hardware_info(hardware_info: Optional[Dict[str, Any]]) -> str:
@@ -109,3 +113,62 @@ def extract_github_repo_name(repo_url: str) -> str:
109
113
  return f"{parts[-2]}/{parts[-1]}"
110
114
 
111
115
  return None
116
+
117
+
118
+ def resolve_dossier_asset_path(
119
+ path_value: Optional[str],
120
+ project_root: Path,
121
+ output_dir: Path,
122
+ *,
123
+ subdir: str,
124
+ prefix: Optional[str] = None,
125
+ ) -> str:
126
+ """Resolve and copy a local asset into the dossier assets folder.
127
+
128
+ If the input path is a URL, it is returned unchanged. If it is a local
129
+ filesystem path, it is resolved relative to the project root, copied into
130
+ the dossier assets folder, and returned as a URL-encoded relative path.
131
+
132
+ Args:
133
+ path_value: The original path or URL string.
134
+ project_root: Root directory of the submission project.
135
+ output_dir: Dossier output directory containing the HTML files.
136
+ subdir: Subdirectory under assets/ for the copied file.
137
+ prefix: Optional prefix for the copied file name to avoid collisions.
138
+
139
+ Returns:
140
+ str: A URL or a relative path suitable for HTML src/href attributes.
141
+ """
142
+ if not path_value:
143
+ return ""
144
+
145
+ parsed = urlparse(path_value)
146
+ if parsed.scheme in {"http", "https", "data"}:
147
+ return path_value
148
+
149
+ if parsed.scheme == "file":
150
+ source_path = Path(parsed.path)
151
+ else:
152
+ source_path = Path(path_value).expanduser()
153
+
154
+ if not source_path.is_absolute():
155
+ source_path = (project_root / source_path).resolve()
156
+ else:
157
+ source_path = source_path.resolve()
158
+
159
+ if not source_path.exists():
160
+ return path_value
161
+
162
+ assets_dir = output_dir / "assets" / subdir
163
+ assets_dir.mkdir(parents=True, exist_ok=True)
164
+
165
+ digest = hashlib.sha1(str(source_path).encode("utf-8")).hexdigest()[:8]
166
+ safe_prefix = prefix or "asset"
167
+ dest_name = f"{safe_prefix}_{digest}_{source_path.name}"
168
+ dest_path = assets_dir / dest_name
169
+
170
+ if not dest_path.exists():
171
+ shutil.copy2(source_path, dest_path)
172
+
173
+ rel_path = dest_path.relative_to(output_dir).as_posix()
174
+ return quote(rel_path)
@@ -134,7 +134,10 @@ class Solution(BaseModel):
134
134
  limb_darkening_model: Optional[str] = None
135
135
  limb_darkening_coeffs: Optional[dict] = None
136
136
  parameter_uncertainties: Optional[dict] = None
137
+ uncertainty_method: Optional[str] = None
138
+ confidence_level: Optional[float] = 0.68
137
139
  physical_parameters: Optional[dict] = None
140
+ physical_parameter_uncertainties: Optional[dict] = None
138
141
  log_likelihood: Optional[float] = None
139
142
  relative_probability: Optional[float] = None
140
143
  n_data_points: Optional[int] = None
@@ -155,6 +158,7 @@ class Solution(BaseModel):
155
158
  higher_order_effects = values.get("higher_order_effects", [])
156
159
  bands = values.get("bands", [])
157
160
  t_ref = values.get("t_ref")
161
+ limb_darkening_coeffs = values.get("limb_darkening_coeffs")
158
162
 
159
163
  # Only check for totally broken objects (e.g., wrong types)
160
164
  basic_errors = []
@@ -176,6 +180,7 @@ class Solution(BaseModel):
176
180
  higher_order_effects=higher_order_effects,
177
181
  bands=bands,
178
182
  t_ref=t_ref,
183
+ limb_darkening_coeffs=limb_darkening_coeffs,
179
184
  )
180
185
  if validation_warnings:
181
186
  warnings.warn(f"Solution created with potential issues: {'; '.join(validation_warnings)}", UserWarning)
@@ -388,6 +393,18 @@ class Solution(BaseModel):
388
393
  )
389
394
  messages.extend(consistency_messages)
390
395
 
396
+ # Check solution metadata (uncertainties, etc.)
397
+ from ..validate_parameters import validate_solution_metadata
398
+
399
+ metadata_messages = validate_solution_metadata(
400
+ parameter_uncertainties=self.parameter_uncertainties,
401
+ physical_parameters=self.physical_parameters,
402
+ physical_parameter_uncertainties=self.physical_parameter_uncertainties,
403
+ uncertainty_method=self.uncertainty_method,
404
+ confidence_level=self.confidence_level,
405
+ )
406
+ messages.extend(metadata_messages)
407
+
391
408
  return messages
392
409
 
393
410
  def _save(self, event_path: Path) -> None:
@@ -18,6 +18,7 @@ import psutil
18
18
  from pydantic import BaseModel, Field
19
19
 
20
20
  from ..text_symbols import symbol
21
+ from ..validate_parameters import count_model_parameters
21
22
  from .event import Event
22
23
  from .solution import Solution
23
24
 
@@ -500,14 +501,15 @@ class Submission(BaseModel):
500
501
  s.log_likelihood is None
501
502
  or s.n_data_points is None
502
503
  or s.n_data_points <= 0
503
- or len(s.parameters) == 0
504
+ or count_model_parameters(s.parameters) == 0
504
505
  ):
505
506
  can_calc = False
506
507
  break
507
508
  remaining = max(1.0 - provided_sum, 0.0)
508
509
  if can_calc:
509
510
  bic_vals = {
510
- s.solution_id: len(s.parameters) * math.log(s.n_data_points) - 2 * s.log_likelihood
511
+ s.solution_id: count_model_parameters(s.parameters) * math.log(s.n_data_points)
512
+ - 2 * s.log_likelihood
511
513
  for s in need_calc
512
514
  }
513
515
  bic_min = min(bic_vals.values())
@@ -38,21 +38,21 @@ Note:
38
38
  from typing import Dict, List, Optional, Set
39
39
 
40
40
  # Tier definitions with their associated event lists
41
+ # --- BEGIN AUTO-GENERATED: TIER_DEFINITIONS ---
41
42
  TIER_DEFINITIONS = {
42
43
  "beginner": {
43
44
  "description": "Beginner challenge tier with limited event set",
44
45
  "event_prefix": "rmdc26_",
45
- "event_range": [0, 200],
46
+ "event_range": [0, 253],
46
47
  },
47
48
  "experienced": {
48
49
  "description": "Experienced challenge tier with full event set",
49
50
  "event_prefix": "rmdc26_",
50
- "event_range": [0, 2000],
51
+ "event_range": [0, 3000],
51
52
  },
52
53
  "test": {
53
54
  "description": "Testing tier for development",
54
55
  "event_list": [
55
- # Add test events here
56
56
  "evt",
57
57
  "test-event",
58
58
  "EVENT001",
@@ -68,17 +68,14 @@ TIER_DEFINITIONS = {
68
68
  "description": "2018 test events tier",
69
69
  "event_prefix": "ulwdc1_",
70
70
  "event_range": [0, 293],
71
- "event_list": [
72
- # Add 2018 test events here
73
- "2018-EVENT-001",
74
- "2018-EVENT-002",
75
- ],
71
+ "event_list": ["2018-EVENT-001", "2018-EVENT-002"],
76
72
  },
77
73
  "None": {
78
74
  "description": "No validation tier (skips event validation)",
79
- "event_list": [], # Empty list means no validation
75
+ "event_list": [],
80
76
  },
81
77
  }
78
+ # --- END AUTO-GENERATED: TIER_DEFINITIONS ---
82
79
 
83
80
  # Cache for event lists to avoid repeated list creation
84
81
  _EVENT_LIST_CACHE: Dict[str, Set[str]] = {}