Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b9124fe
Clude-augment enhancement on the pcmdi_diags
zhangshixuan1987 Apr 30, 2026
fa84fac
Claude-augment enhancement on pcmdi_enso
zhangshixuan1987 Apr 30, 2026
27c1487
Claude-augment enhancement pn pcmdi_mean_climate
zhangshixuan1987 Apr 30, 2026
da6c90b
Claude-augment enhancement on pcmdi_variability_modes
zhangshixuan1987 Apr 30, 2026
a135d3b
Claude-augument enhancement on pcmdi_synthetic_plots
zhangshixuan1987 Apr 30, 2026
c5db2e7
Claude-augument enhancement on pcmdi_setup
zhangshixuan1987 Apr 30, 2026
910fcf0
Claude-Augument enhancement on utils
zhangshixuan1987 Apr 30, 2026
1012e37
Claude-Augument enhancement on viewer
zhangshixuan1987 Apr 30, 2026
201bcd2
Correction on log message to be clearer
zhangshixuan1987 May 1, 2026
49e1452
fix the parallel computing bug
zhangshixuan1987 May 3, 2026
6e20772
bug fix
zhangshixuan1987 May 3, 2026
d6a7f04
Bug fix
zhangshixuan1987 May 3, 2026
40c535f
Bug fix on the enso metric reader and sythentic plot
zhangshixuan1987 May 4, 2026
4ef16c2
fix bug for enso collection extraction from the synthetic plot jason …
zhangshixuan1987 May 4, 2026
6fe4dba
add a safeguard check for enso metrics
zhangshixuan1987 May 4, 2026
ec525a1
Bug fix: the Popen needs to work with shell=True
zhangshixuan1987 May 4, 2026
e408782
Reformat with pre-commit
zhangshixuan1987 May 5, 2026
20df79f
Fix bug for the serial job
zhangshixuan1987 May 12, 2026
dfc7fef
Enhance pcmdi_enso performance
zhangshixuan1987 May 15, 2026
aba7f34
Improve ENSO diagnostics collection robustness
zhangshixuan1987 May 15, 2026
ac6aa3d
Fix by pre-commit check
zhangshixuan1987 May 15, 2026
2799173
Improve ENSO output collection and alias handling
zhangshixuan1987 May 15, 2026
97b6ce8
reformat with pre-commit
zhangshixuan1987 May 15, 2026
dfefdf9
Fix PCMDI viewer EOF links and harden synthetic plotting
zhangshixuan1987 May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ version = { attr = "zppy_interfaces.version.__version__" }
[project.scripts]
zi-global-time-series = "zppy_interfaces.global_time_series.__main__:main"
zi-pcmdi-link-observation = "zppy_interfaces.pcmdi_diags.link_observation:main"
zi-pcmdi-mean-climate = "zppy_interfaces.pcmdi_diags.pcmdi_mean_cimate:main"
zi-pcmdi-mean-climate = "zppy_interfaces.pcmdi_diags.pcmdi_mean_climate:main"
zi-pcmdi-variability-modes = "zppy_interfaces.pcmdi_diags.pcmdi_variability_modes:main"
zi-pcmdi-enso = "zppy_interfaces.pcmdi_diags.pcmdi_enso:main"
zi-pcmdi-synthetic-plots = "zppy_interfaces.pcmdi_diags.pcmdi_synthetic_plots:main"
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/pcmdi_diags/test_pcmdi_mean_climate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List

from zppy_interfaces.pcmdi_diags.pcmdi_mean_cimate import generate_mean_clim_cmds
from zppy_interfaces.pcmdi_diags.pcmdi_mean_climate import generate_mean_clim_cmds


def test_generate_mean_clim_cmds():
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/pcmdi_diags/test_pcmdi_variability_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def test_generate_varmode_cmds():
"v20250923",
)
expected = [
"variability_modes_driver.py -p parameterfile.py --variability_mode mode1 --eofn_mod 1 --eofn_obs 1 --varOBS varOBS --osyear reftyrs --oeyear reftyre --reference_data_name refname --reference_data_path refpath --case_id v20250923",
"variability_modes_driver.py -p parameterfile.py --variability_mode mode2 --eofn_mod 1 --eofn_obs 1 --varOBS varOBS --osyear reftyrs --oeyear reftyre --reference_data_name refname --reference_data_path refpath --case_id v20250923",
'variability_modes_driver.py -p parameterfile.py --variability_mode mode1 --eofn_mod 1 --eofn_obs 1 --varOBS varOBS --osyear reftyrs --oeyear reftyre --reference_data_name refname --reference_data_path "refpath" --case_id v20250923',
'variability_modes_driver.py -p parameterfile.py --variability_mode mode2 --eofn_mod 1 --eofn_obs 1 --varOBS varOBS --osyear reftyrs --oeyear reftyre --reference_data_name refname --reference_data_path "refpath" --case_id v20250923',
]
assert actual == expected
86 changes: 86 additions & 0 deletions tests/unit/pcmdi_diags/test_synthetic_metrics_plotter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import pandas as pd

