Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C6BNBFXFndbqYxu6sZuknK
134 lines
5.4 KiB
Python
134 lines
5.4 KiB
Python
"""측점 설계를 **여러 행 한 번에** 쓰는 자리.
|
|
|
|
왜 (2026-09-06 실측) — [저장]이 측점마다 `update_cross_section_design` ·
|
|
`merge_cross_section_design_patch` 를 불렀고, 그 하나가 `SELECT` + `UPDATE` 두 왕복이다.
|
|
DB 가 원격(`dsm.chemifactory.com`)이라 왕복 하나가 **약 12ms** 다. 22행이면 왕복 44번,
|
|
곧 **670ms**. 측점이 많은 프로젝트일수록 선형으로 늘어난다.
|
|
|
|
여기서는 세 문장으로 끝낸다 —
|
|
① 노선의 측점 행을 **한 번에** 읽고
|
|
② 파이썬에서 chainage 를 맞춰 JSON 을 합치고
|
|
③ `UPDATE … SET data = CASE id …` 한 문장으로 되돌려 쓴다(없는 행은 다중 INSERT).
|
|
|
|
`B06_Section_Repository` 가 685줄이라 700줄 한계에 걸려 파일을 나눴다. 한 행짜리 함수는
|
|
그쪽에 그대로 두고, 여러 행을 쓸 때만 이쪽을 쓴다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
import aiomysql
|
|
|
|
# 측점을 같은 자리로 볼 허용 오차(m) — 한 행짜리 함수와 같은 값을 쓴다.
|
|
_CHAINAGE_TOLERANCE_M = 0.01
|
|
|
|
|
|
async def merge_cross_section_designs(
|
|
connection: aiomysql.Connection,
|
|
*,
|
|
route_id: int,
|
|
entries: list[tuple[float, dict[str, Any]]],
|
|
replace: bool,
|
|
project_id: UUID | None = None,
|
|
) -> int:
|
|
"""측점 여러 곳의 `data.design` 을 한 번에 쓴다. 실제로 바뀐 행 수를 돌려준다.
|
|
|
|
`replace=True` 면 design 을 통째로 갈아 끼우고(`update_cross_section_design` 과 같은 뜻),
|
|
`False` 면 키만 얹는다(`merge_cross_section_design_patch` 와 같은 뜻 · 값 None = 그 키 지움).
|
|
|
|
행이 없는 측점은 `project_id` 가 오면 새로 만든다 — 구조물(비정규) 측점은 B05 확정이
|
|
파일만 쓰고 DB 행을 안 만들기 때문이다(한 행짜리 함수와 같은 규칙).
|
|
"""
|
|
if not entries:
|
|
return 0
|
|
|
|
async with connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"SELECT id, chainage_m, data FROM cross_sections WHERE route_id = %s ORDER BY id",
|
|
(route_id,),
|
|
)
|
|
rows = await cursor.fetchall()
|
|
|
|
existing: dict[int, dict[str, Any]] = {}
|
|
for row_id, _chainage, raw in rows:
|
|
data = json.loads(raw) if isinstance(raw, str) else raw
|
|
existing[int(row_id)] = data if isinstance(data, dict) else {}
|
|
# 같은 측점이 여러 행이면 뒤에 온 것(=큰 id)을 쓴다 — 한 행짜리 함수의 `ORDER BY id DESC`.
|
|
ordered = [(float(chainage), int(row_id)) for row_id, chainage, _ in rows]
|
|
|
|
def find(chainage_m: float) -> int | None:
|
|
best: int | None = None
|
|
for value, row_id in ordered:
|
|
if abs(value - chainage_m) < _CHAINAGE_TOLERANCE_M and (best is None or row_id > best):
|
|
best = row_id
|
|
return best
|
|
|
|
# 행마다 한 벌 — 같은 행에 여러 줄이 오면 앞 줄 결과 위에 얹음(`CASE id` 는 첫 WHEN 만 씀 ·
|
|
# 318-4 옛 조정값 비움 뒤 새 값)
|
|
updates: dict[int, str] = {}
|
|
inserts: list[tuple[str, int, float, str]] = []
|
|
for chainage_m, payload in entries:
|
|
if not payload and not replace:
|
|
continue
|
|
# 얹기의 None = 그 열쇠 지움(JSON merge-patch · 318-4 관 종류 바꿈 옛 조정값)
|
|
drop = [] if replace else [key for key, value in payload.items() if value is None]
|
|
if drop:
|
|
payload = {key: value for key, value in payload.items() if value is not None}
|
|
row_id = find(chainage_m)
|
|
if row_id is None:
|
|
if project_id is None or not (payload or replace):
|
|
continue
|
|
inserts.append(
|
|
(
|
|
str(project_id),
|
|
route_id,
|
|
chainage_m,
|
|
json.dumps({"design": payload}, ensure_ascii=False),
|
|
)
|
|
)
|
|
continue
|
|
data = dict(existing[row_id])
|
|
if replace:
|
|
data["design"] = payload
|
|
else:
|
|
design = data.get("design")
|
|
design = dict(design) if isinstance(design, dict) else {}
|
|
design.update(payload)
|
|
for key in drop:
|
|
design.pop(key, None)
|
|
data["design"] = design
|
|
existing[row_id] = data
|
|
updates[row_id] = json.dumps(data, ensure_ascii=False)
|
|
|
|
written = 0
|
|
async with connection.cursor() as cursor:
|
|
if updates:
|
|
# 한 문장 — `CASE id WHEN … THEN …` 이라 왕복이 한 번이다.
|
|
cases = " ".join("WHEN %s THEN %s" for _ in updates)
|
|
params: list[Any] = []
|
|
for row_id, blob in updates.items():
|
|
params.extend((row_id, blob))
|
|
params.extend(updates)
|
|
placeholders = ", ".join("%s" for _ in updates)
|
|
await cursor.execute(
|
|
f"UPDATE cross_sections SET data = CASE id {cases} END "
|
|
f"WHERE id IN ({placeholders})",
|
|
params,
|
|
)
|
|
written += len(updates)
|
|
if inserts:
|
|
values = ", ".join("(%s, %s, %s, %s, 'DRAFT')" for _ in inserts)
|
|
flat: list[Any] = []
|
|
for item in inserts:
|
|
flat.extend(item)
|
|
await cursor.execute(
|
|
"INSERT INTO cross_sections (project_id, route_id, chainage_m, data, status) "
|
|
f"VALUES {values}",
|
|
flat,
|
|
)
|
|
written += len(inserts)
|
|
return written
|