Files
ai-contract-platform/scripts/import-phase13-library.ps1
T

760 lines
29 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param(
[ValidateSet("Validate", "Review", "Production")]
[string]$Mode = "Validate",
[string]$ApiBaseUrl = "http://127.0.0.1:8000/api/v1",
[string]$LibraryRoot = "",
[string]$ArtifactRoot = "",
[string]$AdminEmail = "admin@example.com",
[string]$AdminPassword = "",
[string]$KnowledgeBaseName = "",
[switch]$ForceReupload,
[switch]$SkipTemplates,
[switch]$SkipReviewRules
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Net.Http
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
if ([string]::IsNullOrWhiteSpace($LibraryRoot)) {
$LibraryRoot = Join-Path $repoRoot "project-docs\phase13-legal-knowledge-template-library"
}
if (-not [System.IO.Path]::IsPathRooted($LibraryRoot)) {
$LibraryRoot = Join-Path $repoRoot $LibraryRoot
}
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase13-import"
}
if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
}
$artifactDir = Join-Path $ArtifactRoot $timestamp
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$summaryJson = Join-Path $artifactDir "phase13-import-summary.json"
$summaryMd = Join-Path $artifactDir "phase13-import-summary.md"
$ApiBaseUrl = $ApiBaseUrl.TrimEnd("/")
if ([string]::IsNullOrWhiteSpace($KnowledgeBaseName)) {
if ($Mode -eq "Production") {
$KnowledgeBaseName = "AI合同平台法律知识库(生产)"
} else {
$KnowledgeBaseName = "阶段13法律知识库(待法务复核)"
}
}
$checks = New-Object System.Collections.Generic.List[object]
$assetReport = New-Object System.Collections.Generic.List[object]
$importReport = New-Object System.Collections.Generic.List[object]
function Add-Check {
param(
[string]$Key,
[string]$Label,
[ValidateSet("pass", "fail", "warn", "skip", "blocked")]
[string]$Status,
[string]$Detail,
[bool]$Required = $true,
[hashtable]$Meta = @{}
)
$checks.Add([ordered]@{
key = $Key
label = $Label
status = $Status
required = $Required
detail = $Detail
meta = $Meta
}) | Out-Null
}
function Save-Json {
param([string]$Path, [object]$Value)
$Value | ConvertTo-Json -Depth 30 | Set-Content -Encoding UTF8 -Path $Path
}
function Import-CsvRows {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return @()
}
return @(Import-Csv -LiteralPath $Path)
}
function Test-RequiredFlag {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) {
return $false
}
return @("true", "yes", "1", "required") -contains $Value.ToLowerInvariant()
}
function Test-PendingReviewer {
param([string]$Value)
if ([string]::IsNullOrWhiteSpace($Value)) {
return $true
}
return @("pending", "待法务复核", "待业务复核", "待复核") -contains $Value.Trim()
}
function Test-ProductionReadyAsset {
param([object]$Row)
$statusAllowed = @("APPROVED", "PUBLISHED") -contains $Row.status
$ready = Test-RequiredFlag -Value $Row.production_ready
$reviewed = -not (Test-PendingReviewer -Value $Row.legal_reviewer)
return ($statusAllowed -and $ready -and $reviewed)
}
function Test-ReviewImportEligibleAsset {
param([object]$Row)
return @("LEGAL_REVIEW", "BUSINESS_REVIEW", "APPROVED", "PUBLISHED") -contains $Row.status
}
function Resolve-LibraryPath {
param([string]$RelativePath)
return Join-Path $LibraryRoot ($RelativePath -replace "/", "\")
}
function Read-LibraryTextIfExists {
param([string]$RelativePath)
if ([string]::IsNullOrWhiteSpace($RelativePath)) {
return ""
}
$absolutePath = Resolve-LibraryPath -RelativePath $RelativePath
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
return ""
}
return Get-Content -LiteralPath $absolutePath -Raw -Encoding UTF8
}
function Get-ResponseData {
param([object]$Response)
if ($null -eq $Response) {
return $null
}
if ($Response.PSObject.Properties.Name -contains "data") {
return $Response.data
}
return $Response
}
function Invoke-ApiJson {
param(
[string]$Method,
[string]$Url,
[hashtable]$Headers = @{},
[object]$Body = $null
)
$args = @{
Method = $Method
Uri = $Url
Headers = $Headers
ErrorAction = "Stop"
}
if ($null -ne $Body) {
$args["ContentType"] = "application/json; charset=utf-8"
$args["Body"] = ($Body | ConvertTo-Json -Depth 20)
}
return Invoke-RestMethod @args
}
function Invoke-FormRequest {
param(
[string]$Method,
[string]$Url,
[hashtable]$Headers = @{},
[hashtable]$Fields = @{},
[string]$FileField = "",
[string]$FileName = "",
[byte[]]$FileContent = $null,
[string]$FileContentType = "text/markdown; charset=utf-8"
)
$client = [System.Net.Http.HttpClient]::new()
try {
foreach ($headerName in $Headers.Keys) {
$client.DefaultRequestHeaders.Remove($headerName) | Out-Null
$client.DefaultRequestHeaders.Add($headerName, [string]$Headers[$headerName])
}
$form = [System.Net.Http.MultipartFormDataContent]::new()
try {
foreach ($fieldName in $Fields.Keys) {
$value = [string]$Fields[$fieldName]
$stringContent = [System.Net.Http.StringContent]::new($value, [System.Text.Encoding]::UTF8)
$form.Add($stringContent, $fieldName)
}
if ($FileContent -ne $null -and -not [string]::IsNullOrWhiteSpace($FileField)) {
$byteContent = [System.Net.Http.ByteArrayContent]::new($FileContent)
$byteContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($FileContentType)
$form.Add($byteContent, $FileField, $FileName)
}
$request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::new($Method), $Url)
$request.Content = $form
$response = $client.SendAsync($request).GetAwaiter().GetResult()
$body = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
if (-not $response.IsSuccessStatusCode) {
throw "API 请求失败:$Method $Url,状态码=$([int]$response.StatusCode),响应=$body"
}
if ([string]::IsNullOrWhiteSpace($body)) {
return $null
}
return $body | ConvertFrom-Json
} finally {
if ($null -ne $form) {
$form.Dispose()
}
}
} finally {
$client.Dispose()
}
}
function Get-PagedApiItems {
param(
[string]$Url,
[hashtable]$Headers
)
$items = New-Object System.Collections.Generic.List[object]
$page = 1
$pageSize = 100
while ($true) {
$separator = if ($Url.Contains("?")) { "&" } else { "?" }
$response = Invoke-ApiJson -Method "GET" -Url "$Url${separator}page=$page&page_size=$pageSize" -Headers $Headers
$data = Get-ResponseData -Response $response
if ($null -eq $data -or $null -eq $data.items) {
break
}
foreach ($item in @($data.items)) {
$items.Add($item) | Out-Null
}
if ($items.Count -ge [int]$data.total -or @($data.items).Count -eq 0) {
break
}
$page += 1
}
return $items.ToArray()
}
function Get-AuthHeaders {
param([string]$Token)
return @{ Authorization = "Bearer $Token" }
}
function Get-AccessToken {
if ($Mode -eq "Validate") {
return ""
}
$password = $AdminPassword
if ([string]::IsNullOrWhiteSpace($password)) {
$password = $env:AICP_ADMIN_PASSWORD
}
if ([string]::IsNullOrWhiteSpace($password)) {
throw "非 Validate 模式需要通过 -AdminPassword 或环境变量 AICP_ADMIN_PASSWORD 提供管理员密码。"
}
$response = Invoke-ApiJson -Method "POST" -Url "$ApiBaseUrl/auth/login" -Body @{
account = $AdminEmail
password = $password
}
$data = Get-ResponseData -Response $response
if ($null -eq $data -or [string]::IsNullOrWhiteSpace($data.access_token)) {
throw "登录成功响应中缺少 access_token。"
}
return [string]$data.access_token
}
function Ensure-KnowledgeBase {
param([hashtable]$Headers)
$items = Get-PagedApiItems -Url "$ApiBaseUrl/knowledge-bases" -Headers $Headers
$existing = @($items | Where-Object { $_.name -eq $KnowledgeBaseName }) | Select-Object -First 1
if ($existing) {
return $existing
}
$response = Invoke-FormRequest -Method "POST" -Url "$ApiBaseUrl/knowledge-bases" -Headers $Headers -Fields @{
name = $KnowledgeBaseName
description = "阶段 13 法律知识卡导入知识库;资料未经法务复核前仅用于内部复核和试点验证。"
}
return Get-ResponseData -Response $response
}
function Get-KnowledgeBaseDocuments {
param([hashtable]$Headers, [string]$KnowledgeBaseId)
return Get-PagedApiItems -Url "$ApiBaseUrl/knowledge-bases/$KnowledgeBaseId/documents" -Headers $Headers
}
function Import-KnowledgeCard {
param(
[hashtable]$Headers,
[string]$KnowledgeBaseId,
[object]$Card,
[string]$AbsolutePath,
[object[]]$ExistingDocuments,
[hashtable]$SourceMetadataLookup
)
$fileName = "$($Card.knowledge_card_id).md"
$existing = @($ExistingDocuments | Where-Object { $_.file_name -eq $fileName }) | Select-Object -First 1
$bytes = [System.IO.File]::ReadAllBytes($AbsolutePath)
$note = "阶段13知识卡导入;编号=$($Card.knowledge_card_id);状态=$($Card.status);法源=$($Card.source_ids)。"
$fields = @{ note = $note }
$metadataFields = Get-KnowledgeCardMetadataFields -Card $Card -SourceMetadataLookup $SourceMetadataLookup
foreach ($fieldName in $metadataFields.Keys) {
$fields[$fieldName] = $metadataFields[$fieldName]
}
if ($existing -and -not $ForceReupload) {
return [ordered]@{
asset_type = "knowledge_card"
asset_id = $Card.knowledge_card_id
action = "skip_existing"
status = "skipped"
target_id = $existing.id
detail = "同名知识卡文档已存在,未重复上传。"
}
}
if ($existing -and $ForceReupload) {
$response = Invoke-FormRequest -Method "POST" -Url "$ApiBaseUrl/knowledge-bases/$KnowledgeBaseId/documents/$($existing.id)/retry" -Headers $Headers -Fields $fields -FileField "file" -FileName $fileName -FileContent $bytes
$data = Get-ResponseData -Response $response
return [ordered]@{
asset_type = "knowledge_card"
asset_id = $Card.knowledge_card_id
action = "retry_existing"
status = $data.status
target_id = $data.id
detail = $data.status_detail
}
}
$response = Invoke-FormRequest -Method "POST" -Url "$ApiBaseUrl/knowledge-bases/$KnowledgeBaseId/documents" -Headers $Headers -Fields $fields -FileField "file" -FileName $fileName -FileContent $bytes
$data = Get-ResponseData -Response $response
return [ordered]@{
asset_type = "knowledge_card"
asset_id = $Card.knowledge_card_id
action = "upload"
status = $data.status
target_id = $data.id
detail = $data.status_detail
}
}
function Get-UniqueValues {
param([object[]]$Values)
return @($Values | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | ForEach-Object { ([string]$_).Trim() } | Select-Object -Unique)
}
function Get-KnowledgeCardMetadataFields {
param(
[object]$Card,
[hashtable]$SourceMetadataLookup
)
$sourceIds = @($Card.source_ids -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$sources = @()
foreach ($sourceId in $sourceIds) {
if ($SourceMetadataLookup.ContainsKey($sourceId)) {
$sources += $SourceMetadataLookup[$sourceId]
}
}
$sourceTypes = Get-UniqueValues -Values @($sources | ForEach-Object { $_.source_type })
$authorities = Get-UniqueValues -Values @($sources | ForEach-Object { $_.issuer })
$effectiveDates = Get-UniqueValues -Values @($sources | ForEach-Object { $_.effective_date } | Where-Object { $_ -and $_ -ne "VERIFY_REQUIRED" })
$domains = Get-UniqueValues -Values @($sources | ForEach-Object { $_.knowledge_domain })
$tags = New-Object System.Collections.Generic.List[string]
foreach ($value in @($sourceIds + $domains)) {
if (-not [string]::IsNullOrWhiteSpace($value) -and -not $tags.Contains($value)) {
$tags.Add($value) | Out-Null
}
}
$fields = @{}
if ($sourceTypes.Count -gt 0) {
$fields["source_type"] = if ($sourceTypes.Count -eq 1) { $sourceTypes[0] } else { ($sourceTypes -join ";") }
}
if ($authorities.Count -gt 0) {
$fields["authority"] = if ($authorities.Count -eq 1) { $authorities[0] } else { ($authorities -join ";") }
}
if ($effectiveDates.Count -gt 0) {
$fields["effective_date"] = if ($effectiveDates.Count -eq 1) { $effectiveDates[0] } else { ($effectiveDates -join ";") }
}
$fields["region"] = "CN"
if ($tags.Count -gt 0) {
$fields["tags_json"] = ($tags.ToArray() | ConvertTo-Json -Compress)
}
return $fields
}
function Get-TemplateVariablesJson {
param([string]$VariableSchemaPath)
if ([string]::IsNullOrWhiteSpace($VariableSchemaPath)) {
return $null
}
$absolutePath = Resolve-LibraryPath -RelativePath $VariableSchemaPath
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
return $null
}
return Get-Content -LiteralPath $absolutePath -Raw -Encoding UTF8
}
function Import-TemplateAsset {
param([hashtable]$Headers, [object]$Template, [object[]]$ExistingTemplates)
$templatePath = Resolve-LibraryPath -RelativePath $Template.template_path
$content = Get-Content -LiteralPath $templatePath -Raw -Encoding UTF8
$variablesJson = Get-TemplateVariablesJson -VariableSchemaPath $Template.variable_schema_path
$fields = @{
name = $Template.template_name
contract_type = $Template.contract_type
description = $Template.notes
content = $content
enabled = "true"
}
if (-not [string]::IsNullOrWhiteSpace($variablesJson)) {
$fields["variables_json"] = $variablesJson
}
$existing = @($ExistingTemplates | Where-Object { $_.name -eq $Template.template_name }) | Select-Object -First 1
if ($existing) {
$response = Invoke-FormRequest -Method "PUT" -Url "$ApiBaseUrl/templates/$($existing.id)" -Headers $Headers -Fields $fields
$data = Get-ResponseData -Response $response
return [ordered]@{ asset_type = "template"; asset_id = $Template.template_id; action = "update"; status = "upserted"; target_id = $data.id; detail = "模板已更新。" }
}
$response = Invoke-FormRequest -Method "POST" -Url "$ApiBaseUrl/templates" -Headers $Headers -Fields $fields
$created = Get-ResponseData -Response $response
return [ordered]@{ asset_type = "template"; asset_id = $Template.template_id; action = "create"; status = "created"; target_id = $created.id; detail = "模板已创建。" }
}
function Import-ReviewRuleAsset {
param([hashtable]$Headers, [object]$Rule, [object[]]$ExistingRules)
$ruleBody = Read-LibraryTextIfExists -RelativePath $Rule.modification_suggestion_path
$description = $Rule.review_output_template
if (-not [string]::IsNullOrWhiteSpace($ruleBody)) {
$description = "$description`n`n$ruleBody"
}
$suggestion = if (-not [string]::IsNullOrWhiteSpace($ruleBody)) {
$ruleBody
} else {
$Rule.modification_suggestion_path
}
$isActive = if ($Mode -eq "Production") {
Test-ProductionReadyAsset -Row $Rule
} else {
Test-ReviewImportEligibleAsset -Row $Rule
}
$fields = @{
name = $Rule.risk_name
keyword = $Rule.trigger_condition
contract_type = "all"
risk_level = $Rule.severity.ToLowerInvariant()
description = $description
suggestion = $suggestion
legal_basis = $Rule.source_ids
is_active = ([string]$isActive).ToLowerInvariant()
}
$existing = @($ExistingRules | Where-Object { $_.name -eq $Rule.risk_name -or $_.keyword -eq $Rule.trigger_condition }) | Select-Object -First 1
if ($existing) {
$response = Invoke-FormRequest -Method "PUT" -Url "$ApiBaseUrl/review-rules/$($existing.id)" -Headers $Headers -Fields $fields
$data = Get-ResponseData -Response $response
return [ordered]@{ asset_type = "review_rule"; asset_id = $Rule.rule_id; action = "update"; status = "upserted"; target_id = $data.id; detail = "审核规则已更新。" }
}
$response = Invoke-FormRequest -Method "POST" -Url "$ApiBaseUrl/review-rules" -Headers $Headers -Fields $fields
$created = Get-ResponseData -Response $response
return [ordered]@{ asset_type = "review_rule"; asset_id = $Rule.rule_id; action = "create"; status = "created"; target_id = $created.id; detail = "审核规则已创建。" }
}
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
Add-Check -Key "gstack" -Label "gstack 前置检查" -Status "fail" -Detail "gstack 缺失"
} else {
Add-Check -Key "gstack" -Label "gstack 前置检查" -Status "pass" -Detail "GSTACK_OK"
}
if (Test-Path -LiteralPath $LibraryRoot -PathType Container) {
Add-Check -Key "library_root" -Label "阶段 13 资料包根目录" -Status "pass" -Detail "资料包根目录存在" -Meta @{ root = $LibraryRoot }
} else {
Add-Check -Key "library_root" -Label "阶段 13 资料包根目录" -Status "fail" -Detail "资料包根目录缺失" -Meta @{ root = $LibraryRoot }
}
$legalSourceInventoryPath = Join-Path $LibraryRoot "inventories\legal-source-inventory.csv"
$knowledgeCardInventoryPath = Join-Path $LibraryRoot "inventories\legal-knowledge-card-inventory.csv"
$templateInventoryPath = Join-Path $LibraryRoot "inventories\contract-template-inventory.csv"
$reviewRuleInventoryPath = Join-Path $LibraryRoot "inventories\review-risk-rule-inventory.csv"
$clauseInventoryPath = Join-Path $LibraryRoot "inventories\clause-library-inventory.csv"
$reviewSampleInventoryPath = Join-Path $LibraryRoot "inventories\review-golden-sample-inventory.csv"
$generationSampleInventoryPath = Join-Path $LibraryRoot "inventories\generation-golden-sample-inventory.csv"
$kbQaSampleInventoryPath = Join-Path $LibraryRoot "inventories\kb-qa-golden-sample-inventory.csv"
$sourceRows = Import-CsvRows -Path $legalSourceInventoryPath
$sourceIdSet = @{}
$sourceMetadataLookup = @{}
foreach ($source in $sourceRows) {
if (-not [string]::IsNullOrWhiteSpace($source.source_id)) {
$sourceIdSet[$source.source_id] = $true
$sourceMetadataLookup[$source.source_id] = $source
}
}
$knowledgeCards = Import-CsvRows -Path $knowledgeCardInventoryPath
$templates = Import-CsvRows -Path $templateInventoryPath
$reviewRules = Import-CsvRows -Path $reviewRuleInventoryPath
$clauses = Import-CsvRows -Path $clauseInventoryPath
$reviewSamples = Import-CsvRows -Path $reviewSampleInventoryPath
$generationSamples = Import-CsvRows -Path $generationSampleInventoryPath
$kbQaSamples = Import-CsvRows -Path $kbQaSampleInventoryPath
if ($knowledgeCards.Count -gt 0) {
Add-Check -Key "knowledge_card_inventory" -Label "法律知识卡清单" -Status "pass" -Detail "知识卡数量=$($knowledgeCards.Count)"
} else {
Add-Check -Key "knowledge_card_inventory" -Label "法律知识卡清单" -Status "warn" -Required $false -Detail "知识卡清单为空"
}
$validationFailures = 0
$eligibleKnowledgeCards = New-Object System.Collections.Generic.List[object]
foreach ($card in $knowledgeCards) {
$absolutePath = Resolve-LibraryPath -RelativePath $card.summary_path
$missing = New-Object System.Collections.Generic.List[string]
foreach ($field in @("knowledge_card_id", "title", "source_ids", "summary_path", "status", "owner", "legal_reviewer")) {
if ([string]::IsNullOrWhiteSpace($card.$field)) {
$missing.Add($field) | Out-Null
}
}
$unknownSources = @()
foreach ($sourceId in @($card.source_ids -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
if (-not $sourceIdSet.ContainsKey($sourceId)) {
$unknownSources += $sourceId
}
}
if ($unknownSources.Count -gt 0) {
$missing.Add("unknown_source_ids=$($unknownSources -join ';')") | Out-Null
}
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
$missing.Add("summary_path_missing") | Out-Null
}
$productionReady = Test-ProductionReadyAsset -Row $card
$reviewEligible = @("LEGAL_REVIEW", "BUSINESS_REVIEW", "APPROVED", "PUBLISHED") -contains $card.status
$eligible = if ($Mode -eq "Production") { $productionReady } elseif ($Mode -eq "Review") { $reviewEligible } else { $reviewEligible -or $productionReady }
if ($missing.Count -gt 0) {
$validationFailures += 1
} elseif ($eligible -and ($Mode -ne "Production" -or $productionReady)) {
$eligibleKnowledgeCards.Add($card) | Out-Null
}
$assetReport.Add([ordered]@{
asset_type = "knowledge_card"
asset_id = $card.knowledge_card_id
title = $card.title
status = $card.status
production_ready = $card.production_ready
legal_reviewer = $card.legal_reviewer
eligible_for_mode = $eligible
validation_status = if ($missing.Count -eq 0) { "pass" } else { "fail" }
validation_detail = if ($missing.Count -eq 0) { "字段、法源和文件路径通过" } else { "问题:" + ($missing -join ";") }
summary_path = $card.summary_path
}) | Out-Null
}
if ($validationFailures -eq 0) {
Add-Check -Key "knowledge_card_validation" -Label "知识卡字段与路径校验" -Status "pass" -Detail "知识卡字段、法源编号和摘要文件路径通过"
} else {
Add-Check -Key "knowledge_card_validation" -Label "知识卡字段与路径校验" -Status "fail" -Detail "存在 $validationFailures 张知识卡未通过校验"
}
$token = ""
$headers = @{}
$targetKnowledgeBase = $null
if ($Mode -ne "Validate" -and $validationFailures -eq 0) {
$token = Get-AccessToken
$headers = Get-AuthHeaders -Token $token
$targetKnowledgeBase = Ensure-KnowledgeBase -Headers $headers
Add-Check -Key "api_login" -Label "管理员登录与知识库确认" -Status "pass" -Detail "目标知识库:$($targetKnowledgeBase.name)" -Meta @{ knowledge_base_id = $targetKnowledgeBase.id }
} elseif ($Mode -eq "Validate") {
Add-Check -Key "api_login" -Label "管理员登录与知识库确认" -Status "skip" -Required $false -Detail "Validate 模式不调用系统 API"
}
if ($Mode -ne "Validate" -and $validationFailures -eq 0) {
$existingDocs = Get-KnowledgeBaseDocuments -Headers $headers -KnowledgeBaseId $targetKnowledgeBase.id
foreach ($card in $eligibleKnowledgeCards) {
$absolutePath = Resolve-LibraryPath -RelativePath $card.summary_path
try {
$result = Import-KnowledgeCard -Headers $headers -KnowledgeBaseId $targetKnowledgeBase.id -Card $card -AbsolutePath $absolutePath -ExistingDocuments $existingDocs -SourceMetadataLookup $sourceMetadataLookup
$importReport.Add($result) | Out-Null
} catch {
$importReport.Add([ordered]@{
asset_type = "knowledge_card"
asset_id = $card.knowledge_card_id
action = "upload"
status = "failed"
target_id = ""
detail = $_.Exception.Message
}) | Out-Null
}
}
if (-not $SkipTemplates -and $templates.Count -gt 0) {
$existingTemplates = Get-PagedApiItems -Url "$ApiBaseUrl/templates" -Headers $headers
foreach ($template in $templates) {
$templateEligible = if ($Mode -eq "Production") { Test-ProductionReadyAsset -Row $template } else { Test-ReviewImportEligibleAsset -Row $template }
if (-not $templateEligible) {
continue
}
try {
$importReport.Add((Import-TemplateAsset -Headers $headers -Template $template -ExistingTemplates $existingTemplates)) | Out-Null
} catch {
$importReport.Add([ordered]@{ asset_type = "template"; asset_id = $template.template_id; action = "upsert"; status = "failed"; target_id = ""; detail = $_.Exception.Message }) | Out-Null
}
}
}
if (-not $SkipReviewRules -and $reviewRules.Count -gt 0) {
$existingRules = Get-PagedApiItems -Url "$ApiBaseUrl/review-rules" -Headers $headers
foreach ($rule in $reviewRules) {
$ruleEligible = if ($Mode -eq "Production") { Test-ProductionReadyAsset -Row $rule } else { Test-ReviewImportEligibleAsset -Row $rule }
if (-not $ruleEligible) {
continue
}
try {
$importReport.Add((Import-ReviewRuleAsset -Headers $headers -Rule $rule -ExistingRules $existingRules)) | Out-Null
} catch {
$importReport.Add([ordered]@{ asset_type = "review_rule"; asset_id = $rule.rule_id; action = "upsert"; status = "failed"; target_id = ""; detail = $_.Exception.Message }) | Out-Null
}
}
}
}
$importFailures = @($importReport | Where-Object { $_.status -eq "failed" }).Count
$blockedByReview = @($assetReport | Where-Object { $_.asset_type -eq "knowledge_card" -and -not $_.eligible_for_mode }).Count
$importedOrSkipped = @($importReport | Where-Object { $_.status -ne "failed" }).Count
if ($Mode -eq "Validate") {
Add-Check -Key "import_execution" -Label "导入执行" -Status "skip" -Required $false -Detail "Validate 模式只完成资料校验"
} elseif ($importFailures -eq 0) {
Add-Check -Key "import_execution" -Label "导入执行" -Status "pass" -Detail "导入或跳过成功数量=$importedOrSkipped"
} else {
Add-Check -Key "import_execution" -Label "导入执行" -Status "fail" -Detail "导入失败数量=$importFailures"
}
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" }).Count
$warnings = @($checks | Where-Object { $_.status -eq "warn" }).Count
$overallStatus = if ($failedRequired -gt 0 -or $validationFailures -gt 0 -or $importFailures -gt 0) {
"FAIL"
} elseif ($Mode -eq "Production" -and $blockedByReview -gt 0) {
"PASS_WITH_REVIEW_BLOCKED_ASSETS"
} elseif ($warnings -gt 0) {
"WARN"
} else {
"PASS"
}
$summary = [ordered]@{
name = "phase13-library-import"
mode = $Mode
status = $overallStatus
generated_at = (Get-Date).ToUniversalTime().ToString("o")
api_base_url = $ApiBaseUrl
artifact_dir = $artifactDir
library_root = $LibraryRoot
knowledge_base_name = $KnowledgeBaseName
knowledge_base_id = if ($targetKnowledgeBase) { $targetKnowledgeBase.id } else { "" }
checks = $checks.ToArray()
assets = $assetReport.ToArray()
imports = $importReport.ToArray()
totals = [ordered]@{
legal_sources = $sourceRows.Count
knowledge_cards = $knowledgeCards.Count
eligible_knowledge_cards = $eligibleKnowledgeCards.Count
clauses = $clauses.Count
templates = $templates.Count
review_rules = $reviewRules.Count
review_samples = $reviewSamples.Count
generation_samples = $generationSamples.Count
kb_qa_samples = $kbQaSamples.Count
validation_failures = $validationFailures
import_failures = $importFailures
blocked_by_review = $blockedByReview
}
}
Save-Json -Path $summaryJson -Value $summary
$md = New-Object System.Collections.Generic.List[string]
$md.Add("# 阶段 13 资料导入报告") | Out-Null
$md.Add("") | Out-Null
$md.Add("- 模式:$Mode") | Out-Null
$md.Add("- 状态:$overallStatus") | Out-Null
$md.Add("- 生成时间:$($summary.generated_at)") | Out-Null
$md.Add("- 接口地址:$ApiBaseUrl") | Out-Null
$md.Add("- 资料包:$LibraryRoot") | Out-Null
$md.Add("- 目标知识库:$KnowledgeBaseName") | Out-Null
if ($targetKnowledgeBase) {
$md.Add("- 目标知识库编号:$($targetKnowledgeBase.id)") | Out-Null
}
$md.Add("") | Out-Null
$md.Add("## 统计") | Out-Null
$md.Add("") | Out-Null
$md.Add("| 指标 | 数量 |") | Out-Null
$md.Add("|---|---:|") | Out-Null
$md.Add("| 法源 | $($sourceRows.Count) |") | Out-Null
$md.Add("| 知识卡 | $($knowledgeCards.Count) |") | Out-Null
$md.Add("| 当前模式可导入知识卡 | $($eligibleKnowledgeCards.Count) |") | Out-Null
$md.Add("| 标准条款 | $($clauses.Count) |") | Out-Null
$md.Add("| 合同模板 | $($templates.Count) |") | Out-Null
$md.Add("| 审核规则 | $($reviewRules.Count) |") | Out-Null
$md.Add("| 合同审核黄金样本 | $($reviewSamples.Count) |") | Out-Null
$md.Add("| 合同生成黄金样本 | $($generationSamples.Count) |") | Out-Null
$md.Add("| 知识库问答黄金样本 | $($kbQaSamples.Count) |") | Out-Null
$md.Add("| 校验失败 | $validationFailures |") | Out-Null
$md.Add("| 导入失败 | $importFailures |") | Out-Null
$md.Add("| 当前模式阻塞资料 | $blockedByReview |") | Out-Null
$md.Add("") | Out-Null
$md.Add("## 检查项") | Out-Null
$md.Add("") | Out-Null
$md.Add("| 检查项 | 必需 | 状态 | 说明 |") | Out-Null
$md.Add("|---|---:|---|---|") | Out-Null
foreach ($check in $checks) {
$requiredText = if ($check.required) { "是" } else { "否" }
$detail = ([string]$check.detail).Replace("|", "/")
$md.Add("| $($check.label) | $requiredText | $($check.status) | $detail |") | Out-Null
}
$md.Add("") | Out-Null
$md.Add("## 导入结果") | Out-Null
$md.Add("") | Out-Null
if ($importReport.Count -eq 0) {
$md.Add("本次未执行 API 导入。") | Out-Null
} else {
$md.Add("| 类型 | 编号 | 动作 | 状态 | 说明 |") | Out-Null
$md.Add("|---|---|---|---|---|") | Out-Null
foreach ($item in $importReport) {
$detail = ([string]$item.detail).Replace("|", "/")
$md.Add("| $($item.asset_type) | $($item.asset_id) | $($item.action) | $($item.status) | $detail |") | Out-Null
}
}
$md.Add("") | Out-Null
$md.Add("本地产物:") | Out-Null
$md.Add("") | Out-Null
$md.Add('- `phase13-import-summary.json`') | Out-Null
$md.Add('- `phase13-import-summary.md`') | Out-Null
$md | Set-Content -Encoding UTF8 -Path $summaryMd
Write-Host "PHASE13_IMPORT_$overallStatus $artifactDir"
if ($overallStatus -eq "FAIL") {
throw "阶段 13 资料导入失败。查看:$summaryMd"
}