makepst 0.1.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.
Files changed (72) hide show
  1. makepst-0.3.0/CHANGELOG.md +73 -0
  2. {makepst-0.1.0 → makepst-0.3.0}/CITATION.cff +2 -2
  3. {makepst-0.1.0 → makepst-0.3.0}/MANIFEST.in +2 -1
  4. {makepst-0.1.0 → makepst-0.3.0}/PKG-INFO +144 -15
  5. {makepst-0.1.0 → makepst-0.3.0}/README.md +143 -14
  6. {makepst-0.1.0 → makepst-0.3.0}/makepst/__init__.py +5 -2
  7. {makepst-0.1.0 → makepst-0.3.0}/makepst/checks.py +114 -2
  8. makepst-0.3.0/makepst/cli.py +683 -0
  9. {makepst-0.1.0 → makepst-0.3.0}/makepst/excel.py +229 -41
  10. makepst-0.3.0/makepst/hpstart.py +64 -0
  11. makepst-0.3.0/makepst/model_files.py +181 -0
  12. makepst-0.3.0/makepst/provenance.py +251 -0
  13. makepst-0.3.0/makepst/rules.py +403 -0
  14. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/PKG-INFO +144 -15
  15. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/SOURCES.txt +8 -0
  16. {makepst-0.1.0 → makepst-0.3.0}/pyproject.toml +1 -1
  17. {makepst-0.1.0 → makepst-0.3.0}/tests/test_checks.py +20 -0
  18. makepst-0.3.0/tests/test_convenience.py +60 -0
  19. {makepst-0.1.0 → makepst-0.3.0}/tests/test_fixture.py +35 -0
  20. makepst-0.3.0/tests/test_hpstart.py +68 -0
  21. makepst-0.3.0/tests/test_log_bundle.py +98 -0
  22. {makepst-0.1.0 → makepst-0.3.0}/tests/test_makepst.py +2 -1
  23. makepst-0.3.0/tests/test_model_files.py +207 -0
  24. {makepst-0.1.0 → makepst-0.3.0}/tests/test_provenance.py +4 -6
  25. makepst-0.3.0/tests/test_rules.py +194 -0
  26. makepst-0.1.0/CHANGELOG.md +0 -32
  27. makepst-0.1.0/makepst/cli.py +0 -351
  28. makepst-0.1.0/makepst/provenance.py +0 -89
  29. {makepst-0.1.0 → makepst-0.3.0}/LICENSE +0 -0
  30. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/control.csv +0 -0
  31. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/flow.ins +0 -0
  32. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/flow.out +0 -0
  33. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/heads.ins +0 -0
  34. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/heads.out +0 -0
  35. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/io.csv +0 -0
  36. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/model.in +0 -0
  37. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/model.par +0 -0
  38. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/model.py +0 -0
  39. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/model.res +0 -0
  40. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/model.tpl +0 -0
  41. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/obs.csv +0 -0
  42. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/par.csv +0 -0
  43. {makepst-0.1.0 → makepst-0.3.0}/examples/minimal/pargp.csv +0 -0
  44. {makepst-0.1.0 → makepst-0.3.0}/makepst/__main__.py +0 -0
  45. {makepst-0.1.0 → makepst-0.3.0}/makepst/diff.py +0 -0
  46. {makepst-0.1.0 → makepst-0.3.0}/makepst/phi.py +0 -0
  47. {makepst-0.1.0 → makepst-0.3.0}/makepst/pst.py +0 -0
  48. {makepst-0.1.0 → makepst-0.3.0}/makepst/pyemu_bridge.py +0 -0
  49. {makepst-0.1.0 → makepst-0.3.0}/makepst/reader.py +0 -0
  50. {makepst-0.1.0 → makepst-0.3.0}/makepst/sections.py +0 -0
  51. {makepst-0.1.0 → makepst-0.3.0}/makepst/starter.py +0 -0
  52. {makepst-0.1.0 → makepst-0.3.0}/makepst/writer.py +0 -0
  53. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/dependency_links.txt +0 -0
  54. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/entry_points.txt +0 -0
  55. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/requires.txt +0 -0
  56. {makepst-0.1.0 → makepst-0.3.0}/makepst.egg-info/top_level.txt +0 -0
  57. {makepst-0.1.0 → makepst-0.3.0}/setup.cfg +0 -0
  58. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.3.obs.csv +0 -0
  59. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.3.par.csv +0 -0
  60. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.par +0 -0
  61. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.phi.actual.csv +0 -0
  62. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.pst +0 -0
  63. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.res +0 -0
  64. {makepst-0.1.0 → makepst-0.3.0}/tests/data/demo.xlsx +0 -0
  65. {makepst-0.1.0 → makepst-0.3.0}/tests/make_fixture.py +0 -0
  66. {makepst-0.1.0 → makepst-0.3.0}/tests/test_diff.py +0 -0
  67. {makepst-0.1.0 → makepst-0.3.0}/tests/test_examples.py +0 -0
  68. {makepst-0.1.0 → makepst-0.3.0}/tests/test_phi.py +0 -0
  69. {makepst-0.1.0 → makepst-0.3.0}/tests/test_pyemu.py +0 -0
  70. {makepst-0.1.0 → makepst-0.3.0}/tests/test_starter.py +0 -0
  71. {makepst-0.1.0 → makepst-0.3.0}/tests/test_v2.py +0 -0
  72. {makepst-0.1.0 → makepst-0.3.0}/tests/test_version.py +0 -0
