---
name: neqsim-root-cause-analysis
version: "1.3.0"
description: "Root cause analysis for process equipment - Bayesian-style diagnosis combining reliability priors (IOGP/SINTEF, CCPS, IEEE 493, Lees, OREDA), historian evidence and NeqSim verification, including the control-loop correlation trap (difference both series, resolve direction from the lag sign). USE WHEN: diagnosing compressor trips, vibration, efficiency loss, separator carryover, exchanger fouling or any operational anomaly. Anchors on neqsim.process.diagnostics."
last_verified: "2026-09-17"
requires:
  java_packages: [neqsim.process.diagnostics, neqsim.process.equipment.failure, neqsim.process.automation]
---

# NeqSim Root Cause Analysis Skill

Systematic equipment-level root cause analysis integrating process simulation,
multi-source reliability data, plant historian time-series, and STID design conditions.

## When to Use

- Equipment trip investigation (compressor, pump, turbine)
- High vibration diagnosis
- Efficiency degradation analysis
- Separator liquid carryover or gas blow-by
- Heat exchanger fouling assessment
- Valve erosion or malfunction
- Any operational anomaly requiring structured root cause identification

## Already-closed incident with a "document learning" ask

When the failure is already resolved and recorded (for example a closed Blalys whose
only open subtask is to document learning), do not re-derive the cause. Cite the
recorded cause, test it for consistency against the historian and alarm evidence
(consistent, contradicted or not testable), then deliver a consequence and learning
screening: capacity impact (for a derated driver use `Compressor.getDriverLimitedEnvelope()`),
the barrier or early indicator that would have shortened the outage, and the cost of
recurrence. Mark causal claims beyond the recorded cause as hypotheses.
## Rapid Triage Mode (live disturbance)

A full RCA is a study. When the plant is **still down** and a response team needs a
direction within minutes, run the same framework in a reduced, explicitly labelled
mode rather than a different method:

| | Full RCA | Rapid triage |
|---|---|---|
| Prior | Multi-source reliability data | Reliability prior only, no source reconciliation |
| Likelihood | Full historian window, quality-screened | Last stable period to now, unscreened |
| Verification | NeqSim simulation of each hypothesis | Skipped; verification is deferred |
| Output | Ranked hypotheses with confidence | Top 3 candidates, confidence capped at `MODERATE` |
| Cause claim | Established when evidence supports it | `NOT_DETERMINED` unless a single candidate dominates |

Rules that make the reduced mode safe to act on:

1. **Label the mode in the output.** A triage result must never be presented, or
   later quoted, as a completed RCA.
2. **Cap the confidence.** Without simulation verification the confidence ceiling is
   `MODERATE`, regardless of how strongly the historian agrees.
3. **Time-box it.** State the box up front. When it expires, report the ranking as it
   stands plus what is still unknown — restoration decisions do not wait for cause.
4. **Report single-source dependence.** If every piece of evidence for the leading
   candidate comes from one instrument or one system, say so; that is the most common
   way a triage conclusion turns out wrong.
5. **Promote, do not restart.** When the plant is stable, re-run the same hypotheses
   in full mode with simulation verification; the triage ranking becomes the prior.

Rapid triage is the diagnose step of a live-disturbance response loop. The incident
coordination, phase gating, and restoration-option evaluation around it live in the
operations agents that call this skill.

## Architecture Overview

The RCA framework uses a Bayesian-inspired three-stage methodology:

```
Prior (Reliability Data) × Likelihood (Historian) × Verification (Simulation) = Confidence
```

### Reliability Data Sources

`ReliabilityDataSource` loads from multiple public databases automatically:

- **IOGP Report 434 / SINTEF** — offshore O&G equipment (free)
- **CCPS 1989** — process vessels, piping, valves (published handbook)
- **IEEE 493-2007** — industrial electrical equipment (purchasable standard)
- **Lees' Loss Prevention 2012** — generic process industry rates (textbook)
- **SINTEF PDS** — safety-instrumented systems (purchasable)
- **OREDA** — offshore equipment (optional, commercial)

No commercial license is required for the default data.

### Core Classes

