Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6def5c7
transformer asset integration initial commit
Rohith-Kanathur Mar 27, 2026
92dd883
added failure modes for transformer
Rohith-Kanathur Mar 29, 2026
4cfb668
add transformer health index prediction tool
Rohith-Kanathur Apr 1, 2026
22c21f1
added transformer asset docs
Rohith-Kanathur Apr 1, 2026
618e2a6
added mocks and docstrings
Rohith-Kanathur Apr 1, 2026
a059746
feat: implement scenario generator with --log, structured error handl…
Sagar-CK Apr 3, 2026
4189553
add summary of fetched study links
Sagar-CK Apr 3, 2026
b4156c3
feat(scenarios): add Scenario Generator Agent — multi-phase LLM pipel…
Sagar-CK Apr 3, 2026
f1a4ed4
add README.md for scenario generation pipeline
Sagar-CK Apr 3, 2026
8f3fbc5
update workflow reference to agent
Sagar-CK Apr 3, 2026
82b8fd4
for validated scenarios add truncation to adhere to budget
Sagar-CK Apr 3, 2026
38ef49b
Refine scenario retrieval and clean up generator flow
Sagar-CK Apr 7, 2026
974a63b
llm: allow optional max_tokens override in LiteLLMBackend.generate
Sagar-CK Apr 11, 2026
7d57037
servers: add CouchDB coverage APIs, FMSR failure-mode aliases, and Io…
Sagar-CK Apr 11, 2026
aafb7b1
scenarios: modular generator with grounding, constraints, and split p…
Sagar-CK Apr 11, 2026
ede66d1
Add certifi dependency for verified HTTPS in retrieval
Sagar-CK Apr 11, 2026
379fd13
Add Semantic Scholar retrieval, research digest synthesis, and PDF pr…
Sagar-CK Apr 11, 2026
f7d7b79
convert scenarios to package
Rohith-Kanathur Apr 11, 2026
cf7f470
feat(llm): return LLMResult with optional usage from generate()
Sagar-CK Apr 12, 2026
e90d9ae
refactor(agent): consume LLMResult.text in plan-execute and tests
Sagar-CK Apr 12, 2026
b45af08
refactor: use LLMResult.text in scenario pipeline and FMSR server
Sagar-CK Apr 12, 2026
6353d0a
docs: list IoT MCP tools as get_sites, get_assets, get_sensors, get_h…
Sagar-CK Apr 12, 2026
e930acd
test: align plan-execute and Claude runner tests with IoT get_* tool …
Sagar-CK Apr 12, 2026
563c2b1
test: call IoT MCP tools by get_sites, get_assets, get_sensors, get_h…
Sagar-CK Apr 12, 2026
de56fbc
feat(scenarios): add negative scenario track with stricter hardness a…
Sagar-CK Apr 21, 2026
9837baa
update README.md
Sagar-CK Apr 26, 2026
87bfdd7
Merge branch 'main' into feat/scenario-generator
Sagar-CK May 4, 2026
8d0f6eb
revert some changes
Rohith-Kanathur May 7, 2026
64f0ae6
revert some more changes
Rohith-Kanathur May 7, 2026
711d9f1
Revert "revert some more changes"
Rohith-Kanathur May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.public
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
IOT_DBNAME=chiller
WO_DBNAME=workorder
TRANSFORMER_DBNAME=transformer

# ── IBM WatsonX (plan-execute runner) ────────────────────────────────────────
WATSONX_APIKEY=
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ mcp/couchdb/sample_data/bulk_docs.json
.env
mcp/servers/tsfm/artifacts/tsfm_models/
src/tmp/
src/scenarios/tmp/

# ignore generated scenario outputs and manual scenario analyses
generated/