@@ -0,0 +1,73 @@
1
+ # Changelog
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
+
19
+ ## 0.2.0 (2026-09-19)
20
+
21
+ - `update`: the xlwings backend reads only headers, name and target columns, writes contiguous
22
+ cells in one call per run, suspends events and calculation while writing and recalculates
23
+ once before saving (1,068 parameters over eight sheets of a 27-sheet workbook: ~9 s, was
24
+ minutes); `--par` with `--pst` no longer rewrites the observation sheets
25
+ - `tempchek`: check a template file or write model input files from parameter values, with
26
+ PEST's number writer (maximum precision in the space, `PRECIS` / `DPOINT`, one word per
27
+ parameter, right-justified; checked against `tempchek.exe`); for a template + `.par` file
28
+ or for a whole case
29
+ - `inschek`: check an instruction file or read a model output file with it into an `.obf`
30
+ file, for one file or a whole case
31
+ - `validate`: values that cannot be written into their template space are errors; a line
32
+ advance not at the start of an instruction line and `t` / fixed instructions moving left
33
+ are errors (INSCHEK's rules); three more pestchek warnings (empty observation group,
34
+ `ICOV`/`ICOR`/`IEIG` with more than 300 adjustable parameters, PEST_HP-only variables —
35
+ the last as a note); an all-zero-weight observation group is now a note, not a warning
36
+ - `update`: cells inside array formulas or dynamic-array spill ranges are left alone (Excel
37
+ refuses them; xlwings no longer shows a dialog); names present on only one side are reported
38
+ with counts and examples
39
+ - `validate`: pestchek's rules added (parameter data, groups, observations, prior information,
40
+ ~60 control-variable ranges and consistency rules, template and instruction syntax), derived
41
+ from PEST 17's `pestchek.F` / `cheksub.F`; file paths resolved from the directory PEST runs
42
+ in, not only the control file's folder
43
+
44
+ ## 0.1.0 (2026-09-19)
45
+
46
+ First packaged release; replaces the single-file `makePst.py` script.
47
+
48
+ - `init`: starter workbook with headers, defaults, descriptions, drop-down lists, comments and
49
+ a `BUILD` sheet
50
+ - `build`: Excel/CSV tables -> control file; sheet globs (`book,PAR_*`); a workbook with a
51
+ `BUILD` sheet as the only argument; counts computed; PEST_HP keyed tokens and
52
+ `++` options; regularisation equations from `PRIOR`/`WEIGHT` columns; validation of tied
53
+ targets, groups, prior references and bounds
54
+ - `dump`: control file -> workbook with a `BUILD` sheet that rebuilds it
55
+ - `update`: `.par` / `.res` / `.rei` / PESTPP-IES ensembles into an existing workbook, matched
56
+ by name across sheets; formula cells protected; `--sheet` / `--group` filters; `--real`;
57
+ `PHI` sheet (objective function by observation group) and `PHI_IES` (realization phis)
58
+ whenever residuals are available
59
+ - `validate`: pestchek-style report on a control file or workbook: table checks, missing
60
+ files, parameter / observation names against template and instruction files, optional run
61
+ of instruction files against model outputs; exit 1 on errors
62
+ - PEST++ version-2 control files (`pcf version=2`, keyword control data, external csv
63
+ tables) are read and written (`--v2`); observation covariance files are kept
64
+ - `diff`: semantic comparison of two control files / workbooks (tables, effective control
65
+ values, options, io, comments), text report or workbook; exit 1 on differences
66
+ - pyEMU bridge: `to_pyemu()` / `from_pyemu()` via a temporary control file (`pyemu` extra)
67
+ - provenance: every producing command writes `<output>.manifest.json` (version, command
68
+ line, source hashes and sheets, output hash and counts)
69
+ - `parrep`: `.par` or IES realization into a control file (or a workbook with a `BUILD` sheet),
70
+ with `--set NAME=VALUE`
71
+ - Fixes over the old script: `jacupdate` was never written; values were truncated to 6
72
+ significant digits; `phistopthresh` could not be set; SVD / regularisation values in the
73
+ CONTROL sheet were ignored; prior information in estimation mode produced an invalid file
@@ -6,8 +6,8 @@ authors:
6
6
  - family-names: Ou