| Class | Purpose |
|-------|---------|
| `RootCauseAnalyzer` | Main orchestrator — takes ProcessSystem + equipment name, runs full analysis |
| `Symptom` | Enum of 12 equipment symptoms (TRIP, HIGH_VIBRATION, etc.) |
| `Hypothesis` | Candidate root cause with expected signal fingerprints, evidence, prior/likelihood/verification scores |
| `HypothesisGenerator` | Generates candidate hypotheses from built-in libraries and reliability-data-adjusted priors per equipment type |
| `EvidenceCollector` | Analyzes historian/STID data and attaches only hypothesis-relevant supporting or contradictory evidence |
| `DiagnosisCase` | Captures equipment, window, provenance, process-model identity, context, and data quality |
| `SimulationVerifier` | Clones process and reports explicit status, KPI coverage, and magnitude-aware comparison |
| `RootCauseReport` | Ranked output with JSON and text report generation |

### Package Location

All classes in `neqsim.process.diagnostics`.

## Quick Start

### Java

```java
// 1. Create analyzer with running process and target equipment
RootCauseAnalyzer rca = new RootCauseAnalyzer(processSystem, "Compressor-1");

// 2. Set the observed symptom
rca.setSymptom(Symptom.HIGH_VIBRATION);

// 3. Provide historian data (from tagreader or CSV)
Map<String, double[]> historianData = new HashMap<>();
historianData.put("vibration_mm_s", vibrationTimeSeries);
historianData.put("discharge_temp_C", tempTimeSeries);
historianData.put("power_kW", powerTimeSeries);
rca.setHistorianData(historianData, timestamps);

// 4. Set design limits from STID
rca.setDesignLimit("vibration_mm_s", Double.NaN, 7.1);
rca.setDesignLimit("discharge_temp_C", Double.NaN, 180.0);

// 5. Optionally provide STID design values
Map<String, String> stidData = new HashMap<>();
stidData.put("design_flow_kg_hr", "50000");
stidData.put("design_efficiency_pct", "82");
rca.setStidData(stidData);

// 6. Run analysis
RootCauseReport report = rca.analyze();

// 7. Get results
String textReport = report.toTextReport();
String json = report.toJson();
Hypothesis topCause = report.getTopHypothesis();
```

### Python (Jupyter)

```python
RootCauseAnalyzer = ns.JClass("neqsim.process.diagnostics.RootCauseAnalyzer")
Symptom = ns.JClass("neqsim.process.diagnostics.Symptom")

rca = RootCauseAnalyzer(process, "Compressor-1")
rca.setSymptom(Symptom.HIGH_VIBRATION)

# Convert numpy arrays to Java
from jpype import JArray, JDouble
historian = ns.HashMap()
historian.put("vibration_mm_s", JArray(JDouble)(vibration_data.tolist()))
rca.setHistorianData(historian, JArray(JDouble)(timestamps.tolist()))

report = rca.analyze()
print(str(report.toTextReport()))
```

## Symptom Reference

| Symptom | Description | Related Categories |
|---------|-------------|-------------------|
| `TRIP` | Equipment trip/shutdown | MECHANICAL, CONTROL, EXTERNAL |
| `HIGH_VIBRATION` | Vibration above limits | MECHANICAL |
| `SEAL_FAILURE` | Seal leakage or failure | MECHANICAL |
| `HIGH_TEMPERATURE` | Temperature exceeding limits | MECHANICAL, PROCESS |
| `LOW_EFFICIENCY` | Performance below design | PROCESS, MECHANICAL |
| `PRESSURE_DEVIATION` | Unexpected pressure change | PROCESS, CONTROL |
| `FLOW_DEVIATION` | Unexpected flow change | PROCESS, CONTROL |
| `HIGH_POWER` | Power consumption above normal | PROCESS, MECHANICAL |
| `SURGE_EVENT` | Compressor surge | PROCESS, CONTROL |
| `FOULING` | Fouling or deposit buildup | PROCESS |
| `ABNORMAL_NOISE` | Unusual noise patterns | MECHANICAL |
| `LIQUID_CARRYOVER` | Liquid in gas outlet | PROCESS |

## Built-in Hypothesis Libraries

### Compressor

