heatmap_cli 0.25.2__tar.gz → 0.25.3__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.
- {heatmap_cli-0.25.2/src/heatmap_cli.egg-info → heatmap_cli-0.25.3}/PKG-INFO +1 -1
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/pyproject.toml +1 -1
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli/__init__.py +5 -2
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli/heatmap.py +31 -15
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3/src/heatmap_cli.egg-info}/PKG-INFO +1 -1
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_demo_option.py +20 -0
- heatmap_cli-0.25.3/tests/test_heatmap_open.py +83 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_heatmap_wrapper.py +20 -4
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_iso_53_week_year.py +27 -0
- heatmap_cli-0.25.2/tests/test_heatmap_open.py +0 -46
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/LICENSE.md +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/README.md +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/setup.cfg +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli/__main__.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli/cli.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli.egg-info/SOURCES.txt +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli.egg-info/dependency_links.txt +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli.egg-info/entry_points.txt +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli.egg-info/requires.txt +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/src/heatmap_cli.egg-info/top_level.txt +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_animate_by_week.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_animate_no_frames.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_animate_open.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_annotate_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_annotation_truncation.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_cbar_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_cmap_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_cmax_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_cmin_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_configure_cbar.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_debug_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_end_date_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_env_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_heatmap_direct.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_heatmap_errors.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_heatmap_utils.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_help_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_no_annotate.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_open_heatmap.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_output_dir_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_purge_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_quiet_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_refresh_output_dir.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_setup_logging.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_start_date_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_title_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_verbose_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_version_flag.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_week_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_year_option.py +0 -0
- {heatmap_cli-0.25.2 → heatmap_cli-0.25.3}/tests/test_yes_flag.py +0 -0
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
27
27
|
]
|
|
28
|
-
version = "0.25.
|
|
28
|
+
version = "0.25.3"
|
|
29
29
|
keywords = ["heatmap", "cli", "visualization", "calendar", "data-viz"]
|
|
30
30
|
dependencies = [
|
|
31
31
|
"matplotlib",
|
|
@@ -33,6 +33,7 @@ import pandas as pd
|
|
|
33
33
|
import seaborn as sns
|
|
34
34
|
from PIL import Image
|
|
35
35
|
|
|
36
|
+
from heatmap_cli.heatmap import _get_output_dir
|
|
36
37
|
from heatmap_cli.heatmap import run as generate_heatmaps
|
|
37
38
|
|
|
38
39
|
__version__ = metadata.version("heatmap_cli")
|
|
@@ -135,7 +136,9 @@ def generate_demo(config: argparse.Namespace) -> None:
|
|
|
135
136
|
"""
|
|
136
137
|
random.seed(config.seed if config.seed is not None else DEFAULT_DEMO_SEED)
|
|
137
138
|
config.cmap = random.sample(CMAPS, config.demo)
|
|
138
|
-
config.input_filename = str(
|
|
139
|
+
config.input_filename = str(
|
|
140
|
+
Path(config.output_dir) / DEMO_SAMPLE_FILENAME,
|
|
141
|
+
)
|
|
139
142
|
generate_heatmaps(config)
|
|
140
143
|
|
|
141
144
|
|
|
@@ -162,7 +165,7 @@ def _generate_sample_csv(config: argparse.Namespace) -> Path:
|
|
|
162
165
|
)
|
|
163
166
|
df_dates["count"] = random.sample(range(DEMO_MAX_COUNT), len(df_dates))
|
|
164
167
|
|
|
165
|
-
csv_filename =
|
|
168
|
+
csv_filename = _get_output_dir(config) / DEMO_SAMPLE_FILENAME
|
|
166
169
|
csv_filename.parent.mkdir(parents=True, exist_ok=True)
|
|
167
170
|
df_dates.to_csv(csv_filename, sep=",", index=False, header=False)
|
|
168
171
|
logger.debug("Generate sample CSV file: %s", csv_filename)
|
|
@@ -27,7 +27,7 @@ import shutil
|
|
|
27
27
|
import webbrowser
|
|
28
28
|
from datetime import date, datetime, timedelta, timezone
|
|
29
29
|
from pathlib import Path
|
|
30
|
-
from typing import TYPE_CHECKING, Any
|
|
30
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
33
|
import argparse
|
|
@@ -106,6 +106,13 @@ def run(config: argparse.Namespace) -> None:
|
|
|
106
106
|
|
|
107
107
|
_refresh_output_dir(config)
|
|
108
108
|
|
|
109
|
+
# For a demo the sample CSV is generated after the output directory is
|
|
110
|
+
# refreshed, so that --purge does not delete the freshly written data file.
|
|
111
|
+
if config.demo:
|
|
112
|
+
from heatmap_cli import _generate_sample_csv # noqa: PLC0415
|
|
113
|
+
|
|
114
|
+
config.input_filename = str(_generate_sample_csv(config))
|
|
115
|
+
|
|
109
116
|
base_dataframe = _read_and_prepare_data(config)
|
|
110
117
|
|
|
111
118
|
for year in config.year:
|
|
@@ -183,7 +190,7 @@ def _generate_animated_heatmap(
|
|
|
183
190
|
start_of_week = _week_start_date(config.year, config.week)
|
|
184
191
|
dates_in_week = [start_of_week + timedelta(days=i) for i in range(7)]
|
|
185
192
|
|
|
186
|
-
frames = []
|
|
193
|
+
frames: list[Image.Image] = []
|
|
187
194
|
for seq, day in enumerate(dates_in_week, 1):
|
|
188
195
|
frame_pivoted_df = final_pivoted_df.copy()
|
|
189
196
|
|
|
@@ -210,7 +217,7 @@ def _generate_animated_heatmap(
|
|
|
210
217
|
return_image=True,
|
|
211
218
|
)
|
|
212
219
|
if frame:
|
|
213
|
-
frames.append(frame)
|
|
220
|
+
frames.append(cast("Image.Image", frame))
|
|
214
221
|
|
|
215
222
|
if not frames:
|
|
216
223
|
logger.warning("no frames generated to create GIF animation.")
|
|
@@ -278,7 +285,14 @@ def _generate_static_heatmaps(
|
|
|
278
285
|
initializer=_init_worker_logging,
|
|
279
286
|
initargs=(config,),
|
|
280
287
|
) as pool:
|
|
281
|
-
pool.starmap(_generate_heatmap_wrapper, args)
|
|
288
|
+
generated_files = pool.starmap(_generate_heatmap_wrapper, args)
|
|
289
|
+
|
|
290
|
+
# Workers never open browsers; open the finished heatmaps from the main
|
|
291
|
+
# process instead, so -o does not spawn a tab per background worker.
|
|
292
|
+
if config.open:
|
|
293
|
+
for img_filename in generated_files:
|
|
294
|
+
if img_filename:
|
|
295
|
+
_open_heatmap(img_filename)
|
|
282
296
|
|
|
283
297
|
|
|
284
298
|
def _generate_heatmap_wrapper(
|
|
@@ -286,7 +300,7 @@ def _generate_heatmap_wrapper(
|
|
|
286
300
|
cmap: str,
|
|
287
301
|
config: argparse.Namespace,
|
|
288
302
|
dataframe: pd.DataFrame,
|
|
289
|
-
) -> Image.Image | None:
|
|
303
|
+
) -> Image.Image | Path | None:
|
|
290
304
|
"""Wrapper for _generate_heatmap to handle args in Pool."""
|
|
291
305
|
return _generate_heatmap(
|
|
292
306
|
seq,
|
|
@@ -400,10 +414,15 @@ def _filter_and_pivot_data(
|
|
|
400
414
|
|
|
401
415
|
logger.debug("Last date in filtered data: %s", max(steps["date"]).date())
|
|
402
416
|
|
|
403
|
-
#
|
|
404
|
-
#
|
|
417
|
+
# The ISO year spans from the Monday of its first week, which may fall in
|
|
418
|
+
# the previous calendar year, through the Sunday before week 1 of the next
|
|
419
|
+
# ISO year. Using these boundaries keeps every date inside a single ISO
|
|
420
|
+
# year, so each (weekday, week) cell maps to exactly one date. Late
|
|
421
|
+
# December days that already belong to next year's ISO week 1 are excluded,
|
|
422
|
+
# otherwise they would be averaged with the real count in the same cell and
|
|
423
|
+
# silently halve it (pivot_table default aggfunc is "mean").
|
|
405
424
|
start_date = _week_start_date(config.year, 1)
|
|
406
|
-
end_date =
|
|
425
|
+
end_date = _week_start_date(config.year + 1, 1) - timedelta(days=1)
|
|
407
426
|
logger.debug("start date: %s", start_date)
|
|
408
427
|
logger.debug("end date: %s", end_date)
|
|
409
428
|
full_year_dates = pd.date_range(start=start_date, end=end_date, freq="D")
|
|
@@ -507,7 +526,7 @@ def _generate_heatmap(
|
|
|
507
526
|
dataframe: pd.DataFrame,
|
|
508
527
|
*,
|
|
509
528
|
return_image: bool = False,
|
|
510
|
-
) -> Image.Image | None:
|
|
529
|
+
) -> Image.Image | Path | None:
|
|
511
530
|
"""Generate a heatmap.
|
|
512
531
|
|
|
513
532
|
Args:
|
|
@@ -520,8 +539,8 @@ def _generate_heatmap(
|
|
|
520
539
|
saving to disk.
|
|
521
540
|
|
|
522
541
|
Returns:
|
|
523
|
-
Optional[Image.Image]: The PIL Image object if return_image is
|
|
524
|
-
|
|
542
|
+
Optional[Image.Image | Path]: The PIL Image object if return_image is
|
|
543
|
+
True, otherwise the Path of the saved heatmap file.
|
|
525
544
|
"""
|
|
526
545
|
fig, axis = _setup_plot_artists(cmap, config, dataframe)
|
|
527
546
|
|
|
@@ -534,10 +553,7 @@ def _generate_heatmap(
|
|
|
534
553
|
img_filename = _save_heatmap_figure(fig, config, seq, cmap, title)
|
|
535
554
|
logger.info("generate heatmap: %s", img_filename)
|
|
536
555
|
|
|
537
|
-
|
|
538
|
-
_open_heatmap(img_filename)
|
|
539
|
-
|
|
540
|
-
return None
|
|
556
|
+
return img_filename
|
|
541
557
|
|
|
542
558
|
|
|
543
559
|
def _configure_heatmap_options(
|
|
@@ -100,3 +100,23 @@ def test_demo_does_not_clobber_user_sample(tmpdir, cli_runner):
|
|
|
100
100
|
|
|
101
101
|
assert user_sample.read_text() == "user,data\n"
|
|
102
102
|
assert (output_dir / DEMO_SAMPLE_FILENAME).exists()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_demo_with_purge_survives(cli_runner):
|
|
106
|
+
"""--demo --purge must not delete the sample CSV it just generated."""
|
|
107
|
+
ret = cli_runner("--demo", 1, "-p", "-Y", expect_error=False)
|
|
108
|
+
assert ret.returncode == 0
|
|
109
|
+
assert "Input file not found" not in ret.stderr
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_demo_with_tilde_output_dir(cli_runner, monkeypatch, tmpdir):
|
|
113
|
+
"""--demo must expand ~ in --output-dir like the rest of the pipeline."""
|
|
114
|
+
home = Path(tmpdir, "home")
|
|
115
|
+
home.mkdir()
|
|
116
|
+
monkeypatch.setenv("HOME", str(home))
|
|
117
|
+
|
|
118
|
+
ret = cli_runner("--demo", 1, "-O", "~/demoout", expect_error=False)
|
|
119
|
+
|
|
120
|
+
assert ret.returncode == 0
|
|
121
|
+
assert (home / "demoout" / DEMO_SAMPLE_FILENAME).exists()
|
|
122
|
+
assert not Path("~/demoout").is_dir()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright (C) 2026 Kian-Meng Ang
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU Affero General Public License as published
|
|
5
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU Affero General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from unittest.mock import patch
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
from heatmap_cli.heatmap import _generate_heatmap, _generate_static_heatmaps
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_heatmap_config(tmpdir, *, should_open: bool):
|
|
26
|
+
return argparse.Namespace(
|
|
27
|
+
annotate=False,
|
|
28
|
+
cbar=False,
|
|
29
|
+
cmap_min=None,
|
|
30
|
+
cmap_max=None,
|
|
31
|
+
author="Test Author",
|
|
32
|
+
title="Test Title",
|
|
33
|
+
output_dir=str(tmpdir),
|
|
34
|
+
format="png",
|
|
35
|
+
open=should_open,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_generate_heatmap_returns_saved_path(tmpdir):
|
|
40
|
+
config = make_heatmap_config(tmpdir, should_open=True)
|
|
41
|
+
|
|
42
|
+
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
43
|
+
|
|
44
|
+
result = _generate_heatmap(1, "vlag", config, df)
|
|
45
|
+
assert isinstance(result, Path)
|
|
46
|
+
assert result.exists()
|
|
47
|
+
|
|
48
|
+
output_files = list(Path(str(tmpdir)).glob("*.png"))
|
|
49
|
+
assert len(output_files) >= 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_generate_heatmap_does_not_open(tmpdir):
|
|
53
|
+
"""Opening is the caller's job, so _generate_heatmap never opens."""
|
|
54
|
+
config = make_heatmap_config(tmpdir, should_open=True)
|
|
55
|
+
|
|
56
|
+
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
57
|
+
|
|
58
|
+
with patch("heatmap_cli.heatmap._open_heatmap") as mocked_open:
|
|
59
|
+
_generate_heatmap(1, "vlag", config, df)
|
|
60
|
+
mocked_open.assert_not_called()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_static_heatmaps_open_once_after_pool(tmpdir):
|
|
64
|
+
"""-o opens the finished heatmaps from the main process after the pool."""
|
|
65
|
+
config = argparse.Namespace(
|
|
66
|
+
annotate=False,
|
|
67
|
+
cbar=False,
|
|
68
|
+
cmap_min=None,
|
|
69
|
+
cmap_max=None,
|
|
70
|
+
author="Test Author",
|
|
71
|
+
title="Test Title",
|
|
72
|
+
output_dir=str(tmpdir),
|
|
73
|
+
format="png",
|
|
74
|
+
open=True,
|
|
75
|
+
cmap=["vlag"],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
79
|
+
|
|
80
|
+
with patch("heatmap_cli.heatmap._open_heatmap") as mocked_open:
|
|
81
|
+
_generate_static_heatmaps(df, config)
|
|
82
|
+
assert mocked_open.call_count == 1
|
|
83
|
+
assert mocked_open.call_args.args[0].exists()
|
|
@@ -21,8 +21,8 @@ import pandas as pd
|
|
|
21
21
|
from heatmap_cli.heatmap import _generate_heatmap_wrapper
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
|
|
24
|
+
def _make_config(tmpdir, *, should_open: bool):
|
|
25
|
+
return argparse.Namespace(
|
|
26
26
|
annotate=False,
|
|
27
27
|
cbar=False,
|
|
28
28
|
cmap_min=None,
|
|
@@ -31,13 +31,29 @@ def test_generate_heatmap_wrapper(tmpdir):
|
|
|
31
31
|
title="Test Title",
|
|
32
32
|
output_dir=str(tmpdir),
|
|
33
33
|
format="png",
|
|
34
|
-
open=
|
|
34
|
+
open=should_open,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
|
|
38
|
+
def test_generate_heatmap_wrapper(tmpdir):
|
|
39
|
+
config = _make_config(tmpdir, should_open=False)
|
|
40
|
+
|
|
37
41
|
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
38
42
|
|
|
39
43
|
result = _generate_heatmap_wrapper(1, "vlag", config, df)
|
|
40
|
-
assert result
|
|
44
|
+
assert isinstance(result, Path)
|
|
45
|
+
assert result.exists()
|
|
41
46
|
|
|
42
47
|
output_files = list(Path(str(tmpdir)).glob("*.png"))
|
|
43
48
|
assert len(output_files) >= 1
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_wrapper_uses_opened_heatmap_path(tmpdir):
|
|
52
|
+
"""The pool wrapper returns the saved filename for the caller to open."""
|
|
53
|
+
config = _make_config(tmpdir, should_open=True)
|
|
54
|
+
|
|
55
|
+
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
56
|
+
|
|
57
|
+
result = _generate_heatmap_wrapper(1, "vlag", config, df)
|
|
58
|
+
assert isinstance(result, Path)
|
|
59
|
+
assert result.exists()
|
|
@@ -81,3 +81,30 @@ def test_week_53_dropped_when_partial_year_requested():
|
|
|
81
81
|
pivoted = _filter_and_pivot_data(df, config)
|
|
82
82
|
|
|
83
83
|
assert pivoted["53"].sum() == 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_iso_year_start_counts_kept_intact():
|
|
87
|
+
"""Late December days of the prior ISO week 1 must not halve counts."""
|
|
88
|
+
# ISO year 2025 runs from 2024-12-30 through 2025-12-28. The Monday of
|
|
89
|
+
# ISO week 1 (2024-12-30) used to be averaged with 2025-12-29, which
|
|
90
|
+
# already belongs to ISO 2026 week 1 and landed in the same cell as an
|
|
91
|
+
# implied zero, halving the real count.
|
|
92
|
+
df = _base_dataframe(
|
|
93
|
+
[
|
|
94
|
+
("2024-12-30", 5000),
|
|
95
|
+
("2025-01-06", 4000),
|
|
96
|
+
("2025-12-28", 3000),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
config = argparse.Namespace(
|
|
100
|
+
year=2025,
|
|
101
|
+
week=WEEKS_IN_YEAR,
|
|
102
|
+
start_date=None,
|
|
103
|
+
end_date=None,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
pivoted = _filter_and_pivot_data(df, config)
|
|
107
|
+
|
|
108
|
+
assert pivoted.loc[1, "01"] == 5000
|
|
109
|
+
assert pivoted.loc[1, "02"] == 4000
|
|
110
|
+
assert pivoted.loc[7, "52"] == 3000
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# Copyright (C) 2026 Kian-Meng Ang
|
|
2
|
-
#
|
|
3
|
-
# This program is free software: you can redistribute it and/or modify
|
|
4
|
-
# it under the terms of the GNU Affero General Public License as published
|
|
5
|
-
# by the Free Software Foundation, either version 3 of the License, or
|
|
6
|
-
# (at your option) any later version.
|
|
7
|
-
#
|
|
8
|
-
# This program is distributed in the hope that it will be useful,
|
|
9
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
-
# GNU Affero General Public License for more details.
|
|
12
|
-
#
|
|
13
|
-
# You should have received a copy of the GNU Affero General Public License
|
|
14
|
-
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
-
|
|
16
|
-
import argparse
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
from unittest.mock import patch
|
|
19
|
-
|
|
20
|
-
import pandas as pd
|
|
21
|
-
|
|
22
|
-
from heatmap_cli.heatmap import _generate_heatmap
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_generate_heatmap_with_open(tmpdir):
|
|
26
|
-
config = argparse.Namespace(
|
|
27
|
-
annotate=False,
|
|
28
|
-
cbar=False,
|
|
29
|
-
cmap_min=None,
|
|
30
|
-
cmap_max=None,
|
|
31
|
-
author="Test Author",
|
|
32
|
-
title="Test Title",
|
|
33
|
-
output_dir=str(tmpdir),
|
|
34
|
-
format="png",
|
|
35
|
-
open=True,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
df = pd.DataFrame([[10, 20], [30, 40]], index=[1, 2], columns=["01", "02"])
|
|
39
|
-
|
|
40
|
-
with patch("heatmap_cli.heatmap._open_heatmap") as mocked_open:
|
|
41
|
-
result = _generate_heatmap(1, "vlag", config, df)
|
|
42
|
-
assert result is None
|
|
43
|
-
mocked_open.assert_called_once()
|
|
44
|
-
|
|
45
|
-
output_files = list(Path(str(tmpdir)).glob("*.png"))
|
|
46
|
-
assert len(output_files) >= 1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|