7
7
  given-names: Michael
8
8
  email: ougengxin@gmail.com
9
- version: 0.1.0
10
- date-released: 2026-09-19
9
+ version: 0.3.0
10
+ date-released: 2026-09-21
11
11
  repository-code: "https://github.com/ougx/makePst"
12
12
  license: MIT
13
13
  keywords:
@@ -1,4 +1,5 @@
1
1
  include LICENSE README.md CHANGELOG.md CITATION.cff
2
2
  recursive-include tests *.py *.xlsx *.pst *.par *.res *.csv
3
- recursive-include examples *.csv *.py *.tpl *.ins *.in *.out *.par *.res
3
+ recursive-include examples/minimal *.csv *.py *.tpl *.ins *.in *.out *.par *.res
4
+ prune examples/mnnrd
4
5
  global-exclude __pycache__ *.py[cod] *.manifest.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makepst
3
- Version: 0.1.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
@@ -120,6 +123,10 @@ makepst build model.pst regul --set_ctl_csv control.csv --add_pargp_csv pargp.cs
120
123
  # 2. check it against the template / instruction / output files, like pestchek
121
124
  makepst validate model.pst --outputs
122
125
 
126
+ # 2b. what PEST will do with them: write the model input file, read the model output files
127
+ makepst tempchek model.pst --dry_run
128
+ makepst inschek model.pst
129
+
123
130
  # 3. control file -> workbook (CONTROL / PARGP / PAR / OBS / PRIOR / IO sheets + a BUILD sheet)
124
131
  makepst dump model.pst model.xlsx
125
132
 
@@ -154,8 +161,10 @@ makepst validate model.pst --outputs
154
161
  makepst validate book.xlsm --strict
155
162
  ```
156
163
 
157
- See [Validation](#validation). Template and instruction paths are resolved relative to the
158
- control file (or workbook); `--base_dir` overrides that. `--strict` also fails on warnings,
164
+ See [Validation](#validation). Template and instruction paths are resolved the way PEST
165
+ resolves them, from the directory PEST is run in: makePst tries the control file's folder,
166
+ the current directory and the control file's parent, uses the one where the files are, and
167
+ says which; `--base_dir` overrides that. `--strict` also fails on warnings,
159
168
  `--quiet` hides the informational lines.
160
169
 
161
170
  ### build — tables → .pst
@@ -217,6 +226,7 @@ makepst update book.xlsm --res run.res # adds MODELLED
217
226
  makepst update book.xlsm --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
218
227
  makepst update book.xlsm --par run.3.par.csv --real best # PESTPP-IES ensemble
219
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
220
230
  [--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsm] [--backend openpyxl|xlwings]
221
231
  ```
222
232
 
@@ -225,10 +235,19 @@ makepst update book.xlsm --obs_csv run.3.obs.csv --pst run.pst # IES simulated
225
235
 
226
236
  Every worksheet whose header row contains `PARNME` (or `OBSNME`) is matched row by row on the
227
237
  name, so tables spread over several sheets are all found; only the requested columns are
228
- written and only for names present in the source. Cells that hold formulas are left alone
229
- unless `--overwrite_formulas`. `--sheet` (glob, case-insensitive) restricts the worksheets;
238
+ written and only for names present in both: names in the results with no row in the workbook,
239
+ and workbook rows absent from the results, are left alone and reported as a warning with
240
+ counts and examples. Cells that hold formulas are left alone unless `--overwrite_formulas`;
241
+ cells inside an array formula or a dynamic-array spill range are never written (Excel would
242
+ refuse), only counted. `--sheet` (glob, case-insensitive) restricts the worksheets;
230
243
  `--group` restricts the parameter / observation groups (a `.par` file carries no groups, so
231
- add `--pst` to supply them).
244
+ add `--pst` to supply them). `--pst` on its own writes the control file's parameter and
245
+ observation columns; next to `--par`, `--res` or `--obs_csv` it only supplies groups and
246
+ measured values, and the observation sheets are not touched by a parameter update.
247
+
248
+ The xlwings backend reads only each sheet's header, name column and target columns, writes
249
+ contiguous runs of cells in one call each and recalculates once before saving: the
250
+ demonstration workbook (27 sheets, 1,068 parameters over eight sheets) updates in about 9 s.
232
251
 
