백엔드 로그 추가

This commit is contained in:
2026-07-18 18:31:21 +09:00
parent 050a5a2ba3
commit cb86e4b40f
5 changed files with 177 additions and 22 deletions
+18
View File
@@ -0,0 +1,18 @@
"""B05 DEBUG 모드 전용 구조화 로그."""
import json
import logging
from typing import Any
from config.config_system import DEBUG
def log_b05_debug(logger: logging.Logger, event: str, **payload: Any) -> None:
"""DEBUG=True일 때만 B05 계산·DB 저장 정보를 출력한다."""
if not DEBUG:
return
logger.info(
"[B05 DEBUG] %s | %s",
event,
json.dumps(payload, ensure_ascii=False, default=str),
)
+45 -1
View File
@@ -5,9 +5,11 @@ GeoJSON으로 저장하며 DB 기록용 데이터(메타·렌더링 샘플·통
라우터에서 asyncio.to_thread로 호출한다.
"""
import logging
from pathlib import Path
from typing import Any
from B05_wf2_Route.B05_wf2_Route_Debug import log_b05_debug
from B05_wf2_Route.B05_wf2_Route_Engine_RidgeValley import solve_ridge_valley_route
from B05_wf2_Route.B05_wf2_Route_Engine_Solver import solve_optimal_route
from common_util.common_util_json import atomic_write_json
@@ -15,6 +17,7 @@ from common_util.common_util_json import atomic_write_json
_ROUTE_SUBDIR = Path("B05_wf2_Route") / "route"
# route_points 테이블에 저장할 렌더링 샘플 최대 개수
_MAX_RENDER_POINTS = 500
logger = logging.getLogger(__name__)
def _route_geojson(polyline: list[list[float]]) -> dict[str, Any]:
@@ -87,6 +90,22 @@ def run_route_design(
- render_points: route_points 테이블 저장용 샘플
- statistics: route_statistics 저장용 요약
"""
log_b05_debug(
logger,
"calculation.start",
algorithm=algorithm,
filter_key=filter_key,
method=method,
smooth=smooth,
point_counts={
"bp": 1 if points_data.get("bp") else 0,
"ep": 1 if points_data.get("ep") else 0,
"cp": len(points_data.get("cp", [])),
"ap": len(points_data.get("ap", [])),
"fp": len(points_data.get("fp", [])),
},
options=options,
)
if algorithm == "ridge_valley":
result = solve_ridge_valley_route(
project_root, filter_key, smooth, points_data, options, method=method
@@ -97,11 +116,27 @@ def run_route_design(
)
polyline = result["polyline"]
chainage_m = result["chainage_m"]
log_b05_debug(
logger,
"calculation.solver_complete",
algorithm=algorithm,
polyline_count=len(polyline),
segment_count=len(result.get("segments", [])),
metrics=result.get("metrics", {}),
required_points_ok=result.get("required_points_ok"),
warning_count=len(result.get("curve_warning_segments", [])),
)
route_dir = project_root / _ROUTE_SUBDIR
route_dir.mkdir(parents=True, exist_ok=True)
geojson_path = route_dir / "route_main.geojson"
atomic_write_json(geojson_path, _route_geojson(polyline))
log_b05_debug(
logger,
"calculation.geojson_saved",
path=geojson_path.relative_to(project_root).as_posix(),
coordinate_count=len(polyline),
)
# 통계 요약 (solver 메트릭에서 파생)
metrics = result["metrics"]
@@ -112,10 +147,19 @@ def run_route_design(
"cost_score": None,
}
render_points = _sample_render_points(polyline, chainage_m, _MAX_RENDER_POINTS)
log_b05_debug(
logger,
"calculation.output_prepared",
render_point_count=len(render_points),
first_render_point=render_points[0] if render_points else None,
last_render_point=render_points[-1] if render_points else None,
statistics=statistics,
)
return {
"route_data_path": geojson_path.relative_to(project_root).as_posix(),
"solver_result": result,
"render_points": _sample_render_points(polyline, chainage_m, _MAX_RENDER_POINTS),
"render_points": render_points,
"statistics": statistics,
"grade_percent": [seg.get("max_grade_pct") for seg in result.get("segments", [])],
"constraints": result.get("conditions_snapshot", {}),
+105 -19
View File
@@ -10,6 +10,7 @@ from fastapi import APIRouter
from fastapi.responses import JSONResponse
from B03_FileInput.B03_FileInput_Repository import get_project_storage_relative_path
from B05_wf2_Route.B05_wf2_Route_Debug import log_b05_debug
from B05_wf2_Route.B05_wf2_Route_Engine import run_route_design
from B05_wf2_Route.B05_wf2_Route_Repository import (
confirm_route,
@@ -55,10 +56,23 @@ async def solve_route(
"algorithm": request.algorithm,
"surface_model_id": request.surface_model_id,
}
log_b05_debug(
logger,
"request.solve",
project_id=str(project_id),
workflow_stage_params=params,
)
async with pool.acquire() as connection:
async with connection.cursor() as cursor:
await start_stage(cursor, str(project_id), 2, params)
await connection.commit()
log_b05_debug(
logger,
"db.project_workflow_stages.start_stage_committed",
project_id=str(project_id),
stage_no=2,
params=params,
)
stored_path = await get_project_storage_relative_path(connection, project_id)
project_root = Path(resolve_stored_project_path(stored_path))
@@ -76,38 +90,88 @@ async def solve_route(
)
solver = design["solver_result"]
metrics = solver["metrics"]
log_b05_debug(
logger,
"calculation.complete",
project_id=str(project_id),
metrics=metrics,
required_points_ok=solver.get("required_points_ok"),
route_data_path=design["route_data_path"],
)
await connection.begin()
try:
route_id = await create_route(
connection,
project_id=project_id,
surface_model_id=request.surface_model_id,
total_length_m=metrics.get("length_m"),
start_chainage_m=0.0,
end_chainage_m=metrics.get("length_m"),
grade_percent=design["grade_percent"],
constraints=design["constraints"],
algorithm_params={
route_record = {
"project_id": str(project_id),
"surface_model_id": request.surface_model_id,
"status": "DRAFT",
"start_chainage_m": 0.0,
"end_chainage_m": metrics.get("length_m"),
"total_length_m": metrics.get("length_m"),
"grade_percent": design["grade_percent"],
"constraints": design["constraints"],
"algorithm_params": {
**design["algorithm_params"],
"metrics": metrics,
"curve_warning_segments": solver.get("curve_warning_segments", []),
},
route_data_path=design["route_data_path"],
"route_data_path": design["route_data_path"],
}
log_b05_debug(logger, "db.routes.insert", record=route_record)
route_id = await create_route(
connection,
project_id=project_id,
surface_model_id=route_record["surface_model_id"],
total_length_m=route_record["total_length_m"],
start_chainage_m=route_record["start_chainage_m"],
end_chainage_m=route_record["end_chainage_m"],
grade_percent=route_record["grade_percent"],
constraints=route_record["constraints"],
algorithm_params=route_record["algorithm_params"],
route_data_path=route_record["route_data_path"],
)
await insert_route_points(connection, route_id, design["render_points"])
render_points = design["render_points"]
log_b05_debug(
logger,
"db.route_points.insert_many",
route_id=route_id,
row_count=len(render_points),
first_row=render_points[0] if render_points else None,
last_row=render_points[-1] if render_points else None,
)
await insert_route_points(connection, route_id, render_points)
stats = design["statistics"]
statistics_record = {
"route_id": route_id,
"min_slope": stats["min_slope"],
"max_slope": stats["max_slope"],
"mean_slope": stats["mean_slope"],
"cost_score": stats["cost_score"],
}
log_b05_debug(
logger,
"db.route_statistics.insert",
record=statistics_record,
)
await create_route_statistics(
connection,
route_id=route_id,
min_slope=stats["min_slope"],
max_slope=stats["max_slope"],
mean_slope=stats["mean_slope"],
cost_score=stats["cost_score"],
**statistics_record,
)
await connection.commit()
except Exception:
log_b05_debug(
logger,
"db.route_transaction.committed",
project_id=str(project_id),
route_id=route_id,
)
except Exception as exc:
await connection.rollback()
log_b05_debug(
logger,
"db.route_transaction.rolled_back",
project_id=str(project_id),
reason=str(exc),
)
raise
return RouteSolveResponse(
@@ -188,12 +252,34 @@ async def confirm_latest_route(project_id: UUID) -> RouteConfirmResponse | JSONR
)
await connection.begin()
try:
log_b05_debug(
logger,
"db.routes.confirm",
project_id=str(project_id),
route_id=latest["id"],
previous_status=latest["status"],
next_status="CONFIRMED",
)
await confirm_route(connection, latest["id"])
async with connection.cursor() as cursor:
await complete_stage(cursor, str(project_id), 2)
await connection.commit()
except Exception:
log_b05_debug(
logger,
"db.route_confirmation.committed",
project_id=str(project_id),
route_id=latest["id"],
completed_stage=2,
)
except Exception as exc:
await connection.rollback()
log_b05_debug(
logger,
"db.route_confirmation.rolled_back",
project_id=str(project_id),
route_id=latest["id"],
reason=str(exc),
)
raise
return RouteConfirmResponse(project_id=str(project_id), route_id=latest["id"])
except Exception:
+1
View File
@@ -224,6 +224,7 @@ export async function renderB05Route(root: HTMLElement): Promise<void> {
await confirmRoute(activeProjectId);
renderLatest(await fetchLatestRoute(activeProjectId));
showToast("경로를 확정했습니다.", "success");
goToWorkflowStage(activeProjectId, WORKFLOW_STEP_ROUTES[3]);
} catch (error) {
showToast(error instanceof Error ? error.message : "경로 확정에 실패했습니다.", "error");
} finally {
+8 -2
View File
@@ -29,16 +29,19 @@
.b05-route__panel {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
gap: var(--spacing-16);
min-width: 300px;
padding-bottom: var(--spacing-24);
}
.b05-route__panel-section {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
gap: var(--spacing-8);
padding: var(--spacing-16);
padding: calc(var(--spacing-8) + var(--spacing-4));
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
@@ -55,6 +58,7 @@
.b05-route__metrics {
display: flex;
flex-direction: column;
min-width: 0;
gap: var(--spacing-8);
}
@@ -69,6 +73,7 @@
.b05-route__field select,
.b05-route__panel-section > select {
width: 100%;
min-width: 0;
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
@@ -86,6 +91,7 @@
.b05-route__palette {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
min-width: 0;
gap: var(--spacing-8);
}