# Observability artifacts (OTLP-JSON traces + per-run trajectory JSON).
traces/
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ description = "Add your description here"
readme = "INSTRUCTIONS.md"
requires-python = ">=3.12"
dependencies = [
"certifi>=2024.8.30",
"couchdb3>=2.0.2",
"fastmcp>=2.14.5",
"mcp[cli]>=1.26.0",
Expand All @@ -29,6 +30,9 @@ dependencies = [
"langchain-openai>=1.1.0",
"python-dotenv>=1.0",
"scipy>=1.10.0",
"scikit-learn>=1.8.0",
"datasets>=4.8.4",
"pypdf>=6.9.2",
]

[project.scripts]
Expand Down
13 changes: 13 additions & 0 deletions src/couchdb/couchdb_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,18 @@ else
echo "⚠️ $VIBRATION_FILE not found, skipping vibration data."
fi

# Load transformer sample data (Transformer_01) into a dedicated database
TRANSFORMER_FILE="/sample_data/iot/bulk_docs_transformer.json"
if [ -f "$TRANSFORMER_FILE" ]; then
echo "Loading transformer data..."
COUCHDB_URL="http://localhost:5984" \
python3 /couchdb/init_asset_data.py \
--data-file "$TRANSFORMER_FILE" \
--db "${TRANSFORMER_DBNAME:-transformer}" \
--drop
else
echo "⚠️ $TRANSFORMER_FILE not found, skipping transformer data."
fi

echo "✅ All databases initialised."
tail -f /dev/null
1 change: 1 addition & 0 deletions src/couchdb/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ services:
IOT_DBNAME: chiller
WO_DBNAME: workorder
VIBRATION_DBNAME: vibration
TRANSFORMER_DBNAME: transformer
ports:
- "5984:5984"
volumes:
Expand Down
172 changes: 172 additions & 0 deletions src/couchdb/sample_data/iot/bulk_docs_transformer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
[
{
"asset_id": "Transformer 1",
"timestamp": "2024-01-15T00:00:00.000000",
"hydrogen": 2845,
"oxygen": 5860,
"nitrogen": 27842,
"methane": 7406,
"co": 32,
"co2": 1344,
"ethylene": 16684,
"ethane": 5467,
"acetylene": 7,
"dbds": 19,
"power_factor": 1,
"interfacial_v": 45,
"dielectric_rigidity": 55,
"water_content": 0,
"vl1": 110,
"vl2": 115,
"vl3": 108,
"il1": 100,
"il2": 105,
"il3": 95,
"vl12": 225,
"vl23": 220,
"vl31": 218,
"inut": 5,
"rated_mva": 100,
"wti": 80,
"oti": 85,
"ati": 78,
"oti_a": 2,
"oti_t": 1
},
{
"asset_id": "Transformer 1",
"timestamp": "2024-01-16T00:00:00.000000",
"hydrogen": 2950,
"oxygen": 5700,
"nitrogen": 28000,
"methane": 7200,
"co": 30,
"co2": 1300,
"ethylene": 16500,
"ethane": 5400,
"acetylene": 6,
"dbds": 18,
"power_factor": 1.2,
"interfacial_v": 44,
"dielectric_rigidity": 53,
"water_content": 1,
"vl1": 112,
"vl2": 114,
"vl3": 109,
"il1": 102,
"il2": 103,
"il3": 96,
"vl12": 226,
"vl23": 221,
"vl31": 219,
"inut": 6,
"rated_mva": 100,
"wti": 82,
"oti": 87,
"ati": 79,
"oti_a": 2,
"oti_t": 1
},
{
"asset_id": "Transformer 1",
"timestamp": "2024-01-17T00:00:00.000000",
"hydrogen": 2800,
"oxygen": 5900,
"nitrogen": 27700,
"methane": 7300,
"co": 35,
"co2": 1350,
"ethylene": 16700,
"ethane": 5500,
"acetylene": 8,
"dbds": 20,
"power_factor": 0.9,
"interfacial_v": 46,
"dielectric_rigidity": 56,
"water_content": 0,
"vl1": 109,
"vl2": 116,
"vl3": 107,
"il1": 101,
"il2": 104,
"il3": 94,
"vl12": 224,
"vl23": 219,
"vl31": 217,
"inut": 5,
"rated_mva": 100,
"wti": 79,
"oti": 84,
"ati": 77,
"oti_a": 1,
"oti_t": 1
},
{
"asset_id": "Transformer 1",
"timestamp": "2024-01-18T00:00:00.000000",
"hydrogen": 2900,
"oxygen": 5800,
"nitrogen": 27950,
"methane": 7350,
"co": 33,
"co2": 1325,
"ethylene": 16600,
"ethane": 5450,
"acetylene": 7,
"dbds": 19,
"power_factor": 1.1,
"interfacial_v": 45,
"dielectric_rigidity": 54,
"water_content": 1,
"vl1": 111,
"vl2": 115,
"vl3": 108,
"il1": 100,
"il2": 106,
"il3": 95,
"vl12": 225,
"vl23": 220,
"vl31": 218,
"inut": 5,
"rated_mva": 100,
"wti": 81,
"oti": 86,
"ati": 78,
"oti_a": 2,
"oti_t": 1
},
{
"asset_id": "Transformer 1",
"timestamp": "2024-01-19T00:00:00.000000",
"hydrogen": 2850,
"oxygen": 5850,
"nitrogen": 27850,
"methane": 7400,
"co": 32,
"co2": 1340,
"ethylene": 16650,
"ethane": 5470,
"acetylene": 7,
"dbds": 19,
"power_factor": 1,
"interfacial_v": 45,
"dielectric_rigidity": 55,
"water_content": 0,
"vl1": 110,
"vl2": 115,
"vl3": 108,
"il1": 100,
"il2": 105,
"il3": 95,
"vl12": 225,
"vl23": 220,
"vl31": 218,
"inut": 5,
"rated_mva": 100,
"wti": 80,
"oti": 85,
"ati": 78,
"oti_a": 2,
"oti_t": 1
}
]
30 changes: 20 additions & 10 deletions src/llm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,44 @@

@dataclass(frozen=True)
class LLMResult:
"""Return type for :meth:`LLMBackend.generate_with_usage`.
"""Return type for :meth:`LLMBackend.generate` / :meth:`generate_with_usage`.

``input_tokens`` / ``output_tokens`` are ``0`` when the backend can't
report usage (e.g. mocks in unit tests).
``input_tokens`` / ``output_tokens`` / ``total_tokens`` are ``0`` when the
backend can't report usage (e.g. mocks in unit tests). ``total_tokens``
should reflect the provider total when available; otherwise callers may use
``input_tokens + output_tokens``.
"""

text: str
input_tokens: int = 0
output_tokens: int = 0
total_tokens: int = 0


class LLMBackend(ABC):
"""Abstract interface for LLM backends."""

@abstractmethod
def generate(self, prompt: str, temperature: float = 0.0) -> str:
"""Generate text given a prompt."""
def generate(
self,
prompt: str,
temperature: float = 0.0,
max_tokens: int | None = None,
) -> LLMResult:
"""Generate text given a prompt; includes usage when the backend provides it."""
...

def generate_with_usage(
self, prompt: str, temperature: float = 0.0
self,
prompt: str,
temperature: float = 0.0,
max_tokens: int | None = None,
) -> LLMResult:
"""Generate text and report token usage.

Default impl delegates to :meth:`generate` and reports zero usage
backends that can surface counts (e.g. LiteLLM) should override.
Default impl delegates to :meth:`generate` (same fields as usage-aware
backends).
"""
return LLMResult(text=self.generate(prompt, temperature))
return self.generate(prompt, temperature, max_tokens)

@property
def model_id(self) -> str:
Expand Down
35 changes: 28 additions & 7 deletions src/llm/litellm.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@

from __future__ import annotations

import logging
import os

from .base import LLMBackend, LLMResult

_log = logging.getLogger(__name__)
_WATSONX_PREFIX = "watsonx/"


Expand All @@ -33,19 +35,30 @@ class LiteLLMBackend(LLMBackend):
def __init__(self, model_id: str) -> None:
self._model_id = model_id

def generate(self, prompt: str, temperature: float = 0.0) -> str:
return self.generate_with_usage(prompt, temperature).text
def generate(
self,
prompt: str,
temperature: float = 0.0,
max_tokens: int | None = None,
) -> LLMResult:
return self.generate_with_usage(prompt, temperature, max_tokens)

def generate_with_usage(
self, prompt: str, temperature: float = 0.0
self,
prompt: str,
temperature: float = 0.0,
max_tokens: int | None = None,
) -> LLMResult:
import litellm

kwargs: dict = {
"model": self._model_id,
"messages": [{"role": "user", "content": prompt}],
"temperature": temperature,
"max_tokens": 2048,
"max_tokens": max_tokens or 2048,
# Explicit non-streaming: avoids union defaults and reduces Pydantic
# serializer warnings (Choices vs StreamingChoices) on model_dump.
"stream": False,
}

if self._model_id.startswith(_WATSONX_PREFIX):
Expand All @@ -59,8 +72,16 @@ def generate_with_usage(

response = litellm.completion(**kwargs)
usage = getattr(response, "usage", None)
prompt_n = int(getattr(usage, "prompt_tokens", 0) or 0)
completion_n = int(getattr(usage, "completion_tokens", 0) or 0)
raw_total = getattr(usage, "total_tokens", None) if usage else None
total_n = int(raw_total) if raw_total is not None else prompt_n + completion_n
text = response.choices[0].message.content
if text is None:
text = ""
return LLMResult(
text=response.choices[0].message.content,
input_tokens=int(getattr(usage, "prompt_tokens", 0) or 0),
output_tokens=int(getattr(usage, "completion_tokens", 0) or 0),
text=text,
input_tokens=prompt_n,
output_tokens=completion_n,
total_tokens=total_n,
)
Loading