160 lines
5.0 KiB
Python
160 lines
5.0 KiB
Python
from pathlib import Path
|
|
|
|
import ezdxf
|
|
|
|
from civilplan_mcp.config import Settings
|
|
from civilplan_mcp.tools.benchmark_validator import validate_against_benchmark
|
|
from civilplan_mcp.tools.budget_report_generator import generate_budget_report
|
|
from civilplan_mcp.tools.dxf_generator import generate_dxf_drawing
|
|
from civilplan_mcp.tools.feasibility_analyzer import analyze_feasibility
|
|
from civilplan_mcp.tools.land_info_query import (
|
|
build_land_use_bbox_params,
|
|
extract_address_result,
|
|
extract_feature_properties,
|
|
query_land_info,
|
|
)
|
|
from civilplan_mcp.tools.project_parser import parse_project
|
|
from civilplan_mcp.tools.quantity_estimator import estimate_quantities
|
|
|
|
|
|
def test_query_land_info_returns_graceful_message_without_api_keys() -> None:
|
|
result = query_land_info(address="경기도 양주시 덕계동 123", pnu=None)
|
|
|
|
assert result["status"] == "disabled"
|
|
|
|
|
|
def test_settings_keep_public_data_key_for_nara_usage() -> None:
|
|
settings = Settings(data_go_kr_api_key="abc")
|
|
|
|
assert settings.data_go_kr_api_key == "abc"
|
|
|
|
|
|
def test_extract_address_result_reads_vworld_shape() -> None:
|
|
payload = {
|
|
"response": {
|
|
"status": "OK",
|
|
"result": {
|
|
"point": {"x": "127.061", "y": "37.821"},
|
|
"items": [
|
|
{
|
|
"id": "4163010100101230000",
|
|
"address": {"parcel": "경기도 양주시 덕계동 123"}
|
|
}
|
|
],
|
|
},
|
|
}
|
|
}
|
|
|
|
parsed = extract_address_result(payload)
|
|
assert parsed["pnu"] == "4163010100101230000"
|
|
assert parsed["x"] == 127.061
|
|
|
|
|
|
def test_extract_feature_properties_returns_first_feature() -> None:
|
|
payload = {
|
|
"response": {
|
|
"status": "OK",
|
|
"result": {
|
|
"featureCollection": {
|
|
"features": [
|
|
{"properties": {"pnu": "4163010100101230000", "jimok": "대"}}
|
|
]
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
parsed = extract_feature_properties(payload)
|
|
assert parsed["jimok"] == "대"
|
|
|
|
|
|
def test_build_land_use_bbox_params_uses_wfs_bbox_pattern() -> None:
|
|
params = build_land_use_bbox_params(127.0, 37.0, "test-key")
|
|
|
|
assert params["SERVICE"] == "WFS"
|
|
assert params["REQUEST"] == "GetFeature"
|
|
assert params["TYPENAME"] == "lt_c_lhblpn"
|
|
assert params["SRSNAME"] == "EPSG:4326"
|
|
assert params["KEY"] == "test-key"
|
|
assert params["OUTPUTFORMAT"] == "application/json"
|
|
assert params["BBOX"] == "126.9995,36.9995,127.0005,37.0005,EPSG:4326"
|
|
|
|
|
|
def test_analyze_feasibility_returns_positive_cost_structure() -> None:
|
|
result = analyze_feasibility(
|
|
land_area_m2=600,
|
|
land_price_per_m2=850000,
|
|
land_price_multiplier=1.0,
|
|
construction_cost_total=890000000,
|
|
other_costs_million=50,
|
|
revenue_type="임대",
|
|
building_floor_area_m2=720,
|
|
sale_price_per_m2=None,
|
|
monthly_rent_per_m2=16000,
|
|
vacancy_rate_pct=10.0,
|
|
operating_expense_pct=20.0,
|
|
equity_ratio_pct=30.0,
|
|
loan_rate_pct=5.5,
|
|
loan_term_years=10,
|
|
construction_months=24,
|
|
sale_months=12,
|
|
)
|
|
|
|
assert result["cost_structure"]["total_investment"] > 0
|
|
assert "irr_pct" in result["returns"]
|
|
|
|
|
|
def test_validate_against_benchmark_includes_bid_warning() -> None:
|
|
result = validate_against_benchmark(
|
|
project_type="소로_도로",
|
|
road_length_m=890,
|
|
floor_area_m2=None,
|
|
region="경기도",
|
|
our_estimate_won=1067000000,
|
|
)
|
|
|
|
assert "낙찰가 ≠ 사업비" in result["bid_rate_reference"]["경고"]
|
|
|
|
|
|
def test_generate_budget_report_creates_docx(tmp_path: Path) -> None:
|
|
project_data = parse_project(description="복지관 신축 2026~2028 경기도")
|
|
project_data["output_dir"] = str(tmp_path)
|
|
result = generate_budget_report(
|
|
report_type="예산편성요구서",
|
|
project_data=project_data,
|
|
boq_summary={"total_cost": 1067000000, "direct_cost": 900422000, "indirect_cost": 166578000},
|
|
department="도로과",
|
|
requester="22B Labs",
|
|
output_filename="budget.docx",
|
|
)
|
|
|
|
assert Path(result["file_path"]).exists()
|
|
|
|
|
|
def test_generate_dxf_drawing_creates_dxf(tmp_path: Path) -> None:
|
|
project_spec = parse_project(description="소로 신설 L=890m B=6m 경기도")
|
|
project_spec["output_dir"] = str(tmp_path)
|
|
quantities = estimate_quantities(
|
|
road_class="소로",
|
|
length_m=890,
|
|
width_m=6.0,
|
|
terrain="평지",
|
|
pavement_type="아스콘",
|
|
include_water_supply=False,
|
|
include_sewage=False,
|
|
include_retaining_wall=False,
|
|
include_bridge=False,
|
|
bridge_length_m=0.0,
|
|
)
|
|
|
|
result = generate_dxf_drawing(
|
|
drawing_type="횡단면도",
|
|
project_spec=project_spec,
|
|
quantities=quantities,
|
|
scale="1:200",
|
|
output_filename="road.dxf",
|
|
)
|
|
|
|
doc = ezdxf.readfile(result["file_path"])
|
|
assert doc is not None
|