Files
zyt/server/app/adminapi/service/AiLosslessSourceEncoding.php
T
2026-10-27 14:03:35 +08:00

93 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace app\adminapi\service;
/** Repeated JSON keys only: no text summarization, row filtering or value coercion. */
final class AiLosslessSourceEncoding
{
public const VERSION = 'clinical-row-tables-v1';
public static function encode(array $snapshot): string
{
$paths = [];
$data = self::pack($snapshot, [], $paths);
return json_encode([
'encoding' => self::VERSION,
'reading_rule' => 'table_paths指定的节点使用columns和rows:每行各值按columns的同序字段对应。'
. '这是完整原始记录的无损表示,不是摘要;所有行、日期、数值、原文、null和空项均保留。'
. '表外对象照常读取;历史记录不能当成本次查体,空项不能当成患者否认或正常。',
'table_paths' => $paths,
'data' => $data,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
| JSON_INVALID_UTF8_SUBSTITUTE | JSON_THROW_ON_ERROR);
}
/** Reversible verification helper. Table paths also disambiguate literal columns/rows objects. */
public static function decode(string $encoded): array
{
$wire = json_decode($encoded, true, 512, JSON_THROW_ON_ERROR);
if (($wire['encoding'] ?? null) !== self::VERSION || !is_array($wire['table_paths'] ?? null)
|| !is_array($wire['data'] ?? null)) {
throw new \InvalidArgumentException('Invalid clinical row encoding');
}
$tables = [];
foreach ($wire['table_paths'] as $path) {
if (!is_array($path) || !array_is_list($path)) throw new \InvalidArgumentException('Invalid table path');
$tables[serialize($path)] = true;
}
return self::unpack($wire['data'], [], $tables);
}
private static function pack($node, array $path, array &$paths)
{
if (!is_array($node)) return $node;
if (array_is_list($node) && count($node) > 1 && is_array($node[0]) && !array_is_list($node[0])) {
$columns = array_keys($node[0]);
$sameShape = true;
foreach ($node as $row) {
if (!is_array($row) || array_keys($row) !== $columns) { $sameShape = false; break; }
}
if ($sameShape) {
$paths[] = $path;
$rows = [];
foreach ($node as $r => $row) {
$values = [];
foreach (array_values($row) as $c => $value) {
$values[] = self::pack($value, [...$path, 'rows', $r, $c], $paths);
}
$rows[] = $values;
}
return ['columns' => $columns, 'rows' => $rows];
}
}
$result = [];
foreach ($node as $key => $value) $result[$key] = self::pack($value, [...$path, $key], $paths);
return $result;
}
private static function unpack($node, array $path, array $tables)
{
if (!is_array($node)) return $node;
if (isset($tables[serialize($path)])) {
if (!is_array($node['columns'] ?? null) || !is_array($node['rows'] ?? null)) {
throw new \InvalidArgumentException('Invalid clinical row table');
}
$result = [];
foreach ($node['rows'] as $r => $row) {
if (!is_array($row) || count($row) !== count($node['columns'])) {
throw new \InvalidArgumentException('Invalid clinical row width');
}
$values = [];
foreach ($row as $c => $value) $values[] = self::unpack($value, [...$path, 'rows', $r, $c], $tables);
$result[] = array_combine($node['columns'], $values);
}
return $result;
}
$result = [];
foreach ($node as $key => $value) $result[$key] = self::unpack($value, [...$path, $key], $tables);
return $result;
}
}