6d2edc6753
Remove all local PDF/OCR extraction and quality gate logic from review_service.py, replacing it with calls to the shared-ai-platform document extraction service via the SDK. Removed from review_service.py: - 17 local PDF/OCR methods (_extract_text_from_pdf, _classify_pdf_source, _extract_text_from_pdf_with_ocr, _extract_text_from_image_with_ocr, _evaluate_ocr_quality, _looks_like_platform_review_report, etc.) - All PDF/OCR constants (PDF_TEXT_PATTERN, PDF_NOISE_LINE_PATTERN, REPORT_PDF_MARKERS, OCR_GATE_*, PDF_SOURCE_KIND_*, etc.) - Imports: pdfplumber, pypdfium2, pytesseract, PIL.Image Added: - extract_review_upload_text() now calls shared platform's client.extract_document_text() directly - _detect_upload_type() retained locally for upload validation only - Protocol methods extract_document_text, classify_pdf_source in client Removed from requirements.txt: pdfplumber, pytesseract Added to config: shared_ai_platform_document_extraction_enabled Also includes prior changes: - Model settings and chat history stabilization - Mobile UI polish - Governance launch-closure enhancements Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
219 lines
8.0 KiB
PowerShell
219 lines
8.0 KiB
PowerShell
param(
|
|
[int]$BackendPort = 8003,
|
|
[int]$FrontendPort = 3006,
|
|
[switch]$SkipBaseline
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
|
$frontendDir = Join-Path $repoRoot "frontend"
|
|
$backendDir = Join-Path $repoRoot "backend"
|
|
$backendPython = Join-Path $backendDir ".venv\Scripts\python.exe"
|
|
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
|
$artifactDir = Join-Path $repoRoot "project-docs\qa-artifacts\phase3-closed-loop\$timestamp"
|
|
$runtimeDir = Join-Path $artifactDir "runtime"
|
|
$frontendLog = Join-Path $artifactDir "frontend-dev-server.log"
|
|
$backendLog = Join-Path $artifactDir "backend-server.log"
|
|
$sharedAiLog = Join-Path $artifactDir "shared-ai-stub.log"
|
|
$npmAuditLog = Join-Path $artifactDir "npm-audit-high.json"
|
|
$pipCheckLog = Join-Path $artifactDir "pip-check.txt"
|
|
$backendUrl = "http://127.0.0.1:$BackendPort"
|
|
$webUrl = "http://127.0.0.1:$FrontendPort"
|
|
$sqliteDbPath = Join-Path $runtimeDir "phase3-contract-platform.db"
|
|
$sqliteDbUrl = ("sqlite+pysqlite:///" + (($sqliteDbPath -replace "\\", "/")))
|
|
$localStorageDir = Join-Path $runtimeDir "storage"
|
|
$sharedAiPort = 18011
|
|
$sharedAiBaseUrl = "http://127.0.0.1:$sharedAiPort"
|
|
$sharedAiApiPrefix = "/api/v0.2"
|
|
$sharedAiAppId = "ai-contract-platform"
|
|
$sharedAiApiKey = "shared-ai-platform-admin-dev-token"
|
|
|
|
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
|
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
|
|
New-Item -ItemType Directory -Force -Path $localStorageDir | Out-Null
|
|
|
|
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
|
throw "gstack is required for all AI-assisted work in this repo."
|
|
}
|
|
|
|
function Wait-HttpReady {
|
|
param(
|
|
[string]$Url,
|
|
[int]$TimeoutSeconds = 90
|
|
)
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
do {
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $Url -TimeoutSec 3
|
|
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 500) {
|
|
return
|
|
}
|
|
} catch {
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
} while ((Get-Date) -lt $deadline)
|
|
|
|
throw "Timed out waiting for $Url"
|
|
}
|
|
|
|
function Stop-Listener {
|
|
param([int]$Port)
|
|
$listener = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.State -eq "Listen" } |
|
|
Select-Object -First 1
|
|
if ($listener) {
|
|
Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Stop-ExistingNextDevProcess {
|
|
param([string]$TargetDirectory)
|
|
|
|
$normalizedTarget = $TargetDirectory.ToLowerInvariant()
|
|
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.Name -eq "node.exe" -and
|
|
$_.CommandLine -and
|
|
$_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and
|
|
(
|
|
$_.CommandLine -match "next\\dist\\bin\\next" -or
|
|
$_.CommandLine -match "start-server\.js"
|
|
)
|
|
}
|
|
|
|
foreach ($process in $processes) {
|
|
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
$frontendProcess = $null
|
|
$backendProcess = $null
|
|
$sharedAiProcess = $null
|
|
|
|
try {
|
|
if (-not (Test-Path (Join-Path $frontendDir "node_modules"))) {
|
|
Push-Location $frontendDir
|
|
npm.cmd install --no-package-lock
|
|
if ($LASTEXITCODE -ne 0) { throw "npm install failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
if (-not (Test-Path (Join-Path $frontendDir "node_modules\playwright"))) {
|
|
Push-Location $frontendDir
|
|
npm.cmd install --no-save --no-package-lock playwright
|
|
if ($LASTEXITCODE -ne 0) { throw "playwright install failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
if (-not $SkipBaseline) {
|
|
Push-Location $frontendDir
|
|
npm.cmd run lint
|
|
if ($LASTEXITCODE -ne 0) { throw "frontend lint failed" }
|
|
npm.cmd run build
|
|
if ($LASTEXITCODE -ne 0) { throw "frontend build failed" }
|
|
npm.cmd audit --audit-level=high --json > $npmAuditLog
|
|
if ($LASTEXITCODE -ne 0) { throw "frontend npm audit high threshold failed" }
|
|
Pop-Location
|
|
|
|
Push-Location $repoRoot
|
|
.\backend\.venv\Scripts\python.exe -m pytest -q backend/tests
|
|
if ($LASTEXITCODE -ne 0) { throw "backend pytest failed" }
|
|
.\backend\.venv\Scripts\python.exe -m pip check > $pipCheckLog
|
|
if ($LASTEXITCODE -ne 0) { throw "backend pip check failed" }
|
|
Pop-Location
|
|
}
|
|
|
|
Stop-Listener -Port $BackendPort
|
|
Stop-ExistingNextDevProcess -TargetDirectory $frontendDir
|
|
Stop-Listener -Port $FrontendPort
|
|
Stop-Listener -Port $sharedAiPort
|
|
|
|
$env:PYTHONPATH = $backendDir
|
|
$env:DATABASE_URL = $sqliteDbUrl
|
|
$env:FILE_STORAGE_BACKEND = "local"
|
|
$env:LOCAL_STORAGE_DIR = $localStorageDir
|
|
$env:SHARED_AI_PLATFORM_BASE_URL = $sharedAiBaseUrl
|
|
$env:SHARED_AI_PLATFORM_API_PREFIX = $sharedAiApiPrefix
|
|
$env:SHARED_AI_PLATFORM_APP_ID = $sharedAiAppId
|
|
$env:SHARED_AI_PLATFORM_API_KEY = $sharedAiApiKey
|
|
$env:SHARED_AI_PLATFORM_TIMEOUT_SECONDS = "180"
|
|
$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED = "true"
|
|
$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL = "false"
|
|
|
|
$sharedAiCommand = "`$env:SLICE4_SHARED_AI_STUB_PORT='$sharedAiPort'; node .\\frontend\\scripts\\slice4-shared-ai-stub.mjs *> '$sharedAiLog'"
|
|
$sharedAiProcess = Start-Process -FilePath "powershell.exe" `
|
|
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $sharedAiCommand) `
|
|
-WorkingDirectory $repoRoot `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
|
|
Wait-HttpReady -Url "$sharedAiBaseUrl/health" -TimeoutSeconds 60
|
|
|
|
@'
|
|
from app.db import Base, engine
|
|
import app.models # noqa: F401
|
|
Base.metadata.create_all(bind=engine)
|
|
print("PHASE3_SQLITE_SCHEMA_READY")
|
|
'@ | & $backendPython -
|
|
if ($LASTEXITCODE -ne 0) { throw "phase3 sqlite schema bootstrap failed" }
|
|
|
|
$backendCommand = @"
|
|
`$env:PYTHONPATH='$backendDir'
|
|
`$env:DATABASE_URL='$sqliteDbUrl'
|
|
`$env:FILE_STORAGE_BACKEND='local'
|
|
`$env:LOCAL_STORAGE_DIR='$localStorageDir'
|
|
`$env:SHARED_AI_PLATFORM_BASE_URL='$sharedAiBaseUrl'
|
|
`$env:SHARED_AI_PLATFORM_API_PREFIX='$sharedAiApiPrefix'
|
|
`$env:SHARED_AI_PLATFORM_APP_ID='$sharedAiAppId'
|
|
`$env:SHARED_AI_PLATFORM_API_KEY='$sharedAiApiKey'
|
|
`$env:SHARED_AI_PLATFORM_TIMEOUT_SECONDS='180'
|
|
`$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED='true'
|
|
`$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL='false'
|
|
& '$backendPython' -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'
|
|
"@
|
|
$backendProcess = Start-Process -FilePath "powershell.exe" `
|
|
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) `
|
|
-WorkingDirectory $backendDir `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
|
|
Wait-HttpReady -Url "$backendUrl/health" -TimeoutSeconds 90
|
|
Wait-HttpReady -Url "$backendUrl/api/v1/system/readiness" -TimeoutSeconds 90
|
|
|
|
$frontendCommand = "`$env:NEXT_PUBLIC_API_BASE_URL='/api/v1'; `$env:BACKEND_BASE_URL='$backendUrl'; npm.cmd run dev -- --hostname 127.0.0.1 --port $FrontendPort *> '$frontendLog'"
|
|
$frontendProcess = Start-Process -FilePath "powershell.exe" `
|
|
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $frontendCommand) `
|
|
-WorkingDirectory $frontendDir `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
|
|
Wait-HttpReady -Url "$webUrl/login" -TimeoutSeconds 120
|
|
|
|
Push-Location $frontendDir
|
|
$env:PHASE3_WEB_URL = $webUrl
|
|
$env:PHASE3_API_URL = "$webUrl/api/v1"
|
|
$env:PHASE3_HEALTH_URL = "$backendUrl/health"
|
|
$env:PHASE3_READINESS_URL = "$backendUrl/api/v1/system/readiness"
|
|
$env:PHASE3_ARTIFACT_DIR = $artifactDir
|
|
$env:AI_CONTRACT_ADMIN_PASSWORD = "123456"
|
|
node .\scripts\phase3-closed-loop-e2e.mjs
|
|
if ($LASTEXITCODE -ne 0) { throw "phase3 closed-loop e2e failed" }
|
|
Pop-Location
|
|
|
|
Write-Host "PHASE3_CLOSED_LOOP_PASS $artifactDir"
|
|
} finally {
|
|
if ($frontendProcess -and -not $frontendProcess.HasExited) {
|
|
Stop-Process -Id $frontendProcess.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
if ($backendProcess -and -not $backendProcess.HasExited) {
|
|
Stop-Process -Id $backendProcess.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
if ($sharedAiProcess -and -not $sharedAiProcess.HasExited) {
|
|
Stop-Process -Id $sharedAiProcess.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Stop-Listener -Port $FrontendPort
|
|
Stop-Listener -Port $BackendPort
|
|
Stop-Listener -Port $sharedAiPort
|
|
}
|