- **HIGH_VIBRATION**: Bearing wear, impeller imbalance, shaft misalignment, fouled impeller, loose foundation
- **HIGH_TEMPERATURE**: Seal degradation, process gas composition change, fouled intercooler, recycle valve malfunction
- **LOW_EFFICIENCY**: Internal leakage, fouled impeller, eroded impeller, gas composition change
- **SURGE_EVENT**: Low suction flow, blocked suction, anti-surge valve failure, gas composition change
- **TRIP**: Bearing failure, high vibration, high discharge temperature, low lube oil pressure, process upset

### Pump

- **HIGH_VIBRATION**: Cavitation, impeller wear, shaft misalignment
- **LOW_EFFICIENCY**: Impeller wear, internal recirculation
- **TRIP**: Bearing failure, seal failure, low suction pressure

### Separator

- **LIQUID_CARRYOVER**: Demister fouled, high liquid level, feed rate exceeded, foam formation
- **PRESSURE_DEVIATION**: Pressure control valve malfunction, blocked outlet, relief valve leaking
- **FOULING**: Wax deposition, scale buildup, sand accumulation

### Heat Exchanger

- **HIGH_TEMPERATURE**: Fouled tubes, loss of cooling, bypass valve stuck, air in cooling water
- **LOW_EFFICIENCY**: Fouling, tube leak, flow maldistribution
- **PRESSURE_DEVIATION**: Tube blockage, shell-side fouling

### Valve

- **FLOW_DEVIATION**: Trim erosion, actuator malfunction, positioner calibration drift
- **ABNORMAL_NOISE**: Cavitation, flashing, vibration from upstream piping

## Evidence Analysis Methods

Evidence is hypothesis-specific. Built-in hypotheses define expected signal
fingerprints such as `vibration|bearing` increasing, `lubeOilPressure` below
limit, `demisterDp` increasing, or `antiSurgeValve` not opening. Historian and
STID observations are matched by alias pattern. Matching observations become
supporting evidence; opposite observations become contradictory evidence;
unrelated trends are ignored instead of inflating all hypotheses.

Each evidence item includes:

- `supporting`: whether the observation supports or contradicts the hypothesis
- `weight`: the importance of the expected signal in the hypothesis fingerprint
- `sourceReference`: optional source/tag/document reference for traceability

### Trend Analysis
Linear regression on time-series data. Reports slope, R-squared, and percent change.
- **STRONG**: R-squared > 0.7 and > 10% change
- **MODERATE**: R-squared > 0.5
- **WEAK**: R-squared > 0.3

### Threshold Exceedance
Checks how many data points exceed design limits.
- **STRONG**: > 20% of data exceeding limits
- **MODERATE**: > 5%
- **WEAK**: 0-5%

### Rate-of-Change Detection
Identifies sudden step changes using 3-sigma detection on first differences.
Step changes produce STRONG evidence.

### Correlation Analysis
Pearson correlation between all parameter pairs. Reports correlations with |r| > 0.7.
- **STRONG**: |r| > 0.9
- **MODERATE**: 0.7 < |r| < 0.9

### Correlation when a controller closes the loop

The most common way a historian correlation misleads is that **a control loop
already connects the two signals being correlated**. The loop guarantees a
correlation with exactly the sign the wrong hypothesis predicts, so the raw
number looks like confirmation of a mechanism that is not there.

**1. Ask what the controller does before reading the correlation.**
Write the loop down first: *does the plant move B in response to A?* If it does,
a correlation between A and B is expected under the null hypothesis and proves
nothing. Do not run the correlation at all until this question is answered.

**2. Difference both series.**
First differences remove a shared trend — seasonal, production ramp, or
instrument drift — which is the other manufacturer of spurious correlation.
What survives is genuine short-term coupling.

**3. Use the sign of the lag to resolve direction.**
This is the part that discriminates. Scan lags in **both** directions and ask
which signal leads:

| Pattern | Interpretation |
|---------|----------------|
| Driver leads by the physical transport time | The causal mechanism under test |
| Response leads the driver | The controller acting — the loop read backwards |
| Peak at zero lag only | Shared common cause, or sampling too coarse to separate |
| Flat across all lags | No coupling at the resolution available |

