Sathvika-Alla commited on
Commit
027f0de
·
verified ·
1 Parent(s): afcf4ac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +487 -292
app.py CHANGED
@@ -1,414 +1,609 @@
1
-
2
  import json
3
  import gradio as gr
4
  import pandas as pd
5
  import datetime
 
6
  import uuid
7
- import re
8
  from typing import Dict, Any
9
- from collections import OrderedDict
 
 
10
 
11
- # ======================
12
- # Configuration
13
- # ======================
14
- MAIN_JSON_PATH = "./converters_with_links_and_pricelist.json"
15
- METADATA_JSON_PATH = "./converters_metadata.json"
16
- SECONDARY_JSON_PATH = "./converters_improved.json"
17
 
18
- # ======================
19
- # Data Transformation Functions
20
- # ======================
21
- def rename_and_restructure_item_cosmos(item):
22
- if "id" not in item:
23
- item["id"] = str(uuid.uuid4())
24
-
25
- key_mapping = {
26
- "TYPE": "type",
27
- "ARTNR": "artnr",
28
- "CONVERTER DESCRIPTION": "converter_description",
29
- "STRAIN RELIEF": "strain_relief",
30
- "LOCATION": "location",
31
- "DIMMABILITY": "dimmability",
32
- "CCR (AMPLITUDE)": "ccr_amplitude",
33
- "SIZE: L*B*H (mm)": "size",
34
- "EFFICIENCY @full load": "efficiency_full_load",
35
- "IP": "ip",
36
- "CLASS": "class",
37
- "NOM. INPUT VOLTAGE (V)": "nom_input_voltage_v",
38
- "OUTPUT VOLTAGE (V)": "output_voltage_v",
39
- "Barcode": "barcode",
40
- "Name": "name",
41
- "Listprice": "listprice",
42
- "Unit": "unit",
43
- "LifeCycle": "lifecycle",
44
- "pdf_link": "pdf_link"
45
- }
46
-
47
- renamed_item = {}
48
- for old_key, value in item.items():
49
- new_key = key_mapping.get(old_key, old_key.lower())
50
- renamed_item[new_key] = value
51
-
52
- return renamed_item
53
 
54
- def clean_keys(obj):
55
- if isinstance(obj, dict):
56
- new_obj = OrderedDict()
57
- for k, v in obj.items():
58
- if k == "lamps":
59
- new_obj[k] = v
60
- else:
61
- cleaned_key = re.sub(r'[^a-zA-Z0-9_]', '', k)
62
- new_obj[cleaned_key] = clean_keys(v)
63
- return new_obj
64
- elif isinstance(obj, list):
65
- return [clean_keys(item) for item in obj]
66
- else:
67
- return obj
68
 
69
- # ======================
70
- # Core Sync Functionality
71
- # ======================
72
- def sync_secondary_json():
73
- """Transforms and saves data to secondary JSON format"""
74
- try:
75
- with open(MAIN_JSON_PATH, "r", encoding="utf-8") as f:
76
- main_data = json.load(f)
77
- with open(METADATA_JSON_PATH, "r", encoding="utf-8") as f:
78
- metadata = json.load(f)
79
-
80
- transformed_list = []
81
- for converter_id, value in main_data.items():
82
- # Skip deleted items using metadata
83
- if metadata.get(converter_id, {}).get("deleted_at"):
84
- continue
85
-
86
- # Apply transformations
87
- item = rename_and_restructure_item_cosmos(value)
88
- cleaned_item = clean_keys(item)
89
-
90
- # Ensure required fields
91
- if "id" not in cleaned_item:
92
- cleaned_item["id"] = str(uuid.uuid4())
93
- transformed_list.append(cleaned_item)
94
-
95
- with open(SECONDARY_JSON_PATH, "w", encoding="utf-8") as f:
96
- json.dump(transformed_list, f, indent=4, ensure_ascii=False)
97
-
98
- print("✅ Successfully synced secondary JSON")
99
- except Exception as e:
100
- print(f"❌ Sync failed: {str(e)}")
101
 
102
- # ======================
103
- # CRUD Functions with Metadata Management
104
- # ======================
105
- def load_json() -> Dict[str, Any]:
106
- with open(MAIN_JSON_PATH, "r", encoding="utf-8") as f:
107
- return json.load(f)
108
 
109
- def save_json(data: Dict[str, Any]):
110
- with open(MAIN_JSON_PATH, "w", encoding="utf-8") as f:
111
- json.dump(data, f, indent=4, ensure_ascii=False)
112
- sync_secondary_json()
 
 
 
 
 
 
 
 
113
 
114
- def load_metadata() -> Dict[str, Any]:
115
  try:
116
- with open(METADATA_JSON_PATH, "r", encoding="utf-8") as f:
117
  return json.load(f)
118
  except FileNotFoundError:
119
  return {}
120
 
121
- def save_metadata(data: Dict[str, Any]):
122
- with open(METADATA_JSON_PATH, "w", encoding="utf-8") as f:
123
  json.dump(data, f, indent=4, ensure_ascii=False)
124
 
125
  def get_current_time():
126
  return datetime.datetime.now().isoformat()
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  def add_converter(
129
- converter_id, converter_type, artnr, description, strain_relief, location, dimmability,
130
- ccr, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
131
- price, unit, lifecycle, pdf_link
132
  ):
133
  converter_id = converter_id.strip()
134
- data = load_json()
135
- metadata = load_metadata()
136
-
137
  if converter_id in data:
138
  return f"Converter '{converter_id}' already exists."
139
 
140
- now = get_current_time()
141
-
142
- # Main data entry
143
- data[converter_id] = {
144
- "TYPE": converter_type,
145
- "ARTNR": float(artnr) if artnr else None,
146
- "CONVERTER DESCRIPTION:": description,
147
- "STRAIN RELIEF": strain_relief,
148
- "LOCATION": location,
149
- "DIMMABILITY": dimmability,
150
- "CCR (AMPLITUDE)": ccr,
151
- "SIZE: L*B*H (mm)": size,
152
- "EFFICIENCY @full load": float(efficiency) if efficiency else None,
153
- "IP": float(ip) if ip else None,
154
- "CLASS": float(class_) if class_ else None,
155
- "NOM. INPUT VOLTAGE (V)": input_voltage,
156
- "OUTPUT VOLTAGE (V)": output_voltage,
157
- "Barcode": barcode,
158
- "Name": name,
159
- "Listprice": float(price) if price else None,
160
- "Unit": unit,
161
- "LifeCycle": lifecycle,
162
- "pdf_link": pdf_link,
163
  "lamps": {}
164
  }
