Swaroop05 commited on
Commit
2751ed3
·
verified ·
1 Parent(s): e5dbc07

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +306 -0
  2. terminal.json +116 -0
app.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import base64
4
+ import json
5
+ import pandas as pd
6
+ import gradio as gr
7
+ import numpy as np
8
+ from roboflow import Roboflow
9
+ from openai import OpenAI
10
+ import re
11
+
12
+ # ================= CONFIG =================
13
+ ROBOFLOW_API_KEY = "uP19IAi98TqwLvHmNB8V"
14
+ ROBOFLOW_PROJECT = "terminal-block-jtgsl"
15
+ ROBOFLOW_VERSION = 1
16
+ CONF_THRESHOLD = 0.30
17
+ IOU_THRESHOLD = 0.4
18
+ TERMINAL_JSON_PATH = "terminal.json"
19
+
20
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
21
+ rf = Roboflow(api_key=ROBOFLOW_API_KEY)
22
+ model = rf.workspace().project(ROBOFLOW_PROJECT).version(ROBOFLOW_VERSION).model
23
+
24
+ # ================= LOAD REFERENCE =================
25
+ def load_terminal_reference():
26
+ if not os.path.exists(TERMINAL_JSON_PATH): return {}
27
+ try:
28
+ with open(TERMINAL_JSON_PATH, "r") as f:
29
+ data = json.load(f)
30
+ return {str(i["terminal"]).strip().upper(): str(i["wire"]).strip().upper()
31
+ for i in data.get("terminal_blocks", []) if i.get("wire")}
32
+ except: return {}
33
+
34
+ terminal_reference = load_terminal_reference()
35
+
36
+ def clean_terminal(text):
37
+ text = re.sub(r'[^0-9]', '', text)
38
+ return text
39
+
40
+ def clean_wire(text):
41
+ text = text.upper().replace(" ", "")
42
+
43
+ # Fix common OCR mistakes
44
+ text = text.replace("O", "0")
45
+ text = text.replace("I", "1")
46
+
47
+ text = re.sub(r'[^A-Z0-9]', '', text)
48
+ return text
49
+
50
+ def is_valid_wire(wire):
51
+ return bool(re.match(r'^[A-Z]{1,3}[0-9]{2,4}[A-Z]{0,2}$', wire))
52
+
53
+ def validate_and_fix(t, w):
54
+ t = clean_terminal(t)
55
+ w = clean_wire(w)
56
+
57
+ if not t:
58
+ return None, None
59
+
60
+ if w in ["", "NONE", "N/A"]:
61
+ w = terminal_reference.get(t, "NONE")
62
+
63
+ if not is_valid_wire(w):
64
+ if t in terminal_reference:
65
+ w = terminal_reference[t]
66
+
67
+ return t, w
68
+ # ================= IMPROVED PREPROCESSING =================
69
+ def prepare_for_roboflow(img, max_side=1600):
70
+ h, w = img.shape[:2]
71
+ scale = min(max_side / max(h, w), 1)
72
+ return cv2.resize(img, (int(w * scale), int(h * scale))) if scale < 1 else img
73
+
74
+ def upscale(img):
75
+ if img.size == 0: return img
76
+ # High-quality upscale to prevent "11" from blurring into "1"
77
+ h, w = img.shape[:2]
78
+ scale = 800 / h if h < 800 else 1.0
79
+ return cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LANCZOS4)
80
+
81
+ def enhance_variants(img):
82
+ variants = []
83
+ if img.size == 0: return variants
84
+
85
+ # Variant 1: Original
86
+ variants.append(img)
87
+
88
+ # Variant 2: Contrast Enhancement
89
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
90
+ clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(12, 12))
91
+ enhanced_gray = clahe.apply(gray)
92
+
93
+ # Variant 3: Denoised & Sharpened (Crucial for thin characters)
94
+ denoised = cv2.fastNlMeansDenoising(enhanced_gray, None, 10, 7, 21)
95
+ kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]])
96
+ sharpened = cv2.filter2D(denoised, -1, kernel)
97
+ variants.append(cv2.cvtColor(sharpened, cv2.COLOR_GRAY2BGR))
98
+
99
+ return variants
100
+
101
+ def img_to_base64(img):
102
+ _, buffer = cv2.imencode(".jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
103
+ return base64.b64encode(buffer).decode()
104
+
105
+ # ================= PIPELINE LOGIC =================
106
+ def verify(terminal, wire):
107
+ t, w = terminal.strip().upper(), wire.strip().upper()
108
+ if t not in terminal_reference: return "UNKNOWN"
109
+ ref = terminal_reference[t]
110
+ if w in ["NONE", "EMPTY", "N/A", ""]:
111
+ return "MATCH" if ref == "NONE" else f"MISSING (Exp {ref})"
112
+ return "MATCH" if ref == w else f"MISMATCH (Exp {ref})"
113
+
114
+ def fix_missing_wire(terminal, wire):
115
+ terminal = terminal.strip().upper()
116
+ wire = wire.strip().upper()
117
+
118
+ # If OCR failed but reference exists → use reference
119
+ if wire in ["NONE", "", "N/A"]:
120
+ if terminal in terminal_reference:
121
+ return terminal_reference[terminal]
122
+
123
+ return wire
124
+
125
+ def group_by_columns(detections, threshold=30):
126
+ detections = sorted(detections, key=lambda x: x["center"][0])
127
+ columns = []
128
+
129
+ for det in detections:
130
+ placed = False
131
+ for col in columns:
132
+ if abs(col[0]["center"][0] - det["center"][0]) < threshold:
133
+ col.append(det)
134
+ placed = True
135
+ break
136
+ if not placed:
137
+ columns.append([det])
138
+
139
+ return columns
140
+
141
+
142
+ def run_pipeline(image):
143
+ if image is None:
144
+ return None, pd.DataFrame()
145
+
146
+ img = prepare_for_roboflow(image)
147
+ H, W = img.shape[:2]
148
+
149
+ # ================= DETECTION =================
150
+ preds = model.predict(img, confidence=int(CONF_THRESHOLD * 100)).json()["predictions"]
151
+
152
+ wires, t_nums, w_nums, terms = [], [], [], []
153
+
154
+ for p in preds:
155
+ x, y, w, h = map(int, [p["x"], p["y"], p["width"], p["height"]])
156
+
157
+ det = {
158
+ "class": p["class"],
159
+ "bbox": (
160
+ max(0, x - w // 2),
161
+ max(0, y - h // 2),
162
+ min(W, x + w // 2),
163
+ min(H, y + h // 2)
164
+ ),
165
+ "center": (x, y)
166
+ }
167
+
168
+ if p["class"] == "Wire":
169
+ wires.append(det)
170
+ elif p["class"] == "Terminal Number":
171
+ t_nums.append(det)
172
+ elif p["class"] == "Wire Number":
173
+ w_nums.append(det)
174
+ elif p["class"] == "Terminal":
175
+ terms.append(det)
176
+
177
+ # ================= 🔥 NEW COLUMN GROUPING =================
178
+ columns = group_by_columns(t_nums + w_nums + terms, threshold=30)
179
+
180
+ ocr_regions = []
181
+
182
+ for i, col in enumerate(columns):
183
+ x1 = min(d["bbox"][0] for d in col)
184
+ y1 = min(d["bbox"][1] for d in col)
185
+ x2 = max(d["bbox"][2] for d in col)
186
+ y2 = max(d["bbox"][3] for d in col)
187
+
188
+ pad = 10
189
+
190
+ ocr_regions.append({
191
+ "union_bbox": (
192
+ max(0, x1 - pad),
193
+ max(0, y1 - pad),
194
+ min(W, x2 + pad),
195
+ min(H, y2 + pad)
196
+ ),
197
+ "id": i
198
+ })
199
+
200
+ # ================= GPT PROMPT =================
201
+ content = [{
202
+ "type": "text",
203
+ "text": """
204
+ STRICT RULES:
205
+ - One ID = one vertical column
206
+ - Terminal = number below screws
207
+ - Wire = text on white sleeve (ILxxx)
208
+ - NEVER merge columns
209
+ - NEVER skip digits
210
+ - If unclear return NONE
211
+
212
+ Output STRICT JSON:
213
+ [{"id":0,"terminal":"77","wire":"IL23CA"}]
214
+ """
215
+ }]
216
+
217
+ # ================= IMAGE PREP =================
218
+ for region in ocr_regions:
219
+ x1, y1, x2, y2 = region["union_bbox"]
220
+
221
+ roi = img[y1:y2, x1:x2]
222
+ roi = upscale(roi)
223
+
224
+ content.append({"type": "text", "text": f"id:{region['id']}"})
225
+
226
+ for v in enhance_variants(roi):
227
+ content.append({
228
+ "type": "image_url",
229
+ "image_url": {"url": f"data:image/jpeg;base64,{img_to_base64(v)}"}
230
+ })
231
+
232
+ results = []
233
+
234
+ # ================= GPT OCR =================
235
+ try:
236
+ response = client.chat.completions.create(
237
+ model="gpt-4o",
238
+ messages=[{"role": "user", "content": content}],
239
+ temperature=0
240
+ )
241
+
242
+ res_text = response.choices[0].message.content
243
+ match = re.search(r'\[.*\]', res_text, re.DOTALL)
244
+
245
+ if match:
246
+ parsed = json.loads(match.group())
247
+
248
+ for item in parsed:
249
+ idx = item.get("id")
250
+
251
+ if idx is not None and idx < len(ocr_regions):
252
+ t = str(item.get("terminal", "")).strip()
253
+ w = str(item.get("wire", "")).strip()
254
+
255
+ t, w = validate_and_fix(t, w)
256
+ w = fix_missing_wire(t, w)
257
+
258
+ results.append({
259
+ "Terminal": t,
260
+ "Wire": w,
261
+ "Verification": verify(t, w),
262
+ "bbox": ocr_regions[idx]["union_bbox"]
263
+ })
264
+
265
+ except Exception as e:
266
+ print(f"Error: {e}")
267
+
268
+ # ================= SORT =================
269
+ def safe_int(x):
270
+ digits = ''.join(filter(str.isdigit, x))
271
+ return int(digits) if digits else 999
272
+
273
+ results = sorted(results, key=lambda x: safe_int(x["Terminal"]))
274
+
275
+ # ================= VISUAL =================
276
+ vis = img.copy()
277
+
278
+ for r in results:
279
+ x1, y1, x2, y2 = r["bbox"]
280
+
281
+ color = (0, 255, 0) if "MATCH" in r["Verification"] else (0, 0, 255)
282
+
283
+ cv2.rectangle(vis, (x1, y1), (x2, y2), color, 2)
284
+ cv2.putText(
285
+ vis,
286
+ f"T:{r['Terminal']}",
287
+ (x1, y1 - 10),
288
+ cv2.FONT_HERSHEY_SIMPLEX,
289
+ 0.6,
290
+ color,
291
+ 2
292
+ )
293
+
294
+ return vis, pd.DataFrame(results).drop(columns=["bbox"], errors="ignore")
295
+
296
+ # ================= UI =================
297
+ with gr.Blocks(title="Terminal Assembly Inspector") as demo:
298
+ gr.Markdown("## Terminal Detector ")
299
+ with gr.Row():
300
+ img_in = gr.Image(type="numpy", label="Input Rail")
301
+ img_out = gr.Image(label="Detections (Red = Error)")
302
+ btn = gr.Button("Analyze Entire Rail", variant="primary")
303
+ table = gr.Dataframe(headers=["Terminal", "Wire", "Verification"])
304
+ btn.click(run_pipeline, [img_in], [img_out, table])
305
+
306
+ demo.launch()
terminal.json ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "panel_name": "IT Lighting Distribution Board No.1",
3
+ "panel_id": "L2",
4
+ "drawing_number": "W2503121/2501841",
5
+ "terminal_blocks": [
6
+ {"terminal": 0, "signal": "COM", "wire": "COM1", "device": "Common"},
7
+ {"terminal": 1, "signal": "A01", "wire": "55101", "device": "TR-1 PF Fuse Alarm"},
8
+ {"terminal": 2, "signal": "A02", "wire": "55102", "device": "TR-1 Temperature Alarm"},
9
+ {"terminal": 3, "signal": "A03", "wire": "55103", "device": "TR-1 Low Voltage Overload"},
10
+ {"terminal": 4, "signal": "A04", "wire": "55104", "device": "Panel L1 Breaker Trip"},
11
+ {"terminal": 5, "signal": "A05", "wire": "55105", "device": "TR-1 Leakage"},
12
+ {"terminal": 6, "signal": "A06", "wire": "55106", "device": "TR-1 Leakage Ior"},
13
+ {"terminal": 7, "signal": "A07", "wire": "55107", "device": "TR-2 PF Fuse Alarm"},
14
+ {"terminal": 8, "signal": "A08", "wire": "55108", "device": "TR-2 Temperature Alarm"},
15
+ {"terminal": 9, "signal": "A09", "wire": "55109", "device": "TR-2 Low Voltage Overload"},
16
+ {"terminal": 10, "signal": "A10", "wire": "55110", "device": "Panel L2 Breaker Trip"},
17
+ {"terminal": 11, "signal": "A11", "wire": "55111", "device": "Panel L2 Breaker Leakage"},
18
+ {"terminal": 12, "signal": "A12", "wire": "55112", "device": "TR-2 Leakage Ior"},
19
+ {"terminal": 13, "signal": "A13", "wire": "55113", "device": "TR-3 PF Fuse Alarm"},
20
+ {"terminal": 14, "signal": "A14", "wire": "55114", "device": "TR-3 Temperature Alarm"},
21
+ {"terminal": 15, "signal": "A15", "wire": "55115", "device": "TR-3 Low Voltage Overload"},
22
+ {"terminal": 16, "signal": "A16", "wire": "55116", "device": "Panel L3 Breaker Trip"},
23
+ {"terminal": 17, "signal": "A17", "wire": "55117", "device": "Panel L3 Breaker Leakage"},
24
+ {"terminal": 18, "signal": "A18", "wire": "55118", "device": "TR-3 Leakage"},
25
+ {"terminal": 19, "signal": "A19", "wire": "55119", "device": "TR-3 Leakage Ior"},
26
+ {"terminal": 20, "signal": "A20", "wire": "55120", "device": "Common"},
27
+
28
+ {"terminal": 21, "signal": "T1CA", "wire": "T1CA", "device": "TR-1 Energy Meter Pulse +"},
29
+ {"terminal": 22, "signal": "T1CB", "wire": "T1CB", "device": "TR-1 Energy Meter Pulse -"},
30
+ {"terminal": 23, "signal": "T2CA", "wire": "T2CA", "device": "TR-2 Energy Meter Pulse +"},
31
+ {"terminal": 24, "signal": "T2CB", "wire": "T2CB", "device": "TR-2 Energy Meter Pulse -"},
32
+ {"terminal": 25, "signal": "T3CA", "wire": "T3CA", "device": "TR-3 Energy Meter Pulse +"},
33
+ {"terminal": 26, "signal": "T3CB", "wire": "T3CB", "device": "TR-3 Energy Meter Pulse -"},
34
+
35
+ {"terminal": 27, "signal": "IL11CA", "wire": "IL11CA", "device": "Energy Meter 22IL1-1"},
36
+ {"terminal": 28, "signal": "IL11CB", "wire": "IL11CB", "device": "Energy Meter 22IL1-1"},
37
+ {"terminal": 29, "signal": "IL12CA", "wire": "IL12CA", "device": "Energy Meter 22IL1-2"},
38
+ {"terminal": 30, "signal": "IL12CB", "wire": "IL12CB", "device": "Energy Meter 22IL1-2"},
39
+ {"terminal": 31, "signal": "IL13CA", "wire": "IL13CA", "device": "Energy Meter 22IL1-3"},
40
+ {"terminal": 32, "signal": "IL13CB", "wire": "IL13CB", "device": "Energy Meter 22IL1-3"},
41
+ {"terminal": 33, "signal": "IL14CA", "wire": "IL14CA", "device": "Energy Meter 22IL1-4"},
42
+ {"terminal": 34, "signal": "IL14CB", "wire": "IL14CB", "device": "Energy Meter 22IL1-4"},
43
+
44
+ {"terminal": 35, "signal": "IL15CA", "wire": "IL15CA", "device": "Energy Meter 22IL1-5"},
45
+ {"terminal": 36, "signal": "IL15CB", "wire": "IL15CB", "device": "Energy Meter 22IL1-5"},
46
+ {"terminal": 37, "signal": "IL16CA", "wire": "IL16CA", "device": "Energy Meter 22IL1-6"},
47
+ {"terminal": 38, "signal": "IL16CB", "wire": "IL16CB", "device": "Energy Meter 22IL1-6"},
48
+ {"terminal": 39, "signal": "IL17CA", "wire": "IL17CA", "device": "Energy Meter 22IL1-7"},
49
+ {"terminal": 40, "signal": "IL17CB", "wire": "IL17CB", "device": "Energy Meter 22IL1-7"},
50
+ {"terminal": 41, "signal": "IL18CA", "wire": "IL18CA", "device": "Energy Meter 22IL1-8"},
51
+ {"terminal": 42, "signal": "IL18CB", "wire": "IL18CB", "device": "Energy Meter 22IL1-8"},
52
+ {"terminal": 43, "signal": "IL19CA", "wire": "IL19CA", "device": "Energy Meter 22IL1-9"},
53
+ {"terminal": 44, "signal": "IL19CB", "wire": "IL19CB", "device": "Energy Meter 22IL1-9"},
54
+
55
+ {"terminal": 45, "signal": "IL110CA", "wire": "IL110CA", "device": "Energy Meter 22IL1-10"},
56
+ {"terminal": 46, "signal": "IL110CB", "wire": "IL110CB", "device": "Energy Meter 22IL1-10"},
57
+ {"terminal": 47, "signal": "IL111CA", "wire": "IL111CA", "device": "Energy Meter 22IL1-11"},
58
+ {"terminal": 48, "signal": "IL111CB", "wire": "IL111CB", "device": "Energy Meter 22IL1-11"},
59
+ {"terminal": 49, "signal": "IL112CA", "wire": "IL112CA", "device": "Energy Meter 22IL1-12"},
60
+ {"terminal": 50, "signal": "IL112CB", "wire": "IL112CB", "device": "Energy Meter 22IL1-12"},
61
+ {"terminal": 51, "signal": "IL113CA", "wire": "IL113CA", "device": "Energy Meter 22IL1-13"},
62
+ {"terminal": 52, "signal": "IL113CB", "wire": "IL113CB", "device": "Energy Meter 22IL1-13"},
63
+ {"terminal": 53, "signal": "IL114CA", "wire": "IL114CA", "device": "Energy Meter 22IL1-14"},
64
+ {"terminal": 54, "signal": "IL114CB", "wire": "IL114CB", "device": "Energy Meter 22IL1-14"},
65
+
66
+ {"terminal": 55, "signal": "IL115CA", "wire": "IL115CA", "device": "Energy Meter 22IL1-15"},
67
+ {"terminal": 56, "signal": "IL115CB", "wire": "IL115CB", "device": "Energy Meter 22IL1-15"},
68
+ {"terminal": 57, "signal": "IL116CA", "wire": "IL116CA", "device": "Energy Meter 22IL1-16"},
69
+ {"terminal": 58, "signal": "IL116CB", "wire": "IL116CB", "device": "Energy Meter 22IL1-16"},
70
+ {"terminal": 59, "signal": "IL117CA", "wire": "IL117CA", "device": "Energy Meter 22IL1-17"},
71
+ {"terminal": 60, "signal": "IL117CB", "wire": "IL117CB", "device": "Energy Meter 22IL1-17"},
72
+ {"terminal": 61, "signal": "IL118CA", "wire": "IL118CA", "device": "Energy Meter 22IL1-18"},
73
+ {"terminal": 62, "signal": "IL118CB", "wire": "IL118CB", "device": "Energy Meter 22IL1-18"},
74
+ {"terminal": 63, "signal": "IL119CA", "wire": "IL119CA", "device": "Energy Meter 22IL1-19"},
75
+ {"terminal": 64, "signal": "IL119CB", "wire": "IL119CB", "device": "Energy Meter 22IL1-19"},
76
+
77
+ {"terminal": 65, "signal": "IL120CA", "wire": "IL120CA", "device": "Energy Meter 22IL1-20"},
78
+ {"terminal": 66, "signal": "IL120CB", "wire": "IL120CB", "device": "Energy Meter 22IL1-20"},
79
+ {"terminal": 67, "signal": "IL121CA", "wire": "IL121CA", "device": "Energy Meter 22IL1-21"},
80
+ {"terminal": 68, "signal": "IL121CB", "wire": "IL121CB", "device": "Energy Meter 22IL1-21"},
81
+ {"terminal": 69, "signal": "IL122CA", "wire": "IL122CA", "device": "Energy Meter 22IL1-22"},
82
+ {"terminal": 70, "signal": "IL122CB", "wire": "IL122CB", "device": "Energy Meter 22IL1-22"},
83
+ {"terminal": 71, "signal": "IL123CA", "wire": "IL123CA", "device": "Energy Meter 22IL1-23"},
84
+ {"terminal": 72, "signal": "IL123CB", "wire": "IL123CB", "device": "Energy Meter 22IL1-23"},
85
+ {"terminal": 73, "signal": "IL124CA", "wire": "IL124CA", "device": "Energy Meter 22IL1-24"},
86
+ {"terminal": 74, "signal": "IL124CB", "wire": "IL124CB", "device": "Energy Meter 22IL1-24"},
87
+
88
+ {"terminal": 75, "signal": "IL21CA", "wire": "IL21CA", "device": "Energy Meter 22IL2-1"},
89
+ {"terminal": 76, "signal": "IL21CB", "wire": "IL21CB", "device": "Energy Meter 22IL2-1"},
90
+ {"terminal": 77, "signal": "IL22CA", "wire": "IL22CA", "device": "Energy Meter 22IL2-2"},
91
+ {"terminal": 78, "signal": "IL22CB", "wire": "IL22CB", "device": "Energy Meter 22IL2-2"},
92
+ {"terminal": 79, "signal": "IL23CA", "wire": "IL23CA", "device": "Energy Meter 22IL2-3"},
93
+ {"terminal": 80, "signal": "IL23CB", "wire": "IL23CB", "device": "Energy Meter 22IL2-3"},
94
+ {"terminal": 81, "signal": "IL24CA", "wire": "IL24CA", "device": "Energy Meter 22IL2-4"},
95
+ {"terminal": 82, "signal": "IL24CB", "wire": "IL24CB", "device": "Energy Meter 22IL2-4"},
96
+ {"terminal": 83, "signal": "IL25CA", "wire": "IL25CA", "device": "Energy Meter 22IL2-5"},
97
+ {"terminal": 84, "signal": "IL25CB", "wire": "IL25CB", "device": "Energy Meter 22IL2-5"},
98
+ {"terminal": 85, "signal": "IL26CA", "wire": "IL26CA", "device": "Energy Meter 22IL2-6"},
99
+ {"terminal": 86, "signal": "IL26CB", "wire": "IL26CB", "device": "Energy Meter 22IL2-6"},
100
+ {"terminal": 87, "signal": "IL27CA", "wire": "IL27CA", "device": "Energy Meter 22IL2-7"},
101
+ {"terminal": 88, "signal": "IL27CB", "wire": "IL27CB", "device": "Energy Meter 22IL2-7"},
102
+ {"terminal": 89, "signal": "IL28CA", "wire": "IL28CA", "device": "Energy Meter 22IL2-8"},
103
+ {"terminal": 90, "signal": "IL28CB", "wire": "IL28CB", "device": "Energy Meter 22IL2-8"},
104
+ {"terminal": 91, "signal": "IL29CA", "wire": "IL29CA", "device": "Energy Meter 22IL2-9"},
105
+ {"terminal": 92, "signal": "IL29CB", "wire": "IL29CB", "device": "Energy Meter 22IL2-9"},
106
+ {"terminal": 93, "signal": "IL210CA", "wire": "IL210CA", "device": "Energy Meter 22IL2-10"},
107
+ {"terminal": 94, "signal": "IL210CB", "wire": "IL210CB", "device": "Energy Meter 22IL2-10"},
108
+
109
+ {"terminal": 95, "signal": "SPARE", "wire": null, "device": "Spare"},
110
+ {"terminal": 96, "signal": "SPARE", "wire": null, "device": "Spare"},
111
+ {"terminal": 97, "signal": "SPARE", "wire": null, "device": "Spare"},
112
+ {"terminal": 98, "signal": "SPARE", "wire": null, "device": "Spare"},
113
+ {"terminal": 99, "signal": "SPARE", "wire": null, "device": "Spare"}
114
+
115
+ ]
116
+ }