feat(cli): separate-branches rename, short aliases, clean dry-run

- --normalize-to-one renamed to --separate-branches (no alias; the
  name now describes the output choice: total spectrum with
  intensity-normalized branches vs separate branches, each
  normalized to unit area with intensities left to the data user).
  SpectrumConfig.normalize_to_one -> separate_branches, CSV header
  word to_one -> separate.
- every long option gains a single-letter alias: -N -i -s -m -o -p
  -L -d -c -V -S -b (only -v/-q existed before)
- dry-run drops the top-level Transition: line — the per-branch
  list below already reports each branch's transition type
- version bumped to 0.4.1 (pyproject, __version__, CSV semantics
  docstring, TODO roadmap entry)
This commit is contained in:
2026-10-03 01:08:51 +03:00
parent 7f47a876ae
commit 2788ea0683
12 changed files with 210 additions and 53 deletions
+2 -2
View File
@@ -132,7 +132,7 @@ In `beta_spectrum.output`. Provides debugging and visualization tools:
- `export_to_csv(filename)` — export via pandas DataFrame with YAML-style metadata header:
- the basic spectrum is always the core `phase_space × fermi` (the header carries a `# core:` line); finite size, charge distribution, screening, exchange and radiative are higher-order corrections
- `branch_{i}_spectrum` columns contain the *physical* branch spectra weighted by intensity and normalized so that the sum of branch columns equals `total_spectrum`
- with `normalize_to_one=True` each branch column integrates to 1 (intensity ignored, no total column)
- with `separate_branches=True` (CLI `--separate-branches`) each branch column integrates to 1 (intensity ignored, no total_spectrum column)
- `parse_spectrum_csv(path)` — read an exported CSV and its metadata header (module function in `beta_spectrum.output.visualization`)
- `get_data()` — return all numerical data for custom analysis
@@ -194,5 +194,5 @@ For a complete list of implemented and planned features, see [TODO.md](TODO.md).
- **Multi-branch decay**: intensity-weighted sum of branch spectra, per-branch calculators with branch-specific endpoint, transition type (from ENSDF), and `--intensity-cutoff` CLI filter
- **Analysis tools**: χ² fitter (SpectrumFitter + CurveFitter), C(W) extraction pipeline, Kurie plot analysis, g_V/g_A extraction
- **Experimental data**: ExpSpectrum container, energy calibration, peak fitting, dead-time/pile-up correction
- **CLI**: `bs_pnpi --nuclide Tc99` — full paceENSDF integration (auto-lookup of Z, A, Q-value, transition type, half-life, branches, forbiddenness from ENSDF); for JSON input the file's values are used and CLI flags (`--e-step`, `--neutrino-mass`, `--intensity-cutoff`, `--csv-separator`, `--normalize-to-one`) override them only when explicitly given
- **CLI**: `bs_pnpi --nuclide Tc99` — full paceENSDF integration (auto-lookup of Z, A, Q-value, transition type, half-life, branches, forbiddenness from ENSDF); for JSON input the file's values are used and CLI flags (`--e-step`, `--neutrino-mass`, `--intensity-cutoff`, `--csv-separator`, `--separate-branches`) override them only when explicitly given; every long option has a single-letter alias (`-N`, `-i`, `-s`, `-m`, `-o`, `-p`, `-L`, `-d`, `-c`, `-V`, `-S`, `-b`)
- **paceENSDF integration**: `get_decay_info_from_paceENSDF()`, `create_config_from_source("paceENSDF", nuclide="Tc99")`, `_parse_nuclide_symbol()` for Z=1..98, FORBIDDENNESS_MAP, `--nuclide` CLI flag; per-branch endpoints are the measured transition endpoint energies from ENSDF (max endpoint across branches defines the spectrum endpoint)
+7 -1
View File
@@ -322,7 +322,7 @@ ______________________________________________________________________
## Current Status
**Version:** 0.3.1 → **In progress: 0.4.0 — Package restructuring**
**Version:** 0.4.0 → **v0.4.1 — CLI options refresh & normalization fixes** (released on `dev/core-fermi-csv`)
### Completed (v0.3.x)
@@ -354,6 +354,12 @@ ______________________________________________________________________
- README updated with new architecture diagram and Quick Start examples
- Version bumped to 0.4.0
### Completed (v0.4.1)
- Core spectrum made unconditional (`phase_space × fermi`): core toggles removed, physical branch spectra in CSV export, plots built from the exported CSV
- Normalization audit fixes: lone surviving branch defines the spectrum, CLI options propagate through the JSON flow, positive-integral guard for both export modes, paceENSDF per-branch measured endpoints (Co60 fixed), single delegated paceENSDF config builder
- CLI surface refresh: `--normalize-to-one` renamed to `--separate-branches` (no alias), single-letter aliases for every option, dry-run drops the redundant top-level `Transition:` line, stale detector_* JSON schema keys removed
### Next Immediate Step
- **Track A: ⁹⁹Tc spectrum-shape analysis** — start real data processing pipeline
+1 -1
View File
@@ -52,4 +52,4 @@ __all__ = [
"DEFAULT_JSON_SCHEMA",
]
__version__ = "0.4.0"
__version__ = "0.4.1"
+19 -6
View File
@@ -65,12 +65,14 @@ def _build_parser() -> argparse.ArgumentParser:
# Source selection flags (backward compatible, mutually exclusive)
source_group = parser.add_mutually_exclusive_group(required=False)
source_group.add_argument(
"-N",
"--nuclide",
type=str,
help="Nuclide symbol for paceENSDF lookup (e.g. 'Tc99', 'Co60'). "
"Retrieves decay parameters from the ENSDF database via paceENSDF.",
)
source_group.add_argument(
"-i",
"--input",
type=str,
metavar="FILE",
@@ -79,6 +81,7 @@ def _build_parser() -> argparse.ArgumentParser:
# Common parameters
parser.add_argument(
"-s",
"--e-step",
type=float,
default=None,
@@ -87,6 +90,7 @@ def _build_parser() -> argparse.ArgumentParser:
"omitted). All energies are in MeV throughout the toolkit.",
)
parser.add_argument(
"-m",
"--neutrino-mass",
type=float,
default=None,
@@ -97,6 +101,7 @@ def _build_parser() -> argparse.ArgumentParser:
# Output
parser.add_argument(
"-o",
"--output",
type=str,
metavar="FILE",
@@ -105,6 +110,7 @@ def _build_parser() -> argparse.ArgumentParser:
# Plotting
parser.add_argument(
"-p",
"--plot",
type=str,
metavar="FILE",
@@ -128,6 +134,7 @@ def _build_parser() -> argparse.ArgumentParser:
help="Suppress all terminal output (only errors shown).",
)
log_group.add_argument(
"-L",
"--log-file",
type=str,
metavar="PATH",
@@ -135,11 +142,13 @@ def _build_parser() -> argparse.ArgumentParser:
"Log file is always opened in append mode at DEBUG level.",
)
log_group.add_argument(
"-d",
"--dry-run",
action="store_true",
help="Validate input and display resolved configuration without computing spectrum.",
)
log_group.add_argument(
"-c",
"--intensity-cutoff",
type=float,
default=None,
@@ -149,6 +158,7 @@ def _build_parser() -> argparse.ArgumentParser:
"Branches below this threshold are excluded from the calculation and plots.",
)
log_group.add_argument(
"-V",
"--csv-verbosity",
type=str,
default="full",
@@ -156,16 +166,20 @@ def _build_parser() -> argparse.ArgumentParser:
help="CSV output verbosity: 'full' (all components) or 'concise' (total spectrum and normalized branch components only) (default: full)",
)
log_group.add_argument(
"-S",
"--csv-separator",
type=str,
default=None,
help="CSV column separator (default: ','). Use 'tab' for tab separation.",
)
log_group.add_argument(
"--normalize-to-one",
"-b",
"--separate-branches",
action="store_true",
default=None,
help="Normalize each branch to 1, ignoring intensity. In this case, total spectrum will not be produced (only energy and branch-wise spectra).",
help="Emit separate branch spectra, each normalized to integrate to "
"1 (intensities ignored; no total_spectrum column). Default: total "
"spectrum with intensity-normalized branch columns.",
)
return parser
@@ -239,7 +253,7 @@ def _run(args: argparse.Namespace) -> None:
0.0 if args.intensity_cutoff is None else float(args.intensity_cutoff)
)
csv_separator = "," if args.csv_separator is None else args.csv_separator
normalize_to_one = bool(args.normalize_to_one)
separate_branches = bool(args.separate_branches)
# Build config — a single delegated path for every data source.
config: SpectrumConfig
@@ -252,7 +266,7 @@ def _run(args: argparse.Namespace) -> None:
intensity_cutoff=intensity_cutoff,
neutrino_mass_MeV=neutrino_mass_MeV,
csv_separator=csv_separator,
normalize_to_one=normalize_to_one,
separate_branches=separate_branches,
)
else:
logger.info("Source: JSON file=%s", source_value)
@@ -268,7 +282,7 @@ def _run(args: argparse.Namespace) -> None:
config.intensity_cutoff = intensity_cutoff
if args.csv_separator is not None:
config.csv_separator = csv_separator
config.normalize_to_one = normalize_to_one
config.separate_branches = separate_branches
# Dry run: display resolved config and exit
if args.dry_run:
@@ -352,7 +366,6 @@ def _print_dry_run_output(config, source_type: str) -> None:
f"Nuclide: {_decay_notation(config.Z_parent, config.Z_daughter, config.A_number)}"
)
print(f"Endpoint: {config.endpoint_MeV * 1000:.1f} keV")
print(f"Transition: {config.transition_type}")
print(f"e_step: {config.e_step_MeV:.4f} MeV")
print(f"neutrino_mass_MeV: {config.neutrino_mass_MeV:.6f}")
print(f"Corrections: {', '.join(enabled) if enabled else 'none'}")
+7 -6
View File
@@ -477,7 +477,7 @@ def decay_info_to_config(
intensity_cutoff: float = 0.0,
neutrino_mass_MeV: float = 0.0,
csv_separator: str = ",",
normalize_to_one: bool = False,
separate_branches: bool = False,
) -> SpectrumConfig:
"""
Convert DecayInfo to a SpectrumConfig.
@@ -494,8 +494,9 @@ def decay_info_to_config(
Neutrino mass in MeV.
csv_separator : str
CSV column separator.
normalize_to_one : bool
If True, normalize each branch to 1, ignoring intensity.
separate_branches : bool
If True, emit separate branch spectra, each normalized to
integrate to 1 (intensity ignored; no total spectrum column).
Returns
-------
@@ -535,7 +536,7 @@ def decay_info_to_config(
branches=branches,
intensity_cutoff=intensity_cutoff,
csv_separator=csv_separator,
normalize_to_one=normalize_to_one,
separate_branches=separate_branches,
)
@@ -708,7 +709,7 @@ def create_config_from_source(
intensity_cutoff = kwargs.get("intensity_cutoff", 0.0)
neutrino_mass = kwargs.get("neutrino_mass_MeV", 0.0)
csv_separator = kwargs.get("csv_separator", ",")
normalize_to_one = kwargs.get("normalize_to_one", False)
separate_branches = kwargs.get("separate_branches", False)
info = get_decay_info_from_paceENSDF(nuclide, decay_type, decay_index)
return decay_info_to_config(
info,
@@ -716,7 +717,7 @@ def create_config_from_source(
intensity_cutoff=intensity_cutoff,
neutrino_mass_MeV=neutrino_mass,
csv_separator=csv_separator,
normalize_to_one=normalize_to_one,
separate_branches=separate_branches,
)
elif source == "json":
if not json_path:
+7 -7
View File
@@ -1,7 +1,7 @@
"""
CSV export module — CSV export functionality for BetaSpectrumAnalyzer.
Column semantics (v0.4.0 onward):
Column semantics (v0.4.1 onward):
- The basic spectrum is ALWAYS core = phase_space x fermi; the Fermi function
is a core spectrum component, never an optional correction. Higher-order
corrections (finite size, charge distribution, screening, exchange,
@@ -12,7 +12,7 @@ Column semantics (v0.4.0 onward):
(the total column itself is normalized to integrate to 1). In the legacy
export branch columns contained only the per-branch phase-space x
radiative product WITHOUT the Fermi function — physically wrong.
- ``normalize_to_one`` mode: each branch column is normalized to
- ``separate_branches`` mode: each branch column is normalized to
integrate to 1 individually, intensity is ignored, no total column.
"""
@@ -62,7 +62,7 @@ class CSVEmitter:
"""
components = analyzer.components
config = analyzer.config
to_one = config.normalize_to_one
separate = config.separate_branches
n_branches = analyzer._n_branches
# Physical branch spectra: core(phase_space x fermi) x corrections.
@@ -127,9 +127,9 @@ class CSVEmitter:
header_lines.append(f"# e_step: {config.e_step_MeV:.4f} MeV")
header_lines.append(f"# n_points: {len(analyzer.energies_MeV)}")
if to_one:
if separate:
header_lines.append(
"# branch normalization: to_one "
"# branch normalization: separate "
"(each branch integrates to 1, intensity ignored)"
)
else:
@@ -189,7 +189,7 @@ class CSVEmitter:
"is degenerate (check neutrino_mass vs branch endpoints "
"and intensity_cutoff)"
)
if not to_one:
if not separate:
data["total_spectrum"] = total_raw / integral
for i in range(n_branches):
@@ -199,7 +199,7 @@ class CSVEmitter:
else:
col_name = f"branch_{key_i}_spectrum"
if to_one:
if separate:
area = float(np.trapezoid(physical[i], analyzer.energies_MeV))
if area > 0.0:
data[col_name] = physical[i] / area
+3 -5
View File
@@ -57,9 +57,6 @@ class SpectrumConfig:
"""
Configuration for beta spectrum calculation.
Supports declarative detector response specification:
to enable automatic detector smearing via convolve_with_detector().
Supports multi-branch decay via the `branches` field. When branches are
provided, the total spectrum is the intensity-weighted sum of individual
branch spectra. Each branch has its own endpoint energy and transition type.
@@ -90,9 +87,10 @@ class SpectrumConfig:
# CSV output options
csv_separator: str = "," # Default separator for CSV output
normalize_to_one: bool = (
False # If True, normalize each branch to 1, ignoring intensity
separate_branches: bool = (
False # If True: emit separate branch spectra, each normalized to
)
# integrate to 1 (intensity ignored); no total spectrum column
def _effective_single_fields(config: SpectrumConfig) -> Tuple[float, str]:
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "beta-spectrum"
version = "0.4.0"
version = "0.4.1"
description = "High-precision beta spectrum calculation for nuclear physics"
authors = [
{ name = "Evgeniy Unzhakov" }
+5 -5
View File
@@ -66,12 +66,12 @@ def n_points_of(header: List[str]) -> int:
class TestCLIJsonFlows:
"""Options must propagate through the JSON config flow (D2)."""
def test_json_honors_normalize_to_one_and_tab_separator(
def test_json_honors_separate_branches_and_tab_separator(
self, tmp_path: Any
) -> None:
src = write_json(tmp_path)
out = str(tmp_path / "to_one.csv")
main([src, "--output", out, "--normalize-to-one", "--csv-separator", "tab"])
main([src, "--output", out, "--separate-branches", "--csv-separator", "tab"])
with open(out) as f:
body = "".join(ln for ln in f if not ln.startswith("#"))
@@ -136,7 +136,7 @@ class TestCLIDegenerateFlows:
out,
"--neutrino-mass",
"0.35",
"--normalize-to-one",
"--separate-branches",
]
)
assert exc.value.code == 1
@@ -147,9 +147,9 @@ class TestCLIDegenerateFlows:
class TestCLIPaceENSDF:
"""paceENSDF flow honors the same export options."""
def test_paceensdf_honors_normalize_to_one(self, tmp_path: Any) -> None:
def test_paceensdf_honors_separate_branches(self, tmp_path: Any) -> None:
out = str(tmp_path / "p32.csv")
main(["P32", "--output", out, "--normalize-to-one"])
main(["P32", "--output", out, "--separate-branches"])
df = read_export(out)
assert "total_spectrum" not in df.columns
+20 -18
View File
@@ -313,8 +313,8 @@ class TestCSVExportCoreSemantics:
assert "phase_space" in core_line[0]
assert "fermi" in core_line[0]
def test_normalize_to_one_per_branch_unit_area(self, tmp_path: Any) -> None:
config = make_multi_config(normalize_to_one=True)
def test_separate_branches_per_branch_unit_area(self, tmp_path: Any) -> None:
config = make_multi_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
_, df = export_and_read(analyzer, tmp_path)
@@ -334,12 +334,12 @@ class TestCSVExportCoreSemantics:
analyzer.export_to_csv(path)
assert not (tmp_path / "degenerate.csv").exists()
def test_degenerate_export_raises_in_normalize_to_one_mode(
def test_degenerate_export_raises_in_separate_branches_mode(
self, tmp_path: Any
) -> None:
"""The positive-integral guard must apply in to_one mode too:
"""The positive-integral guard must apply in separate-branches mode too:
no all-zero CSV may be written for a degenerate spectrum."""
config = make_degenerate_config(normalize_to_one=True)
config = make_degenerate_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
path = str(tmp_path / "degenerate_to_one.csv")
@@ -567,13 +567,13 @@ class TestAnalyzerUniversalFactor:
class TestNormalizationModeMatrix:
"""to_one and intensity modes must behave consistently across CSV
"""separate_branches and intensity modes must behave consistently across CSV
verbosity, branch count and the CSV-driven visualizer."""
def test_to_one_concise_multi_unit_area_no_total_no_components(
def test_separate_branches_concise_multi_unit_area_no_total_no_components(
self, tmp_path: Any
) -> None:
config = make_multi_config(normalize_to_one=True)
config = make_multi_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
path, df = export_and_read(
@@ -587,14 +587,14 @@ class TestNormalizationModeMatrix:
"branch_3",
}
header = header_lines_of(path)
assert any("branch normalization: to_one" in h for h in header)
assert any("branch normalization: separate" in h for h in header)
energies = df["energy_MeV"].to_numpy()
for col in ("branch_1", "branch_2", "branch_3"):
area = np.trapezoid(df[col].to_numpy(), energies)
assert np.isclose(area, 1.0, rtol=5e-4)
def test_to_one_single_mode_unit_area(self, tmp_path: Any) -> None:
config = make_single_config(normalize_to_one=True)
def test_separate_branches_single_mode_unit_area(self, tmp_path: Any) -> None:
config = make_single_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
_path, df = export_and_read(analyzer, tmp_path, "to_one_single.csv")
@@ -622,10 +622,10 @@ class TestNormalizationModeMatrix:
nonzero = total > 1e-12
assert np.all(np.abs(summed[nonzero] - total[nonzero]) <= total[nonzero] * 5e-4)
def test_plot_from_csv_to_one_plots_branches_without_total(
def test_plot_from_csv_separate_branches_plots_branches_without_total(
self, tmp_path: Any
) -> None:
config = make_multi_config(normalize_to_one=True)
config = make_multi_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
path, df = export_and_read(analyzer, tmp_path, "to_one_plot.csv")
@@ -633,7 +633,7 @@ class TestNormalizationModeMatrix:
fig = Visualizer.plot_from_csv(path, save_path=None, show_components=True)
assert len(fig.axes) == 4
# Panel 1 has exactly the 3 branch curves — no total spectrum
# exists in to_one mode.
# exists in separate-branches mode.
panel = fig.axes[0]
curves = [np.asarray(ln.get_xydata()) for ln in panel.lines]
assert len(curves) == 3
@@ -641,8 +641,10 @@ class TestNormalizationModeMatrix:
assert np.allclose(curves[0][:, 0], energies)
assert np.allclose(curves[0][:, 1], df["branch_1_spectrum"].to_numpy())
def test_plot_analysis_to_one_returns_figure(self, tmp_path: Any) -> None:
config = make_multi_config(normalize_to_one=True)
def test_plot_analysis_separate_branches_returns_figure(
self, tmp_path: Any
) -> None:
config = make_multi_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
save = str(tmp_path / "analysis.png")
@@ -650,10 +652,10 @@ class TestNormalizationModeMatrix:
assert len(fig.axes) == 4
assert (tmp_path / "analysis.png").exists()
def test_plot_from_csv_to_one_concise_warns_and_falls_back(
def test_plot_from_csv_separate_branches_concise_warns_and_falls_back(
self, tmp_path: Any, capsys: Any
) -> None:
config = make_multi_config(normalize_to_one=True)
config = make_multi_config(separate_branches=True)
spectrum = BetaSpectrum.from_config(config)
analyzer = BetaSpectrumAnalyzer(spectrum, config)
path, df = export_and_read(
+137
View File
@@ -0,0 +1,137 @@
"""CLI option surface tests: renamed branch-separation flag, short
aliases for every long option, and dry-run output shape (audit follow-up
by the maintainer: rename --normalize-to-one, add short options, drop the
redundant top-level Transition line)."""
from __future__ import annotations
import dataclasses
from typing import Any, Optional
import pytest
from beta_spectrum import BranchConfig, SpectrumConfig
from beta_spectrum.cli import _build_parser, _print_dry_run_output
pytestmark = pytest.mark.quality
def make_config() -> SpectrumConfig:
return SpectrumConfig(
Z_parent=59,
Z_daughter=60,
A_number=144,
endpoint_MeV=2.98,
transition_type="F1",
e_step_MeV=0.001,
branches=[
BranchConfig(endpoint_MeV=2.98, transition_type="F1", intensity=0.9),
BranchConfig(endpoint_MeV=2.1, transition_type="A", intensity=0.1),
],
)
class TestSeparateBranchesRename:
"""--normalize-to-one was renamed to --separate-branches (no alias)."""
def test_flag_parses_true(self) -> None:
parser = _build_parser()
args = parser.parse_args(["input.json", "--separate-branches"])
assert args.separate_branches is True
def test_flag_default_is_none(self) -> None:
parser = _build_parser()
args = parser.parse_args(["input.json"])
assert args.separate_branches is None
def test_old_flag_is_rejected(self) -> None:
parser = _build_parser()
with pytest.raises(SystemExit):
parser.parse_args(["input.json", "--normalize-to-one"])
def test_config_field_renamed(self) -> None:
names = {f.name for f in dataclasses.fields(SpectrumConfig)}
assert "separate_branches" in names
assert "normalize_to_one" not in names
def test_config_old_kwarg_rejected(self) -> None:
kwargs: dict[str, Any] = {
"Z_parent": 15,
"Z_daughter": 16,
"A_number": 32,
"endpoint_MeV": 1.7103,
"transition_type": "A",
"normalize_to_one": True,
}
with pytest.raises(TypeError):
SpectrumConfig(**kwargs)
# (short, long, value-or-None-for-store_true)
_SHORT_EQUIV = [
("-N", "--nuclide", "Cs137"),
("-i", "--input", "in.json"),
("-s", "--e-step", "0.01"),
("-m", "--neutrino-mass", "0.15"),
("-o", "--output", "out.csv"),
("-p", "--plot", "plot.png"),
("-L", "--log-file", "calc.log"),
("-d", "--dry-run", None),
("-c", "--intensity-cutoff", "0.5"),
("-V", "--csv-verbosity", "concise"),
("-S", "--csv-separator", "tab"),
("-b", "--separate-branches", None),
]
class TestShortOptions:
"""Every long option gains a single-letter alias (only -v/-q existed)."""
@pytest.mark.parametrize(
"short,long,value",
_SHORT_EQUIV,
ids=[s.lstrip("-") for s, _, _ in _SHORT_EQUIV],
)
def test_short_matches_long(
self, short: str, long: str, value: Optional[str]
) -> None:
parser = _build_parser()
argv_long = [long] if value is None else [long, value]
argv_short = [short] if value is None else [short, value]
args_long = parser.parse_args(["Tc99"] + argv_long)
args_short = parser.parse_args(["Tc99"] + argv_short)
dest = long.lstrip("-").replace("-", "_")
assert getattr(args_short, dest) == getattr(args_long, dest)
assert getattr(args_long, dest) is not None or value is None
def test_existing_shorts_unchanged(self) -> None:
parser = _build_parser()
args = parser.parse_args(["Tc99", "-v", "-q"])
assert args.verbose == 1
assert args.quiet is True
class TestDryRunOutputShape:
"""The top-level Transition line is redundant with the per-branch
list (for multi-branch runs) and was removed."""
def test_multi_branch_dry_run_no_top_level_transition(self, capsys: Any) -> None:
_print_dry_run_output(make_config(), "paceENSDF")
out = capsys.readouterr().out
assert "Transition:" not in out
assert "transition=F1" in out
assert "transition=A" in out
assert "Multi-branch mode: 2 branches" in out
def test_single_branch_dry_run_reports_endpoint(self, capsys: Any) -> None:
config = SpectrumConfig(
Z_parent=15,
Z_daughter=16,
A_number=32,
endpoint_MeV=1.7103,
transition_type="A",
)
_print_dry_run_output(config, "json")
out = capsys.readouterr().out
assert "Transition:" not in out
assert "Mode: single-branch" in out
+1 -1
View File
@@ -154,7 +154,7 @@ class TestCSVMetadataHeader:
lines = f.readlines()
assert any("# beta-spectrum v" in line for line in lines)
assert any("0.4.0" in line for line in lines)
assert any("0.4.1" in line for line in lines)
os.unlink(filename)
def test_header_contains_timestamp(self):