233
252
  PESTPP-IES ensembles (`case.N.par.csv`, `case.N.obs.csv`) are recognised by their
234
253
  `real_name` header. `--real` picks the realization: a name (`base`, `17`) or `best` — the
@@ -245,8 +264,13 @@ Both sheets are rewritten on each run (`--no_phi` to skip).
245
264
 
246
265
  Two backends: **xlwings** (used when installed; drives Excel invisibly, so everything in the
247
266
  workbook is preserved and formulas recalculate; Windows/macOS with Excel) or **openpyxl**
248
- (no Excel needed; keeps VBA macros; drops charts and images; formula results are stale until
249
- Excel next opens the file). The backend used is printed.
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.
250
274
 
251
275
  ### parrep — .par values → new .pst
252
276
 
@@ -268,6 +292,61 @@ to that workbook; other relative names resolve against the workbook's folder. No
268
292
  route rebuilds from the workbook's *current* state, so any edits made since the original
269
293
  control file was written are picked up too.
270
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
+
315
+ ### tempchek — templates → model input files
316
+
317
+ ```
318
+ makepst tempchek model.tpl # check the template, list its parameters
319
+ makepst tempchek model.tpl model.in [model.par] # write model.in (values from model.par by default)
320
+ makepst tempchek model.pst [--par run.par] [--real best] [--dry_run] # every template of the case
321
+ ```
322
+
323
+ Like PEST's TEMPCHEK: a template is checked, then its model input file is written with
324
+ parameter values from a `.par` file (or, given a control file / workbook, from `PARVAL1`
325
+ with `SCALE` and `OFFSET` applied, or from `--par` — a `.par` file or an IES realization).
326
+ Numbers are written the way PEST writes them (its `WRTSIG`): into the parameter space with
327
+ as much precision as the space allows, fixed or exponential notation, right-justified, one
328
+ word per parameter sized for its narrowest space, `PRECIS` / `DPOINT` honoured
329
+ (`double`, `nopoint`). Checked against `tempchek.exe` on a grid of values and widths from 3
330
+ to 23 characters: the same fields are refused, and the written value is never further from
331
+ the true value than PEST's. A value that cannot be written (`1e-10` in a five-character
332
+ space, say) is an error and nothing is written; parameters in the `.par` file that the
333
+ template does not cite are warnings. `--dry_run` checks without writing.
334
+
335
+ ### inschek — model output files → observation values
336
+
337
+ ```
338
+ makepst inschek heads.ins # check the instruction file, list its observations
339
+ makepst inschek heads.ins heads.out [--out heads.obf]
340
+ makepst inschek model.pst [--out model.obf] # every instruction file of the case, one .obf
341
+ ```
342
+
343
+ Like PEST's INSCHEK: the instruction file is checked (the syntax rules in the validation
344
+ table below, including that a line advance can only start an instruction line and that fixed
345
+ / tab instructions must move right), then read against the model output file with the
346
+ interpreter that `validate --outputs` uses, and the values go to an observation value file
347
+ (`name value` per line, INSCHEK's `.obf`). With a control file, all instruction files are
348
+ read and observations the control file lists but no file produced are reported.
349
+
271
350
  ### diff — what changed
272
351
 
273
352
  ```
@@ -283,6 +362,39 @@ tolerance, so `49.7377` vs `49.73775` shows up at the default `1e-9` and disappe
283
362
  `--rtol 1e-5`. Prints a table per section and a one-line summary; `--xlsx` writes the same
284
363
  tables to a workbook for review. Exits 1 when there are differences, like `diff`.
285
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
+
286
398
  ## What round-tripping preserves
287
399
 
288
400
  `dump` → `build` and `read_pst` → `write_pst` are **semantically lossless for supported
@@ -358,6 +470,12 @@ makepst validate tr13.xlsm # a workbook with a BUILD sheet is built f
358
470
  | malformed prior equation; non-numeric right-hand side; `log()` used on a non-log parameter or vice versa | error |
359
471
  | `DERCOM` beyond the number of model command lines | error |
360
472
  | name longer than PEST's limit (12 parameter / 20 observation / 12 group; PEST++ allows 200) | warning |
473
+ | **pestchek's rules** (from PEST 17's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
474
+ | pestchek's warnings: derivative increment larger than a third of the range; initial value 0 with a relative increment; group with only fixed/tied parameters; log parameters with a non-relative increment; observation group listed but empty (dropped on write); `MAXSING` above the adjustable-parameter count; more than 300 adjustable parameters with `ICOV`/`ICOR`/`IEIG` on; `NOPTMAX 0` | warning |
475
+ | PEST_HP-only control variables present (`WIN_MRUN_HOURS`, `UPTESTMIN`, `RRFSAVE`, …; plain PEST needs `/hpstart`); observation group whose weights are all zero | note |
476
+ | template: parameter space narrower than 3 characters or containing a tab | error |
477
+ | template: a value that cannot be written into its space as PEST writes numbers (`PRECIS` / `DPOINT` honoured), e.g. `1e-10` in five characters | error |
478
+ | instruction file syntax: `l` / `t` integers, marker delimiters, `!` balance, `[name]n1:n2` order, `dum` in a fixed field, continuation `&` placement, `l` only at the start of a line, `t` and fixed columns moving left to right, unknown instructions — checked statically, without model output | error |
361
479
  | section in the `.pst` that makePst drops on read | warning |
362
480
  | template or instruction file missing; bad `ptf` / `pif` line | error |
363
481
  | parameter cited in no template; template citing an unknown parameter | error |
@@ -383,7 +501,7 @@ from?" after the fact:
383
501
 
384
502
  ```json
385
503
  {
386
- "makepst": "0.1.0",
504
+ "makepst": "0.3.0",
387
505
  "command": "build",
388
506
  "argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsm,CONTROL", "..."],
389
507
  "created": "2026-09-19T13:51:39-04:00",
@@ -395,7 +513,7 @@ from?" after the fact:
395
513
  "sheets": ["CONTROL", "PARGP", "PAR_HK", "PAR_VK", "...", "PPglm"]}
