Spaces:
Running
Running
Update agents/live/app/services/sql_agent.py
Browse files
agents/live/app/services/sql_agent.py
CHANGED
|
@@ -382,6 +382,66 @@ FROM tank_reading
|
|
| 382 |
|
| 383 |
def try_builtin_query(self, question: str, asset_name: str, schema_name: str | None = None, asset_id: str | None = None) -> SQLPlan | None:
|
| 384 |
lowered = question.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
if not asset_name:
|
| 386 |
return None
|
| 387 |
|
|
|
|
| 382 |
|
| 383 |
def try_builtin_query(self, question: str, asset_name: str, schema_name: str | None = None, asset_id: str | None = None) -> SQLPlan | None:
|
| 384 |
lowered = question.lower()
|
| 385 |
+
|
| 386 |
+
# Multi-asset query check (e.g., "coldroom 1,2,3", "ct1,2,3", "tank 1 and 2", "coldroom 1, coldroom 2, coldroom 3")
|
| 387 |
+
multi_asset_match = re.findall(r'\b(coldroom|cold\s+room|ct|st|physical|tank|boiler|machine)\s*[-_]?\s*(\d+)\b', lowered)
|
| 388 |
+
digits = re.findall(r'\d+', lowered)
|
| 389 |
+
if (len(multi_asset_match) >= 2 or (',' in lowered and len(digits) >= 2)) and digits:
|
| 390 |
+
prefix = multi_asset_match[0][0].replace(" ", "") if multi_asset_match else "coldroom"
|
| 391 |
+
schema = schema_name or self.settings.openremote_schema
|
| 392 |
+
target_norms = []
|
| 393 |
+
for d in digits:
|
| 394 |
+
if prefix in ('coldroom', 'cold'):
|
| 395 |
+
target_norms.extend([f"coldroom{d}", f"coldroom0{d}"])
|
| 396 |
+
elif prefix in ('ct', 'tank', 'physical', 'st'):
|
| 397 |
+
target_norms.extend([f"ct{d}", f"tank{d}", f"physical{int(d):02d}", f"physical{d}"])
|
| 398 |
+
elif prefix == 'boiler':
|
| 399 |
+
target_norms.extend([f"boiler{d}", f"boiler0{d}"])
|
| 400 |
+
else:
|
| 401 |
+
target_norms.append(f"{prefix}{d}")
|
| 402 |
+
|
| 403 |
+
if target_norms:
|
| 404 |
+
in_clause = ", ".join(f"'{tn}'" for tn in target_norms)
|
| 405 |
+
sql = f"""
|
| 406 |
+
WITH target_assets AS (
|
| 407 |
+
SELECT DISTINCT ON (REPLACE(LOWER(name), ' ', ''))
|
| 408 |
+
id, name
|
| 409 |
+
FROM {schema}.asset
|
| 410 |
+
WHERE REPLACE(LOWER(name), ' ', '') IN ({in_clause})
|
| 411 |
+
ORDER BY REPLACE(LOWER(name), ' ', ''), version DESC
|
| 412 |
+
),
|
| 413 |
+
latest_dp AS (
|
| 414 |
+
SELECT DISTINCT ON (entity_id, attribute_name)
|
| 415 |
+
entity_id, attribute_name, value, timestamp
|
| 416 |
+
FROM {schema}.asset_datapoint
|
| 417 |
+
WHERE entity_id IN (SELECT id FROM target_assets)
|
| 418 |
+
ORDER BY entity_id, attribute_name, timestamp DESC
|
| 419 |
+
),
|
| 420 |
+
agg_dp AS (
|
| 421 |
+
SELECT
|
| 422 |
+
entity_id,
|
| 423 |
+
COALESCE(jsonb_object_agg(attribute_name, value), '{{}}'::jsonb) AS sensor_data
|
| 424 |
+
FROM latest_dp
|
| 425 |
+
GROUP BY entity_id
|
| 426 |
+
)
|
| 427 |
+
SELECT
|
| 428 |
+
ta.name AS asset_name,
|
| 429 |
+
NOW() + INTERVAL '3 hours' AS local_time,
|
| 430 |
+
(
|
| 431 |
+
COALESCE(
|
| 432 |
+
(SELECT jsonb_object_agg(key, val->'value')
|
| 433 |
+
FROM jsonb_each(a.attributes::jsonb) AS x(key, val)
|
| 434 |
+
WHERE val->'value' IS NOT NULL AND key NOT IN ('notes', 'location', 'info', 'displayIndex')),
|
| 435 |
+
'{{}}'::jsonb
|
| 436 |
+
) || COALESCE(ad.sensor_data, '{{}}'::jsonb)
|
| 437 |
+
) AS sensor_data
|
| 438 |
+
FROM target_assets ta
|
| 439 |
+
JOIN {schema}.asset a ON ta.id = a.id
|
| 440 |
+
LEFT JOIN agg_dp ad ON ta.id = ad.entity_id
|
| 441 |
+
ORDER BY ta.name ASC
|
| 442 |
+
""".strip()
|
| 443 |
+
return SQLPlan(needs_sql=True, scenario="multi_asset_comparison", sql=sql, answer_if_no_sql="")
|
| 444 |
+
|
| 445 |
if not asset_name:
|
| 446 |
return None
|
| 447 |
|