refactor(cli): delegate paceENSDF config building to library
Remove the duplicated inline paceENSDF config builder in the CLI. It renormalized branch intensities before filtering, diverging from the library decay_info_to_config path that library callers use (audit finding D5). CLI paceENSDF configs now come from create_config_from_source exactly once per run. Regression test spies on create_config_from_source during a CLI run.
This commit is contained in:
+11
-59
@@ -178,15 +178,10 @@ def _run(args: argparse.Namespace) -> None:
|
|||||||
from beta_spectrum.logging_utils import get_logger
|
from beta_spectrum.logging_utils import get_logger
|
||||||
from beta_spectrum.nuclear_data import (
|
from beta_spectrum.nuclear_data import (
|
||||||
create_config_from_source,
|
create_config_from_source,
|
||||||
get_decay_info_from_paceENSDF,
|
|
||||||
json_to_config,
|
json_to_config,
|
||||||
load_json_input,
|
load_json_input,
|
||||||
)
|
)
|
||||||
from beta_spectrum.spectrum import (
|
from beta_spectrum.spectrum import BetaSpectrum
|
||||||
BetaSpectrum,
|
|
||||||
BranchConfig,
|
|
||||||
SpectrumConfig,
|
|
||||||
)
|
|
||||||
from beta_spectrum.output import BetaSpectrumAnalyzer
|
from beta_spectrum.output import BetaSpectrumAnalyzer
|
||||||
|
|
||||||
# Determine logging level
|
# Determine logging level
|
||||||
@@ -246,62 +241,19 @@ def _run(args: argparse.Namespace) -> None:
|
|||||||
csv_separator = "," if args.csv_separator is None else args.csv_separator
|
csv_separator = "," if args.csv_separator is None else args.csv_separator
|
||||||
normalize_to_one = bool(args.normalize_to_one)
|
normalize_to_one = bool(args.normalize_to_one)
|
||||||
|
|
||||||
# Build config
|
# Build config — a single delegated path for every data source.
|
||||||
config: SpectrumConfig
|
config: SpectrumConfig
|
||||||
if source_type == "paceENSDF":
|
if source_type == "paceENSDF":
|
||||||
logger.info("Source: paceENSDF nuclide=%s", source_value)
|
logger.info("Source: paceENSDF nuclide=%s", source_value)
|
||||||
decay_info = get_decay_info_from_paceENSDF(source_value, "beta_minus")
|
config = create_config_from_source(
|
||||||
|
"paceENSDF",
|
||||||
# Normalize branch intensities to sum to 1.0
|
nuclide=source_value,
|
||||||
if decay_info.branches:
|
e_step_MeV=e_step_MeV,
|
||||||
total_intensity = sum(b.intensity for b in decay_info.branches)
|
intensity_cutoff=intensity_cutoff,
|
||||||
if total_intensity > 0:
|
neutrino_mass_MeV=neutrino_mass_MeV,
|
||||||
branches = [
|
csv_separator=csv_separator,
|
||||||
BranchConfig(
|
normalize_to_one=normalize_to_one,
|
||||||
endpoint_MeV=(
|
)
|
||||||
(decay_info.endpoint_MeV - b.level_energy_keV / 1000.0)
|
|
||||||
if b.level_energy_keV > 0
|
|
||||||
else decay_info.endpoint_MeV
|
|
||||||
),
|
|
||||||
transition_type=b.transition_type,
|
|
||||||
intensity=b.intensity / total_intensity,
|
|
||||||
)
|
|
||||||
for b in decay_info.branches
|
|
||||||
if b.intensity / total_intensity >= intensity_cutoff
|
|
||||||
]
|
|
||||||
config = SpectrumConfig(
|
|
||||||
Z_parent=decay_info.Z_parent,
|
|
||||||
Z_daughter=decay_info.Z_daughter,
|
|
||||||
A_number=decay_info.A_number,
|
|
||||||
endpoint_MeV=decay_info.endpoint_MeV,
|
|
||||||
transition_type=decay_info.transition_type,
|
|
||||||
e_step_MeV=e_step_MeV,
|
|
||||||
neutrino_mass_MeV=neutrino_mass_MeV,
|
|
||||||
branches=branches,
|
|
||||||
intensity_cutoff=intensity_cutoff,
|
|
||||||
csv_separator=csv_separator,
|
|
||||||
normalize_to_one=normalize_to_one,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
config = create_config_from_source(
|
|
||||||
"paceENSDF",
|
|
||||||
nuclide=source_value,
|
|
||||||
e_step_MeV=e_step_MeV,
|
|
||||||
intensity_cutoff=intensity_cutoff,
|
|
||||||
neutrino_mass_MeV=neutrino_mass_MeV,
|
|
||||||
csv_separator=csv_separator,
|
|
||||||
normalize_to_one=normalize_to_one,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
config = create_config_from_source(
|
|
||||||
"paceENSDF",
|
|
||||||
nuclide=source_value,
|
|
||||||
e_step_MeV=e_step_MeV,
|
|
||||||
intensity_cutoff=intensity_cutoff,
|
|
||||||
neutrino_mass_MeV=neutrino_mass_MeV,
|
|
||||||
csv_separator=csv_separator,
|
|
||||||
normalize_to_one=normalize_to_one,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.info("Source: JSON file=%s", source_value)
|
logger.info("Source: JSON file=%s", source_value)
|
||||||
data = load_json_input(source_value)
|
data = load_json_input(source_value)
|
||||||
|
|||||||
@@ -162,3 +162,30 @@ class TestCLIPaceENSDF:
|
|||||||
main(["Tc99", "--output", out, "--neutrino-mass", "0.35"])
|
main(["Tc99", "--output", out, "--neutrino-mass", "0.35"])
|
||||||
assert exc.value.code == 1
|
assert exc.value.code == 1
|
||||||
assert not (tmp_path / "tc99deg.csv").exists()
|
assert not (tmp_path / "tc99deg.csv").exists()
|
||||||
|
|
||||||
|
def test_cli_builds_paceensdf_config_via_library(
|
||||||
|
self, monkeypatch: Any, capsys: Any
|
||||||
|
) -> None:
|
||||||
|
"""CLI must delegate paceENSDF config building to the library
|
||||||
|
(create_config_from_source), not duplicate it inline (D5)."""
|
||||||
|
import beta_spectrum.nuclear_data as nuclear_data
|
||||||
|
|
||||||
|
real_builder = nuclear_data.create_config_from_source
|
||||||
|
calls: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
def spy(source: str, **kwargs: Any) -> Any:
|
||||||
|
calls.append({"source": source, **kwargs})
|
||||||
|
return real_builder(source, **kwargs)
|
||||||
|
|
||||||
|
monkeypatch.setattr(nuclear_data, "create_config_from_source", spy)
|
||||||
|
|
||||||
|
main(["Cs137", "--dry-run", "--intensity-cutoff", "0.9"])
|
||||||
|
capsys.readouterr()
|
||||||
|
|
||||||
|
assert len(calls) == 1, (
|
||||||
|
"CLI must delegate paceENSDF config building to the library "
|
||||||
|
"builder exactly once"
|
||||||
|
)
|
||||||
|
assert calls[0]["source"] == "paceENSDF"
|
||||||
|
assert calls[0]["nuclide"] == "Cs137"
|
||||||
|
assert calls[0]["intensity_cutoff"] == 0.9
|
||||||
|
|||||||
Reference in New Issue
Block a user