Asoba Ona Documentation

PV Insight Service

Turn JEPA inverter anomaly detections into grounded O&M follow-up recommendations. The service retrieves site-relevant manuals and troubleshooting guides using hybrid RAG, then synthesizes a recommendation using Nehanda 27B (Asoba’s fine-tuned LLM for RAG synthesis).

πŸ‘οΈ Bounded Intelligence: JEPA World Models for Per-Inverter Anomaly Detection

How the System 1 / System 2 OODA architecture works β€” JEPA as the fast perceptual detector, RAG + Nehanda as the deliberate diagnostic layer. Per-inverter AUROC results and F1 comparisons against rule-based thresholds.

</a>

Architecture:

JEPA anomaly detection payload
  β”‚
  β”‚  action: 'pv-insight'  (via terminalApi)
  β–Ό
pvInsightService
  β”œβ”€ 1. Build retrieval query from detection fields
  β”‚      (manufacturer, power deviation, temp, streak, inverter state)
  β”‚
  β”œβ”€ 2. Hybrid retrieval
  β”‚      pgvector (BGE 768-d) + BM25 sparse + RRF fusion
  β”‚      + cross-encoder rerank β†’ top N chunks (default 5)
  β”‚
  β”œβ”€ 3. Build Nehanda synthesis prompt
  β”‚      O&M analyst persona + detection report + cited chunks
  β”‚
  └─ 4. Return enriched payload with llm_analysis

SDK Usage

PV Insight synthesis is exposed on the TerminalClient as run_pv_insight_synthesis (Python) and runPvInsightSynthesis (JavaScript). Pass a detection object β€” typically from run_detection / runDetection β€” and an optional user_query prompt.

Python

from asoba import OnaClient

client = OnaClient()

# Typically you get this from run_detection; here shown as a direct call
detection = {
    "asset_id": "INV-BN2441041190",
    "severity_label": "high",
    "severity_score": 0.82,
    "fault_type": "behavioral_anomaly",
    "summary": "Inverter 1 - World model anomaly score 0.0891 (Streak: 6)",
    "metrics": {
        "latest_power_kw": 45.2,
        "baseline_power_kw": 280.5,
        "latest_temperature_c": 68.3,
        "latest_inverter_state": 513,
        "world_model_streak_length": 6,
    },
    "energy_at_risk_kw": 235.3,
}

result = client.terminal.run_pv_insight_synthesis(
    detection=detection,
    user_query="Analyze JEPA Anomaly & Recommend BOM",  # default β€” can omit
)

analysis = result["llm_analysis"]
if analysis["status"] == "ok":
    print(f"Recommendation:\n{analysis['recommendation']}")
    for source in analysis["cited_sources"]:
        print(f"  Source: {source['doc_title']} β†’ {source['section_path']}")

JavaScript

const { OnaSDK } = require('@asobacloud/sdk');

const sdk = new OnaSDK({
  region: 'af-south-1',
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  endpoints: {
    terminal: process.env.ASOBA_TERMINAL_ENDPOINT || 'https://api.asoba.co',
  },
  timeout: 90000,  // synthesis can take up to ~90s
});

const detection = {
  asset_id: 'INV-BN2441041190',
  severity_label: 'high',
  severity_score: 0.82,
  fault_type: 'behavioral_anomaly',
  summary: 'Inverter 1 - World model anomaly score 0.0891 (Streak: 6)',
  metrics: {
    latest_power_kw: 45.2,
    baseline_power_kw: 280.5,
    latest_temperature_c: 68.3,
    latest_inverter_state: 513,
    world_model_streak_length: 6,
  },
  energy_at_risk_kw: 235.3,
};

const result = await sdk.terminal.runPvInsightSynthesis({
  detection,
  user_query: 'Analyze JEPA Anomaly & Recommend BOM',  // default β€” can omit
});

const analysis = result.llm_analysis;
if (analysis.status === 'ok') {
  console.log('Recommendation:', analysis.recommendation);
  analysis.cited_sources.forEach(s => {
    console.log(`  Source: ${s.doc_title} β†’ ${s.section_path}`);
  });
}

Method Reference

Β  Python JavaScript
Method client.terminal.run_pv_insight_synthesis(detection, user_query=…) sdk.terminal.runPvInsightSynthesis({ detection, user_query })
detection dict β€” required Object β€” required
user_query str, default "Analyze JEPA Anomaly & Recommend BOM" string, same default
Returns dict with detection (echo) + llm_analysis Promise<Object>, same shape
Timeout 120 s (OnaConfig default) Set timeout: 90000 in SDK options

Response Shape

{
  "detection": { "...": "echo of submitted detection object" },
  "llm_analysis": {
    "status": "ok",
    "recommendation": "The inverter is exhibiting a significant behavioral anomaly...",
    "cited_sources": [
      {
        "doc_title": "Huawei SUN2000 Troubleshooting Guide",
        "section_path": "Error Code 513",
        "source_key": "pv-ops-and-maintenance/OEM Manuals/.../513.pdf"
      }
    ],
    "retrieval_query": "Huawei SUN2000 string inverter behavioral anomaly...",
    "chunks_retrieved": 5,
    "model": "nehanda-rag-synthesis-27b",
    "error": null
  }
}

