test: add file download resolver v2 diagnostic
This commit is contained in:
@@ -4,6 +4,8 @@ param(
|
||||
[string]$SQLiteDllPath = $env:ISPHERE_MSGLIB_SQLITE_DLL,
|
||||
[string]$DbPath = $env:ISPHERE_MSGLIB_DB,
|
||||
[string]$Password = "123",
|
||||
[string]$FixtureRecordsPath = "",
|
||||
[string]$SanitizedOutputPath = "",
|
||||
[switch]$SkipDb
|
||||
)
|
||||
|
||||
@@ -116,6 +118,118 @@ function Read-CacheSummaryFromRoot([string]$Path) {
|
||||
return $summary
|
||||
}
|
||||
|
||||
function ConvertTo-SafeFileRecord($Value) {
|
||||
$createdAt = $null
|
||||
if (-not [string]::IsNullOrWhiteSpace([string]$Value.created_at)) {
|
||||
[datetime]$parsed = [datetime]::MinValue
|
||||
if ([datetime]::TryParse([string]$Value.created_at, [ref]$parsed)) {
|
||||
$createdAt = $parsed.ToUniversalTime()
|
||||
}
|
||||
}
|
||||
return [pscustomobject]@{
|
||||
normalized_name = Normalize-FileName ([string]$Value.file_name)
|
||||
size_bytes = if ($null -ne $Value.size_bytes) { [int64]$Value.size_bytes } else { -1 }
|
||||
source_id = ([string]$Value.source_id).Trim().ToLowerInvariant()
|
||||
created_at_utc = $createdAt
|
||||
}
|
||||
}
|
||||
|
||||
function Read-FixtureFileRecords([string]$Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "FixtureRecordsPath not found"
|
||||
}
|
||||
$json = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
$safeRecords = @()
|
||||
foreach ($record in $json) {
|
||||
$safeRecords += (ConvertTo-SafeFileRecord $record)
|
||||
}
|
||||
return $safeRecords
|
||||
}
|
||||
|
||||
function Read-CacheCandidatesForScoring([string]$Path) {
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "CacheRoot not found"
|
||||
}
|
||||
$candidates = @()
|
||||
foreach ($file in Get-ChildItem -LiteralPath $Path -File -Recurse) {
|
||||
$searchText = (($file.Name + " " + $file.DirectoryName) -replace '[\\/]', ' ').ToLowerInvariant()
|
||||
$candidates += [pscustomobject]@{
|
||||
normalized_name = Normalize-FileName $file.Name
|
||||
size_bytes = [int64]$file.Length
|
||||
last_write_utc = $file.LastWriteTimeUtc
|
||||
search_text = $searchText
|
||||
}
|
||||
}
|
||||
return $candidates
|
||||
}
|
||||
|
||||
function Test-CloseTimestamp($Record, $Candidate) {
|
||||
if ($null -eq $Record.created_at_utc -or $null -eq $Candidate.last_write_utc) {
|
||||
return $false
|
||||
}
|
||||
$delta = [Math]::Abs((New-TimeSpan -Start $Record.created_at_utc -End $Candidate.last_write_utc).TotalSeconds)
|
||||
return $delta -le 600
|
||||
}
|
||||
|
||||
function Get-ResolverScore($Record, $Candidate) {
|
||||
$nameMatches = ($Record.normalized_name -ne "" -and $Record.normalized_name -eq $Candidate.normalized_name)
|
||||
$sizeMatches = ($Record.size_bytes -ge 0 -and $Record.size_bytes -eq $Candidate.size_bytes)
|
||||
$sourceMatches = ($Record.source_id -ne "" -and $Candidate.search_text.Contains($Record.source_id))
|
||||
if ($nameMatches -and $sizeMatches -and (Test-CloseTimestamp $Record $Candidate)) {
|
||||
return 100
|
||||
}
|
||||
if ($sizeMatches -and $sourceMatches) {
|
||||
return 80
|
||||
}
|
||||
if ($nameMatches) {
|
||||
return 40
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function Invoke-ResolverScoring($Records, $Candidates) {
|
||||
$accepted = 0
|
||||
$ambiguous = 0
|
||||
$score100 = 0
|
||||
$score80 = 0
|
||||
$score40Only = 0
|
||||
foreach ($record in @($Records)) {
|
||||
$scores = @()
|
||||
foreach ($candidate in @($Candidates)) {
|
||||
$score = Get-ResolverScore $record $candidate
|
||||
if ($score -gt 0) {
|
||||
$scores += $score
|
||||
}
|
||||
}
|
||||
if ($scores.Count -eq 0) {
|
||||
continue
|
||||
}
|
||||
$top = ($scores | Sort-Object -Descending | Select-Object -First 1)
|
||||
$topCount = @($scores | Where-Object { $_ -eq $top }).Count
|
||||
if ($top -ge 80 -and $topCount -eq 1) {
|
||||
$accepted++
|
||||
if ($top -eq 100) { $score100++ }
|
||||
elseif ($top -eq 80) { $score80++ }
|
||||
}
|
||||
elseif ($top -ge 80) {
|
||||
$ambiguous++
|
||||
}
|
||||
elseif ($top -eq 40) {
|
||||
$score40Only++
|
||||
}
|
||||
}
|
||||
return [ordered]@{
|
||||
records_checked = @($Records).Count
|
||||
cache_candidates_checked = @($Candidates).Count
|
||||
accepted_matches = $accepted
|
||||
ambiguous_matches = $ambiguous
|
||||
score_100_matches = $score100
|
||||
score_80_matches = $score80
|
||||
score_40_only_matches = $score40Only
|
||||
used_fixture_records = $true
|
||||
}
|
||||
}
|
||||
|
||||
function Convert-CountObjectToHashtable($Value) {
|
||||
$map = @{}
|
||||
if ($null -eq $Value) { return $map }
|
||||
@@ -522,7 +636,30 @@ elseif ($filenameOnlyMatches -gt 0 -or $sourceIDHashDirMatches -gt 0) {
|
||||
$recommendedNextNode = "additional evidence request: collect file size or cache root metadata"
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
$resolverSummary = [ordered]@{
|
||||
records_checked = [int]$dbProbe.record_count
|
||||
cache_candidates_checked = [int]$cacheSummary["candidate_count"]
|
||||
accepted_matches = 0
|
||||
ambiguous_matches = 0
|
||||
score_100_matches = 0
|
||||
score_80_matches = 0
|
||||
score_40_only_matches = $filenameOnlyMatches
|
||||
used_fixture_records = $false
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($FixtureRecordsPath)) {
|
||||
if ([string]::IsNullOrWhiteSpace($CacheRoot)) {
|
||||
throw "FixtureRecordsPath requires CacheRoot"
|
||||
}
|
||||
$fixtureRecords = Read-FixtureFileRecords $FixtureRecordsPath
|
||||
$cacheCandidates = Read-CacheCandidatesForScoring $CacheRoot
|
||||
$resolverSummary = Invoke-ResolverScoring $fixtureRecords $cacheCandidates
|
||||
$mappingValidated = ([int]$resolverSummary.accepted_matches -gt 0)
|
||||
if ($mappingValidated) {
|
||||
$recommendedNextNode = "R11 download preview resolver contract"
|
||||
}
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
ok = $true
|
||||
verification = "file_cache_mapping"
|
||||
db_checked = $dbChecked
|
||||
@@ -542,6 +679,14 @@ elseif ($filenameOnlyMatches -gt 0 -or $sourceIDHashDirMatches -gt 0) {
|
||||
filename_size_match_count = $filenameSizeMatches
|
||||
unique_filename_size_match_count = $uniqueFilenameSizeMatches
|
||||
source_id_hash_dir_match_count = $sourceIDHashDirMatches
|
||||
records_checked = [int]$resolverSummary.records_checked
|
||||
cache_candidates_checked = [int]$resolverSummary.cache_candidates_checked
|
||||
accepted_matches = [int]$resolverSummary.accepted_matches
|
||||
ambiguous_matches = [int]$resolverSummary.ambiguous_matches
|
||||
score_100_matches = [int]$resolverSummary.score_100_matches
|
||||
score_80_matches = [int]$resolverSummary.score_80_matches
|
||||
score_40_only_matches = [int]$resolverSummary.score_40_only_matches
|
||||
used_fixture_records = [bool]$resolverSummary.used_fixture_records
|
||||
mapping_validated = $mappingValidated
|
||||
recommended_next_node = $recommendedNextNode
|
||||
raw_paths_printed = $false
|
||||
@@ -549,4 +694,14 @@ elseif ($filenameOnlyMatches -gt 0 -or $sourceIDHashDirMatches -gt 0) {
|
||||
file_paths_returned = $false
|
||||
raw_rows_returned = $false
|
||||
message_body_values_printed = $false
|
||||
} | ConvertTo-Json -Depth 8 -Compress
|
||||
}
|
||||
|
||||
$json = $summary | ConvertTo-Json -Depth 8 -Compress
|
||||
if (-not [string]::IsNullOrWhiteSpace($SanitizedOutputPath)) {
|
||||
$parent = Split-Path -Parent $SanitizedOutputPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($parent)) {
|
||||
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
||||
}
|
||||
Set-Content -LiteralPath $SanitizedOutputPath -Value $json -Encoding UTF8
|
||||
}
|
||||
$json
|
||||
|
||||
Reference in New Issue
Block a user