백엔드 로그 추가
This commit is contained in:
@@ -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),
|
||||
)
|
||||
@@ -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", {}),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user