165
 
166
- # Metadata entry
167
- metadata[converter_id] = {
 
168
  "created_at": now,
169
  "updated_at": now,
170
  "deleted_at": None,
171
- "price_history": [{"timestamp": now, "price": float(price)}] if price else []
 
172
  }
173
 
174
- save_json(data)
175
- save_metadata(metadata)
176
- return f"Added converter '{converter_id}'."
 
 
 
 
 
177
 
178
  def update_converter(
179
- converter_id, converter_type, artnr, description, strain_relief, location, dimmability,
180
- ccr, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
181
- price, unit, lifecycle, pdf_link
182
  ):
183
  converter_id = converter_id.strip()
184
- data = load_json()
185
- metadata = load_metadata()
186
-
187
  if converter_id not in data:
188
  return f"Converter '{converter_id}' does not exist."
189
 
190
- now = get_current_time()
191
-
192
- # Update main data
193
  info = data[converter_id]
194
- update_fields = {
195
- "TYPE": converter_type,
196
- "ARTNR": float(artnr) if artnr else None,
197
- "CONVERTER DESCRIPTION:": description,
198
- "STRAIN RELIEF": strain_relief,
199
- "LOCATION": location,
200
- "DIMMABILITY": dimmability,
201
- "CCR (AMPLITUDE)": ccr,
202
- "SIZE: L*B*H (mm)": size,
203
- "EFFICIENCY @full load": float(efficiency) if efficiency else None,
204
- "IP": float(ip) if ip else None,
205
- "CLASS": float(class_) if class_ else None,
206
- "NOM. INPUT VOLTAGE (V)": input_voltage,
207
- "OUTPUT VOLTAGE (V)": output_voltage,
208
- "Barcode": barcode,
209
- "Name": name,
210
- "Unit": unit,
211
- "LifeCycle": lifecycle,
212
- "pdf_link": pdf_link
213
- }
214
-
215
- for key, value in update_fields.items():
216
- if value:
217
- info[key] = value
218
 
219
- # Handle price changes in metadata
220
- current_price = float(price) if price else None
221
- if current_price:
222
- if "Listprice" not in info or current_price != info.get("Listprice"):
223
- metadata[converter_id]["price_history"].append({
224
- "timestamp": now,
225
- "price": current_price
226
- })
227
- info["Listprice"] = current_price
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
  # Update metadata
230
- metadata[converter_id]["updated_at"] = now
 
 
 
 
 
 
 
 
231
 
232
- # Handle ID change
233
- new_type = info.get("TYPE", "")
234
- new_artnr = info.get("ARTNR", 0)
235
- new_id = f"{new_type}mA - {int(new_artnr)}"
236
 
237
  if new_id != converter_id:
 
 
238
  data[new_id] = info
 
239
  del data[converter_id]
240
- metadata[new_id] = metadata.pop(converter_id)
241
- save_json(data)
242
- save_metadata(metadata)
243
- return f"Updated converter. ID changed to '{new_id}'."
 
 
 
244
  else:
245
- save_json(data)
246
- save_metadata(metadata)
247
- return f"Updated converter '{converter_id}'."
 
 
 
 
248
 
249
  def delete_converter(converter_id):
250
  converter_id = converter_id.strip()
251
- data = load_json()
252
- metadata = load_metadata()
253
-
254
  if converter_id not in data:
255
  return f"Converter '{converter_id}' does not exist."
256
 
257
- metadata[converter_id]["deleted_at"] = get_current_time()
258
- save_metadata(metadata)
259
- return f"Deleted converter '{converter_id}'."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
261
  def add_or_update_lamp(converter_id, lamp_name, min_val, max_val):
262
  converter_id = converter_id.strip()
263
- data = load_json()
 
 
264
  if converter_id not in data:
265
  return f"Converter '{converter_id}' does not exist."
266
 
267
- lamps = data[converter_id].setdefault("lamps", {})
268
- lamps[lamp_name.strip()] = {
269
- "min": float(min_val),
270
- "max": float(max_val)
271
- }
272
- save_json(data)
273
- return f"Added/updated lamp '{lamp_name}' in converter '{converter_id}'."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
  def delete_lamp(converter_id, lamp_name):
276
  converter_id = converter_id.strip()
277
- data = load_json()
 
 
278
  if converter_id not in data:
279
  return f"Converter '{converter_id}' does not exist."
280
 
281
  lamps = data[converter_id].get("lamps", {})
282
- if lamp_name.strip() not in lamps:
283
- return f"Lamp '{lamp_name}' not found."
 
 
284
 
285
- del lamps[lamp_name.strip()]
286
- save_json(data)
287
- return f"Deleted lamp '{lamp_name}' from converter '{converter_id}'."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
  def get_converter(converter_id):
