Initial CivilPlan MCP implementation
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
|
||||
from civilplan_mcp.config import get_settings
|
||||
from civilplan_mcp.models import ProjectDomain
|
||||
from civilplan_mcp.tools._base import wrap_response
|
||||
|
||||
|
||||
def _resolve_output_dir(project_spec: dict[str, Any]) -> Path:
|
||||
if project_spec.get("output_dir"):
|
||||
path = Path(project_spec["output_dir"])
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
return path
|
||||
return get_settings().output_dir
|
||||
|
||||
|
||||
def _price_lookup(unit_prices: dict[str, Any]) -> dict[str, int]:
|
||||
return {item["item"]: item["adjusted_price"] for item in unit_prices["results"]}
|
||||
|
||||
|
||||
def generate_boq_excel(
|
||||
*,
|
||||
project_name: str,
|
||||
project_spec: dict[str, Any],
|
||||
quantities: dict[str, Any],
|
||||
unit_prices: dict[str, Any],
|
||||
region: str,
|
||||
year: int,
|
||||
output_filename: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
output_dir = _resolve_output_dir(project_spec)
|
||||
path = output_dir / (output_filename or f"{project_name}_{year}.xlsx")
|
||||
workbook = Workbook()
|
||||
overview = workbook.active
|
||||
overview.title = "사업개요"
|
||||
boq_sheet = workbook.create_sheet("사업내역서(BOQ)")
|
||||
workbook.create_sheet("물량산출근거")
|
||||
workbook.create_sheet("간접비산출")
|
||||
workbook.create_sheet("총사업비요약")
|
||||
workbook.create_sheet("연도별투자계획")
|
||||
|
||||
header_fill = PatternFill("solid", fgColor="1F4E79")
|
||||
header_font = Font(color="FFFFFF", bold=True)
|
||||
|
||||
overview["A1"] = "CivilPlan BOQ"
|
||||
overview["A2"] = project_name
|
||||
overview["A3"] = "본 자료는 개략 검토용(±20~30% 오차)으로 공식 발주·계약·제출에 사용 불가합니다."
|
||||
|
||||
boq_sheet.append(["공종", "항목", "수량", "단위단가", "금액"])
|
||||
for cell in boq_sheet[1]:
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
|
||||
price_map = _price_lookup(unit_prices)
|
||||
direct_cost = 0
|
||||
for category, items in quantities["quantities"].items():
|
||||
for item_name, quantity in items.items():
|
||||
mapped_price = next((price for name, price in price_map.items() if name.split("(")[0] in item_name or item_name in name), 50000)
|
||||
amount = round(float(quantity) * mapped_price)
|
||||
direct_cost += amount
|
||||
boq_sheet.append([category, item_name, quantity, mapped_price, amount])
|
||||
|
||||
indirect_cost = round(direct_cost * 0.185)
|
||||
total_cost = direct_cost + indirect_cost
|
||||
|
||||
summary_sheet = workbook["총사업비요약"]
|
||||
summary_sheet.append(["직접공사비", direct_cost])
|
||||
summary_sheet.append(["간접비", indirect_cost])
|
||||
summary_sheet.append(["총사업비", total_cost])
|
||||
|
||||
plan_sheet = workbook["연도별투자계획"]
|
||||
start = project_spec.get("year_start", year)
|
||||
end = project_spec.get("year_end", year)
|
||||
years = list(range(start, end + 1))
|
||||
default_ratios = [0.3, 0.5, 0.2][: len(years)] or [1.0]
|
||||
if len(default_ratios) < len(years):
|
||||
default_ratios = [round(1 / len(years), 2) for _ in years]
|
||||
for target_year, ratio in zip(years, default_ratios):
|
||||
plan_sheet.append([target_year, round(total_cost * ratio)])
|
||||
|
||||
workbook.save(path)
|
||||
|
||||
return wrap_response(
|
||||
{
|
||||
"status": "success",
|
||||
"file_path": str(path),
|
||||
"summary": {
|
||||
"direct_cost": direct_cost,
|
||||
"indirect_cost": indirect_cost,
|
||||
"total_cost": total_cost,
|
||||
"total_cost_billion": round(total_cost / 100000000, 2),
|
||||
"sheets": workbook.sheetnames,
|
||||
"region": region,
|
||||
"year": year,
|
||||
},
|
||||
},
|
||||
ProjectDomain.복합,
|
||||
)
|
||||
Reference in New Issue
Block a user