Initial import of NavSea pbf project

This commit is contained in:
OpenAI Codex
2026-03-17 19:48:15 +08:00
commit 5f3f7409b3
84 changed files with 43889 additions and 0 deletions

237
navsea_mapping_registry.py Normal file
View File

@@ -0,0 +1,237 @@
from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any
import pymysql
@dataclass(frozen=True)
class FieldValueRule:
field_name_jp: str
legacy_value: str
standardized_field: str
standardized_value: str
geom_scope: tuple[str, ...]
source_layer_scope: tuple[str, ...]
canonical_object_scope: tuple[str, ...]
def matches(self, context: dict[str, str]) -> bool:
if self.geom_scope and context.get("geom_type", "") not in self.geom_scope:
return False
if self.source_layer_scope and context.get("source_layer", "") not in self.source_layer_scope:
return False
if self.canonical_object_scope and context.get("canonical_object_type", "") not in self.canonical_object_scope:
return False
return True
@dataclass(frozen=True)
class RenderRule:
rule_id: str
priority: int
match_expr: dict[str, Any]
output: dict[str, Any]
def matches(self, context: dict[str, Any]) -> bool:
return evaluate_match_expr(self.match_expr, context)
def normalize_csv_scope(value: str | None) -> tuple[str, ...]:
if not value:
return ()
return tuple(part.strip() for part in str(value).split(",") if part.strip())
def evaluate_match_expr(match_expr: dict[str, Any], context: dict[str, Any]) -> bool:
clauses = match_expr.get("all") or []
for clause in clauses:
field = clause.get("field")
op = clause.get("op")
expected = clause.get("value")
actual = context.get(str(field))
actual_text = "" if actual is None else str(actual)
if op == "eq":
if actual_text != str(expected):
return False
elif op == "neq":
if actual_text == str(expected):
return False
elif op == "in":
if actual_text not in {str(item) for item in (expected or [])}:
return False
elif op == "contains":
if str(expected) not in actual_text:
return False
elif op == "contains_any":
values = [str(item) for item in (expected or [])]
if not any(item in actual_text for item in values):
return False
elif op == "is_null":
if actual not in (None, ""):
return False
elif op == "not_null":
if actual in (None, ""):
return False
else:
return False
return True
class NavSeaMappingRegistry:
def __init__(
self,
bundle_id: str,
bundle_version: str,
source_layer_rules: dict[str, dict[str, str]],
field_value_rules: dict[tuple[str, str], list[FieldValueRule]],
render_rules: list[RenderRule],
) -> None:
self.bundle_id = bundle_id
self.bundle_version = bundle_version
self.source_layer_rules = source_layer_rules
self.field_value_rules = field_value_rules
self.render_rules = render_rules
@classmethod
def load(
cls,
conn: pymysql.Connection,
bundle_id: str | None = None,
) -> "NavSeaMappingRegistry":
with conn.cursor() as cur:
if bundle_id:
cur.execute(
"""
SELECT bundle_id, bundle_version
FROM navsea_rule_bundle
WHERE bundle_id = %s
ORDER BY created_at DESC
LIMIT 1
""",
(bundle_id,),
)
else:
cur.execute(
"""
SELECT bundle_id, bundle_version
FROM navsea_rule_bundle
ORDER BY created_at DESC
LIMIT 1
"""
)
bundle = cur.fetchone()
if not bundle:
raise RuntimeError("navsea mapping registry is empty")
resolved_bundle_id = str(bundle["bundle_id"])
resolved_bundle_version = str(bundle["bundle_version"])
cur.execute(
"""
SELECT source_layer_jp, source_layer_std
FROM navsea_source_layer_rules
WHERE bundle_id = %s AND bundle_version = %s
""",
(resolved_bundle_id, resolved_bundle_version),
)
source_layer_rules = {
str(row["source_layer_jp"]): {
"source_layer_std": str(row["source_layer_std"]),
"source_layer_rule_id": f"SLR:{row['source_layer_jp']}",
}
for row in cur.fetchall()
}
cur.execute(
"""
SELECT
field_name_jp,
legacy_value,
standardized_field,
standardized_value,
geom_scope,
source_layer_scope,
canonical_object_scope
FROM navsea_field_value_rules
WHERE bundle_id = %s AND bundle_version = %s
ORDER BY rule_priority ASC, rule_id ASC, rule_revision ASC
""",
(resolved_bundle_id, resolved_bundle_version),
)
field_value_rules: dict[tuple[str, str], list[FieldValueRule]] = {}
for row in cur.fetchall():
key = (str(row["field_name_jp"]), str(row["legacy_value"]))
field_value_rules.setdefault(key, []).append(
FieldValueRule(
field_name_jp=str(row["field_name_jp"]),
legacy_value=str(row["legacy_value"]),
standardized_field=str(row["standardized_field"]),
standardized_value=str(row["standardized_value"]),
geom_scope=normalize_csv_scope(row.get("geom_scope")),
source_layer_scope=normalize_csv_scope(row.get("source_layer_scope")),
canonical_object_scope=normalize_csv_scope(row.get("canonical_object_scope")),
)
)
cur.execute(
"""
SELECT rule_id, priority, match_expr_json, output_json
FROM navsea_render_rules
WHERE bundle_id = %s AND bundle_version = %s AND enabled = 1
ORDER BY priority ASC, rule_id ASC, rule_revision ASC
""",
(resolved_bundle_id, resolved_bundle_version),
)
render_rules = []
for row in cur.fetchall():
render_rules.append(
RenderRule(
rule_id=str(row["rule_id"]),
priority=int(row["priority"]),
match_expr=json.loads(row["match_expr_json"]),
output=json.loads(row["output_json"]),
)
)
return cls(
bundle_id=resolved_bundle_id,
bundle_version=resolved_bundle_version,
source_layer_rules=source_layer_rules,
field_value_rules=field_value_rules,
render_rules=render_rules,
)
def resolve_source_layer(self, source_layer_jp: str) -> tuple[str, str]:
rule = self.source_layer_rules.get(source_layer_jp)
if not rule:
return source_layer_jp, f"SLR:{source_layer_jp}:identity"
return rule["source_layer_std"], rule["source_layer_rule_id"]
def standardize_field_value(
self,
field_name_jp: str,
legacy_value: object,
standardized_field: str,
*,
context: dict[str, str] | None = None,
) -> str | None:
if legacy_value in (None, ""):
return None
key = (field_name_jp, str(legacy_value))
rules = self.field_value_rules.get(key, [])
match_context = context or {}
for rule in rules:
if rule.standardized_field != standardized_field:
continue
if rule.matches(match_context):
return rule.standardized_value
return None
def resolve_render_rule(self, context: dict[str, Any]) -> tuple[dict[str, Any], str | None]:
for rule in self.render_rules:
if rule.matches(context):
return dict(rule.output), rule.rule_id
return {}, None