74 lines
4.1 KiB
Python
74 lines
4.1 KiB
Python
|
|
"""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))
|