"""Deterministic P3 5,000-product acceptance pilot (isolated, no network). Run: python scripts/pilot_5000.py --report pilot.json Optional full build: python scripts/pilot_5000.py --build --report pilot-build.json No production data are opened or modified. Uses an independent temporary SQLite volume, 10 x 500 rows; builds the full automotive static site and runs static QA. A successful test is NOT a commercial content/SEO readiness certificate. """ import argparse import json import os import sys import tempfile import time import subprocess from pathlib import Path ROOT=Path(__file__).resolve().parent.parent sys.path.insert(0,str(ROOT)) def run(build=False): started=time.monotonic() with tempfile.TemporaryDirectory(prefix='siteforge-5k-pilot-') as work: os.environ['SITEFORGE_DATA_DIR']=work os.environ.setdefault('SITEFORGE_ADMIN_PASSWORD','offline-pilot-only-long-password-123456789') os.environ.setdefault('SITEFORGE_SESSION_SECRET','offline-pilot-only-session-1234567890123456789') from fastapi.testclient import TestClient from app.main import app from app import db,builder,quality,v8_workflows as wf from app.core import uid with TestClient(app): sid=uid();now=db.now() db.insert('INSERT INTO sites(id,name,theme,created_at,updated_at) VALUES(?,?,?,?,?)', (sid,'5K Acceptance Pilot','automotive',now,now)) for offset in range(0,5000,500): data=[{'车型名称':f'Vehicle {i:05d}','品牌':f'Brand {i%20:02d}', 'SEO描述':f'Demo vehicle {i:05d}, pilot only'} for i in range(offset,offset+500)] batch=wf.stage(sid,data,kind='vehicle',origin=f'pilot-{offset//500}', source_keys=[str(i) for i in range(offset,offset+500)]) result=wf.commit(sid,batch['batch_id']) assert result['created']==500 and not result['errors'],result import_sec=round(time.monotonic()-started,2) assert db.one("SELECT COUNT(*) n FROM entries WHERE site_id=? AND type='vehicle'",(sid,))['n']==5000 if not build: return {'status':'import_passed_build_not_run','source_rows':5000, 'committed':5000,'batches':10,'import_seconds':import_sec, 'note':'Use --build in a sufficiently provisioned CI runner for full static-build QA.'} report=builder.build(sid,False) build_sec=round(time.monotonic()-started-import_sec,2) # Release/SEO QA runs in its own short-lived process in production. # Do not reuse the large catalogue renderer's heap for the audit. audit_code=("import json,sys; from app import quality; " "print(json.dumps(quality.report(sys.argv[1],sys.argv[2])))") audit=subprocess.run([sys.executable,'-c',audit_code,sid,report['version']], cwd=str(ROOT),text=True,capture_output=True,timeout=120,check=True, env=dict(os.environ)) qa=json.loads(audit.stdout) result={'status':'passed' if qa['errors']==0 and report['pages']>=5000 else 'failed', 'source_rows':5000,'batches':10,'pages':report['pages'],'html_files':qa['files'], 'static_errors':qa['errors'],'static_warnings':qa['warnings'], 'import_seconds':import_sec,'build_seconds':build_sec, 'total_seconds':round(time.monotonic()-started,2), 'disclaimer':'Fixed sample data; not live MySQL, not Docker, not production SEO audit.'} if result['status']!='passed':raise AssertionError(result) return result if __name__=='__main__': p=argparse.ArgumentParser();p.add_argument('--report');p.add_argument('--build',action='store_true');args=p.parse_args() result=run(build=args.build) if args.report:Path(args.report).write_text(json.dumps(result,ensure_ascii=False,indent=2)) print(json.dumps(result,ensure_ascii=False,indent=2))