Files
2026-07-13 12:59:42 +08:00

212 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""Generate speed comparison report for docling experiments.
Reads results from all experiment runs and generates a summary report.
Usage:
python scripts/experiments/docling_speed_report.py
"""
import json
import sys
from datetime import datetime
from pathlib import Path
RESULTS_DIR = Path(__file__).parent.parent.parent / "docs" / "hybrid" / "experiments"
REPORT_FILE = RESULTS_DIR / f"speed-experiment-{datetime.now().strftime('%Y-%m-%d')}.md"
def load_results(filename: str) -> dict | None:
"""Load results from JSON file."""
path = RESULTS_DIR / filename
if not path.exists():
return None
with open(path, encoding="utf-8") as f:
return json.load(f)
def main():
"""Generate comparison report."""
print("Loading experiment results...")
baseline = load_results("baseline_results.json")
fastapi = load_results("fastapi_results.json")
subprocess = load_results("subprocess_results.json")
if not any([baseline, fastapi, subprocess]):
print("ERROR: No experiment results found", file=sys.stderr)
sys.exit(1)
# Print console summary
print()
print("=" * 70)
print("DOCLING SPEED EXPERIMENT RESULTS")
print("=" * 70)
print()
approaches = []
if baseline:
approaches.append(("baseline", "docling-serve HTTP", baseline))
if fastapi:
approaches.append(("fastapi", "FastAPI + SDK singleton", fastapi))
if subprocess:
approaches.append(("subprocess", "Persistent subprocess", subprocess))
# Table header
print(f"{'Approach':<15} {'Description':<25} {'Avg (s/doc)':<12} {'Target':<10} {'Status':<10} {'Speedup':<10}")
print("-" * 70)
baseline_time = baseline["statistics"]["elapsed_per_doc"] if baseline else None
for name, desc, data in approaches:
stats = data["statistics"]
avg_time = stats["elapsed_per_doc"]
threshold = data.get("threshold", {})
target = threshold.get("target", "-")
passed = threshold.get("passed", None)
if passed is True:
status = "PASS"
elif passed is False:
status = "FAIL"
else:
status = "-"
# Calculate speedup vs baseline
if baseline_time and name != "baseline":
speedup = f"{baseline_time / avg_time:.1f}x"
else:
speedup = "-"
print(f"{name:<15} {desc:<25} {avg_time:<12.3f} {str(target):<10} {status:<10} {speedup:<10}")
print("-" * 70)
print()
# Decision summary
print("DECISION SUMMARY:")
print("-" * 40)
fastapi_passed = fastapi and fastapi.get("threshold", {}).get("passed", False)
subprocess_passed = subprocess and subprocess.get("threshold", {}).get("passed", False)
if fastapi_passed:
print("FastAPI approach: APPROVED (proceed to Phase 1)")
else:
print("FastAPI approach: REJECTED (plan discarded)")
if subprocess_passed:
print("Subprocess approach: APPROVED (proceed to Phase 1)")
else:
print("Subprocess approach: REJECTED (excluded from plan)")
print()
if fastapi_passed:
print("OVERALL: Phase 0 PASSED - Proceed to implementation")
print()
# Recommendation
if subprocess_passed:
fastapi_time = fastapi["statistics"]["elapsed_per_doc"]
subprocess_time = subprocess["statistics"]["elapsed_per_doc"]
if subprocess_time < fastapi_time:
print(f"RECOMMENDATION: subprocess approach is slightly faster ({subprocess_time:.3f}s vs {fastapi_time:.3f}s)")
print(" However, FastAPI is more production-ready (health checks, easier deployment)")
else:
print(f"RECOMMENDATION: FastAPI approach is faster and more production-ready")
else:
print("OVERALL: Phase 0 FAILED - Plan should be discarded")
print("=" * 70)
# Generate markdown report
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
report = []
report.append("# Docling Speed Experiment Results")
report.append("")
report.append(f"**Date**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report.append("")
report.append("## Summary")
report.append("")
report.append("| Approach | Description | Avg (s/doc) | Target | Status | Speedup |")
report.append("|----------|-------------|-------------|--------|--------|---------|")
for name, desc, data in approaches:
stats = data["statistics"]
avg_time = stats["elapsed_per_doc"]
threshold = data.get("threshold", {})
target = threshold.get("target", "-")
passed = threshold.get("passed", None)
if passed is True:
status = "PASS"
elif passed is False:
status = "FAIL"
else:
status = "-"
if baseline_time and name != "baseline":
speedup = f"{baseline_time / avg_time:.1f}x"
else:
speedup = "-"
report.append(f"| {name} | {desc} | {avg_time:.3f} | {target} | {status} | {speedup} |")
report.append("")
report.append("## Decision")
report.append("")
if fastapi_passed:
report.append("**Phase 0 PASSED** - FastAPI approach meets the < 0.8s threshold.")
report.append("")
report.append("Proceed to Phase 1 implementation:")
report.append("")
report.append("- [ ] Task 1.1: docling_subprocess_worker.py")
report.append("- [ ] Task 1.2: docling_fast_server.py")
report.append("- [ ] Task 2.1: DoclingSubprocessClient.java")
report.append("- [ ] Task 2.2: DoclingFastServerClient.java")
report.append("- [ ] Task 2.3: HybridClientFactory modification")
report.append("- [ ] Task 3: Benchmark integration")
report.append("- [ ] Task 4: Final validation")
if subprocess_passed:
report.append("")
report.append("Subprocess approach also passed - both approaches available for implementation.")
else:
report.append("**Phase 0 FAILED** - FastAPI approach exceeds 0.8s threshold.")
report.append("")
report.append("Plan should be discarded. Consider alternative approaches.")
report.append("")
report.append("## Detailed Statistics")
report.append("")
for name, desc, data in approaches:
stats = data["statistics"]
report.append(f"### {name.title()}")
report.append("")
report.append(f"- **Description**: {data['description']}")
report.append(f"- **Timestamp**: {data['timestamp']}")
report.append(f"- **Total documents**: {stats['total_documents']}")
report.append(f"- **Successful**: {stats['successful']}")
report.append(f"- **Failed**: {stats['failed']}")
report.append(f"- **Total elapsed**: {stats['total_elapsed']:.1f}s")
report.append(f"- **Average per doc**: {stats['elapsed_per_doc']:.4f}s")
report.append(f"- **Min**: {stats['min_elapsed']:.4f}s")
report.append(f"- **Max**: {stats['max_elapsed']:.4f}s")
report.append("")
# Write report
with open(REPORT_FILE, "w", encoding="utf-8") as f:
f.write("\n".join(report))
print(f"\nReport saved to: {REPORT_FILE}")
if __name__ == "__main__":
main()