"""Fresh CPU training, export, gate, registration and real loopback parity drill.""" import argparse import copy import json import platform import sys import tempfile import threading import urllib.request from pathlib import Path from pipeline import HERE, baseline, batch, decode, digest, encode, export, load, predict, quality_gate, register, request_for from server import server def run(): data = export() version = digest(data) gate = quality_gate(data, version) validation = baseline.split_rows(baseline.load_rows())['validation'] rows = [next(row for row in validation if row['label'] == label) for label in baseline.LABELS] candidate = decode(data) rows.append(min(validation, key=lambda row: abs( predict(candidate, request_for(row), version)['positive_probability'] - 0.5))) requests = [request_for(row) for row in rows] with tempfile.TemporaryDirectory() as directory: register(directory, data, version) bundle = load(directory, version) registered_digest = digest((Path(directory) / (version + '.json')).read_bytes()) register(directory, data, version) constant = copy.deepcopy(bundle) constant['weights'] = [0.0] * len(baseline.FEATURES) constant['bias'] = 100.0 bad_data = encode(constant) failed_gate = quality_gate(bad_data, digest(bad_data)) try: register(directory, bad_data, digest(bad_data)) except ValueError as error: refused = str(error) else: raise AssertionError('constant classifier should not register') offline = batch(bundle, requests, version) http = server(bundle, version) worker = threading.Thread(target=http.serve_forever, daemon=True) worker.start() try: responses = [] for request in requests: outgoing = urllib.request.Request( f'http://127.0.0.1:{http.server_port}/predict', data=encode(request), headers={'Content-Type': 'application/json'}) with urllib.request.urlopen(outgoing, timeout=3) as response: responses.append(json.load(response)) finally: http.shutdown() worker.join(timeout=3) http.server_close() if responses != offline: raise ValueError('batch/HTTP output mismatch') return {'execution_mode': 'fresh local CPU training and loopback HTTP inference', 'gate': gate, 'batch_http_exact_match': True, 'compared_requests': len(requests), 'export': {'features': bundle['features'], 'weights': bundle['weights'], 'bias': bundle['bias'], 'mean': bundle['mean'], 'std': bundle['std']}, 'registry': {'selected_digest': version, 'registered_digest': registered_digest, 'identical_reregistration': 'accepted', 'constant_candidate_passed': failed_gate['passed'], 'constant_registration': refused}, 'requests': requests, 'predictions': offline, 'scope': 'No held-out test fitting, tuning or scoring; no calibration or deployment claim.'}, data if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--record', action='store_true') args = parser.parse_args() result, data = run() text = json.dumps(result, indent=2, allow_nan=False) + '\n' print(text, end='') if args.record: (HERE / 'candidate.json').write_bytes(data) (HERE / 'result.json').write_text(text) (HERE / 'run-output.txt').write_text(text) metadata = {'command': ['python', 'run.py', '--record'], 'exit_status': 0, 'execution_mode': result['execution_mode'], 'python': platform.python_version(), 'platform': platform.platform(), 'inputs': {'vendor/rice.csv': digest((HERE / 'vendor/rice.csv').read_bytes())}, 'sources': {str(path.relative_to(HERE)): digest(path.read_bytes()) for path in [HERE / 'pipeline.py', HERE / 'server.py', HERE / 'run.py', HERE / 'vendor/baseline.py']}} (HERE / 'run-metadata.json').write_text(json.dumps(metadata, indent=2) + '\n')