Skip to content

Commit 4dc3d84

Browse files
Merge pull request #137 from OVVO-Financial/claude/boost-reconcile-13.2
boost: reconcile nns_boost with R 13.2 NNS.boost; version 2.0.0
2 parents e7c6d9c + d755f19 commit 4dc3d84

13 files changed

Lines changed: 754 additions & 504 deletions

.github/workflows/inspect-r-api-update.yml

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,120 @@ jobs:
185185
import os
186186
from pathlib import Path
187187
188-
path = Path('tests/_r_cache.json')
189-
if not path.exists():
190-
raise SystemExit('tests/_r_cache.json was not generated')
191-
payload = json.loads(path.read_text(encoding='utf-8'))
192-
actual = payload.get('nns_version')
188+
cache_dir = Path('tests/_r_cache')
189+
shards = sorted(cache_dir.glob('*.json')) if cache_dir.is_dir() else []
190+
if not shards:
191+
raise SystemExit('tests/_r_cache/ contains no shard files')
193192
expected = os.environ['EXPECTED_VERSION']
194-
if actual != expected:
195-
raise SystemExit(f'cache version {actual!r} != dispatched version {expected!r}')
196-
entries = payload.get('entries')
197-
if not isinstance(entries, dict) or not entries:
198-
raise SystemExit('regenerated cache contains no entries')
199-
print(f'Validated {len(entries)} cache entries for NNS {actual}.')
193+
total = 0
194+
for shard_path in shards:
195+
shard = json.loads(shard_path.read_text(encoding='utf-8'))
196+
actual = shard.get('nns_version')
197+
if actual != expected:
198+
raise SystemExit(
199+
f'{shard_path.name}: cache version {actual!r} '
200+
f'!= dispatched version {expected!r}'
201+
)
202+
entries = shard.get('entries')
203+
if not isinstance(entries, dict) or not entries:
204+
raise SystemExit(f'{shard_path.name} contains no entries')
205+
total += len(entries)
206+
print(
207+
f'Validated {total} cache entries across {len(shards)} '
208+
f'shards for NNS {expected}.'
209+
)
200210
PY
201211
202212
- name: Remove transient cache files
203213
if: always()
204214
shell: bash
205-
run: rm -f tests/_r_cache.json.bak tests/_r_cache.lock
215+
run: rm -rf tests/_r_cache.bak && rm -f tests/_r_cache.lock
216+
217+
- name: Record cache provenance in sync manifest
218+
if: always()
219+
shell: bash
220+
env:
221+
R_COMMIT: ${{ steps.package.outputs.r_commit }}
222+
R_VERSION: ${{ steps.package.outputs.r_version }}
223+
run: |
224+
python - <<'PY'
225+
import json
226+
import os
227+
from pathlib import Path
228+
229+
path = Path('sync/nns_source.json')
230+
manifest = json.loads(path.read_text(encoding='utf-8'))
231+
manifest['r_commit'] = os.environ['R_COMMIT']
232+
manifest['r_version'] = os.environ['R_VERSION']
233+
path.write_text(json.dumps(manifest, indent=2) + '\n', encoding='utf-8')
234+
print(f"Manifest now records {manifest['r_repo']}@{manifest['r_commit']} "
235+
f"(NNS {manifest['r_version']}).")
236+
PY
237+
238+
- name: Summarize cache changes by function
239+
id: cachediff
240+
if: always()
241+
shell: bash
242+
run: |
243+
python - <<'PY'
244+
import json
245+
import subprocess
246+
from pathlib import Path
247+
248+
def committed(name: str) -> dict:
249+
proc = subprocess.run(
250+
['git', 'show', f'HEAD:tests/_r_cache/{name}'],
251+
capture_output=True, text=True,
252+
)
253+
if proc.returncode != 0:
254+
return {}
255+
return json.loads(proc.stdout).get('entries', {})
256+
257+
cache_dir = Path('tests/_r_cache')
258+
lines = ['### Cache changes by function', '']
259+
if not cache_dir.is_dir():
260+
lines.append('_No cache directory was generated; see the regeneration log._')
261+
else:
262+
proc = subprocess.run(
263+
['git', 'ls-tree', '--name-only', 'HEAD', 'tests/_r_cache/'],
264+
capture_output=True, text=True,
265+
)
266+
old_names = {Path(p).name for p in proc.stdout.split() if p.endswith('.json')}
267+
new_names = {p.name for p in cache_dir.glob('*.json')}
268+
rows = []
269+
for name in sorted(old_names | new_names):
270+
before = committed(name) if name in old_names else {}
271+
after = (
272+
json.loads((cache_dir / name).read_text()).get('entries', {})
273+
if name in new_names else {}
274+
)
275+
added = len(set(after) - set(before))
276+
removed = len(set(before) - set(after))
277+
changed = sum(
278+
1 for k in set(before) & set(after) if before[k] != after[k]
279+
)
280+
if added or removed or changed:
281+
label = name[: -len('.json')]
282+
rows.append(
283+
f'| `{label}` | {len(before)} | {len(after)} '
284+
f'| {added} | {removed} | {changed} |'
285+
)
286+
if rows:
287+
lines += [
288+
'| function | before | after | added | removed | changed |',
289+
'| --- | --- | --- | --- | --- | --- |',
290+
*rows,
291+
]
292+
else:
293+
lines.append('_No cache entries changed._')
294+
Path('cache-diff.md').write_text('\n'.join(lines) + '\n', encoding='utf-8')
295+
print('\n'.join(lines))
296+
PY
297+
{
298+
echo 'summary<<CACHE_DIFF_EOF'
299+
cat cache-diff.md
300+
echo 'CACHE_DIFF_EOF'
301+
} >> "$GITHUB_OUTPUT"
206302
207303
- name: Verify committed-cache mode
208304
id: verify
@@ -246,10 +342,13 @@ jobs:
246342
- Live regeneration result: `${{ steps.regenerate.outcome }}`
247343
- Cache-only verification result: `${{ steps.verify.outcome }}`
248344
249-
The exact R source package was installed and the committed parity cache was regenerated. Any remaining Python/R parity mismatches are retained in the workflow diagnostics and should be repaired against this R-authored baseline.
345+
The exact R source package was installed and the committed parity cache was regenerated. `sync/nns_source.json` records this R commit as the behavioral-truth provenance. Any remaining Python/R parity mismatches are retained in the workflow diagnostics and should be repaired against this R-authored baseline.
346+
347+
${{ steps.cachediff.outputs.summary }}
250348
add-paths: |
251349
tests/_r.py
252-
tests/_r_cache.json
350+
tests/_r_cache/**
351+
sync/nns_source.json
253352
254353
- name: Report parity status
255354
if: always()

.github/workflows/native-backend-ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
name: Native backend CI
22

3+
# pull_request covers every branch with an open PR; push is limited to main so
4+
# a PR-branch push does not run the identical matrix twice (once per event).
35
on:
46
pull_request:
57
push:
8+
branches: [main]
69

710
permissions:
811
contents: read
912

13+
# A superseded push to the same ref cancels the in-flight run.
14+
concurrency:
15+
group: native-backend-ci-${{ github.event.pull_request.number || github.ref }}
16+
cancel-in-progress: true
17+
1018
jobs:
1119
native-backend:
1220
name: Python ${{ matrix.python-version }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ native and does not call R at runtime.
2929
|---|---|
3030
| Distribution package | `ovvo-nns` |
3131
| Import package | `nns` |
32-
| Current version | `1.6.0` |
32+
| Current version | `2.0.0` |
3333
| Python | `>=3.11` |
3434
| Required runtime dependencies | NumPy, SciPy, Matplotlib |
3535
| R required at runtime | No |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ovvo-nns"
3-
version = "1.6.0"
3+
version = "2.0.0"
44
description = "Python port of nonlinear nonparametric statistics from R NNS"
55
readme = "README.md"
66
requires-python = ">=3.11"

src/nns/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from nns.pm_matrix import pm_matrix as pm_matrix
66

7-
__version__ = "1.6.0"
7+
__version__ = "2.0.0"
88

99
_EXPORTS = {
1010
"BoostResult": ("nns.boost", "BoostResult"),

0 commit comments

Comments
 (0)