sync
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s

This commit is contained in:
ik
2026-09-17 07:35:04 +07:00
parent 5221442aeb
commit 722c88d436
25 changed files with 673 additions and 40 deletions
+66 -2
View File
@@ -8,10 +8,10 @@ from dataclasses import asdict
from .config import settings
from .database import SessionLocal
from .importer import import_records
from .community_importer import stage_observations
from .community_importer import stage_observations, update_waterbody_detail, update_waterbody_details, upsert_waterbody_catalog
from .retention import RetentionPolicy, apply_retention
from .storage import delete_screenshot
from .catalog_audit import audit_catalog
from .catalog_audit import audit_catalog, audit_waterbody_catalog
from .community_scheduler import run_source, configured_sources
# Static registry for argparse choices — no DB required for --help
@@ -34,11 +34,20 @@ def main() -> int:
community = sub.add_parser("stage-community-json")
community.add_argument("--input", default="-", help="JSON array path or - for stdin")
community.add_argument("--limit", type=int, default=500)
waterbodies = sub.add_parser("import-waterbody-catalog")
waterbodies.add_argument("--input", required=True, help="JSON snapshot path or - for stdin")
waterbodies.add_argument("--limit", type=int, default=100)
detail = sub.add_parser("import-waterbody-detail")
detail.add_argument("--input", required=True, help="JSON detail snapshot path")
details = sub.add_parser("import-waterbody-details")
details.add_argument("--input", required=True, help="JSON array of detail snapshots")
fetch_community = sub.add_parser("fetch-community")
fetch_community.add_argument("source", choices=STATIC_SOURCE_CHOICES)
cleanup = sub.add_parser("cleanup-retention")
cleanup.add_argument("--apply", action="store_true", help="apply changes; default is dry-run")
sub.add_parser("audit-catalog")
waterbody_audit = sub.add_parser("audit-waterbody-catalog")
waterbody_audit.add_argument("--input", required=True, help="JSON snapshot path")
args = parser.parse_args()
with SessionLocal() as session:
if args.command == "import-records":
@@ -57,6 +66,43 @@ def main() -> int:
parser.error("input must be a JSON array")
created, updated = stage_observations(session, payload[:args.limit])
print(f"staged: created={created} updated={updated}")
elif args.command == "import-waterbody-catalog":
if not 1 <= args.limit <= 500:
parser.error("--limit must be between 1 and 500")
stream = sys.stdin if args.input == "-" else open(args.input, encoding="utf-8")
try:
snapshot = json.load(stream)
finally:
if stream is not sys.stdin:
stream.close()
if isinstance(snapshot, dict):
payload = snapshot.get("items")
source_system = snapshot.get("source_system")
if isinstance(payload, list) and isinstance(source_system, str):
payload = [
{"source_system": source_system, **item}
for item in payload if isinstance(item, dict)
]
else:
payload = snapshot
if not isinstance(payload, list):
parser.error("input must be a JSON array or an object with an items array")
created, updated = upsert_waterbody_catalog(session, payload[:args.limit])
print(f"waterbodies: created={created} updated={updated}")
elif args.command == "import-waterbody-detail":
with open(args.input, encoding="utf-8") as stream:
payload = json.load(stream)
if not isinstance(payload, dict):
parser.error("input must be a JSON object")
update_waterbody_detail(session, payload)
print(f"waterbody detail: updated={payload.get('source_external_id', 'unknown')}")
elif args.command == "import-waterbody-details":
with open(args.input, encoding="utf-8") as stream:
payload = json.load(stream)
if not isinstance(payload, list):
parser.error("input must be a JSON array")
created, updated = update_waterbody_details(session, payload)
print(f"waterbody details: created={created} updated={updated}")
elif args.command == "fetch-community":
# A09: Verify source is enabled at runtime (not just in static choices)
enabled = configured_sources()
@@ -76,6 +122,24 @@ def main() -> int:
)
counts = apply_retention(session, policy=policy, dry_run=not args.apply, delete_object=delete_screenshot)
print(json.dumps({"mode": "apply" if args.apply else "dry-run", "policy": asdict(policy), "counts": counts}, ensure_ascii=False))
elif args.command == "audit-waterbody-catalog":
stream = sys.stdin if args.input == "-" else open(args.input, encoding="utf-8")
try:
snapshot = json.load(stream)
finally:
if stream is not sys.stdin:
stream.close()
items = snapshot.get("items") if isinstance(snapshot, dict) else snapshot
if not isinstance(items, list):
parser.error("input must be a JSON array or an object with an items array")
expected_ids = {
str(item["source_external_id"])
for item in items
if isinstance(item, dict) and item.get("source_external_id")
}
result = audit_waterbody_catalog(session, expected_ids)
print(json.dumps(result, ensure_ascii=False))
return 1 if result["failures"] else 0
else:
result = audit_catalog(session)
print(json.dumps(result, ensure_ascii=False))