If the detection’s severity_label is below the service’s configured min_severity threshold, the detection is returned unchanged with "llm_analysis": null.


The Underlying /analyze Endpoint

The SDK routes through terminalApi (action pv-insight), which internally calls the pvInsightService. You can also call the service directly:

Request

{
  "detection": {
    "asset_id": "INV-1000000054495195",
    "severity_label": "critical",
    "severity_score": 0.82,
    "status": "detected",
    "fault_type": "behavioral_anomaly",
    "summary": "World model anomaly score 0.0891 vs threshold 0.0234 β€” 6 consecutive windows",
    "anomalies": [],
    "metrics": {
      "latest_power_kw": 45.2,
      "baseline_power_kw": 280.5,
      "latest_irradiance_wm2": 850.0,
      "latest_temperature_c": 68.3,
      "latest_inverter_state": 513,
      "latest_run_state": 1,
      "world_model_latest_score": 0.0891,
      "world_model_max_score": 0.0891,
      "world_model_threshold": 0.0234,
      "world_model_streak_length": 6
    },
    "energy_at_risk_kw": 235.3,
    "last_observation": "2026-07-23T12:30:00Z"
  },
  "top_n": 5,
  "min_severity": "moderate"
}

Python with httpx

import httpx

detection = {
    "asset_id": "INV-1000000054495195",
    "severity_label": "critical",
    "severity_score": 0.82,
    "status": "detected",
    "fault_type": "behavioral_anomaly",
    "summary": "World model anomaly score 0.0891 vs threshold 0.0234 β€” 6 consecutive windows",
    "metrics": {
        "latest_power_kw": 45.2,
        "baseline_power_kw": 280.5,
        "latest_irradiance_wm2": 850.0,
        "latest_temperature_c": 68.3,
        "latest_inverter_state": 513,
        "world_model_streak_length": 6,
    },
    "energy_at_risk_kw": 235.3,
    "last_observation": "2026-07-23T12:30:00Z",
}

r = httpx.post(
    "https://pvinsight.up.railway.app/analyze",
    json={"detection": detection, "top_n": 5, "min_severity": "moderate"},
    timeout=60.0,
)
result = r.json()
analysis = result.get("llm_analysis")
if analysis:
    print(f"Status: {analysis['status']}")
    print(f"Recommendation:\n{analysis['recommendation']}")
    print(f"Sources: {len(analysis['cited_sources'])}")

JavaScript with fetch

const response = await fetch('https://pvinsight.up.railway.app/analyze', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    detection: {
      asset_id: 'INV-1000000054495195',
      severity_label: 'critical',
      severity_score: 0.82,
      fault_type: 'behavioral_anomaly',
      summary: 'World model anomaly score 0.0891 vs threshold 0.0234',
      metrics: {
        latest_power_kw: 45.2,
        baseline_power_kw: 280.5,
        latest_temperature_c: 68.3,
        latest_inverter_state: 513,
        world_model_streak_length: 6,
      },
      energy_at_risk_kw: 235.3,
      last_observation: '2026-07-23T12:30:00Z',
    },
    top_n: 5,
    min_severity: 'moderate',
  }),
});

const result = await response.json();
const analysis = result.llm_analysis;
if (analysis) {
  console.log('Status:', analysis.status);
  console.log('Recommendation:', analysis.recommendation);
  console.log('Sources:', analysis.cited_sources.length);
}

Other Endpoints

Health Check

curl https://pvinsight.up.railway.app/health
{
  "status": "ok",
  "chunk_count": 40908,
  "last_ingest": "2026-07-23T10:00:00Z",
  "nehanda_endpoint": "nehanda-rag-synthesis-27b"
}

Collection Info

curl https://pvinsight.up.railway.app/collection/info
{
  "chunk_count": 40908,
  "last_ingest": "2026-07-23T10:00:00Z"
}

Corpus Ingest (Admin)

curl -X POST https://pvinsight.up.railway.app/ingest/s3 \
  -H "X-Api-Key: $ADMIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prefix": "pv-ops-and-maintenance/"}'

How It Fits Together

The PV Insight Service sits downstream of the JEPA anomaly detection pipeline:

  1. anomalyModelService computes JEPA world-model detections
  2. When severity β‰₯ moderate or streak β‰₯ 3, the detection is sent to pvInsightService /analyze
  3. The service retrieves relevant O&M documentation and synthesizes a recommendation via Nehanda 27B
  4. The enriched detection (with llm_analysis) is returned for display in dashboards or O&M workflows

Note: The caller gate (step 2) is not yet wired in anomalyModelService. Until then, call run_pv_insight_synthesis / runPvInsightSynthesis directly with any detection payload.


See Also