suvradeepp's picture
Deploy reno scheduling engine (FastAPI + Streamlit)
9c50399 verified
Raw
History Blame Contribute Delete
2.72 kB
"""Rules Engine: pure expansion of a BOQ item into activity + dependency drafts.
Template-driven (NO hardcoded if/else on procurement_type or category). Reads
activity_templates + dependency_templates via a repo; never writes to the DB.
"""
import re
from app.rules.errors import NoTemplateError
from app.rules.templates_repo import TemplatesRepo
from app.rules.types import ActivityDraft, BOQItemInput, DependencyDraft, ExpandResult
_PLACEHOLDER = re.compile(r"\{(\w+)\}")
def _render_template(template: str, boq: BOQItemInput) -> str:
return _PLACEHOLDER.sub(lambda m: str(getattr(boq, m.group(1), "") or ""), template)
def _num(value) -> float | None:
return None if value is None else float(value)
def expand(boq: BOQItemInput, repo: TemplatesRepo) -> ExpandResult:
templates = repo.find_activity_templates(
category_id=boq.category_id,
procurement_type=boq.procurement_type,
active=True,
)
if not templates:
raise NoTemplateError(boq.category_id, boq.procurement_type)
override_effort = boq.metadata.get("override_effort_days")
override_lead = boq.metadata.get("override_lead_time_days")
activities: list[ActivityDraft] = [
ActivityDraft(
boq_item_id=boq.id,
template_id=t["id"],
kind=t["kind"],
name=_render_template(t["name_template"], boq),
effort_days=_num(override_effort) if override_effort is not None else _num(t["default_effort_days"]),
lead_time_days=_num(override_lead) if override_lead is not None else _num(t["default_lead_time_days"]),
clock=t["clock"],
noisy=bool(t["noisy"]),
responsibility=(
boq.procurement_responsibility if t["kind"] == "PROCUREMENT" else "CONTRACTOR"
),
_tag=f"{boq.id}:{t['kind']}",
)
for t in templates
]
dep_rows = repo.find_dependency_templates(category_id=boq.category_id, active=True)
dependencies: list[DependencyDraft] = []
for d in dep_rows:
pred = next((a for a in activities if a.kind == d["predecessor_kind"]), None)
succ = next((a for a in activities if a.kind == d["successor_kind"]), None)
if pred and succ:
dependencies.append(
DependencyDraft(
predecessor_tag=pred._tag,
successor_tag=succ._tag,
dep_type=d["dep_type"],
lag_days=float(d["lag_days"]),
origin="TEMPLATE",
)
)
return ExpandResult(activities=activities, dependencies=dependencies)