from zppy_interfaces.pcmdi_diags.synthetic_plots import synthetic_metrics_plotter
from zppy_interfaces.pcmdi_diags.synthetic_plots.synthetic_metrics_plotter import (
drop_vars,
mean_climate_plot_driver,
)


def _mean_climate_frame(**values):
data = {
"model": ["CMIP", "E3SM"],
"run": ["r1", "r1"],
"model_run": ["CMIP_r1", "E3SM_r1"],
}
data.update(values)
return pd.DataFrame(data)


def test_drop_vars_removes_requested_variables_missing_from_dataframe():
data_dict, var_names, var_units = drop_vars(
_mean_climate_frame(pr=[1.0, 2.0]),
["pr", "prw"],
["mm/day", "kg/m2"],
)

assert "prw" not in data_dict.columns
assert var_names == ["pr"]
assert var_units == ["mm/day"]


def test_mean_climate_portrait_skips_region_missing_variables(monkeypatch, tmp_path):
captured = {}

def fake_portrait_metric_plot(
region,
stat,
group,
data_dict,
stat_name,
model_name,
var_list,
model_list,
out_path,
fig_format,
):
captured["region"] = region
captured["var_list"] = var_list
captured["data_dict"] = data_dict

monkeypatch.setattr(
synthetic_metrics_plotter,
"portrait_metric_plot",
fake_portrait_metric_plot,
)

metric_dict = {
"type": ["portrait"],
"region": ["ocean"],
"season": ["djf", "mam", "jja", "son"],
"name": "Mean Bias",
}
df_dict = {
"djf": {"ocean": _mean_climate_frame(pr=[1.0, 2.0], prw=[3.0, 4.0])},
"mam": {"ocean": _mean_climate_frame(pr=[1.0, 2.0])},
"jja": {"ocean": _mean_climate_frame(pr=[1.0, 2.0], prw=[3.0, 4.0])},
"son": {"ocean": _mean_climate_frame(pr=[1.0, 2.0], prw=[3.0, 4.0])},
}

mean_climate_plot_driver(
metric="mean_climate",
stat="mae_xy",
regions=["ocean"],
model_name=["E3SM"],
metric_dict=metric_dict,
df_dict=df_dict,
var_list=["pr", "prw"],
var_unit_list=["mm/day", "kg/m2"],
save_data=False,
out_path=str(tmp_path),
fig_format="png",
)

assert captured["region"] == "ocean"
assert captured["var_list"] == ["pr"]
assert all(values.shape == (1, 2) for values in captured["data_dict"].values())
122 changes: 121 additions & 1 deletion tests/unit/pcmdi_diags/test_viewer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,126 @@
from zppy_interfaces.pcmdi_diags.viewer import safe_join
from zppy_interfaces.pcmdi_diags.viewer import (
CMVARGroupBuilder,
generate_cmvar_table,
generate_data_html,
generate_emovs_table,
safe_join,
)


def test_safe_join():
assert safe_join("a", "b") == "a/b"
assert safe_join("a/", "b") == "a/b"


def test_coupled_mov_compose_eof_uses_mode_eof(tmp_path):
fig_dir = tmp_path / "figures"
diag_dir = tmp_path / "viewer"
yearly_dir = fig_dir / "MOV_compose" / "yearly"
yearly_dir.mkdir(parents=True)
diag_dir.mkdir()

(yearly_dir / "MOV_compose_AMO_yearly_eof1.png").touch()
(yearly_dir / "MOV_compose_PDO_yearly_eof1.png").touch()
(yearly_dir / "MOV_compose_NPGO_yearly_eof2.png").touch()

builder = CMVARGroupBuilder()

amo_row = builder.generate_mcpl_row("AMO", str(diag_dir), str(fig_dir))
amo_compose_cell = amo_row[2]["content"]
assert "AMO (SST)" in amo_row[0]["content"]
assert "MOV_compose_AMO_yearly_eof1.png" in amo_compose_cell