396
514
  ],
397
515
  "output": {"path": "D:\\...\\tr13.pst", "sha256": "d93f317d...", "size": 2163331,
398
- "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 185, "nobsgp": 12,
516
+ "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 159, "nobsgp": 12,
399
517
  "ntplfle": 18, "ninsfle": 7, "pestmode": "regularisation"}
400
518
  }
401
519
  ```
@@ -407,6 +525,15 @@ realization and `--set` values (and, from a workbook, the resolved build argumen
407
525
  left alone, and the backend. A hash mismatch between a manifest and the workbook on disk is
408
526
  the signal that the control file no longer corresponds to the spreadsheet.
409
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
+
410
537
  ## Relationship to pyEMU
411
538
 
412
539
  [pyEMU](https://github.com/pypest/pyemu) is the broader toolkit: it constructs PEST++
@@ -454,7 +581,7 @@ update_workbook('book.xlsm', par='run.par', res='run.res', out='book_results.xls
454
581
  python -m pytest tests -q
455
582
  ```
456
583
 
457
- 95 tests, no external data needed: a synthetic workbook in the real project layout
584
+ 106 tests, no external data needed: a synthetic workbook in the real project layout
458
585
  (`tests/data/demo.xlsx`, generated by `tests/make_fixture.py`) with a golden control file,
459
586
  `.par`, `.res` and IES ensemble files. They cover golden-file builds from Excel and CSV, the