290
- data = load_json()
291
- return json.dumps(data.get(converter_id.strip(), {}), indent=2, ensure_ascii=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  def filter_lamps(filter_type, n_latest):
294
- data = load_json()
 
 
295
  records = []
296
-
297
  for cid, cinfo in data.items():
298
- if cinfo.get("deleted_at") and filter_type != "Deleted":
299
- continue
300
-
 
 
 
 
301
  record = {
302
  "Converter ID": cid,
303
- "Created At": cinfo.get("created_at", ""),
304
- "Updated At": cinfo.get("updated_at", ""),
305
- "Price": cinfo.get("Listprice", ""),
306
- "Lamps": ", ".join(cinfo.get("lamps", {}).keys())
 
307
  }
308
-
309
- if filter_type == "Deleted":
310
- if cinfo.get("deleted_at"):
311
- record["Deleted At"] = cinfo["deleted_at"]
312
- records.append(record)
313
- elif filter_type == "Price Change" and len(cinfo.get("price_history", [])) > 1:
314
- record["Price History"] = str(cinfo["price_history"])
315
- records.append(record)
316
- else:
317
  records.append(record)
 
 
 
 
 
 
 
 
 
 
 
 
 
318
 
 
319
  if filter_type == "Latest Added":
320
- records.sort(key=lambda x: x["Created At"], reverse=True)
321
  elif filter_type == "Latest Updated":
322
- records.sort(key=lambda x: x["Updated At"], reverse=True)
323
 
324
- return pd.DataFrame(records[:n_latest])
325
 
326
- # ======================
327
- # Gradio Interface
328
- # ======================
329
  with gr.Blocks(title="TAL Converter JSON Editor") as demo:
330
  gr.Markdown("# TAL Converter JSON Editor")
331
-
332
  with gr.Tab("Add Converter"):
333
- inputs = [
334
- gr.Textbox(label=label) for label in [
335
- "Converter ID (e.g. 350mA - 930537)", "TYPE", "ARTNR", "CONVERTER DESCRIPTION:",
336
- "STRAIN RELIEF", "LOCATION", "DIMMABILITY", "CCR (AMPLITUDE)", "SIZE: L*B*H (mm)",
337
- "EFFICIENCY @full load", "IP", "CLASS", "NOM. INPUT VOLTAGE (V)", "OUTPUT VOLTAGE (V)",
338
- "Barcode", "Name", "Listprice", "Unit", "LifeCycle", "pdf_link"
339
- ]
340
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  add_btn = gr.Button("Add Converter")
342
  add_output = gr.Textbox(label="Result")
343
  add_btn.click(
344
  add_converter,
345
- inputs=inputs,
 
 
 
 
346
  outputs=add_output
347
- ).then(lambda: [gr.update(value="") for _ in inputs], outputs=inputs)
348
-
 
 
 
 
 
 
 
 
 
 
 
 
349
  with gr.Tab("Update Converter"):
350
- update_inputs = [gr.Textbox(label=f"{label} (update)") for label in [
351
- "Converter ID", "TYPE", "ARTNR", "CONVERTER DESCRIPTION:", "STRAIN RELIEF",
352
- "LOCATION", "DIMMABILITY", "CCR (AMPLITUDE)", "SIZE: L*B*H (mm)",
353
- "EFFICIENCY @full load", "IP", "CLASS", "NOM. INPUT VOLTAGE (V)",
354
- "OUTPUT VOLTAGE (V)", "Barcode", "Name", "Listprice", "Unit", "LifeCycle", "pdf_link"
355
- ]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  update_btn = gr.Button("Update Converter")
357
  update_output = gr.Textbox(label="Result")
358
  update_btn.click(
359
  update_converter,
360
- inputs=update_inputs,
 
 
 
 
361
  outputs=update_output
362
- ).then(lambda: [gr.update(value="") for _ in update_inputs], outputs=update_inputs)
363
-
 
 
 
 
 
 
 
 
 
 
 
 
364
  with gr.Tab("Delete Converter"):
365
- converter_id_d = gr.Textbox(label="Converter ID")
 
366
  delete_btn = gr.Button("Delete Converter")
367
  delete_output = gr.Textbox(label="Result")
368
  delete_btn.click(
369
- delete_converter,
370
- inputs=converter_id_d,
371
  outputs=delete_output
372
- ).then(lambda: gr.update(value=""), outputs=converter_id_d)
373
-
374
- with gr.Tab("Lamp Management"):
375
- lamp_inputs = [
376
- gr.Textbox(label=label) for label in [
377
- "Converter ID", "Lamp Name", "Min Value", "Max Value"
378
- ]
379
- ]
380
- lamp_btns = [
381
- gr.Button("Add/Update Lamp"),
382
- gr.Button("Delete Lamp")
383
- ]
 
384
  lamp_output = gr.Textbox(label="Result")
385
- lamp_btns[0].click(
386
  add_or_update_lamp,
387
- inputs=lamp_inputs,
388
  outputs=lamp_output
389
- ).then(lambda: [gr.update(value="") for _ in lamp_inputs], outputs=lamp_inputs)
390
- lamp_btns[1].click(
 
 
 
 
 
 
 
 
 
 
391
  delete_lamp,
392
- inputs=lamp_inputs[:2],
393
- outputs=lamp_output
394
- ).then(lambda: [gr.update(value="") for _ in lamp_inputs[:2]], outputs=lamp_inputs[:2])
395
-
 
 
 
 
396
  with gr.Tab("View Converter"):
397
- converter_id_v = gr.Textbox(label="Converter ID")
398
  view_btn = gr.Button("View Converter")
399
  view_output = gr.Textbox(label="Converter Data", lines=10)
400
  view_btn.click(get_converter, inputs=converter_id_v, outputs=view_output)
401
-
402
- with gr.Tab("Converters Table"):
403
  filter_type = gr.Dropdown(
404
- choices=["Latest Added", "Latest Updated", "Deleted", "Price Change"],
405
- value="Latest Added",
406
  label="Filter Type"
407
  )
408
- n_latest = gr.Slider(1, 200, value=5, label="Number of Results")
 
409
  filter_btn = gr.Button("Apply Filter")
410
- lamp_table = gr.DataFrame(label="Converters")
411
- filter_btn.click(filter_lamps, inputs=[filter_type, n_latest], outputs=lamp_table)
 
 
 
412
 
413
  if __name__ == "__main__":
414
- demo.launch()
 
 
1
  import json
2
  import gradio as gr
3
  import pandas as pd
4
  import datetime
5
+ import os
6
  import uuid
 
7
  from typing import Dict, Any
8
+ from azure.cosmos import CosmosClient, PartitionKey, exceptions
9
+ from azure.cosmos.exceptions import CosmosResourceNotFoundError
10
+ from dotenv import load_dotenv
11
 
12
+ # Load environment variables from .env file
13
+ load_dotenv()
 
 
 
 
14
 
15
+ # File paths
16
+ DATA_PATH = "./converters_with_links_and_pricelist.json"
17
+ META_PATH = "./converters_metadata.json"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # Cosmos DB configuration from environment variables
20
+ COSMOS_ENDPOINT = os.getenv("AZURE_COSMOS_DB_ENDPOINT")
21
+ COSMOS_KEY = os.getenv("AZURE_COSMOS_DB_KEY")
22
+ DATABASE_NAME = os.getenv("AZURE_COSMOS_DB_DATABASE", "TAL_DB") # Default to TAL_DB
23
+ print(DATABASE_NAME)
24
+ CONTAINER_NAME = os.getenv("AZURE_COSMOS_DB_CONTAINER", "Converters") # Default to Converters_with_embeddings
 
 
 
 
 
 
 
 
25
 
26
+ # Indexing policy
27
+ INDEXING_POLICY = {
28
+ "indexingMode": "consistent",
29
+ "includedPaths": [{"path": "/*"}],
30
+ "excludedPaths": [
31
+ {"path": "/\"_etag\"/?",
32
+ "path": "/embedding/*"}
33
+ ]
34
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
+ # Validate environment variables
37
+ if not all([COSMOS_ENDPOINT, COSMOS_KEY, DATABASE_NAME]):
38
+ raise ValueError("Missing required Cosmos DB environment variables (AZURE_COSMOS_DB_ENDPOINT, AZURE_COSMOS_DB_KEY, AZURE_COSMOS_DB_DATABASE). Check .env file.")
 
 
 
39
 
40
+ # Initialize Cosmos DB client
41
+ try:
42
+ client = CosmosClient(COSMOS_ENDPOINT, COSMOS_KEY)
43
+ database = client.create_database_if_not_exists(id=DATABASE_NAME)
44
+ container = database.create_container_if_not_exists(
45
+ id=CONTAINER_NAME,
46
+ partition_key=PartitionKey(path="/artnr"),
47
+ indexing_policy=INDEXING_POLICY,
48
+ default_ttl=-1
49
+ )
50
+ except exceptions.CosmosHttpResponseError as e:
51
+ raise ValueError(f"Failed to initialize Cosmos DB client or create database/container: {str(e)}")
52
 
53
+ def load_json(path) -> Dict[str, Any]:
54
  try:
55
+ with open(path, "r", encoding="utf-8") as f:
56
  return json.load(f)
57
  except FileNotFoundError:
58
  return {}
59
 
60
+ def save_json(data: Dict[str, Any], path):
61
+ with open(path, "w", encoding="utf-8") as f:
62
  json.dump(data, f, indent=4, ensure_ascii=False)
63
 
64
  def get_current_time():
65
  return datetime.datetime.now().isoformat()
66
 
67
+ def transform_to_cosmos_format(converter_id: str, converter_data: Dict[str, Any]) -> Dict[str, Any]:
68
+ """Transform converter data to match Cosmos DB document structure."""
69
+ return {
70
+ "id": str(uuid.uuid4()), # Generate UUID for Cosmos DB
71
+ "type": converter_data.get("TYPE", ""),
72
+ "artnr": int(converter_data.get("ARTNR", 0)),
73
+ "converter_description": converter_data.get("CONVERTER DESCRIPTION:", ""),
74
+ "dimlist_type": converter_data.get("dimlist_type", ""),
75
+ "strain_relief": converter_data.get("STRAIN RELIEF", ""),
76
+ "location": converter_data.get("LOCATION", ""),
77
+ "dimmability": converter_data.get("DIMMABILITY", ""),
78
+ "ccr_(amplitude)": converter_data.get("CCR (AMPLITUDE)", ""),
79
+ "size_l*b*h_(mm)": converter_data.get("SIZE: L*B*H (mm)", ""),
80
+ "ip": float(converter_data.get("IP", 0)),
81
+ "class": float(converter_data.get("CLASS", 0)),
82
+ "barcode": converter_data.get("Barcode", ""),
83
+ "name": converter_data.get("Name", ""),
84
+ "listprice": float(converter_data.get("Listprice", 0)),
85
+ "unit": converter_data.get("Unit", ""),
86
+ "gross_weight": float(converter_data.get("gross_weight", 0)),
87
+ "lifecycle": converter_data.get("LifeCycle", ""),
88
+ "pdf_link": converter_data.get("pdf_link", ""),
89
+ "lamps": converter_data.get("lamps", {}),
90
+ "output_voltage": converter_data.get("OUTPUT VOLTAGE (V)", ""),
91
+ "nom_input_voltage": converter_data.get("NOM. INPUT VOLTAGE (V)", ""),
92
+ "efficiency_full_load": float(converter_data.get("EFFICIENCY @full load", 0))
93
+ }
94
+
95
+ def sync_to_cosmos_db(converter_id: str, converter_data: Dict[str, Any], meta_data: Dict[str, Any], operation="upsert"):
96
+ """Sync converter data to Cosmos DB with UUID and transformed format."""
97
+ if operation == "delete":
98
+ cosmos_id = meta_data.get("cosmos_id")
99
+ if cosmos_id:
100
+ try:
101
+ # Use artnr as partition key value
102
+ artnr = int(converter_data.get("ARTNR", 0)) if converter_data else 0
103
+ container.delete_item(item=cosmos_id, partition_key=artnr)
104
+ except CosmosResourceNotFoundError:
105
+ print(f"Document with ID {cosmos_id} not found in Cosmos DB. Continuing deletion.")
106
+ except exceptions.CosmosHttpResponseError as e:
107
+ print(f"Error deleting document {cosmos_id}: {str(e)}")
108
+ return False
109
+ return True
110
+
111
+ # Transform data to Cosmos DB format
112
+ document = transform_to_cosmos_format(converter_id, converter_data)
113
+
114
+ # Store the UUID in metadata
115
+ meta_data["cosmos_id"] = document["id"]
116
+
117
+ try:
118
+ # Use artnr as partition key value
119
+ doc = container.create_item(document)
120
+ if doc:
121
+ print(doc)
122
+ print(container.read_item(item=doc["id"], partition_key=doc["artnr"]))
123
+ return True
124
+ except exceptions.CosmosHttpResponseError as e:
125
+ print(f"Error syncing to Cosmos DB: {str(e)}")
126
+ return False
127
+
128
  def add_converter(
129
+ converter_id, type_, artnr, converter_description, dimlist_type, strain_relief, location, dimmability,
130
+ ccr_amplitude, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
131
+ price, unit, gross_weight, lifecycle, pdf_link
132
  ):
133
  converter_id = converter_id.strip()
134
+ data = load_json(DATA_PATH)
135
+ meta = load_json(META_PATH)
 
136
  if converter_id in data:
137
  return f"Converter '{converter_id}' already exists."
138
 
139
+ info = {
140
+ "TYPE": type_ or "",
141
+ "ARTNR": float(artnr) if artnr else 0,
142
+ "CONVERTER DESCRIPTION:": converter_description or "",
143
+ "dimlist_type": dimlist_type or "",
144
+ "STRAIN RELIEF": strain_relief or "",
145
+ "LOCATION": location or "",
146
+ "DIMMABILITY": dimmability or "",
147
+ "CCR (AMPLITUDE)": ccr_amplitude or "",
148
+ "SIZE: L*B*H (mm)": size or "",
149
+ "EFFICIENCY @full load": float(efficiency) if efficiency else 0,
150
+ "IP": float(ip) if ip else 0,
151
+ "CLASS": float(class_) if class_ else 0,
152
+ "NOM. INPUT VOLTAGE (V)": input_voltage or "",
153
+ "OUTPUT VOLTAGE (V)": output_voltage or "",
154
+ "Barcode": barcode or "",
155
+ "Name": name or "",
156
+ "Listprice": float(price) if price else 0,
157
+ "Unit": unit or "",
158
+ "gross_weight": float(gross_weight) if gross_weight else 0,
159
+ "LifeCycle": lifecycle or "",
160
+ "pdf_link": pdf_link or "",
 
161
  "lamps": {}
162
  }
163
 
164
+ data[converter_id] = info
165
+ now = get_current_time()
166
+ meta[converter_id] = {
167
  "created_at": now,
168
  "updated_at": now,
169
  "deleted_at": None,
170
+ "price_history": [{"timestamp": now, "price": float(price)}] if price else [],
171
+ "cosmos_id": None # Will be updated after syncing
172
  }
173
 
174
+ # Sync to Cosmos DB and update metadata with cosmos_id
175
+ if sync_to_cosmos_db(converter_id, info, meta[converter_id]):
176
+ # Save to JSON files
177
+ save_json(data, DATA_PATH)
178
+ save_json(meta, META_PATH)
179
+ return f"Added converter '{converter_id}' and synced to Cosmos DB."
180
+ else:
181
+ return f"Failed to add converter '{converter_id}' to Cosmos DB."
182
 
183
  def update_converter(
184
+ converter_id, type_, artnr, converter_description, dimlist_type, strain_relief, location, dimmability,
185
+ ccr_amplitude, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
186
+ price, unit, gross_weight, lifecycle, pdf_link
187
  ):
188
  converter_id = converter_id.strip()
189
+ data = load_json(DATA_PATH)
190
+ meta = load_json(META_PATH)
 
191
  if converter_id not in data:
192
  return f"Converter '{converter_id}' does not exist."
193
 
 
 
 
194
  info = data[converter_id]
195
+ now = get_current_time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
+ # Update fields if provided
198
+ if type_: info["TYPE"] = type_
199
+ if artnr: info["ARTNR"] = float(artnr)
200
+ if converter_description: info["CONVERTER DESCRIPTION:"] = converter_description
201
+ if dimlist_type: info["dimlist_type"] = dimlist_type
202
+ if strain_relief: info["STRAIN RELIEF"] = strain_relief
203
+ if location: info["LOCATION"] = location
204
+ if dimmability: info["DIMMABILITY"] = dimmability
205
+ if ccr_amplitude: info["CCR (AMPLITUDE)"] = ccr_amplitude
206
+ if size: info["SIZE: L*B*H (mm)"] = size
207
+ if efficiency: info["EFFICIENCY @full load"] = float(efficiency)
208
+ if ip: info["IP"] = float(ip)
209
+ if class_: info["CLASS"] = float(class_)
210
+ if input_voltage: info["NOM. INPUT VOLTAGE (V)"] = input_voltage
211
+ if output_voltage: info["OUTPUT VOLTAGE (V)"] = output_voltage
212
+ if barcode: info["Barcode"] = barcode
213
+ if name: info["Name"] = name
214
+ if price:
215
+ price = float(price)
216
+ if "Listprice" not in info or price != info.get("Listprice"):
217
+ if converter_id not in meta:
218
+ meta[converter_id] = {
219
+ "created_at": now,
220
+ "updated_at": now,
221
+ "deleted_at": None,
222
+ "price_history": [],
223
+ "cosmos_id": None
224
+ }
225
+ if "price_history" not in meta[converter_id]:
226
+ meta[converter_id]["price_history"] = []
227
+ meta[converter_id]["price_history"].append({"timestamp": now, "price": price})
228
+ info["Listprice"] = price
229
+ if unit: info["Unit"] = unit
230
+ if gross_weight: info["gross_weight"] = float(gross_weight)
231
+ if lifecycle: info["LifeCycle"] = lifecycle
232
+ if pdf_link: info["pdf_link"] = pdf_link
233
 
234
  # Update metadata
235
+ if converter_id not in meta:
236
+ meta[converter_id] = {
237
+ "created_at": now,
238
+ "updated_at": now,
239
+ "deleted_at": None,
240
+ "price_history": [],
241
+ "cosmos_id": None
242
+ }
243
+ meta[converter_id]["updated_at"] = now
244
 
245
+ # Compute new id
246
+ new_type = info["TYPE"]
247
+ new_artnr = int(info["ARTNR"])
248
+ new_id = f"{new_type}mA - {new_artnr}"
249
 
250
  if new_id != converter_id:
251
+ # Delete old Cosmos DB document
252
+ sync_to_cosmos_db(converter_id, data[converter_id], meta[converter_id], operation="delete")
253
  data[new_id] = info
254
+ meta[new_id] = meta.pop(converter_id)
255
  del data[converter_id]
256
+ # Sync new document
257
+ if sync_to_cosmos_db(new_id, info, meta[new_id]):
258
+ save_json(data, DATA_PATH)
259
+ save_json(meta, META_PATH)
260
+ return f"Updated converter. ID changed to '{new_id}' and synced to Cosmos DB."
261
+ else:
262
+ return f"Failed to update converter to '{new_id}' in Cosmos DB."
263
  else:
264
+ if sync_to_cosmos_db(converter_id, info, meta[converter_id]):
265
+ data[converter_id] = info
266
+ save_json(data, DATA_PATH)
267
+ save_json(meta, META_PATH)
268
+ return f"Updated converter '{converter_id}' and synced to Cosmos DB."
269
+ else:
270
+ return f"Failed to update converter '{converter_id}' in Cosmos DB."
271
 
272
  def delete_converter(converter_id):
273
  converter_id = converter_id.strip()
274
+ data = load_json(DATA_PATH)
275
+ meta = load_json(META_PATH)
 
276
  if converter_id not in data:
277
  return f"Converter '{converter_id}' does not exist."
278
 
279
+ now = get_current_time()
280
+ if converter_id not in meta:
281
+ meta[converter_id] = {
282
+ "created_at": None,
283
+ "updated_at": now,
284
+ "deleted_at": now,
285
+ "price_history": [],
286
+ "cosmos_id": None
287
+ }
288
+ else:
289
+ meta[converter_id]["deleted_at"] = now
290
+
291
+ # Delete from Cosmos DB
292
+ sync_to_cosmos_db(converter_id, data[converter_id], meta[converter_id], operation="delete")
293
+
294
+ del data[converter_id]
295
+ save_json(data, DATA_PATH)
296
+ save_json(meta, META_PATH)
297
+ return f"Deleted converter '{converter_id}' and removed from Cosmos DB."
298
 
299
  def add_or_update_lamp(converter_id, lamp_name, min_val, max_val):
300
  converter_id = converter_id.strip()
301
+ lamp_name = lamp_name.strip()
302
+ data = load_json(DATA_PATH)
303
+ meta = load_json(META_PATH)
304
  if converter_id not in data:
305
  return f"Converter '{converter_id}' does not exist."
306
 
307
+ if "lamps" not in data[converter_id]:
308
+ data[converter_id]["lamps"] = {}
309
+ data[converter_id]["lamps"][lamp_name] = {"min": min_val, "max": max_val}
310
+
311
+ # Update metadata
312
+ now = get_current_time()
313
+ if converter_id not in meta:
314
+ meta[converter_id] = {
315
+ "created_at": now,
316
+ "updated_at": now,
317
+ "deleted_at": None,
318
+ "price_history": [],
319
+ "cosmos_id": None
320
+ }
321
+ meta[converter_id]["updated_at"] = now
322
+
323
+ # Sync to Cosmos DB
324
+ if sync_to_cosmos_db(converter_id, data[converter_id], meta[converter_id]):
325
+ save_json(data, DATA_PATH)
326
+ save_json(meta, META_PATH)
327
+ return f"Added/updated lamp '{lamp_name}' in converter '{converter_id}' and synced to Cosmos DB."
328
+ else:
329
+ return f"Failed to add/update lamp '{lamp_name}' in Cosmos DB."
330
 
331
  def delete_lamp(converter_id, lamp_name):
332
  converter_id = converter_id.strip()
333
+ lamp_name = lamp_name.strip()
334
+ data = load_json(DATA_PATH)
335
+ meta = load_json(META_PATH)
336
  if converter_id not in data:
337
  return f"Converter '{converter_id}' does not exist."
338
 
339
  lamps = data[converter_id].get("lamps", {})
340
+ if lamp_name not in lamps:
341
+ return f"Lamp '{lamp_name}' does not exist in converter '{converter_id}'."
342
+
343
+ del lamps[lamp_name]
344
 
345
+ # Update metadata
346
+ now = get_current_time()
347
+ if converter_id not in meta:
348
+ meta[converter_id] = {
349
+ "created_at": now,
350
+ "updated_at": now,
351
+ "deleted_at": None,
352
+ "price_history": [],
353
+ "cosmos_id": None
354
+ }
355
+ meta[converter_id]["updated_at"] = now
356
+
357
+ # Sync to Cosmos DB
358
+ if sync_to_cosmos_db(converter_id, data[converter_id], meta[converter_id]):
359
+ save_json(data, DATA_PATH)
360
+ save_json(meta, META_PATH)
361
+ return f"Deleted lamp '{lamp_name}' from converter '{converter_id}' and synced to Cosmos DB."
362
+ else:
363
+ return f"Failed to delete lamp '{lamp_name}' in Cosmos DB."
364
 
365
  def get_converter(converter_id):
366
+ converter_id = converter_id.strip()
367
+ meta = load_json(META_PATH)
368
+ cosmos_id = meta.get(converter_id, {}).get("cosmos_id")
369
+
370
+ if cosmos_id:
371
+ try:
372
+ # Fetch from Cosmos DB using artnr as partition key
373
+ data = load_json(DATA_PATH)
374
+ artnr = int(data.get(converter_id, {}).get("ARTNR", 0))
375
+ item = container.read_item(item=cosmos_id, partition_key=artnr)
376
+ # Add metadata from local file
377
+ item["metadata"] = meta.get(converter_id, {})
378
+ return json.dumps(item, indent=2, ensure_ascii=False)
379
+ except CosmosResourceNotFoundError:
380
+ print(f"Document with ID {cosmos_id} not found in Cosmos DB. Falling back to local JSON.")
381
+ except exceptions.CosmosHttpResponseError as e:
382
+ print(f"Error reading document {cosmos_id}: {str(e)}")
383
+
384
+ # Fallback to local JSON
385
+ data = load_json(DATA_PATH)
386
+ item = data.get(converter_id, {})
387
+ if item:
388
+ item["metadata"] = meta.get(converter_id, {})
389
+ return json.dumps(item, indent=2, ensure_ascii=False)
390
 
391
  def filter_lamps(filter_type, n_latest):
392
+ # Load local JSON files
393
+ data = load_json(DATA_PATH)
394
+ meta = load_json(META_PATH)
395
  records = []
 
396
  for cid, cinfo in data.items():
397
+ m = meta.get(cid, {})
398
+ created_at = m.get("created_at", "")
399
+ updated_at = m.get("updated_at", "")
400
+ deleted_at = m.get("deleted_at", None)
401
+ price_history = m.get("price_history", [])
402
+ price = cinfo.get("Listprice", "")
403
+ lamps = cinfo.get("lamps", {})
404
  record = {
405
  "Converter ID": cid,
406
+ "Created At": created_at,
407
+ "Updated At": updated_at,
408
+ "Deleted At": deleted_at,
409
+ "Price": price,
410
+ "Lamps": ", ".join(lamps.keys())
411
  }
412
+ if filter_type == "Show All":
 
 
 
 
 
 
 
 
413
  records.append(record)
414
+ elif filter_type == "Latest Added":
415
+ if not deleted_at:
416
+ records.append(record)
417
+ elif filter_type == "Latest Updated":
418
+ if not deleted_at:
419
+ records.append(record)
420
+ elif filter_type == "Deleted":
421
+ if deleted_at:
422
+ records.append(record)
423
+ elif filter_type == "Price Change":
424
+ if len(price_history) > 1:
425
+ record["Price History"] = str(price_history)
426
+ records.append(record)
427
 
428
+ # Apply sorting and slicing
429
  if filter_type == "Latest Added":
430
+ records = sorted(records, key=lambda x: x.get("Created At", ""), reverse=True)[:n_latest]
431
  elif filter_type == "Latest Updated":
432
+ records = sorted(records, key=lambda x: x.get("Updated At", ""), reverse=True)[:n_latest]
433
 
434
+ return pd.DataFrame(records)
435
 
436
+ # Gradio interface
 
 
437
  with gr.Blocks(title="TAL Converter JSON Editor") as demo:
438
  gr.Markdown("# TAL Converter JSON Editor")
439
+
440
  with gr.Tab("Add Converter"):
441
+ gr.HTML("<label style='font-weight:bold; color:red;'>Converter ID (e.g. 350mA - 930537) *</label>")
442
+ converter_id = gr.Textbox(show_label=False)
443
+
444
+ gr.HTML("<label style='font-weight:bold; color:red;'>Article Number *</label>")
445
+ artnr = gr.Textbox(show_label=False)
446
+ type_ = gr.Textbox(label="Type")
447
+ # artnr = gr.Textbox(label="Article Number !*required field")
448
+ converter_description = gr.Textbox(label="Converter Description")
449
+ dimlist_type = gr.Textbox(label="Dimlist Type")
450
+ strain_relief = gr.Textbox(label="Strain Relief")
451
+ location = gr.Textbox(label="Location")
452
+ dimmability = gr.Textbox(label="Dimmability")
453
+ ccr_amplitude = gr.Textbox(label="CCR (Amplitude)")
454
+ size = gr.Textbox(label="Size: L*B*H (mm)")
455
+ efficiency = gr.Textbox(label="Efficiency @ Full Load")
456
+ ip = gr.Textbox(label="IP")
457
+ class_ = gr.Textbox(label="Class")
458
+ input_voltage = gr.Textbox(label="Nominal Input Voltage (V)")
459
+ output_voltage = gr.Textbox(label="Output Voltage (V)")
460
+ barcode = gr.Textbox(label="Barcode")
461
+ name = gr.Textbox(label="Name")
462
+ price = gr.Textbox(label="List Price")
463
+ unit = gr.Textbox(label="Unit")
464
+ gross_weight = gr.Textbox(label="Gross Weight")
465
+ lifecycle = gr.Textbox(label="Lifecycle")
466
+ pdf_link = gr.Textbox(label="PDF Link")
467
  add_btn = gr.Button("Add Converter")
468
  add_output = gr.Textbox(label="Result")
469
  add_btn.click(
470
  add_converter,
471
+ inputs=[
472
+ converter_id, type_, artnr, converter_description, dimlist_type, strain_relief, location, dimmability,
473
+ ccr_amplitude, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
474
+ price, unit, gross_weight, lifecycle, pdf_link
475
+ ],
476
  outputs=add_output
477
+ ).then(
478
+ lambda *args: [gr.update(value="") for _ in args],
479
+ inputs=[
480
+ converter_id, type_, artnr, converter_description, dimlist_type, strain_relief, location, dimmability,
481
+ ccr_amplitude, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
482
+ price, unit, gross_weight, lifecycle, pdf_link
483
+ ],
484
+ outputs=[
485
+ converter_id, type_, artnr, converter_description, dimlist_type, strain_relief, location, dimmability,
486
+ ccr_amplitude, size, efficiency, ip, class_, input_voltage, output_voltage, barcode, name,
487
+ price, unit, gross_weight, lifecycle, pdf_link
488
+ ]
489
+ )
490
+
491
  with gr.Tab("Update Converter"):
492
+ gr.HTML("<label style='font-weight:bold; color:red;'>Converter ID (e.g. 350mA - 930537) *</label>")
493
+ converter_id_u = gr.Textbox(show_label=False)
494
+ type_u = gr.Textbox(label="Type")
495
+ artnr_u = gr.Textbox(label="Article Number")
496
+ converter_description_u = gr.Textbox(label="Converter Description")
497
+ dimlist_type_u = gr.Textbox(label="Dimlist Type")
498
+ strain_relief_u = gr.Textbox(label="Strain Relief")
499
+ location_u = gr.Textbox(label="Location")
500
+ dimmability_u = gr.Textbox(label="Dimmability")
501
+ ccr_amplitude_u = gr.Textbox(label="CCR (Amplitude)")
502
+ size_u = gr.Textbox(label="Size: L*B*H (mm)")
503
+ efficiency_u = gr.Textbox(label="Efficiency @ Full Load")
504
+ ip_u = gr.Textbox(label="IP")
505
+ class_u = gr.Textbox(label="Class")
506
+ input_voltage_u = gr.Textbox(label="Nominal Input Voltage (V)")
507
+ output_voltage_u = gr.Textbox(label="Output Voltage (V)")
508
+ barcode_u = gr.Textbox(label="Barcode")
509
+ name_u = gr.Textbox(label="Name")
510
+ price_u = gr.Textbox(label="List Price")
511
+ unit_u = gr.Textbox(label="Unit")
512
+ gross_weight_u = gr.Textbox(label="Gross Weight")
513
+ lifecycle_u = gr.Textbox(label="Lifecycle")
514
+ pdf_link_u = gr.Textbox(label="PDF Link")
515
  update_btn = gr.Button("Update Converter")
516
  update_output = gr.Textbox(label="Result")
517
  update_btn.click(
518
  update_converter,
519
+ inputs=[
520
+ converter_id_u, type_u, artnr_u, converter_description_u, dimlist_type_u, strain_relief_u, location_u, dimmability_u,
521
+ ccr_amplitude_u, size_u, efficiency_u, ip_u, class_u, input_voltage_u, output_voltage_u, barcode_u, name_u,
522
+ price_u, unit_u, gross_weight_u, lifecycle_u, pdf_link_u
523
+ ],
524
  outputs=update_output
525
+ ).then(
526
+ lambda *args: [gr.update(value="") for _ in args],
527
+ inputs=[
528
+ converter_id_u, type_u, artnr_u, converter_description_u, dimlist_type_u, strain_relief_u, location_u, dimmability_u,
529
+ ccr_amplitude_u, size_u, efficiency_u, ip_u, class_u, input_voltage_u, output_voltage_u, barcode_u, name_u,
530
+ price_u, unit_u, gross_weight_u, lifecycle_u, pdf_link_u
531
+ ],
532
+ outputs=[
533
+ converter_id_u, type_u, artnr_u, converter_description_u, dimlist_type_u, strain_relief_u, location_u, dimmability_u,
534
+ ccr_amplitude_u, size_u, efficiency_u, ip_u, class_u, input_voltage_u, output_voltage_u, barcode_u, name_u,
535
+ price_u, unit_u, gross_weight_u, lifecycle_u, pdf_link_u
536
+ ]
537
+ )
538
+
539
  with gr.Tab("Delete Converter"):
540
+ gr.HTML("<label style='font-weight:bold; color:red;'>Converter ID (e.g. 350mA - 930537) *</label>")
541
+ converter_id_d = gr.Textbox(show_label=False)
542
  delete_btn = gr.Button("Delete Converter")
543
  delete_output = gr.Textbox(label="Result")
544
  delete_btn.click(
545
+ delete_converter,
546
+ inputs=converter_id_d,
547
  outputs=delete_output
548
+ ).then(
549
+ lambda _: gr.update(value=""),
550
+ inputs=converter_id_d,
551
+ outputs=converter_id_d
552
+ )
553
+
554
+ with gr.Tab("Add/Update Lamp"):
555
+ gr.HTML("<label style='font-weight:bold; color:red;'>Converter ID (e.g. 350mA - 930537) *</label>")
556
+ converter_id_l = gr.Textbox(show_label=False)
557
+ lamp_name = gr.Textbox(label="Lamp Name")
558
+ min_val = gr.Textbox(label="Min")
559
+ max_val = gr.Textbox(label="Max")
560
+ lamp_btn = gr.Button("Add/Update Lamp")
561
  lamp_output = gr.Textbox(label="Result")
562
+ lamp_btn.click(
563
  add_or_update_lamp,
564
+ inputs=[converter_id_l, lamp_name, min_val, max_val],
565
  outputs=lamp_output
566
+ ).then(
567
+ lambda *args: [gr.update(value="") for _ in args],
568
+ inputs=[converter_id_l, lamp_name, min_val, max_val],
569
+ outputs=[converter_id_l, lamp_name, min_val, max_val]
570
+ )
571
+
572
+ with gr.Tab("Delete Lamp"):
573
+ converter_id_ld = gr.Textbox(label="Converter ID (e.g. 350mA - 930537)")
574
+ lamp_name_d = gr.Textbox(label="Lamp Name")
575
+ lamp_delete_btn = gr.Button("Delete Lamp")
576
+ lamp_delete_output = gr.Textbox(label="Result")
577
+ lamp_delete_btn.click(
578
  delete_lamp,
579
+ inputs=[converter_id_ld, lamp_name_d],
580
+ outputs=lamp_delete_output
581
+ ).then(
582
+ lambda *args: [gr.update(value="") for _ in args],
583
+ inputs=[converter_id_ld, lamp_name_d],
584
+ outputs=[converter_id_ld, lamp_name_d]
585
+ )
586
+
587
  with gr.Tab("View Converter"):
588
+ converter_id_v = gr.Textbox(label="Converter ID (e.g. 350mA - 930537)")
589
  view_btn = gr.Button("View Converter")
590
  view_output = gr.Textbox(label="Converter Data", lines=10)
591
  view_btn.click(get_converter, inputs=converter_id_v, outputs=view_output)
592
+
593
+ with gr.Tab("Converters Table & Filters"):
594
  filter_type = gr.Dropdown(
595
+ choices=["Show All", "Latest Added", "Latest Updated", "Price Change"],
596
+ value="Show All",
597
  label="Filter Type"
598
  )
599
+ n_latest = gr.Slider(1, 20, value=5, step=1, label="How many (for latest)")
600
+ lamp_table = gr.DataFrame(label="Converters Table", interactive=False, show_search="filter")
601
  filter_btn = gr.Button("Apply Filter")
602
+ filter_btn.click(
603
+ filter_lamps,
604
+ inputs=[filter_type, n_latest],
605
+ outputs=lamp_table
606
+ )
607
 
608
  if __name__ == "__main__":
609
+ demo.launch() # Run locally without sharing