Aditya Adaki
Add DCRM Analysis API
fdcec08
# llm.py
import google.generativeai as genai
import json
import PIL.Image
import io
def get_dcrm_prompt(data_str):
return f"""
I have extracted data from a DCRM (Dynamic Contact Resistance Measurement) graph.
Data (Sampled): {data_str}
The columns are:
- 'time': Time in milliseconds.
- 'curr': Current signal amplitude (Blue curve) - represents the test current flowing through the contacts.
- 'res': Dynamic Resistance amplitude (Green curve) - represents the contact resistance in micro-ohms (µΩ).
- 'travel': Travel signal amplitude (Red curve) - represents the mechanical position/displacement of the moving contact.
IMPORTANT: Higher values mean the signal is HIGHER on the graph.
I have also provided the image of the graph. Use the visual information from the image to cross-reference with the data.
=== HEALTHY DCRM SIGNATURE REFERENCE ===
**Resistance (Green) - Healthy Characteristics:**
- Pre-contact: Infinite/Very High (off-scale or flat at top)
- Arcing engagement: Drops sharply with moderate spikes (arcing activity), typically 100-500 µΩ
- Main conduction: LOW and STABLE (30-80 µΩ for healthy contacts), minimal oscillation (<10 µΩ variance)
- Parting: Sharp rise with spikes (arcing during separation)
- Final open: Returns to infinite/very high (off-scale)
**Current (Blue) - Healthy Characteristics:**
- Pre-contact: Near zero baseline
- Arcing engagement: Begins rising as circuit closes
- Main conduction: Stable at test current level (plateau)
- Parting: Maintained until final separation
- Final open: Drops to zero
**Travel (Red) - Healthy Characteristics:**
- Pre-contact: Increasing linearly (contacts approaching)
- Arcing engagement: Continues increasing
- Main conduction: Reaches MAXIMUM and plateaus (fully closed position)
- Parting: Decreases linearly (contacts separating)
- Final open: Stabilizes at minimum (fully open position)
=== TASK: SEGMENT INTO 5 KINEMATIC ZONES ===
Use ALL THREE curves together for accurate boundary detection. Each zone represents a distinct physical state of the circuit breaker.
**Zone 1: Pre-Contact Travel (Initial Closing Motion)**
* **Physical Meaning**: The moving contact is traveling toward the stationary contact but has NOT yet made electrical contact. This is pure mechanical motion with no current flow.
* **Start**: time = 0 ms
* **End Boundary**: Detect when CURRENT (blue) FIRST starts rising significantly from baseline.
* Cross-reference: Resistance (green) should still be very high/infinite
* Cross-reference: Travel (red) should be steadily increasing
* **Typical Duration**: 80-120 ms
* **Detection Logic**: Find the point where 'curr' rises above baseline noise (e.g., >5% of max current)
**Zone 2: Arcing Contact Engagement (Initial Electrical Contact)**
* **Physical Meaning**: The arcing contacts (W-Cu tips) make first contact and establish an electrical path. Current begins flowing through a small contact area, causing arcing and resistance fluctuations. This is the "make" transition.
* **Start**: End of Zone 1
* **End Boundary**: Detect when resistance SETTLES after initial spike activity.
* Primary indicator: Resistance (green) drops from high values, exhibits spikes, then STABILIZES to low plateau
* Cross-reference: Current (blue) should be rising/stabilizing
* Cross-reference: Travel (red) continues increasing toward maximum
* **Typical Duration**: 20-40 ms (Zone 2 typically ends around 110-150 ms total time)
* **Detection Logic**: Find where 'res' completes its descent and spike activity, settling into a stable low range
**Zone 3: Main Contact Conduction (Fully Closed State)**
* **Physical Meaning**: The main contacts (Ag-plated) are fully engaged, providing a large, stable contact area. This is the "healthy contact" signature zone - resistance should be at its MINIMUM and STABLE. The breaker is in its fully closed, current-carrying state.
* **Start**: End of Zone 2
* **End Boundary**: Detect when the breaker begins OPENING (travel reverses direction).
* Primary indicator: Travel (red) reaches MAXIMUM and starts to DESCEND
* Cross-reference: Resistance (green) should remain low and stable throughout this zone
* Cross-reference: Current (blue) should be stable at test level
* **Typical Duration**: 100-200 ms (this is the longest zone, representing the dwell time)
* **Detection Logic**: Find the peak of 'travel' curve and the point where it starts decreasing
**Zone 4: Main Contact Parting (Breaking/Opening Transition)**
* **Physical Meaning**: The main contacts are separating. As the contact area decreases, resistance rises sharply. Arcing occurs during the final separation of the arcing contacts. This is the "break" transition - the most critical phase for fault detection.
* **Start**: End of Zone 3
* **End Boundary**: Detect when resistance STABILIZES at high value after parting spikes.
* Primary indicator: Resistance (green) shoots UP, exhibits parting spikes, then STABILIZES at high/infinite value
* Cross-reference: Travel (red) should be decreasing (opening motion)
* Cross-reference: Current (blue) may drop or fluctuate during final arc extinction
* **Typical Duration**: 40-80 ms (Zone 4 typically ends around 280-340 ms total time)
* **Detection Logic**: Find where 'res' completes its rise and spike activity, becoming constant at high value
* **CRITICAL**: Do NOT extend this zone too long - end AS SOON AS resistance stabilizes
**Zone 5: Final Open State (Fully Open)**
* **Physical Meaning**: The contacts are fully separated with an air gap. No current flows, resistance is infinite. The breaker is in its fully open, non-conducting state.
* **Start**: End of Zone 4
* **End**: The last time point in the dataset
* **Characteristics**:
* Resistance (green): Very high/infinite (flat line at top)
* Current (blue): Zero or near-zero
* Travel (red): Stable at minimum (fully open position)
**MULTI-CURVE ANALYSIS STRATEGY:**
1. Use Current (blue) to identify Zone 1 → Zone 2 transition (first current rise)
2. Use Resistance (green) to identify Zone 2 → Zone 3 transition (resistance settles to low plateau)
3. Use Travel (red) to identify Zone 3 → Zone 4 transition (travel peak and reversal)
4. Use Resistance (green) to identify Zone 4 → Zone 5 transition (resistance stabilizes at high value)
5. Always cross-validate boundaries using all three curves for consistency
**OUTPUT FORMAT (Strict JSON)**
Return ONLY this JSON object:
{{
"zones": {{
"zone_1_pre_contact": {{ "start_ms": float, "end_ms": float, "justification": "string (explain which curve indicators were used)" }},
"zone_2_arcing_engagement": {{ "start_ms": float, "end_ms": float, "justification": "string (explain which curve indicators were used)" }},
"zone_3_main_conduction": {{ "start_ms": float, "end_ms": float, "justification": "string (explain which curve indicators were used)" }},
"zone_4_parting": {{ "start_ms": float, "end_ms": float, "justification": "string (explain which curve indicators were used)" }},
"zone_5_final_open": {{ "start_ms": float, "end_ms": float, "justification": "string (explain which curve indicators were used)" }}
}},
"report_card": {{
"opening_speed": {{ "status": "Pass"|"Warning"|"Fail", "comment": "Assessment of travel curve steepness" }},
"contact_wear": {{ "status": "Pass"|"Warning"|"Fail", "comment": "Based on resistance fluctuations in Zone 2/4" }},
"timing_consistency": {{ "status": "Pass"|"Warning"|"Fail", "comment": "Are phases within expected ranges?" }},
"overall_health": {{ "status": "Healthy"|"Needs Review"|"Critical", "comment": "Overall summary" }}
}},
"detailed_analysis": "Provide a comprehensive technical analysis (in Markdown)..."
}}
"""
def ask_llm_for_breakage(df, api_key, model_name, image_bytes=None):
"""
Sends the DataFrame and optional image to LLM (Gemini) for segmentation.
Returns (df, result_json) where df has a new 'Zone' column.
"""
if not api_key: return df, None
try:
genai.configure(api_key=api_key)
# Configure safety settings
safety_settings = [
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
]
model = genai.GenerativeModel(
model_name=model_name,
safety_settings=safety_settings
)
except Exception as e:
return df, {"error": f"Failed to initialize Gemini API: {str(e)}"}
# Prepare Data
# Rename columns for LLM clarity
df_llm = df[['Time (ms)', 'Current', 'Resistance', 'Travel']].copy()
df_llm.columns = ['time', 'curr', 'res', 'travel']
# Round values
df_llm = df_llm.round(1)
# Sample to keep prompt size manageable (e.g., every 5th row)
# User's code used df.to_string(index=False), implying they might not have sampled,
# but for safety with large CSVs, we'll keep sampling but use to_string format.
df_sampled = df_llm.iloc[::5, :]
data_str = df_sampled.to_string(index=False)
prompt = get_dcrm_prompt(data_str)
content = [prompt]
if image_bytes:
try:
image = PIL.Image.open(io.BytesIO(image_bytes))
content.append(image)
except Exception as e:
return df, {"error": f"Failed to process image: {str(e)}"}
try:
response = model.generate_content(content)
if not response.text:
if hasattr(response, 'prompt_feedback'):
return df, {
"error": "Response blocked by safety filters",
"raw_response": str(response.prompt_feedback)
}
return df, {"error": "LLM returned empty response"}
result = response.text.strip()
# Remove markdown code blocks
if "```json" in result:
result = result.split("```json")[1].split("```")[0].strip()
elif "```" in result:
result = result.split("```")[1].split("```")[0].strip()
# Parse JSON
try:
result_json = json.loads(result)
zones = result_json.get("zones", {})
# Enrich DataFrame with Zones
df['Zone'] = "Unknown"
for zone_name, details in zones.items():
start = details.get("start_ms")
end = details.get("end_ms")
if start is not None and end is not None:
# Map zone name to a simpler label (e.g., "Zone 1")
short_name = zone_name.split('_')[1] # "1", "2", etc.
mask = (df['Time (ms)'] >= start) & (df['Time (ms)'] <= end)
df.loc[mask, 'Zone'] = f"Zone {short_name}"
return df, result_json
except json.JSONDecodeError as je:
return df, {
"error": f"JSON parsing failed: {str(je)}",
"raw_response": result[:1000]
}
except Exception as e:
return df, {"error": f"LLM API error: {str(e)}"}
def analyze_health_with_llm(image_bytes, api_key, model_name, numerical_context=None):
"""
Sends the DCRM image to Gemini for expert diagnostic analysis.
Numerical context is a dict of extracted values (e.g. min resistance) to prevent hallucination.
"""
if not api_key or not image_bytes: return None
try:
genai.configure(api_key=api_key)
safety_settings = [
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
]
model = genai.GenerativeModel(
model_name=model_name,
safety_settings=safety_settings
)
# Build context string
context_str = ""
if numerical_context:
context_str = f"""
NUMERICAL DATA CONTEXT (From Raw Extraction):
- Minimum Static Resistance Found: {numerical_context.get('min_resistance', 'N/A')} µΩ
- Median Resistance Found: {numerical_context.get('median_resistance', 'N/A')} µΩ
NOTE: If the extracted resistance is HIGH (e.g. >200 uOhm) but the curve looks flat and healthy,
it indicates the data extraction scale is uncalibrated, but the relative health is good.
Trust the SHAPE (flatness/noise) over the absolute number if they conflict, but mention the value.
"""
prompt = f"""
System Role: Principal DCRM & Kinematic Analyst
Role:
You are an expert High-Voltage Circuit Breaker Diagnostician. Your task is to interpret Dynamic Contact Resistance (DCRM) traces to detect specific electrical and mechanical faults.
{context_str}
Critical "Anti-Overfitting" Directive:
You must distinguish between Systematic Defects and Artifacts.
Sensor/Manufacturing Noise: A totally flat line is rare in real-world data. Slight "fuzz" or very minute "grassiness" (amplitude < 10 μΩ) is often sensor noise, ADC quantization, or normal manufacturing surface variance. Do not flag this as a defect.
True Degradation: Flag issues only when the visual signature is statistically significant and exceeds the "noise floor."
Capability:
Identify Multiple Concurrent Issues if present. (e.g., A breaker can have both misalignment and contact wear).
there will mostly be 3 line charts in the input
green resistance profile
blue current profile
red travel profile
1. Diagnostic Heuristics & Defect Taxonomy
Map the visual DCRM trace to ONLY the following defect types. Use the specific Visual Heuristics to confirm detection.
Defect Type | Visual Heuristic (The "Hint") | Mechanical Significance (Root Cause)
--- | --- | ---
Main Contact Issue (Corrosion/Oxidation) | "The Significant Grass"<br>In the fully closed plateau, look for pronounced, erratic instability. <br>• Ignore: Uniform, low-amplitude fuzz (sensor noise).<br>• Flag: Jagged, irregular peaks/valleys with significant amplitude (e.g., > 15–20 μΩ variance). The trace looks like a "rough rocky road," not just a "gravel path." | Surface Pathology: The Silver (Ag) plating is compromised (fretting corrosion) or heavy oxidation has occurred. The current path is constantly shifting through microscopic non-conductive spots.
Arcing Contact Wear | "Big Spikes & Short Wipe"<br>Resistance spikes are frequent and significantly large (high amplitude). Crucially, the duration of the arcing zone (the time between first touch and main contact touch) is noticeably shorter than expected. | Ablation: The Tungsten-Copper (W-Cu) tips are heavily eroded. The contact length has physically diminished, risking failure to commutate current during opening.
Misalignment (Main) | "The Struggle to Settle"<br>There are significant, high-amplitude peaks just before the trace tries to settle into the stable plateau. These are not bounces; they are "struggles" to mate that persist longer than 3-5ms. | Mechanical Centering: The moving contact pin is hitting the side or edge of the stationary rosette fingers before forcing its way in. Caused by loose nuts, kinematic play, or guide ring failure.
Misalignment (Arcing) | "Rough Entry"<br>Erratic resistance spikes occurring specifically during the initial entry (commutation), well before the main contacts engage. | Tip Eccentricity: The arcing pin is not entering the nozzle concentrically. It is scraping the nozzle throat or hitting the side, indicating a bent rod or skewed interrupter.
Slow Mechanism | "Stretched Time"<br>The entire resistance profile is elongated along the X-axis. Events happen later than normal. | Energy Starvation: Low spring charge, hydraulic pressure loss, or high friction due to hardened grease in the linkage.
2. Analysis Logic (The "Signal-to-Noise" Filter)
Before declaring a defect, run these logic checks:
The "Noise Floor" Test (For Main Contacts):
Is the plateau variance uniform and small (< 10 μΩ)? -> Classify as Healthy (Sensor/Manufacturing artifact).
Is the variance erratic, jagged, and large (> 15 μΩ)? -> Classify as Corrosion/Oxidation.
The "Duration" Test (For Misalignment):
Are the pre-plateau peaks < 2ms? -> Ignore (Benign Bounce).
Do the peaks persist > 3-5ms before settling? -> Classify as Misalignment.
The "Combination" Check:
Does the trace show both "Rough Entry" AND "Stretched Time"? -> Report Both (Misalignment + Slow Mechanism).
3. Output Structure
Provide a concise Executive Lead followed by the JSON.
Executive Lead (3-4 Lines)
Status: Healthy | Warning | Critical.
Key Findings: Summary of valid defects found (ignoring sensor noise).
Action: "Return to service" or specific repair instruction.
JSON Schema
```json
{
"image_url": "string",
"overall_condition": "Healthy|Warning|Critical",
"health_score": "integer (0-100) where 100 is perfect condition",
"detected_issues": [
{
"issue_type": "Main Contact Issue (Corrosion/Oxidation)|Arcing Contact Wear|Misalignment (Main)|Misalignment (Arcing)|Slow Mechanism",
"confidence": "High|Medium|Low",
"visual_evidence": "string (e.g., 'Plateau instability >20 micro-ohms detected, exceeding sensor noise threshold.')",
"mechanical_significance": "string (Root cause from table)",
"severity": "Low|Medium|High"
}
],
"analysis_metrics": {
"static_resistance_Rp_uOhm": "float",
"signal_noise_level": "Low (Sensor/Mfg)|High (Defect)",
"wipe_quality": "Normal|Short|Erratic"
},
"maintenance_recommendation": "string"
}
```
"""
image = PIL.Image.open(io.BytesIO(image_bytes))
response = model.generate_content([prompt, image])
if not response.text:
return {"error": "LLM returned empty response"}
return response.text
except Exception as e:
return {"error": f"LLM Analysis Error: {str(e)}"}