State the expected lag **before** looking. A transport short-circuit has a
transit time of minutes; a controller responds in seconds; a seasonal common
cause has no lag structure at all. If the observed peak does not sit where the
hypothesis requires, the hypothesis fails regardless of the correlation
magnitude.

**Worked example.** An investigation suspected that a warm discharge stream was
short-circuiting back to a cold intake, and correlated intake temperature
against discharge flow:

```
raw correlation, intake temperature vs discharge flow:   r = +0.37
```

Positive, moderate, right sign — it reads as confirmation. It is not. A
temperature controller opened the discharge valve when the intake ran warm, so
warm intake *caused* high flow rather than the reverse, and both series carried
the same seasonal ramp. Two mechanisms manufactured the correlation, neither of
them the hypothesis under test:

```
after differencing both series, every lag from -3 h to +3 h:   |r| < 0.10
                                                     peak:     r = +0.04
```

The hypothesis fails. The raw figure is dangerous precisely because it is
plausible rather than absurd.

A saturated controller is the mirror image of this failure: the loop that
*acts* fools the correlation, while a loop that has *stopped acting* lets
unchanged disturbances through and makes the process look like it has a new
external problem. Screen for it with `neqsim-control-authority-screening`
before accepting an external cause for "the controlled variable got worse".

### STID Cross-Reference
Compares current (latest) values to STID design values.
- **STRONG**: > 20% deviation from design
- **MODERATE**: 10-20% deviation

### Weather as Evidence (ambient/met-ocean conditions)

Some trips and performance shifts correlate with **weather at the equipment's
location**, not with anything in the historian: an air-cooler/fin-fan or a
gas-turbine driver losing margin on a hot day, a flare/dispersion or
fire-water-monitor case degraded by wind, a marine/topside motion event during
a storm, or a cold snap driving a hydrate/freeze-related valve or instrument
failure. When the hypothesis list includes an ambient-condition driver, pull
the historical weather for the equipment's location and the trip/event window
with the community `neqsim-weather-data` skill (`WeatherDataService.get_historical`
or the `installation-weather-agent`) and compare it against the equipment's
design/rated ambient envelope (ISO ambient for a gas turbine, design ambient
for an air cooler, etc.):

- **STRONG**: the ambient condition at the event time is outside the
  equipment's design/rated envelope (e.g. ambient above ISO plus derate margin).