460
587
  `read → write → read` identity, `dump → build` byte equality, workbook updates (multi-sheet
@@ -476,12 +603,14 @@ makepst/
476
603
  writer.py Pst -> .pst text (+ dump.tpl)
477
604
  reader.py .pst text -> Pst
478
605
  excel.py sheets -> Pst; Pst -> workbook; results -> existing workbook
479
- cli.py init / validate / build / dump / update / parrep / diff
606
+ cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek / log / bundle
480
607
  diff.py semantic comparison of two Pst objects
481
608
  phi.py objective function by group from residuals; IES realization phis
482
609
  pyemu_bridge.py to_pyemu / from_pyemu
483
610
  checks.py the validate report: table checks, template / instruction cross-checks, instruction interpreter
484
- provenance.py the <output>.manifest.json sidecar
611
+ rules.py pestchek's rules (parameters, groups, observations, prior information, control variables)
612
+ model_files.py PEST's number writer, template filling, .obf files (tempchek / inschek)
613
+ provenance.py the <output>.manifest.json sidecar; provenance / log / bundle
485
614
  starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
486
615
  examples/minimal/ the tutorial inputs
487
616
  tests/ suite + fixture generator
@@ -494,7 +623,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
494
623
  `makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
495
624
 
496
625
  ```
497
- git tag v0.2.0 && git push origin main v0.2.0
626
+ git tag v0.3.0 && git push origin main v0.3.0
498
627
  ```
499
628
 
500
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
@@ -89,6 +92,10 @@ makepst build model.pst regul --set_ctl_csv control.csv --add_pargp_csv pargp.cs
89
92
  # 2. check it against the template / instruction / output files, like pestchek
90
93
  makepst validate model.pst --outputs
91
94
 
95
+ # 2b. what PEST will do with them: write the model input file, read the model output files
96
+ makepst tempchek model.pst --dry_run
97
+ makepst inschek model.pst
98
+
92
99
  # 3. control file -> workbook (CONTROL / PARGP / PAR / OBS / PRIOR / IO sheets + a BUILD sheet)
93
100
  makepst dump model.pst model.xlsx
94
101
 
@@ -123,8 +130,10 @@ makepst validate model.pst --outputs
123
130
  makepst validate book.xlsm --strict
124
131
  ```
125
132
 
126
- See [Validation](#validation). Template and instruction paths are resolved relative to the
127
- control file (or workbook); `--base_dir` overrides that. `--strict` also fails on warnings,
133
+ See [Validation](#validation). Template and instruction paths are resolved the way PEST
134
+ resolves them, from the directory PEST is run in: makePst tries the control file's folder,
135
+ the current directory and the control file's parent, uses the one where the files are, and
136
+ says which; `--base_dir` overrides that. `--strict` also fails on warnings,
128
137
  `--quiet` hides the informational lines.
129
138
 
130
139
  ### build — tables → .pst
@@ -186,6 +195,7 @@ makepst update book.xlsm --res run.res # adds MODELLED
186
195
  makepst update book.xlsm --pst run.pst --par_cols PARVAL1,PARTRANS,PARLBND,PARUBND
187
196
  makepst update book.xlsm --par run.3.par.csv --real best # PESTPP-IES ensemble
188
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
189
199
  [--sheet "PAR_*"] [--group hk,sy] [--overwrite_formulas] [--out copy.xlsm] [--backend openpyxl|xlwings]
190
200
  ```
191
201
 
@@ -194,10 +204,19 @@ makepst update book.xlsm --obs_csv run.3.obs.csv --pst run.pst # IES simulated
194
204
 
195
205
  Every worksheet whose header row contains `PARNME` (or `OBSNME`) is matched row by row on the
196
206
  name, so tables spread over several sheets are all found; only the requested columns are
197
- written and only for names present in the source. Cells that hold formulas are left alone
198
- unless `--overwrite_formulas`. `--sheet` (glob, case-insensitive) restricts the worksheets;
207
+ written and only for names present in both: names in the results with no row in the workbook,
208
+ and workbook rows absent from the results, are left alone and reported as a warning with
209
+ counts and examples. Cells that hold formulas are left alone unless `--overwrite_formulas`;
210
+ cells inside an array formula or a dynamic-array spill range are never written (Excel would
211
+ refuse), only counted. `--sheet` (glob, case-insensitive) restricts the worksheets;
199
212
  `--group` restricts the parameter / observation groups (a `.par` file carries no groups, so
200
- add `--pst` to supply them).
213
+ add `--pst` to supply them). `--pst` on its own writes the control file's parameter and
214
+ observation columns; next to `--par`, `--res` or `--obs_csv` it only supplies groups and
215
+ measured values, and the observation sheets are not touched by a parameter update.
216
+
217
+ The xlwings backend reads only each sheet's header, name column and target columns, writes
218
+ contiguous runs of cells in one call each and recalculates once before saving: the
219
+ demonstration workbook (27 sheets, 1,068 parameters over eight sheets) updates in about 9 s.
201
220
 
202
221
  PESTPP-IES ensembles (`case.N.par.csv`, `case.N.obs.csv`) are recognised by their
203
222
  `real_name` header. `--real` picks the realization: a name (`base`, `17`) or `best` — the
@@ -214,8 +233,13 @@ Both sheets are rewritten on each run (`--no_phi` to skip).
214
233
 
215
234
  Two backends: **xlwings** (used when installed; drives Excel invisibly, so everything in the
216
235
  workbook is preserved and formulas recalculate; Windows/macOS with Excel) or **openpyxl**
217
- (no Excel needed; keeps VBA macros; drops charts and images; formula results are stale until
218
- Excel next opens the file). The backend used is printed.
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.
219
243
 
220
244
  ### parrep — .par values → new .pst
221
245
 
@@ -237,6 +261,61 @@ to that workbook; other relative names resolve against the workbook's folder. No
237
261
  route rebuilds from the workbook's *current* state, so any edits made since the original
238
262
  control file was written are picked up too.
239
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
+
284
+ ### tempchek — templates → model input files
285
+
286
+ ```
287
+ makepst tempchek model.tpl # check the template, list its parameters
288
+ makepst tempchek model.tpl model.in [model.par] # write model.in (values from model.par by default)
289
+ makepst tempchek model.pst [--par run.par] [--real best] [--dry_run] # every template of the case
290
+ ```
291
+
292
+ Like PEST's TEMPCHEK: a template is checked, then its model input file is written with
293
+ parameter values from a `.par` file (or, given a control file / workbook, from `PARVAL1`
294
+ with `SCALE` and `OFFSET` applied, or from `--par` — a `.par` file or an IES realization).
295
+ Numbers are written the way PEST writes them (its `WRTSIG`): into the parameter space with
296
+ as much precision as the space allows, fixed or exponential notation, right-justified, one
297
+ word per parameter sized for its narrowest space, `PRECIS` / `DPOINT` honoured
298
+ (`double`, `nopoint`). Checked against `tempchek.exe` on a grid of values and widths from 3
299
+ to 23 characters: the same fields are refused, and the written value is never further from
300
+ the true value than PEST's. A value that cannot be written (`1e-10` in a five-character
301
+ space, say) is an error and nothing is written; parameters in the `.par` file that the
302
+ template does not cite are warnings. `--dry_run` checks without writing.
303
+
304
+ ### inschek — model output files → observation values
305
+
306
+ ```
307
+ makepst inschek heads.ins # check the instruction file, list its observations
308
+ makepst inschek heads.ins heads.out [--out heads.obf]
309
+ makepst inschek model.pst [--out model.obf] # every instruction file of the case, one .obf
310
+ ```
311
+
312
+ Like PEST's INSCHEK: the instruction file is checked (the syntax rules in the validation
313
+ table below, including that a line advance can only start an instruction line and that fixed
314
+ / tab instructions must move right), then read against the model output file with the
315
+ interpreter that `validate --outputs` uses, and the values go to an observation value file
316
+ (`name value` per line, INSCHEK's `.obf`). With a control file, all instruction files are
317
+ read and observations the control file lists but no file produced are reported.
318
+
240
319
  ### diff — what changed
241
320
 
242
321
  ```
@@ -252,6 +331,39 @@ tolerance, so `49.7377` vs `49.73775` shows up at the default `1e-9` and disappe
252
331
  `--rtol 1e-5`. Prints a table per section and a one-line summary; `--xlsx` writes the same
253
332
  tables to a workbook for review. Exits 1 when there are differences, like `diff`.
254
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
+
255
367
  ## What round-tripping preserves
256
368
 
257
369
  `dump` → `build` and `read_pst` → `write_pst` are **semantically lossless for supported
@@ -327,6 +439,12 @@ makepst validate tr13.xlsm # a workbook with a BUILD sheet is built f
327
439
  | malformed prior equation; non-numeric right-hand side; `log()` used on a non-log parameter or vice versa | error |
328
440
  | `DERCOM` beyond the number of model command lines | error |
329
441
  | name longer than PEST's limit (12 parameter / 20 observation / 12 group; PEST++ allows 200) | warning |
442
+ | **pestchek's rules** (from PEST 17's `pestchek.F` / `cheksub.F`): `PARCHGLIM` vocabulary and `absolute(n)` needing `absparmax(n)=`; log parameters factor-limited with positive values; factor-limited bounds of one sign and non-zero; `SCALE ≠ 0`; tied to itself, tied or parent with initial value 0; group `none` reserved; all parameters fixed/tied; `INCTYP` / `FORCEN` / `DERMTHD` vocabularies and their compatibility; split-column ranges; duplicate groups; `dum` as an observation name; prior weights, labels (≤ 20, not an observation name), duplicated parameters, all-zero factors, group `predict`; regularisation needs a `regul*` group, prediction exactly one `predict` observation; ~60 control-variable range and consistency rules (`RLAMFAC`, `PHIRATSUF`, `FACPARMAX`, `NPHISTP`, `RELPARSTP < RELPARMAX`, `EIGTHRESH`, `PHIMLIM < PHIMACCEPT < 1.2·PHIMLIM`, `WFMIN ≤ WFINIT ≤ WFMAX`, `UPTESTMIN`/`UPTESTLIM`, SVD vs AUI vs LSQR exclusivity, …) | error |
443
+ | pestchek's warnings: derivative increment larger than a third of the range; initial value 0 with a relative increment; group with only fixed/tied parameters; log parameters with a non-relative increment; observation group listed but empty (dropped on write); `MAXSING` above the adjustable-parameter count; more than 300 adjustable parameters with `ICOV`/`ICOR`/`IEIG` on; `NOPTMAX 0` | warning |
444
+ | PEST_HP-only control variables present (`WIN_MRUN_HOURS`, `UPTESTMIN`, `RRFSAVE`, …; plain PEST needs `/hpstart`); observation group whose weights are all zero | note |
445
+ | template: parameter space narrower than 3 characters or containing a tab | error |
446
+ | template: a value that cannot be written into its space as PEST writes numbers (`PRECIS` / `DPOINT` honoured), e.g. `1e-10` in five characters | error |
447
+ | instruction file syntax: `l` / `t` integers, marker delimiters, `!` balance, `[name]n1:n2` order, `dum` in a fixed field, continuation `&` placement, `l` only at the start of a line, `t` and fixed columns moving left to right, unknown instructions — checked statically, without model output | error |
330
448
  | section in the `.pst` that makePst drops on read | warning |
331
449
  | template or instruction file missing; bad `ptf` / `pif` line | error |
332
450
  | parameter cited in no template; template citing an unknown parameter | error |
@@ -352,7 +470,7 @@ from?" after the fact:
352
470
 
353
471
  ```json
354
472
  {
355
- "makepst": "0.1.0",
473
+ "makepst": "0.3.0",
356
474
  "command": "build",
357
475
  "argv": ["build", "tr13.pst", "regul", "--set_ctl_xls", "tr13.xlsm,CONTROL", "..."],
358
476
  "created": "2026-09-19T13:51:39-04:00",
@@ -364,7 +482,7 @@ from?" after the fact:
364
482
  "sheets": ["CONTROL", "PARGP", "PAR_HK", "PAR_VK", "...", "PPglm"]}
365
483
  ],
366
484
  "output": {"path": "D:\\...\\tr13.pst", "sha256": "d93f317d...", "size": 2163331,
367
- "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 185, "nobsgp": 12,
485
+ "npar": 1068, "nobs": 41747, "npargp": 12, "nprior": 159, "nobsgp": 12,
368
486
  "ntplfle": 18, "ninsfle": 7, "pestmode": "regularisation"}
369
487
  }
370
488
  ```
@@ -376,6 +494,15 @@ realization and `--set` values (and, from a workbook, the resolved build argumen
376
494
  left alone, and the backend. A hash mismatch between a manifest and the workbook on disk is
377
495
  the signal that the control file no longer corresponds to the spreadsheet.
378
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
+
379
506
  ## Relationship to pyEMU
380
507
 
381
508
  [pyEMU](https://github.com/pypest/pyemu) is the broader toolkit: it constructs PEST++
@@ -423,7 +550,7 @@ update_workbook('book.xlsm', par='run.par', res='run.res', out='book_results.xls
423
550
  python -m pytest tests -q
424
551
  ```
425
552
 
426
- 95 tests, no external data needed: a synthetic workbook in the real project layout
553
+ 106 tests, no external data needed: a synthetic workbook in the real project layout
427
554
  (`tests/data/demo.xlsx`, generated by `tests/make_fixture.py`) with a golden control file,
428
555
  `.par`, `.res` and IES ensemble files. They cover golden-file builds from Excel and CSV, the
429
556
  `read → write → read` identity, `dump → build` byte equality, workbook updates (multi-sheet
@@ -445,12 +572,14 @@ makepst/
445
572
  writer.py Pst -> .pst text (+ dump.tpl)
446
573
  reader.py .pst text -> Pst
447
574
  excel.py sheets -> Pst; Pst -> workbook; results -> existing workbook
448
- cli.py init / validate / build / dump / update / parrep / diff
575
+ cli.py init / validate / build / dump / update / parrep / diff / tempchek / inschek / log / bundle
449
576
  diff.py semantic comparison of two Pst objects
450
577
  phi.py objective function by group from residuals; IES realization phis
451
578
  pyemu_bridge.py to_pyemu / from_pyemu
452
579
  checks.py the validate report: table checks, template / instruction cross-checks, instruction interpreter
453
- provenance.py the <output>.manifest.json sidecar
580
+ rules.py pestchek's rules (parameters, groups, observations, prior information, control variables)
581
+ model_files.py PEST's number writer, template filling, .obf files (tempchek / inschek)
582
+ provenance.py the <output>.manifest.json sidecar; provenance / log / bundle
454
583
  starter.py the `init` workbook: control-variable descriptions, example rows, drop-downs
455
584
  examples/minimal/ the tutorial inputs
456
585
  tests/ suite + fixture generator
@@ -463,7 +592,7 @@ stored anywhere. To release: bump the version in `pyproject.toml` and the fallba
463
592
  `makepst/__init__.py` (a test keeps them equal), add a `CHANGELOG.md` entry, commit, then
464
593
 
465
594
  ```
466
- git tag v0.2.0 && git push origin main v0.2.0
595
+ git tag v0.3.0 && git push origin main v0.3.0
467
596
  ```
468
597
 
469
598
  The `publish` workflow checks that the tag matches the package version, runs the tests, builds