makepst 0.2.0__tar.gz → 0.3.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.
- {makepst-0.2.0 → makepst-0.3.0}/CHANGELOG.md +16 -0
- {makepst-0.2.0 → makepst-0.3.0}/CITATION.cff +2 -2
- {makepst-0.2.0 → makepst-0.3.0}/PKG-INFO +80 -9
- {makepst-0.2.0 → makepst-0.3.0}/README.md +79 -8
- {makepst-0.2.0 → makepst-0.3.0}/makepst/__init__.py +3 -2
- {makepst-0.2.0 → makepst-0.3.0}/makepst/cli.py +124 -7
- {makepst-0.2.0 → makepst-0.3.0}/makepst/excel.py +41 -10
- makepst-0.3.0/makepst/hpstart.py +64 -0
- makepst-0.3.0/makepst/provenance.py +251 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/PKG-INFO +80 -9
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/SOURCES.txt +4 -0
- {makepst-0.2.0 → makepst-0.3.0}/pyproject.toml +1 -1
- makepst-0.3.0/tests/test_convenience.py +60 -0
- makepst-0.3.0/tests/test_hpstart.py +68 -0
- makepst-0.3.0/tests/test_log_bundle.py +98 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_provenance.py +1 -1
- makepst-0.2.0/makepst/provenance.py +0 -89
- {makepst-0.2.0 → makepst-0.3.0}/LICENSE +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/MANIFEST.in +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/control.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/flow.ins +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/flow.out +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/heads.ins +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/heads.out +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/io.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/model.in +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/model.par +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/model.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/model.res +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/model.tpl +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/obs.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/par.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/examples/minimal/pargp.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/__main__.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/checks.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/diff.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/model_files.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/phi.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/pst.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/pyemu_bridge.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/reader.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/rules.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/sections.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/starter.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst/writer.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/dependency_links.txt +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/entry_points.txt +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/requires.txt +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/makepst.egg-info/top_level.txt +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/setup.cfg +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.3.obs.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.3.par.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.par +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.phi.actual.csv +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.pst +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.res +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/data/demo.xlsx +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/make_fixture.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_checks.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_diff.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_examples.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_fixture.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_makepst.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_model_files.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_phi.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_pyemu.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_rules.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_starter.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_v2.py +0 -0
- {makepst-0.2.0 → makepst-0.3.0}/tests/test_version.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 (2026-09-21)
|
|
4
|
+
|
|
5
|
+
- `log`: ledger of every manifest under a folder — when, which command, which output from
|
|
6
|
+
which sources, dimensions, version; `--file` filters by file name, path or sha256 prefix
|
|
7
|
+
("which runs used that version of the workbook?"), `--command`, `--last`, `--check`
|
|
8
|
+
verifies every recorded hash
|
|
9
|
+
- `bundle`: zip a run for reproduction or review — control file, manifest, templates,
|
|
10
|
+
instruction files, model inputs, command-line files; `--sources`, `--outputs`, `--extra`,
|
|
11
|
+
`--list`, `--strict`; the zip gets its own manifest
|
|
12
|
+
- `provenance`: compare the hashes recorded in a manifest with the files on disk
|
|
13
|
+
(`unchanged` / `changed` / `missing`; exit 1 unless all unchanged)
|
|
14
|
+
- `update --dry_run`: preview matched names, target columns and skipped cells without writing
|
|
15
|
+
- `build` warns when a referenced sheet has formulas without cached values (a workbook saved
|
|
16
|
+
by openpyxl and not yet recalculated in Excel)
|
|
17
|
+
- `hpstart`: a PEST_HP `.hp` file with observation values from residuals or an IES realization
|
|
18
|
+
|
|
3
19
|
## 0.2.0 (2026-09-19)
|
|
4
20
|
|
|
5
21
|
- `update`: the xlwings backend reads only headers, name and target columns, writes contiguous
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: makepst
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Build PEST / PEST++ / PEST_HP control files from spreadsheet tables, read them back, and push results into the workbook
|
|
5
5
|
Author-email: Michael Ou <ougengxin@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -45,8 +45,11 @@ reads control files back into editable workbooks, and returns calibration result
|
|
|
45
45
|
Excel / CSV tables -> .pst makepst build
|
|
46
46
|
.pst -> editable workbook makepst dump
|
|
47
47
|
.par / .res / .rei / IES ensemble -> existing workbook makepst update
|
|
48
|
-
.par or IES realization (+ tweaks) -> new .pst makepst parrep
|
|
48
|
+
.par or IES realization (+ tweaks) -> new .pst makepst parrep
|
|
49
|
+
.res/.rei or IES obs + existing .hp -> new .hp makepst hpstart
|
|
49
50
|
two .pst / workbooks -> what changed makepst diff
|
|
51
|
+
manifests in a folder -> run ledger makepst log
|
|
52
|
+
.pst + everything it references -> zip for review makepst bundle
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
See [What round-tripping preserves](#what-round-tripping-preserves) for the exact guarantees
|
|
@@ -223,6 +226,7 @@ makepst update book.xlsm --res run.res # adds MODELLED
|
|
|
223
226
|
makepst update book.xlsm --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
|
|
224
227
|
makepst update book.xlsm --par run.3.par.csv --real best # PESTPP-IES ensemble
|
|
225
228
|
makepst update book.xlsm --obs_csv run.3.obs.csv --pst run.pst # IES simulated values
|
|
229
|
+
makepst update book.xlsm --par run.par --dry_run # preview; write nothing
|
|
226
230
|
[--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsm] [--backend openpyxl|xlwings]
|
|
227
231
|
```
|
|
228
232
|
|
|
@@ -260,8 +264,13 @@ Both sheets are rewritten on each run (`--no_phi` to skip).
|
|
|
260
264
|
|
|
261
265
|
Two backends: **xlwings** (used when installed; drives Excel invisibly, so everything in the
|
|
262
266
|
workbook is preserved and formulas recalculate; Windows/macOS with Excel) or **openpyxl**
|
|
263
|
-
(no Excel needed; keeps VBA macros; drops charts and images;
|
|
264
|
-
|
|
267
|
+
(no Excel needed; keeps VBA macros; drops charts and images; does not recalculate formulas).
|
|
268
|
+
Before building from a workbook updated with openpyxl, recalculate and save it in Excel;
|
|
269
|
+
otherwise the build may read missing or stale cached formula values. The backend used is printed.
|
|
270
|
+
`--dry_run` uses openpyxl to preview matched names, target columns, skipped formulas and
|
|
271
|
+
unmatched names without saving a workbook or manifest. It does not test whether Excel will
|
|
272
|
+
refuse individual writes. During `build`, missing cached formula results in referenced
|
|
273
|
+
tables produce a warning; existing cached values may still be stale and cannot be verified.
|
|
265
274
|
|
|
266
275
|
### parrep — .par values → new .pst
|
|
267
276
|
|
|
@@ -283,6 +292,26 @@ to that workbook; other relative names resolve against the workbook's folder. No
|
|
|
283
292
|
route rebuilds from the workbook's *current* state, so any edits made since the original
|
|
284
293
|
control file was written are picked up too.
|
|
285
294
|
|
|
295
|
+
### hpstart — modeled observations → new PEST_HP .hp file
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
makepst hpstart case.pst start.hp --template case.hp --res case.res
|
|
299
|
+
makepst hpstart case.pst start.hp --template case.hp --obs_csv case.3.obs.csv --real best
|
|
300
|
+
makepst hpstart case.pst start.hp --template case.hp --res case.rei --par case.par
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
By default, parameter values are copied unchanged from the template `.hp`, **not** from
|
|
304
|
+
`PARVAL1` in the `.pst`. Use `--par` to replace them with values from a PEST `.par` file
|
|
305
|
+
or a selected IES parameter realization. The `.pst` supplies parameter and observation
|
|
306
|
+
names and order; the template supplies PEST_HP instruction metadata that a residual or
|
|
307
|
+
IES observation file cannot provide. `hpstart` replaces the template's reference
|
|
308
|
+
modeled-observation array in `.pst` observation order. Every required observation must
|
|
309
|
+
be present with a finite value; extra result rows are ignored. The template must come
|
|
310
|
+
from the same control-file layout and
|
|
311
|
+
observation/parameter order. The binary format stores counts but not names, so matching
|
|
312
|
+
counts alone cannot establish that the template has the right order. The original `.hp`
|
|
313
|
+
is never modified, and the new file receives a provenance manifest by default.
|
|
314
|
+
|
|
286
315
|
### tempchek — templates → model input files
|
|
287
316
|
|
|
288
317
|
```
|
|
@@ -333,6 +362,39 @@ tolerance, so `49.7377` vs `49.73775` shows up at the default `1e-9` and disappe
|
|
|
333
362
|
`--rtol 1e-5`. Prints a table per section and a one-line summary; `--xlsx` writes the same
|
|
334
363
|
tables to a workbook for review. Exits 1 when there are differences, like `diff`.
|
|
335
364
|
|
|
365
|
+
### log — the run ledger
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
makepst log [FOLDER ...] [--file tr13.xlsm] [--command build] [--last 10] [--check] [--no_recursive]
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
Every manifest under the folders (default: the current one, recursively), oldest first, one
|
|
372
|
+
line each: when, which command, which output, from which sources, the dimensions, the
|
|
373
|
+
makePst version. `--file` keeps the entries whose output or sources include a file — by name,
|
|
374
|
+
path or **sha256 prefix**, so `--file b43fc509` answers "which runs used *that* version of
|
|
375
|
+
the workbook?" even after the file was renamed or overwritten. `--check` adds a column from
|
|
376
|
+
`provenance`: `unchanged` / `changed` / `missing`.
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsm [1068 par 41747 obs 159 prior] makepst 0.3.0
|
|
380
|
+
2026-09-21 08:10 update tr13-xlwings.xlsm <- tr13.xlsm, tr13.par [1068 rows in 8 sheets] makepst 0.3.0
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### bundle — a run, zipped for review
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
makepst bundle model.pst [run.zip] [--sources] [--outputs] [--extra "bin/**"] [--list] [--strict]
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Zips the control file with its manifest, every template and instruction file, the model
|
|
390
|
+
input files the templates write, and any file named on the model command line, with paths
|
|
391
|
+
relative to the run directory so the archive unzips runnable. `--sources` adds the manifest's
|
|
392
|
+
sources (the workbook, the `.par` ...), `--outputs` the model output files, `--extra` any
|
|
393
|
+
glob relative to the run directory (the model binaries, say). Files the control file
|
|
394
|
+
references but that don't exist are listed as `MISSING` (exit 1 with `--strict`); `--list`
|
|
395
|
+
shows what would be bundled without writing. The zip gets its own manifest, so `log` shows
|
|
396
|
+
who bundled what and when.
|
|
397
|
+
|
|
336
398
|
## What round-tripping preserves
|
|
337
399
|
|
|
338
400
|
`dump` → `build` and `read_pst` → `write_pst` are **semantically lossless for supported
|
|
@@ -439,7 +501,7 @@ from?" after the fact:
|
|
|
439
501
|
|
|
440
502
|
```json
|
|
441
503
|
{
|
|
442
|
-
"makepst": "0.
|
|
504
|
+
"makepst": "0.3.0",
|
|
443
505
|
"command": "build",
|
|
444
506
|
"argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsm,CONTROL", "..."],
|
|
445
507
|
"created": "2026-09-19T13:51:39-04:00",
|
|
@@ -451,7 +513,7 @@ from?" after the fact:
|
|
|
451
513
|
"sheets": ["CONTROL", "PARGP", "PAR_HK", "PAR_VK", "...", "PPglm"]}
|
|
452
514
|
],
|
|
453
515
|
"output": {"path": "D:\\...\\tr13.pst", "sha256": "d93f317d...", "size": 2163331,
|
|
454
|
-
"npar": 1068, "nobs": 41747, "npargp": 12, "nprior":
|
|
516
|
+
"npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 159, "nobsgp": 12,
|
|
455
517
|
"ntplfle": 18, "ninsfle": 7, "pestmode": "regularisation"}
|
|
456
518
|
}
|
|
457
519
|
```
|
|
@@ -463,6 +525,15 @@ realization and `--set` values (and, from a workbook, the resolved build argumen
|
|
|
463
525
|
left alone, and the backend. A hash mismatch between a manifest and the workbook on disk is
|
|
464
526
|
the signal that the control file no longer corresponds to the spreadsheet.
|
|
465
527
|
|
|
528
|
+
Run `makepst provenance model.pst` (or pass `model.pst.manifest.json`) to compare every
|
|
529
|
+
recorded input and output hash with the files at their recorded paths. It reports
|
|
530
|
+
`unchanged`, `changed`, `missing`, or `not recorded` and exits with status 1 unless all
|
|
531
|
+
records are unchanged. This checks file identity, not the validity of PEST tables; use
|
|
532
|
+
`makepst validate` for the latter. Moving files requires updating their recorded paths.
|
|
533
|
+
`makepst log` lists the manifests of a whole project folder as a ledger (with `--check`,
|
|
534
|
+
each one verified), and `makepst bundle` zips a run with everything its control file
|
|
535
|
+
references — see the two commands above.
|
|
536
|
+
|
|
466
537
|
## Relationship to pyEMU
|
|
467
538
|
|
|
468
539
|
[pyEMU](https://github.com/pypest/pyemu) is the broader toolkit: it constructs PEST++
|
|
@@ -532,14 +603,14 @@ makepst/
|
|
|
532
603
|
writer.py Pst -> .pst text (+ dump.tpl)
|
|
533
604
|
reader.py .pst text -> Pst
|
|
534
605
|
excel.py sheets -> Pst; Pst -> workbook; results -> existing workbook
|
|
535
|
-
cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek
|
|
606
|
+
cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek / log / bundle
|
|
536
607
|
diff.py semantic comparison of two Pst objects
|
|
537
608
|
phi.py objective function by group from residuals; IES realization phis
|
|
538
609
|
pyemu_bridge.py to_pyemu / from_pyemu
|
|
539
610
|
checks.py the validate report: table checks, template / instruction cross-checks, instruction interpreter
|
|
540
611
|
rules.py pestchek's rules (parameters, groups, observations, prior information, control variables)
|
|
541
612
|
model_files.py PEST's number writer, template filling, .obf files (tempchek / inschek)
|
|
542
|
-
provenance.py the <output>.manifest.json sidecar
|
|
613
|
+
provenance.py the <output>.manifest.json sidecar; provenance / log / bundle
|
|
543
614
|
starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
|
|
544
615
|
examples/minimal/ the tutorial inputs
|
|
545
616
|
tests/ suite + fixture generator
|
|
@@ -552,7 +623,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
|
|
|
552
623
|
`makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
|
|
553
624
|
|
|
554
625
|
```
|
|
555
|
-
git tag v0.
|
|
626
|
+
git tag v0.3.0 && git push origin main v0.3.0
|
|
556
627
|
```
|
|
557
628
|
|
|
558
629
|
The `publish` workflow checks that the tag matches the package version, runs the tests, builds
|
|
@@ -14,8 +14,11 @@ reads control files back into editable workbooks, and returns calibration result
|
|
|
14
14
|
Excel / CSV tables -> .pst makepst build
|
|
15
15
|
.pst -> editable workbook makepst dump
|
|
16
16
|
.par / .res / .rei / IES ensemble -> existing workbook makepst update
|
|
17
|
-
.par or IES realization (+ tweaks) -> new .pst makepst parrep
|
|
17
|
+
.par or IES realization (+ tweaks) -> new .pst makepst parrep
|
|
18
|
+
.res/.rei or IES obs + existing .hp -> new .hp makepst hpstart
|
|
18
19
|
two .pst / workbooks -> what changed makepst diff
|
|
20
|
+
manifests in a folder -> run ledger makepst log
|
|
21
|
+
.pst + everything it references -> zip for review makepst bundle
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
See [What round-tripping preserves](#what-round-tripping-preserves) for the exact guarantees
|
|
@@ -192,6 +195,7 @@ makepst update book.xlsm --res run.res # adds MODELLED
|
|
|
192
195
|
makepst update book.xlsm --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
|
|
193
196
|
makepst update book.xlsm --par run.3.par.csv --real best # PESTPP-IES ensemble
|
|
194
197
|
makepst update book.xlsm --obs_csv run.3.obs.csv --pst run.pst # IES simulated values
|
|
198
|
+
makepst update book.xlsm --par run.par --dry_run # preview; write nothing
|
|
195
199
|
[--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsm] [--backend openpyxl|xlwings]
|
|
196
200
|
```
|
|
197
201
|
|
|
@@ -229,8 +233,13 @@ Both sheets are rewritten on each run (`--no_phi` to skip).
|
|
|
229
233
|
|
|
230
234
|
Two backends: **xlwings** (used when installed; drives Excel invisibly, so everything in the
|
|
231
235
|
workbook is preserved and formulas recalculate; Windows/macOS with Excel) or **openpyxl**
|
|
232
|
-
(no Excel needed; keeps VBA macros; drops charts and images;
|
|
233
|
-
|
|
236
|
+
(no Excel needed; keeps VBA macros; drops charts and images; does not recalculate formulas).
|
|
237
|
+
Before building from a workbook updated with openpyxl, recalculate and save it in Excel;
|
|
238
|
+
otherwise the build may read missing or stale cached formula values. The backend used is printed.
|
|
239
|
+
`--dry_run` uses openpyxl to preview matched names, target columns, skipped formulas and
|
|
240
|
+
unmatched names without saving a workbook or manifest. It does not test whether Excel will
|
|
241
|
+
refuse individual writes. During `build`, missing cached formula results in referenced
|
|
242
|
+
tables produce a warning; existing cached values may still be stale and cannot be verified.
|
|
234
243
|
|
|
235
244
|
### parrep — .par values → new .pst
|
|
236
245
|
|
|
@@ -252,6 +261,26 @@ to that workbook; other relative names resolve against the workbook's folder. No
|
|
|
252
261
|
route rebuilds from the workbook's *current* state, so any edits made since the original
|
|
253
262
|
control file was written are picked up too.
|
|
254
263
|
|
|
264
|
+
### hpstart — modeled observations → new PEST_HP .hp file
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
makepst hpstart case.pst start.hp --template case.hp --res case.res
|
|
268
|
+
makepst hpstart case.pst start.hp --template case.hp --obs_csv case.3.obs.csv --real best
|
|
269
|
+
makepst hpstart case.pst start.hp --template case.hp --res case.rei --par case.par
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
By default, parameter values are copied unchanged from the template `.hp`, **not** from
|
|
273
|
+
`PARVAL1` in the `.pst`. Use `--par` to replace them with values from a PEST `.par` file
|
|
274
|
+
or a selected IES parameter realization. The `.pst` supplies parameter and observation
|
|
275
|
+
names and order; the template supplies PEST_HP instruction metadata that a residual or
|
|
276
|
+
IES observation file cannot provide. `hpstart` replaces the template's reference
|
|
277
|
+
modeled-observation array in `.pst` observation order. Every required observation must
|
|
278
|
+
be present with a finite value; extra result rows are ignored. The template must come
|
|
279
|
+
from the same control-file layout and
|
|
280
|
+
observation/parameter order. The binary format stores counts but not names, so matching
|
|
281
|
+
counts alone cannot establish that the template has the right order. The original `.hp`
|
|
282
|
+
is never modified, and the new file receives a provenance manifest by default.
|
|
283
|
+
|
|
255
284
|
### tempchek — templates → model input files
|
|
256
285
|
|
|
257
286
|
```
|
|
@@ -302,6 +331,39 @@ tolerance, so `49.7377` vs `49.73775` shows up at the default `1e-9` and disappe
|
|
|
302
331
|
`--rtol 1e-5`. Prints a table per section and a one-line summary; `--xlsx` writes the same
|
|
303
332
|
tables to a workbook for review. Exits 1 when there are differences, like `diff`.
|
|
304
333
|
|
|
334
|
+
### log — the run ledger
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
makepst log [FOLDER ...] [--file tr13.xlsm] [--command build] [--last 10] [--check] [--no_recursive]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Every manifest under the folders (default: the current one, recursively), oldest first, one
|
|
341
|
+
line each: when, which command, which output, from which sources, the dimensions, the
|
|
342
|
+
makePst version. `--file` keeps the entries whose output or sources include a file — by name,
|
|
343
|
+
path or **sha256 prefix**, so `--file b43fc509` answers "which runs used *that* version of
|
|
344
|
+
the workbook?" even after the file was renamed or overwritten. `--check` adds a column from
|
|
345
|
+
`provenance`: `unchanged` / `changed` / `missing`.
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
2026-09-19 21:14 build pest/tr13.pst <- tr13.xlsm [1068 par 41747 obs 159 prior] makepst 0.3.0
|
|
349
|
+
2026-09-21 08:10 update tr13-xlwings.xlsm <- tr13.xlsm, tr13.par [1068 rows in 8 sheets] makepst 0.3.0
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### bundle — a run, zipped for review
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
makepst bundle model.pst [run.zip] [--sources] [--outputs] [--extra "bin/**"] [--list] [--strict]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Zips the control file with its manifest, every template and instruction file, the model
|
|
359
|
+
input files the templates write, and any file named on the model command line, with paths
|
|
360
|
+
relative to the run directory so the archive unzips runnable. `--sources` adds the manifest's
|
|
361
|
+
sources (the workbook, the `.par` ...), `--outputs` the model output files, `--extra` any
|
|
362
|
+
glob relative to the run directory (the model binaries, say). Files the control file
|
|
363
|
+
references but that don't exist are listed as `MISSING` (exit 1 with `--strict`); `--list`
|
|
364
|
+
shows what would be bundled without writing. The zip gets its own manifest, so `log` shows
|
|
365
|
+
who bundled what and when.
|
|
366
|
+
|
|
305
367
|
## What round-tripping preserves
|
|
306
368
|
|
|
307
369
|
`dump` → `build` and `read_pst` → `write_pst` are **semantically lossless for supported
|
|
@@ -408,7 +470,7 @@ from?" after the fact:
|
|
|
408
470
|
|
|
409
471
|
```json
|
|
410
472
|
{
|
|
411
|
-
"makepst": "0.
|
|
473
|
+
"makepst": "0.3.0",
|
|
412
474
|
"command": "build",
|
|
413
475
|
"argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsm,CONTROL", "..."],
|
|
414
476
|
"created": "2026-09-19T13:51:39-04:00",
|
|
@@ -420,7 +482,7 @@ from?" after the fact:
|
|
|
420
482
|
"sheets": ["CONTROL", "PARGP", "PAR_HK", "PAR_VK", "...", "PPglm"]}
|
|
421
483
|
],
|
|
422
484
|
"output": {"path": "D:\\...\\tr13.pst", "sha256": "d93f317d...", "size": 2163331,
|
|
423
|
-
"npar": 1068, "nobs": 41747, "npargp": 12, "nprior":
|
|
485
|
+
"npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 159, "nobsgp": 12,
|
|
424
486
|
"ntplfle": 18, "ninsfle": 7, "pestmode": "regularisation"}
|
|
425
487
|
}
|
|
426
488
|
```
|
|
@@ -432,6 +494,15 @@ realization and `--set` values (and, from a workbook, the resolved build argumen
|
|
|
432
494
|
left alone, and the backend. A hash mismatch between a manifest and the workbook on disk is
|
|
433
495
|
the signal that the control file no longer corresponds to the spreadsheet.
|
|
434
496
|
|
|
497
|
+
Run `makepst provenance model.pst` (or pass `model.pst.manifest.json`) to compare every
|
|
498
|
+
recorded input and output hash with the files at their recorded paths. It reports
|
|
499
|
+
`unchanged`, `changed`, `missing`, or `not recorded` and exits with status 1 unless all
|
|
500
|
+
records are unchanged. This checks file identity, not the validity of PEST tables; use
|
|
501
|
+
`makepst validate` for the latter. Moving files requires updating their recorded paths.
|
|
502
|
+
`makepst log` lists the manifests of a whole project folder as a ledger (with `--check`,
|
|
503
|
+
each one verified), and `makepst bundle` zips a run with everything its control file
|
|
504
|
+
references — see the two commands above.
|
|
505
|
+
|
|
435
506
|
## Relationship to pyEMU
|
|
436
507
|
|
|
437
508
|
[pyEMU](https://github.com/pypest/pyemu) is the broader toolkit: it constructs PEST++
|
|
@@ -501,14 +572,14 @@ makepst/
|
|
|
501
572
|
writer.py Pst -> .pst text (+ dump.tpl)
|
|
502
573
|
reader.py .pst text -> Pst
|
|
503
574
|
excel.py sheets -> Pst; Pst -> workbook; results -> existing workbook
|
|
504
|
-
cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek
|
|
575
|
+
cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek / log / bundle
|
|
505
576
|
diff.py semantic comparison of two Pst objects
|
|
506
577
|
phi.py objective function by group from residuals; IES realization phis
|
|
507
578
|
pyemu_bridge.py to_pyemu / from_pyemu
|
|
508
579
|
checks.py the validate report: table checks, template / instruction cross-checks, instruction interpreter
|
|
509
580
|
rules.py pestchek's rules (parameters, groups, observations, prior information, control variables)
|
|
510
581
|
model_files.py PEST's number writer, template filling, .obf files (tempchek / inschek)
|
|
511
|
-
provenance.py the <output>.manifest.json sidecar
|
|
582
|
+
provenance.py the <output>.manifest.json sidecar; provenance / log / bundle
|
|
512
583
|
starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
|
|
513
584
|
examples/minimal/ the tutorial inputs
|
|
514
585
|
tests/ suite + fixture generator
|
|
@@ -521,7 +592,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
|
|
|
521
592
|
`makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
|
|
522
593
|
|
|
523
594
|
```
|
|
524
|
-
git tag v0.
|
|
595
|
+
git tag v0.3.0 && git push origin main v0.3.0
|
|
525
596
|
```
|
|
526
597
|
|
|
527
598
|
The `publish` workflow checks that the tag matches the package version, runs the tests, builds
|
|
@@ -3,9 +3,10 @@ try: # the installed package's metadata (
|
|
|
3
3
|
from importlib.metadata import version as _dist_version
|
|
4
4
|
__version__ = _dist_version('makepst')
|
|
5
5
|
except Exception: # a checkout run without installing
|
|
6
|
-
__version__ = '0.
|
|
6
|
+
__version__ = '0.3.0'
|
|
7
7
|
|
|
8
8
|
from .excel import load_table, to_workbook, update_workbook
|
|
9
|
+
from .hpstart import write_hpstart
|
|
9
10
|
from .model_files import fill_template, write_number
|
|
10
11
|
from .provenance import Manifest
|
|
11
12
|
from .pst import Pst, read_ensemble, read_obs_ensemble, read_par, read_res
|
|
@@ -24,4 +25,4 @@ def from_pyemu(ppst):
|
|
|
24
25
|
|
|
25
26
|
__all__ = ['Pst', 'read_pst', 'from_text', 'write_pst', 'to_text',
|
|
26
27
|
'load_table', 'to_workbook', 'update_workbook', 'read_par', 'read_res', 'read_ensemble', 'read_obs_ensemble',
|
|
27
|
-
'fill_template', 'write_number', 'Manifest', '__version__', 'to_pyemu', 'from_pyemu']
|
|
28
|
+
'fill_template', 'write_number', 'write_hpstart', 'Manifest', '__version__', 'to_pyemu', 'from_pyemu']
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Command line: build (Excel/CSV -> pst), dump (pst -> Excel), update (values -> existing workbook),
|
|
2
2
|
parrep (.par values -> pst), init (starter workbook), validate (pestchek-style checks), diff (compare two),
|
|
3
|
-
tempchek (templates -> model input files), inschek (model output files -> observation values)
|
|
3
|
+
tempchek (templates -> model input files), inschek (model output files -> observation values),
|
|
4
|
+
provenance / log / bundle (manifests: verify, list as a ledger, zip a run)."""
|
|
4
5
|
import argparse
|
|
5
6
|
import os
|
|
6
7
|
import shlex
|
|
@@ -8,7 +9,7 @@ import sys
|
|
|
8
9
|
|
|
9
10
|
from . import __version__
|
|
10
11
|
from .excel import expand_spec, load_table, to_workbook, update_workbook
|
|
11
|
-
from .provenance import Manifest
|
|
12
|
+
from .provenance import Manifest, check_manifest
|
|
12
13
|
from .pst import Pst, read_par
|
|
13
14
|
from .reader import read_pst
|
|
14
15
|
from .writer import write_pst
|
|
@@ -445,7 +446,7 @@ def update(args):
|
|
|
445
446
|
def flat(items): # repeated flags, each possibly a comma list
|
|
446
447
|
return [x.strip() for s in items for x in s.split(',') if x.strip()] or None
|
|
447
448
|
|
|
448
|
-
manifest = None if args.no_manifest else Manifest('update', args._argv)
|
|
449
|
+
manifest = None if args.no_manifest or args.dry_run else Manifest('update', args._argv)
|
|
449
450
|
if manifest is not None:
|
|
450
451
|
manifest.add_source(args.workbook, role='workbook')
|
|
451
452
|
for path, role in ((args.par, 'parameter values'), (args.pst, 'control file'),
|
|
@@ -456,7 +457,8 @@ def update(args):
|
|
|
456
457
|
real=args.real, pst=pst, out=args.out, backend=args.backend,
|
|
457
458
|
par_cols=args.par_cols.upper().split(','), obs_cols=args.obs_cols.upper().split(','),
|
|
458
459
|
overwrite_formulas=args.overwrite_formulas,
|
|
459
|
-
sheets=flat(args.sheet), groups=flat(args.group), phi=not args.no_phi
|
|
460
|
+
sheets=flat(args.sheet), groups=flat(args.group), phi=not args.no_phi,
|
|
461
|
+
dry_run=args.dry_run)
|
|
460
462
|
if manifest is not None:
|
|
461
463
|
manifest.set_output(args.out or args.workbook, sheets=result['sheets'], backend=result['backend'])
|
|
462
464
|
manifest.add(realization=args.real, par_cols=args.par_cols, obs_cols=args.obs_cols,
|
|
@@ -464,11 +466,87 @@ def update(args):
|
|
|
464
466
|
print(f'manifest written to {manifest.write()}')
|
|
465
467
|
|
|
466
468
|
|
|
469
|
+
def provenance(args):
|
|
470
|
+
path = args.target if args.target.endswith('.manifest.json') else args.target + '.manifest.json'
|
|
471
|
+
for status, name in check_manifest(path):
|
|
472
|
+
print(f'{status:12s} {name}')
|
|
473
|
+
if status != 'unchanged':
|
|
474
|
+
args._failed = True
|
|
475
|
+
if getattr(args, '_failed', False):
|
|
476
|
+
raise SystemExit(1)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def log(args):
|
|
480
|
+
"""The ledger: every manifest under the folders, oldest first — what produced which file from what."""
|
|
481
|
+
from .provenance import find_manifests, log_line, mentions
|
|
482
|
+
rows = find_manifests(args.folder or ['.'], recursive=not args.no_recursive)
|
|
483
|
+
if args.file:
|
|
484
|
+
rows = [r for r in rows if any(mentions(r, f) for f in args.file)]
|
|
485
|
+
if args.command:
|
|
486
|
+
rows = [r for r in rows if r.get('command') in args.command]
|
|
487
|
+
if args.last:
|
|
488
|
+
rows = rows[-args.last:]
|
|
489
|
+
for r in rows:
|
|
490
|
+
print(log_line(r, check=args.check))
|
|
491
|
+
if not rows:
|
|
492
|
+
print('no manifests found')
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def bundle(args):
|
|
496
|
+
"""Zip a run so it can be reproduced or reviewed elsewhere (control file, manifest, templates,
|
|
497
|
+
instruction files, model inputs, command files; --sources / --outputs / --extra for more)."""
|
|
498
|
+
from .provenance import bundle_run
|
|
499
|
+
pst = read_pst(args.pstfile)
|
|
500
|
+
base = args.base_dir or guess_base_dir(pst, args.pstfile)
|
|
501
|
+
manifest = None if (args.no_manifest or args.list) else Manifest('bundle', args._argv)
|
|
502
|
+
out = None if args.list else (args.out or os.path.splitext(args.pstfile)[0] + '.zip')
|
|
503
|
+
included, missing = bundle_run(pst, args.pstfile, out, base_dir=base, sources=args.sources,
|
|
504
|
+
outputs=args.outputs, extra=args.extra, manifest=manifest)
|
|
505
|
+
|
|
506
|
+
def show(path):
|
|
507
|
+
rel = os.path.relpath(path, base)
|
|
508
|
+
return path if rel.startswith('..') else rel
|
|
509
|
+
|
|
510
|
+
for role, path in included:
|
|
511
|
+
print(f'{"would add" if args.list else "added":10s} {role:18s} {show(path)}')
|
|
512
|
+
for role, path in missing:
|
|
513
|
+
print(f'MISSING {role:18s} {show(path)}')
|
|
514
|
+
if args.list:
|
|
515
|
+
print(f'{len(included)} files, {len(missing)} missing (nothing written)')
|
|
516
|
+
return
|
|
517
|
+
size = os.path.getsize(out) / 1e6
|
|
518
|
+
print(f'written {out}: {len(included)} files, {size:.1f} MB' + (f', {len(missing)} missing' if missing else ''))
|
|
519
|
+
if manifest is not None:
|
|
520
|
+
manifest.set_output(out, files=len(included), missing=[p for _, p in missing])
|
|
521
|
+
print(f'manifest written to {manifest.write()}')
|
|
522
|
+
if missing and args.strict:
|
|
523
|
+
raise SystemExit(1)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def hpstart(args):
|
|
527
|
+
from .hpstart import write_hpstart
|
|
528
|
+
pst = read_pst(args.pstfile)
|
|
529
|
+
manifest = None if args.no_manifest else Manifest('hpstart', args._argv)
|
|
530
|
+
if manifest is not None:
|
|
531
|
+
for path, role in ((args.pstfile, 'control file'), (args.template, 'hp template'),
|
|
532
|
+
(args.res, 'residuals'), (args.obs_csv, 'observation ensemble'),
|
|
533
|
+
(args.par, 'parameter values')):
|
|
534
|
+
if path:
|
|
535
|
+
manifest.add_source(path, role=role)
|
|
536
|
+
counts = write_hpstart(pst, args.template, args.out, res=args.res,
|
|
537
|
+
obs_csv=args.obs_csv, real=args.real, par=args.par)
|
|
538
|
+
print(f'written {args.out}: {counts["npar"]} parameters, {counts["nobs"]} observations')
|
|
539
|
+
if manifest is not None:
|
|
540
|
+
manifest.set_output(args.out, **counts)
|
|
541
|
+
manifest.add(realization=args.real)
|
|
542
|
+
print(f'manifest written to {manifest.write()}')
|
|
543
|
+
|
|
544
|
+
|
|
467
545
|
def main(argv=None):
|
|
468
546
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
469
547
|
# legacy form: makePst.py out.pst mode --add_par_xls ... (no subcommand)
|
|
470
|
-
if argv and argv[0] not in ('build', 'dump', 'update', 'parrep', 'init', 'validate', '
|
|
471
|
-
'-h', '--help', '-v', '--version'):
|
|
548
|
+
if argv and argv[0] not in ('build', 'dump', 'update', 'parrep', 'init', 'validate', 'provenance', 'log', 'bundle',
|
|
549
|
+
'diff', 'tempchek', 'inschek', 'hpstart', '-h', '--help', '-v', '--version'):
|
|
472
550
|
argv.insert(0, 'build')
|
|
473
551
|
|
|
474
552
|
ap = argparse.ArgumentParser(prog='makepst', description=__doc__)
|
|
@@ -500,6 +578,17 @@ def main(argv=None):
|
|
|
500
578
|
rf.add_argument('--v2', action='store_true', help='write a PEST++ version-2 file')
|
|
501
579
|
rf.add_argument('--v1', action='store_true', help='write the classic format (default: same as the input)')
|
|
502
580
|
|
|
581
|
+
h = sub.add_parser('hpstart', help='update a PEST_HP .hp file from residual or IES observation values')
|
|
582
|
+
h.add_argument('pstfile', help='control file defining observation and parameter order')
|
|
583
|
+
h.add_argument('out', help='new .hp file (must differ from the template)')
|
|
584
|
+
h.add_argument('--template', required=True, help='existing .hp with matching order; instruction metadata is preserved')
|
|
585
|
+
hs = h.add_mutually_exclusive_group(required=True)
|
|
586
|
+
hs.add_argument('--res', help='PEST .res/.rei file providing MODELLED values')
|
|
587
|
+
hs.add_argument('--obs_csv', help='PESTPP-IES case.N.obs.csv providing modeled values')
|
|
588
|
+
h.add_argument('--real', help='IES realization name, or best (default base)')
|
|
589
|
+
h.add_argument('--par', help='optional .par or IES parameter file to replace template parameter values')
|
|
590
|
+
h.add_argument('--no_manifest', action='store_true', help="don't write <out>.manifest.json")
|
|
591
|
+
|
|
503
592
|
v = sub.add_parser('validate', help='pestchek-style checks on a control file or a workbook')
|
|
504
593
|
v.add_argument('target', help='control file, or a workbook with a BUILD sheet')
|
|
505
594
|
v.add_argument('--outputs', action='store_true',
|
|
@@ -509,6 +598,31 @@ def main(argv=None):
|
|
|
509
598
|
v.add_argument('--strict', action='store_true', help='exit 1 on warnings as well as errors')
|
|
510
599
|
v.add_argument('--quiet', action='store_true', help='hide informational lines')
|
|
511
600
|
|
|
601
|
+
q = sub.add_parser('provenance', help='compare files against a saved manifest')
|
|
602
|
+
q.add_argument('target', help='manifest path, or output path with a .manifest.json sidecar')
|
|
603
|
+
|
|
604
|
+
g = sub.add_parser('log', help='ledger of every manifest under a folder: what produced which file from what')
|
|
605
|
+
g.add_argument('folder', nargs='*', help='folders (or manifest files) to search; default the current one')
|
|
606
|
+
g.add_argument('--file', action='append', default=[], metavar='NAME',
|
|
607
|
+
help='only entries whose output or sources include this file name, path or sha256 prefix')
|
|
608
|
+
g.add_argument('--command', action='append', default=[], metavar='CMD', help='only this command (repeatable)')
|
|
609
|
+
g.add_argument('--last', type=int, metavar='N', help='only the N most recent entries')
|
|
610
|
+
g.add_argument('--check', action='store_true', help='also compare every recorded hash with the file on disk')
|
|
611
|
+
g.add_argument('--no_recursive', action='store_true', help='do not descend into subfolders')
|
|
612
|
+
|
|
613
|
+
b = sub.add_parser('bundle', help='zip a run for reproduction or review: control file, manifest, templates, '
|
|
614
|
+
'instruction files, model inputs, command files')
|
|
615
|
+
b.add_argument('pstfile', help='control file')
|
|
616
|
+
b.add_argument('out', nargs='?', help='zip file to write (default: the control file name with .zip)')
|
|
617
|
+
b.add_argument('--sources', action='store_true', help="also the manifest's sources (the workbook, .par, ...)")
|
|
618
|
+
b.add_argument('--outputs', action='store_true', help='also the model output files the instruction files read')
|
|
619
|
+
b.add_argument('--extra', action='append', default=[], metavar='GLOB',
|
|
620
|
+
help='more files, relative to the run directory (repeatable; ** allowed)')
|
|
621
|
+
b.add_argument('--base_dir', metavar='DIR', help='the directory PEST runs in (default: guessed as validate does)')
|
|
622
|
+
b.add_argument('--list', action='store_true', help='show what would be bundled, write nothing')
|
|
623
|
+
b.add_argument('--strict', action='store_true', help='exit 1 when a referenced file is missing')
|
|
624
|
+
b.add_argument('--no_manifest', action='store_true', help="don't write <zip>.manifest.json")
|
|
625
|
+
|
|
512
626
|
t = sub.add_parser('tempchek', help='check a template file, or write model input files from parameter values (like TEMPCHEK)')
|
|
513
627
|
t.add_argument('target', help='template file, or a control file / workbook (all of its templates)')
|
|
514
628
|
t.add_argument('modfile', nargs='?', help='with a template: model input file to write')
|
|
@@ -552,6 +666,7 @@ def main(argv=None):
|
|
|
552
666
|
u.add_argument('--overwrite_formulas', action='store_true',
|
|
553
667
|
help='also write into cells that currently hold formulas (default: leave them alone)')
|
|
554
668
|
u.add_argument('--out', help='save to this file instead of in place')
|
|
669
|
+
u.add_argument('--dry_run', action='store_true', help='preview matches and skipped cells without writing files')
|
|
555
670
|
u.add_argument('--backend', choices=['xlwings', 'openpyxl'], help='default: xlwings if installed')
|
|
556
671
|
u.add_argument('--no_manifest', action='store_true', help="don't write <workbook>.manifest.json")
|
|
557
672
|
u.add_argument('--no_phi', action='store_true', help="don't write the PHI / PHI_IES sheets with --res / --obs_csv")
|
|
@@ -559,7 +674,9 @@ def main(argv=None):
|
|
|
559
674
|
args = ap.parse_args(argv)
|
|
560
675
|
args._argv = argv # the command line as given, for the manifest
|
|
561
676
|
{'build': build, 'dump': dump, 'update': update, 'parrep': parrep, 'init': init,
|
|
562
|
-
'validate': validate, '
|
|
677
|
+
'validate': validate, 'provenance': provenance, 'log': log, 'bundle': bundle, 'diff': diff,
|
|
678
|
+
'tempchek': tempchek, 'inschek': inschek,
|
|
679
|
+
'hpstart': hpstart}[args.cmd](args)
|
|
563
680
|
|
|
564
681
|
|
|
565
682
|
if __name__ == '__main__':
|