pdo_row = builder.generate_mcpl_row("PDO", str(diag_dir), str(fig_dir))
pdo_compose_cell = pdo_row[2]["content"]
assert "PDO (SST)" in pdo_row[0]["content"]
assert "MOV_compose_PDO_yearly_eof1.png" in pdo_compose_cell
assert "MOV_compose_PDO_yearly_cbf.png" not in pdo_compose_cell

npgo_row = builder.generate_mcpl_row("NPGO", str(diag_dir), str(fig_dir))
npgo_compose_cell = npgo_row[2]["content"]
assert "NPGO (SST)" in npgo_row[0]["content"]
assert "MOV_compose_NPGO_yearly_eof2.png" in npgo_compose_cell
assert "MOV_compose_NPGO_yearly_eof1.png" not in npgo_compose_cell


def test_coupled_modes_are_normalized_from_config(tmp_path):
fig_dir = tmp_path / "figures"
diag_dir = tmp_path / "viewer"
yearly_dir = fig_dir / "MOV_metric" / "MOV_compose" / "yearly"
yearly_dir.mkdir(parents=True)
diag_dir.mkdir()

(yearly_dir / "MOV_compose_NPGO_yearly_eof2.png").touch()

table = generate_cmvar_table(
str(diag_dir),
str(fig_dir),
enso_show=False,
movc_show=True,
movc_modes=" npgo ",
)

assert len(table) == 1
assert "NPGO (SST)" in table[0][0]["content"]
assert "MOV_compose_NPGO_yearly_eof2.png" in table[0][2]["content"]
assert "MOV_compose_NPGO_yearly_eof1.png" not in table[0][2]["content"]


def test_emovs_table_defaults_to_atmospheric_modes(tmp_path):
table = generate_emovs_table(str(tmp_path / "viewer"), str(tmp_path / "figures"))

first_cells = [row[0]["content"] for row in table if row and "rowspan" in row[0]]
assert first_cells == [
"<b>NAM (PSL)</b>",
"<b>PNA (PSL)</b>",
"<b>NPO (PSL)</b>",
"<b>NAO (PSL)</b>",
"<b>SAM (PSL)</b>",
"<b>PSA1 (PSL)</b>",
"<b>PSA2 (PSL)</b>",
]


def test_emovs_modes_are_normalized_from_config(tmp_path):
fig_dir = tmp_path / "figures"
compose_dir = fig_dir / "MOV_metric" / "MOV_compose" / "DJF"
compose_dir.mkdir(parents=True)
(compose_dir / "MOV_compose_NPO_DJF_eof2.png").touch()
(compose_dir / "MOV_compose_PSA2_DJF_eof3.png").touch()

table = generate_emovs_table(
str(tmp_path / "viewer"),
str(fig_dir),
modes=" npo, psa2 ",
)

first_cells = [row[0]["content"] for row in table if row and "rowspan" in row[0]]
assert first_cells == ["<b>NPO (PSL)</b>", "<b>PSA2 (PSL)</b>"]
assert "MOV_compose_NPO_DJF_eof2.png" in table[1][1]["content"]
assert "MOV_compose_PSA2_DJF_eof3.png" in table[10][1]["content"]


def test_generate_data_html_creates_out_dir_and_keeps_string_lists(tmp_path):
template_dir = tmp_path / "templates"
out_dir = tmp_path / "viewer"
template_dir.mkdir(parents=True)
(template_dir / "data_template.html").write_text(
"{% for section in sections %}"
"{% for row in section.rows %}{{ row.description }}\n{% endfor %}"
"{% endfor %}"
)

out_path = generate_data_html(
{
"template_dir": str(template_dir),
"out_dir": str(out_dir),
"clim_viewer": True,
"clim_vars": "pr,tas",
}
)

html = (out_dir / "diag_data.html").read_text()
assert out_path == str(out_dir / "diag_data.html")
assert out_dir.is_dir()
assert "pr,tas" in html
assert "p, r" not in html
3 changes: 2 additions & 1 deletion zppy_interfaces/pcmdi_diags/link_observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def __init__(
self.obs_sets = obs_sets
self.ts_dir_ref_source = ts_dir_ref_source
self.obstmp_dir = obstmp_dir
self.obs_dic = json.load(open(obs_alias_file))
with open(obs_alias_file) as _f:
self.obs_dic = json.load(_f)
self.altobs_dic = altobs_dic

def _resolve_obs_file(self, varin, obsid):
Expand Down
Loading
Loading