feat: add project-scoped operations
This commit is contained in:
@@ -4,9 +4,9 @@ import asyncio
|
||||
from threading import BoundedSemaphore
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from pydantic import BaseModel
|
||||
from app.mcp.scenario_service import SCENARIO_TOOLS, calculate_scenario
|
||||
from app.mcp.errors import CalculationCapacityExceeded
|
||||
|
||||
# These services synchronously read SCADA/model series or write online results.
|
||||
# Topology-only isolation and model-based sensor placement need only BizDB.
|
||||
@@ -21,10 +21,25 @@ _slots = BoundedSemaphore(2)
|
||||
|
||||
def _calculate(name: str, parameters: dict[str, Any], network: str, username: str) -> dict[str, Any]:
|
||||
if not _slots.acquire(blocking=False):
|
||||
raise HTTPException(503, "Calculation capacity is occupied")
|
||||
raise CalculationCapacityExceeded()
|
||||
try:
|
||||
if name in SCENARIO_TOOLS:
|
||||
return calculate_scenario(name, parameters, network=network, username=username)
|
||||
if name == "calculate_demand_allocation":
|
||||
from app.services import demand_allocation
|
||||
target = parameters.pop("target")
|
||||
limit, offset = parameters.pop("limit"), parameters.pop("offset")
|
||||
demand = parameters["demand"]
|
||||
if target == "nodes":
|
||||
result = demand_allocation.calculate_demand_to_nodes(network, demand, parameters["node_ids"])
|
||||
elif target == "region":
|
||||
result = demand_allocation.calculate_demand_to_region(network, demand, parameters["region_id"])
|
||||
else:
|
||||
result = demand_allocation.calculate_demand_to_network(network, demand)
|
||||
keys = sorted(result)
|
||||
return {"items": [{"node_id": key, "demand": result[key]} for key in keys[offset:offset + limit]],
|
||||
"total": len(keys), "limit": limit, "offset": offset,
|
||||
"has_more": offset + limit < len(keys), "persisted": False}
|
||||
if name == "optimize_sensor_placement":
|
||||
from app.services.sensor_placement import optimize_sensor_placement_by_kmeans, optimize_sensor_placement_by_sensitivity
|
||||
parameters = dict(parameters)
|
||||
@@ -67,3 +82,15 @@ async def calculate(name: str, args: BaseModel, *, network: str, username: str)
|
||||
return await asyncio.to_thread(
|
||||
_calculate, name, args.model_dump(exclude={"context_handle", "timeout_seconds"}, exclude_none=True), network, username,
|
||||
)
|
||||
|
||||
|
||||
async def bounded_calculation(function, *args, **kwargs):
|
||||
"""Use the same native-work capacity for persistent calculation tools."""
|
||||
def run():
|
||||
if not _slots.acquire(blocking=False):
|
||||
raise CalculationCapacityExceeded()
|
||||
try:
|
||||
return function(*args, **kwargs)
|
||||
finally:
|
||||
_slots.release()
|
||||
return await asyncio.to_thread(run)
|
||||
|
||||
Reference in New Issue
Block a user