- **MODERATE**: inside the envelope but at a seasonal or historical extreme
  for the site (compare against `design_site_conditions`'s percentiles).
- Treat this like the control-loop correlation trap above: a weather match is
  circumstantial evidence, not proof. Score it, do not promote it to a
  confirmed cause without a NeqSim verification run (e.g. re-flash the gas
  turbine or air cooler at the observed ambient condition and check the
  predicted margin against what was actually seen).
- There is no built-in ambient-temperature hypothesis in the compressor/pump/
  separator/heat-exchanger/valve libraries above; add one as a custom
  hypothesis (see Extending the Framework) when weather is a candidate driver.

### Incident and Recurrence History (prior layer, not likelihood)

Records of earlier incidents on the same equipment, its sister equipment or the same
unit (company incident database, maintenance failure history, earlier RCA reports) say
how often a failure mode has occurred here. That is a **reliability prior**, so it
enters the prior stage of `Prior × Likelihood × Verification`, never the likelihood
(the current event's own signals) and never as a confirmed cause.

1. Retrieve the history for the equipment tag, then the unit, over a stated window.
   Use the installed incident-history evidence agent when the plugin provides one;
   otherwise ask the user for the case list. Record `no_access` or `not_applicable`
   in `agent_workflow_plan.evidence_sources` rather than silently skipping.
2. Map each record to a hypothesis by failure mode (seal leak, bearing, fouling,
   valve sticking, instrument fault, utility loss ...). Count only records that match
   the failure mode, not every record on the unit. Keep the case numbers.
3. Pass the counts to the analyzer before `analyze()`:

```java
rca.addIncidentHistoryPrior("Seal degradation", 3, "cases 1001, 1007, 1012");
rca.addIncidentHistoryPrior("Bearing", 1, "case 1020");
```

   Each matching hypothesis prior is multiplied by `1 + 0.15 × count`, capped at 1.75,
   and a weak supporting evidence item with the case references is attached for the
   audit trail. The likelihood score is not touched. Confidence is renormalised, so the
   effect is relative. Unmatched names are logged, not applied.
4. Rules that keep it honest:
   - Recurrence is a screening signal, not a demonstrated common cause. Same tag, same
     failure mode and a comparable operating regime strengthen it; same unit alone does not.
   - No recorded incidents never lowers a prior (under-reporting is common).
   - Closed cases with a recorded cause are stronger than open ones; quote the recorded
     cause as a hypothesis to test against the historian, not as the answer.
   - A hypothesis raised only by history and contradicted by the historian stays low:
     the likelihood term dominates once there is measured evidence.
   - Report the history window, record count, matching count and the data gap
     (system coverage, reporting culture) in `assumptions` / `data_gaps`.
   - Remove personal data from case text before it enters a task folder.

## Simulation Verification

The verifier clones the process system, applies a perturbation matching each
hypothesis, runs the modified process, and compares both direction and normalized
response magnitude to the historian data pattern where a defensible KPI match exists.

Every likelihood and verification stage has an explicit `EvaluationStatus`:
`UNKNOWN`, `EVALUATED`, `UNSUPPORTED`, or `FAILED`. Unknown, unsupported, and failed
stages are neutral in confidence multiplication. An `EVALUATED` zero correctly drives
the raw hypothesis score to zero. Reports also expose verification coverage and
comparison count; do not present low-coverage verification as conclusive.

### Perturbation Examples

| Hypothesis | Perturbation Applied |
|-----------|---------------------|
| Seal degradation | Reduce polytropic efficiency by 20% |
| Fouled impeller | Reduce polytropic efficiency by 15% |
| Surge condition | Reduce polytropic efficiency by 30% |
| Heat exchanger fouling | Increase outlet temperature by 10°C |
| Loss of cooling | Set outlet = inlet temperature |

### Verification Score

The score (0-1) combines direction and the ratio of normalized simulated/observed
response magnitudes, then qualifies it by comparable-KPI coverage. Opposite directions
score zero. No defensible comparison yields `UNKNOWN`, not an invented midpoint score.

For operational decisions, attach a `DiagnosisCase` with the historian query/reference,
process-model ID and revision, operating mode, and known quality qualifications. It is
emitted in `RootCauseReport.toJson()` and MCP JSON.

## Integration with Other Skills

| Skill | Integration |
|-------|------------|
| `neqsim-autonomous-investigation` | **Upstream** — when the symptom/driver is NOT given, run the observe→hypothesize→test loop and discover lead-lag relationships (`RelationshipGraph`) first, then feed the discovered candidate causes into RCA instead of a fixed symptom |
| `neqsim-plant-data` | Read historian data via tagreader API |
| `neqsim-control-authority-screening` | Rule out a saturated control valve before accepting an external cause for "the controlled variable got worse" |
| `neqsim-heat-exchanger-fouling-assessment` | Quantify exchanger degradation behind a cooling or duty shortfall hypothesis |
| `neqsim-stid-retriever` | Retrieve STID design documents |
| `neqsim-technical-document-reading` | Extract design values from datasheets |
| `neqsim-troubleshooting` | Recovery strategies after diagnosis |
| `neqsim-process-safety` | Link RCA findings to barrier management |
| `neqsim-pid-process-operations` | P&ID context for equipment relationships |
| `neqsim-weather-data` (community) | Historical ambient temperature, wind and sea-state for the equipment's location and event window, when weather is a candidate driver |

**When the symptom is unknown**, do not ask the user "what should I look for?".
Chain `neqsim-plant-data → neqsim-autonomous-investigation → this skill`: the
investigation skill scans all tags for anomalies and lead-lag relationships, and
hands the candidate causes here for Bayesian scoring + simulation verification.
The one-call path is `RootCauseAnalyzer.analyzeAutonomous()` (or
`analyzeAutonomous(tagToEquipment)` to also classify relationships against the
flowsheet topology) — it infers the symptom with `AnomalyScanner`, discovers
relationships with `RelationshipGraph`, classifies them with `CausalTopologyModel`, and
adds matched anomaly/topology findings to hypothesis evidence before ranking. Ambiguous,
counter-flow, and common-cause edges remain visible but do not silently boost scores.

## MCP Tool

Use `runRootCauseAnalysis` MCP tool for programmatic access:

```json
{
  "tool": "runRootCauseAnalysis",
  "arguments": {
    "processJson": "{ ... NeqSim process JSON ... }",
    "equipmentName": "Compressor-1",
    "symptom": "HIGH_VIBRATION",
    "historianCsv": "timestamp,vibration,temperature\n0,3.5,150\n3600,3.8,152\n...",
    "designLimits": {"vibration": [null, 7.1], "temperature": [null, 180]},
    "simulationEnabled": true,
    "diagnosisCase": {
      "caseId": "trip-2026-07-19",
      "processModelId": "export-compression",
      "processModelRevision": "model-rev-7",
      "dataSources": {"PI": "query-id-42"}
    }
  }
}
```

## Output Format

### JSON Report Structure

```json
{
  "equipment": "Compressor-1",
  "equipmentType": "compressor",
  "symptom": "HIGH_VIBRATION",
  "timestamp": "2026-06-15T10:30:00",
  "dataPointsAnalyzed": 2400,
  "parametersAnalyzed": 8,
  "diagnosisCase": {
    "caseId": "trip-2026-07-19",
    "equipmentName": "Compressor-1",
    "processModelId": "export-compression",
    "processModelRevision": "model-rev-7"
  },
  "summary": "Most likely root cause: Bearing wear (72.3% confidence)...",
  "hypotheses": [
    {
      "rank": 1,
      "name": "Bearing wear",
      "category": "MECHANICAL",
      "confidence": 0.7230,
      "confidenceScore": 0.7230,
      "priorProbability": 0.3000,
      "likelihoodScore": 0.8500,
      "verificationScore": 0.8500,
      "likelihoodStatus": "EVALUATED",
      "verificationStatus": "EVALUATED",
      "verificationCoverage": 0.75,
      "verificationComparisonCount": 3,
      "evidence": [
        {
          "parameter": "vibration_mm_s",
          "observation": "increasing trend...",
          "strength": "STRONG",
          "source": "historian-trend",
          "supporting": true,
          "weight": 3.0,
          "sourceReference": "PI:COMP-100-VIB"
        }
      ],
      "recommendedActions": ["Inspect bearings", "Check lubrication system", "Review vibration spectrum"]
    }
  ]
}
```

## Extending the Framework

### Custom Hypothesis Libraries

```java
HypothesisGenerator gen = new HypothesisGenerator();

// Register custom hypotheses for a specific equipment type and symptom
gen.register("compressor", Symptom.HIGH_VIBRATION,
    new Hypothesis.Builder()
        .name("liquid_ingestion")
        .description("Liquid droplets entering compressor causing vibration")
        .category(Hypothesis.Category.PROCESS)
        .priorProbability(0.1)
        .addExpectedSignal("scrubberLevel|separatorLevel|level",
            Hypothesis.ExpectedBehavior.HIGH_LIMIT, 3.0,
            "High upstream scrubber level supports liquid ingestion")
        .addExpectedSignal("vibration|flowInstability", Hypothesis.ExpectedBehavior.STEP_CHANGE,
            2.5, "Liquid ingestion normally creates a sudden vibration/flow disturbance")
        .addAction("Check scrubber level")
        .addAction("Inspect inlet piping for liquid accumulation")
        .build());

RootCauseAnalyzer rca = new RootCauseAnalyzer(process, "Compressor-1");
rca.setHypothesisGenerator(gen);
```

### Loading Evidence from CSV

```java
EvidenceCollector collector = new EvidenceCollector();
collector.loadFromCsv("path/to/historian_export.csv");
// CSV format: timestamp, param1, param2, ...
```

## Integrated Workflow: Tagreader + STID + NeqSim Simulation

The RCA framework is designed to consume data from three sources simultaneously.
Below is the canonical workflow combining all three.

### Step 1: Retrieve Historian Data via Tagreader

Use the `neqsim-plant-data` skill to pull time-series from the plant historian
(OSIsoft PI or Aspen IP.21) and convert to the CSV format RCA expects.

```python
# Python — pull tagreader data and format for RCA
import pandas as pd

# Tag mapping from STID/P&ID documentation
tag_map = {
    "VT-101.PV": "vibration_mm_s",
    "TT-101.PV": "discharge_temp_C",
    "PT-101.PV": "suction_pressure_bara",
    "FT-101.PV": "mass_flow_kg_hr",
    "JI-101.PV": "power_kW",
}

# Read from historian (tagreader API or CSV export)
df = pd.read_csv("historian_export.csv")
df = df.rename(columns=tag_map)

# Convert to CSV string for RCA input
historian_csv = df.to_csv(index=False)
```

### Step 2: Extract STID Design Data

Use the `neqsim-stid-retriever` and `neqsim-technical-document-reading` skills
to extract design limits and reference values from STID datasheets.

```python
# STID data extracted from datasheet DS-K-101 rev.C
stid_data = {
    "designFlow_kg_hr": "50000",
    "designEfficiency_pct": "82",
    "designDischargePressure_bara": "45.0",
    "maxVibration_mm_s": "7.1",
    "maxDischargeTemp_C": "180",
    "bearingType": "tilting_pad",
    "lastInspectionDate": "2024-06-15",
    "tagreaderSource": "PI Web API: VT-101, TT-101, PT-101, FT-101, JI-101",
    "sourceReference": "STID DS-K-101 rev.C and PI trend 2025-06-01 to 2025-06-15",
}
```

### Step 3: Build Process Model and Run RCA

```python
import json

RootCauseRunner = ns.JClass("neqsim.mcp.runners.RootCauseRunner")

rca_input = {
    "processJson": json.dumps(process_json),
    "equipmentName": "Compressor-1",
    "symptom": "HIGH_VIBRATION",
    "historianCsv": historian_csv,
    "simulationEnabled": True,
    "designLimits": {
        "vibration_mm_s": [None, 7.1],
        "discharge_temp_C": [None, 180.0],
        "mass_flow_kg_hr": [40000, 60000],
    },
    "stidData": stid_data,
}

result = json.loads(str(RootCauseRunner.run(json.dumps(rca_input))))
print(f"Top cause: {result['hypotheses'][0]['name']} "
      f"({result['hypotheses'][0]['confidence']:.1%})")
```

### Step 4: Trace Evidence Back to Source

Every evidence item in the RCA output carries a `sourceReference` field
linking to the original tagreader tag or STID document. This enables
auditable traceability from diagnosis to plant data:

```
hypothesis.evidence[0].sourceReference → "PI:VT-101.PV trend analysis"
hypothesis.evidence[1].sourceReference → "STID DS-K-101: design vibration limit 7.1 mm/s"
```

## MCP Example Catalog

Three pre-built examples demonstrate the integration:

| Example | Equipment | Symptom | Data Sources |
|---------|-----------|---------|-------------|
| `compressor-high-vibration` | Compressor | HIGH_VIBRATION | PI historian, STID datasheet |
| `separator-liquid-carryover` | Separator | LIQUID_CARRYOVER | PI historian (demister dP, level), STID datasheet |
| `hx-fouling` | Heat Exchanger | FOULING | IP.21 historian (outlet temp, shell dP), vendor datasheet |

Access via MCP: `neqsim://examples/root-cause/separator-liquid-carryover`

## Post-Trip Analysis: Detect → Trace → Restart

After the RCA framework identifies *what* went wrong, the post-trip analysis
classes answer three follow-up questions:

1. **What tripped?** — `TripEventDetector` monitors process parameters against thresholds
2. **How did the failure propagate?** — `FailurePropagationTracer` traces cascade through topology
3. **How do we restart?** — `RestartSequenceGenerator` produces an optimised restart plan

### Classes

| Class | Package | Purpose |
|-------|---------|---------|
| `TripEvent` | `neqsim.process.diagnostics` | Immutable data record for a detected trip (equipment, parameter, threshold, actual, severity) |
| `TripEventDetector` | `neqsim.process.diagnostics` | Monitors equipment parameters and fires `TripEvent` when thresholds are breached; supports high/low trips, deadband, first-trip-only mode |
| `FailurePropagationTracer` | `neqsim.process.diagnostics` | Uses `ProcessTopologyAnalyzer` + `DependencyAnalyzer` to BFS-trace how a failure cascades downstream/upstream; produces `PropagationResult` with `PropagationStep` list |
| `RestartStep` | `neqsim.process.diagnostics.restart` | Single step in a restart sequence (equipment, action, precondition, delay, priority) |
| `RestartSequenceGenerator` | `neqsim.process.diagnostics.restart` | Generates topologically-ordered restart plans with safety checks, root-cause verification, utility confirmation, equipment-specific ramp-up actions, and system verification |

### Java Quick Start

```java
// ── Step 1: Detect trip ──
TripEventDetector detector = new TripEventDetector(processSystem);
detector.addTripCondition("Compressor-1", "pressure", 150.0, true,
    TripEvent.Severity.HIGH);
List<TripEvent> trips = detector.check(simulationTime);

// ── Step 2: Trace propagation ──
FailurePropagationTracer tracer = new FailurePropagationTracer(processSystem);
FailurePropagationTracer.PropagationResult propagation = tracer.trace(trips.get(0));
String propagationSummary = propagation.toTextSummary();

// ── Step 3: Generate restart plan ──
RestartSequenceGenerator generator = new RestartSequenceGenerator(processSystem);
generator.setCustomRampUpTime("Compressor-1", 300.0);  // 5 min ramp-up
RestartSequenceGenerator.RestartPlan plan = generator.generate(propagation);
String restartPlanReport = plan.toTextReport();
String json = plan.toJson();
```

### Python (Jupyter)

```python
TripEventDetector = ns.JClass("neqsim.process.diagnostics.TripEventDetector")
TripEvent = ns.JClass("neqsim.process.diagnostics.TripEvent")
FailurePropagationTracer = ns.JClass("neqsim.process.diagnostics.FailurePropagationTracer")
RestartSequenceGenerator = ns.JClass("neqsim.process.diagnostics.restart.RestartSequenceGenerator")

detector = TripEventDetector(process)
detector.addTripCondition("Compressor-1", "pressure", 150.0, True, TripEvent.Severity.HIGH)
trips = detector.check(0.0)

tracer = FailurePropagationTracer(process)
propagation = tracer.trace(trips[0])
print(str(propagation.toTextSummary()))

generator = RestartSequenceGenerator(process)
plan = generator.generate(propagation)
print(str(plan.toTextReport()))
```

### TripEventDetector Features

- **High/low trips**: `addTripCondition(equip, param, threshold, isHigh, severity)`
- **Deadband**: `setDeadbandFraction(0.02)` — 2% of threshold to avoid chatter
- **First-trip-only**: `setFirstTripOnly(true)` — fire once per condition
- **Parameter types**: `"pressure"`, `"temperature"`, `"flowRate"` (reads from equipment outlet)
- **JSON output**: `detector.toJson()` — lists all detected trips

### FailurePropagationTracer Features

- **Trace by name**: `tracer.trace("Compressor-1")`
- **Trace by trip event**: `tracer.trace(tripEvent)`
- **Max cascade depth**: `tracer.setMaxCascadeDepth(3)`
- **Impact levels**: DIRECT, INDIRECT, POTENTIAL per cascade depth
- **Output**: `PropagationResult` with `toJson()`, `toTextSummary()`

### RestartSequenceGenerator Features

- **From propagation**: `generator.generate(propagationResult)`
- **From equipment list**: `generator.generate(Arrays.asList("Comp-1", "Cooler-1"))`
- **Custom ramp-up**: `generator.setCustomRampUpTime("Comp-1", 300.0)`
- **Custom preconditions**: `generator.setCustomPrecondition("Comp-1", "Verify lube oil pressure > 2 bara")`
- **Equipment-specific actions**: auto-selects restart actions by equipment type (compressor, separator, pump, HX, valve, column, pipe)
- **Output**: `RestartPlan` with `toJson()`, `toTextReport()`
- **Plan structure**: Safety check → Root cause verification → Utilities → Equipment (upstream-first) → System verification
