File size: 4,546 Bytes
95c00ee fa7bc29 95c00ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | from __future__ import annotations
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from backend.agents.runtime.contracts import CommandContract
from database.models_platform import ControlCommandDB
from database.session import SessionLocal
def _now():
return datetime.now(timezone.utc).replace(tzinfo=None)
def _dump(row: ControlCommandDB) -> Dict[str, Any]:
return {
"id": row.id,
"command_id": row.command_id,
"opportunity": row.opportunity,
"building_id": row.building_id,
"equipment_id": row.equipment_id,
"point_id": row.point_id,
"old_value": row.old_value,
"new_value": row.new_value,
"reason": row.reason,
"engine_version": row.engine_version,
"config_version": row.config_version,
"safety_gates": row.safety_gates,
"requested_by": row.requested_by,
"approval_id": row.approval_id,
"status": row.status,
"created_at": row.created_at.isoformat() if row.created_at else None,
"applied_at": row.applied_at.isoformat() if row.applied_at else None,
"verified_at": row.verified_at.isoformat() if row.verified_at else None,
"rollback_at": row.rollback_at.isoformat() if row.rollback_at else None,
"payload_json": row.payload_json,
}
def propose(contract: CommandContract, status: str = "PROPOSED") -> Dict[str, Any]:
cid = contract.command_id or f"cmd_{uuid.uuid4().hex[:12]}"
contract.command_id = cid
db = SessionLocal()
try:
row = ControlCommandDB(
id=cid,
command_id=cid,
opportunity=contract.opportunity,
building_id=contract.building,
equipment_id=contract.equipment,
point_id=contract.point,
old_value=contract.old_value,
new_value=contract.new_value,
reason=contract.reason,
engine_version=contract.engine_version,
config_version=contract.config_version,
safety_gates=contract.safety_gates,
requested_by=contract.requested_by,
approval_id=contract.approval_id,
status=status,
created_at=_now(),
payload_json=contract.as_dict(),
)
db.add(row)
db.commit()
db.refresh(row)
return _dump(row)
finally:
db.close()
def set_status(command_id: str, status: str, **times) -> Optional[Dict[str, Any]]:
db = SessionLocal()
try:
row = db.query(ControlCommandDB).filter_by(command_id=command_id).first()
if not row:
return None
row.status = status
for key in ("applied_at", "verified_at", "rollback_at"):
if times.get(key):
setattr(row, key, times[key] if not isinstance(times[key], bool) else _now())
if status == "APPLIED":
row.applied_at = _now()
if status == "VERIFIED":
row.verified_at = _now()
if status == "ROLLED_BACK":
row.rollback_at = _now()
db.commit()
return _dump(row)
finally:
db.close()
def get_command(command_id: str) -> Optional[Dict[str, Any]]:
db = SessionLocal()
try:
row = db.query(ControlCommandDB).filter_by(command_id=command_id).first()
return _dump(row) if row else None
finally:
db.close()
def list_commands(building_id: Optional[str] = None, limit: int = 50) -> List[Dict[str, Any]]:
db = SessionLocal()
try:
q = db.query(ControlCommandDB).order_by(ControlCommandDB.created_at.desc())
if building_id:
q = q.filter(ControlCommandDB.building_id == building_id)
return [_dump(r) for r in q.limit(limit).all()]
except Exception:
return []
finally:
db.close()
def active_for_point(point_id: str) -> Optional[Dict[str, Any]]:
db = SessionLocal()
try:
row = (
db.query(ControlCommandDB)
.filter(
ControlCommandDB.point_id == point_id,
ControlCommandDB.status.in_(("APPROVED", "APPLYING", "APPLIED", "VERIFYING")),
)
.order_by(ControlCommandDB.created_at.desc())
.first()
)
return _dump(row) if row else None
except Exception:
return None
finally:
db.close()
|