Files
claudetools/projects/dataforth-dos/dfwds-research/api_probe.py
Mike Swanson dd5c5afd4b Session log + DFWDS Node port + Hoffman API uploader pipeline
Built the missing piece between the test datasheet pipeline and Dataforth's
new product API. End-to-end:

- Pulled DFWDS (Dataforth Web Datasheet System) VB6 source from
  AD1\Engineering\ENGR\ATE\Test Datasheets\DFWDS to local for analysis
- Decoded its filename validation: A-J prefix decodes (A=10..J=19), all-
  numeric WO# valid (no leading 0), anything else bad
- Ported the validation + move logic to Node (dfwds-process.js)
- Built bulk uploader (upload-delta.js) for Hoffman's Swagger API
  (POST /api/v1/TestReportDataFiles/bulk with OAuth client_credentials)

Sanitized 3 prior reference scripts (fetch-server-inventory, test-scenarios,
test-upload-two) to read CF_* env vars instead of hardcoded creds.

Live drain results:
- 897 files moved Test_Datasheets -> For_Web (all valid, no renames, no
  bad), DFWDS port summary in 1.1s
- Pushed entire For_Web (7,061 files) to Hoffman API in 49.7s @ 142/s:
  Created=803 Updated=114 Unchanged=6,144 Errors=0
- Server count: 489,579 -> 490,382 (+803 net new)

Also:
- Added clients/dataforth/.gitignore to exclude plaintext Oauth.txt note
- Added clients/instrumental-music-center/docs/2026-04-13-ticket-notes.md
  (ticket write-up of 2026-04-11/12/13 IMC1 RDS removal/SQL migration work)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 21:06:50 -07:00

48 lines
2.1 KiB
Python

"""Probe Dataforth API: get token, fetch Swagger, list relevant endpoints."""
import json, os, subprocess, urllib.request
import yaml
creds = yaml.safe_load(subprocess.run(['sops','-d','D:/vault/clients/dataforth/api-oauth.sops.yaml'],
capture_output=True, text=True, timeout=30, check=True).stdout)
TOKEN_URL = creds['endpoints']['token-url']
SWAGGER_JSON = creds['endpoints']['swagger-json']
API_BASE = creds['endpoints']['api-base']
C = creds['credentials']
# 1. Get token
print('[1] fetch OAuth token')
body = (f'grant_type={C["grant-type"]}&client_id={C["client-id"]}'
f'&client_secret={C["client-secret"]}&scope={C["scope"]}').encode()
req = urllib.request.Request(TOKEN_URL, data=body, method='POST',
headers={'Content-Type':'application/x-www-form-urlencoded'})
with urllib.request.urlopen(req, timeout=30) as r:
tok = json.loads(r.read())
print(f' access_token: {tok["access_token"][:30]}... (expires_in={tok.get("expires_in")}s)')
ACCESS = tok['access_token']
# 2. Pull swagger
print('\n[2] fetch swagger')
req = urllib.request.Request(SWAGGER_JSON,
headers={'Authorization': f'Bearer {ACCESS}'})
with urllib.request.urlopen(req, timeout=30) as r:
sw = json.loads(r.read())
print(f' title: {sw.get("info",{}).get("title")}, version: {sw.get("info",{}).get("version")}')
print(f' total paths: {len(sw.get("paths",{}))}')
# 3. Filter paths for upload/datasheet/file/test
print('\n[3] paths matching upload/datasheet/file/test/report:')
hits = []
for path, methods in sw.get('paths',{}).items():
pl = path.lower()
if any(k in pl for k in ('upload','datasheet','testreport','file','data')):
for m, op in methods.items():
if m.startswith('x-'): continue
hits.append((m.upper(), path, op.get('summary','') or op.get('operationId','')))
for m,p,desc in sorted(hits)[:40]:
print(f' {m:6} {p:60} {desc[:60]}')
# 4. Save full swagger for offline reading
out = r'D:\claudetools\projects\dataforth-dos\dfwds-research\swagger.json'
with open(out,'w',encoding='utf-8') as f: json.dump(sw, f, indent=2)
print(f'\nfull swagger saved: {out}')