Upload app_pyvis_new.py

#4
by roncmic - opened
Files changed (1) hide show
  1. app_pyvis_new.py +771 -0
app_pyvis_new.py ADDED
@@ -0,0 +1,771 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ from datetime import date
4
+ import gradio as gr
5
+ from pyvis.network import Network
6
+ import ast
7
+ from openai import OpenAI
8
+ import json
9
+ import string
10
+ from datetime import datetime
11
+ import random
12
+ import geopandas as gpd
13
+ import folium
14
+ from shapely.geometry import mapping
15
+ import dropbox
16
+ from dropbox.exceptions import ApiError
17
+ import io
18
+ import pandas as pd
19
+ import random
20
+ from itertools import combinations
21
+ from typing import Optional, Union
22
+ import torch
23
+ from transformers import T5ForConditionalGeneration, T5Tokenizer
24
+ import inflect
25
+ import random
26
+ from itertools import combinations
27
+ from typing import Optional, Union
28
+ import torch
29
+
30
+
31
+ # Replace these with your actual app key and secret
32
+ APP_KEY = os.environ['APP_KEY']
33
+ APP_SECRET = os.environ['APP_SECRET']
34
+ REFRESH_TOKEN = os.environ['REFRESH_TOKEN']
35
+
36
+
37
+ EMM_RETRIEVERS_OPENAI_API_BASE_URL="https://api-gpt.jrc.ec.europa.eu/v1"
38
+ EMM_RETRIEVERS_OPENAI_API_KEY = os.environ['EMM_RETRIEVERS_OPENAI_API_KEY']
39
+
40
+
41
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
42
+ usr_tkn_consose_read = os.environ['usr_tkn_consose_read']
43
+
44
+ inflect_engine = inflect.engine()
45
+
46
+ model_id = os.environ['ie_model_id']
47
+ tokenizer = T5Tokenizer.from_pretrained(model_id)
48
+ model = T5ForConditionalGeneration.from_pretrained(model_id)
49
+
50
+
51
+ client1 = OpenAI(
52
+ api_key=EMM_RETRIEVERS_OPENAI_API_KEY,
53
+ base_url="https://api-gpt.jrc.ec.europa.eu/v1",
54
+ )
55
+
56
+
57
+ def geocode_emdat(location):
58
+ def process_geocoding(location_to_geocode):
59
+ try:
60
+ return osm.geocode_to_gdf(location_to_geocode)["geometry"].iloc[0]
61
+ except Exception:
62
+ return None
63
+
64
+ geocoded_location = process_geocoding(location)
65
+
66
+ if geocoded_location is None:
67
+ print(f"Error geocoding location '{location}'. Trying to correct with GPT-4.")
68
+ response = client1.chat.completions.create(
69
+ model="gpt-4o",
70
+ stream=False,
71
+ messages=[{"role": "user", "content": f"Correct spelling or grammar or substitute with most commonly used location name by Google Maps, give me only the answer in the form 'Country, Location' filled with the corrected Country and Location: '{location}'"}]
72
+ )
73
+ corrected_location = response.choices[0].message.content.strip()
74
+ geocoded_location = process_geocoding(corrected_location)
75
+
76
+ return geocoded_location
77
+
78
+
79
+ def get_country_boundary(country_name):
80
+ # Filter the world GeoDataFrame for the country
81
+ country = world[world['NAME'] == country_name]
82
+ if not country.empty:
83
+ # Return the country's geometry
84
+ return country.geometry.iloc[0]
85
+ else:
86
+ # Return None if country not found
87
+ return None
88
+
89
+ def get_geometries(row):
90
+ country = row['Country']
91
+ locations = row['Locations']
92
+
93
+ # Return NaN if locations is NaN
94
+ if pd.isna(locations):
95
+ return None
96
+
97
+ # Get the country's boundary
98
+ country_boundary = get_country_boundary(country)
99
+
100
+ # If no country boundary is found, return None
101
+ if country_boundary is None:
102
+ return None
103
+
104
+ locations_list = locations.split(', ')
105
+
106
+ # Get polygons for each location, ignoring None results
107
+ polygons = [geocode_emdat(f"{country}, {location}") for location in locations_list]
108
+ polygons = [polygon for polygon in polygons if polygon is not None]
109
+
110
+ # Filter polygons to remove those outside the country boundary
111
+ valid_polygons = [polygon for polygon in polygons if polygon.within(country_boundary)]
112
+
113
+ # If there are no valid polygons, return None
114
+ if not valid_polygons:
115
+ return None
116
+
117
+ # Combine them into a single geometry using unary_union
118
+ combined_geometry = unary_union(valid_polygons)
119
+
120
+ return combined_geometry
121
+
122
+
123
+ def singularize(text):
124
+ """Convert a word to its singular form."""
125
+ if inflect_engine.singular_noun(text):
126
+ return inflect_engine.singular_noun(text)
127
+ return text
128
+
129
+ def is_singular_plural_pair(word1, word2):
130
+ """Check if two words are singular/plural forms of each other."""
131
+ return singularize(word1) == singularize(word2)
132
+
133
+
134
+ def extract_edge_and_clean(row, relations):
135
+ for relation in relations:
136
+ if relation in row['source']:
137
+ row['source'] = row['source'].replace(relation, '').strip()
138
+ row['edge'] = relation
139
+ elif relation in row['target']:
140
+ row['target'] = row['target'].replace(relation, '').strip()
141
+ row['edge'] = relation
142
+ return row
143
+
144
+ def generate_with_temperature(prompt, temperature=1.0, top_k=50, top_p=0.95, max_length=50):
145
+ inputs = tokenizer(prompt, return_tensors='pt').to(device)
146
+
147
+ outputs = model.generate(
148
+ input_ids=inputs.input_ids,
149
+ max_length=max_length,
150
+ do_sample=True,
151
+ temperature=temperature,
152
+ top_k=top_k,
153
+ top_p=top_p
154
+ )
155
+
156
+ decoded_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True)
157
+ return decoded_texts
158
+
159
+ def generate_new_relations(
160
+ graph_df: pd.DataFrame,
161
+ new_node: str,
162
+ max_combinations_fraction: float = 0.3,
163
+ num_beams: int = 6, # Note: Beams are ignored in sampling
164
+ max_length: int = 50,
165
+ temperature: float = 1.0,
166
+ top_k: int = 50,
167
+ top_p: float = 0.95,
168
+ seed: Optional[int] = None,
169
+ verbose: bool = False
170
+ ) -> pd.DataFrame:
171
+ if seed is not None:
172
+ random.seed(seed)
173
+
174
+ records = graph_df.to_dict('records')
175
+ all_combos = list(combinations(records, 3))
176
+ max_iters = max(1, int(max_combinations_fraction * len(all_combos)))
177
+ num_iters = random.randint(1, max_iters)
178
+
179
+ if verbose:
180
+ print(f"Total possible combinations: {len(all_combos)}")
181
+ print(f"Sampling {num_iters} combos")
182
+
183
+ all_predictions = []
184
+
185
+ for _ in range(num_iters):
186
+ combo = random.choice(all_combos)
187
+ for choice in ('source', 'target'):
188
+ last = combo[-1].copy()
189
+ if choice == 'target':
190
+ prompt_template = f"<extra_id_0> {new_node}."
191
+ else:
192
+ prompt_template = f"{new_node} <extra_id_0>."
193
+
194
+ for _ in range(2):
195
+ perm = list(combo[:-1])
196
+ random.shuffle(perm)
197
+
198
+ parts = ["If"]
199
+ for row in perm:
200
+ parts.append(f"{row['source']} {row['edge']} {row['target']},")
201
+ parts.append("and")
202
+ parts.append(f"{last['source']} {last['edge']} {last['target']},")
203
+ parts.append("then")
204
+ parts.append(prompt_template)
205
+ prompt = " ".join(parts)
206
+
207
+ if verbose:
208
+ print("Generated Prompt:", prompt)
209
+
210
+ preds = generate_with_temperature(
211
+ prompt, temperature, top_k, top_p, max_length
212
+ )
213
+
214
+ if verbose:
215
+ print("Predictions:", preds)
216
+
217
+ for text in preds:
218
+ #print("Generated Prompt:", prompt)
219
+ #print("text = ", text)
220
+ #print("last = ", last)
221
+ all_predictions.append((choice, text, last))
222
+
223
+
224
+
225
+ if not all_predictions:
226
+ return graph_df.copy()
227
+
228
+ grouped = {}
229
+ for choice, text, last in all_predictions:
230
+ key = (choice, last['source'], last['edge'], last['target'])
231
+ grouped.setdefault(key, []).append(text)
232
+
233
+ new_edges = []
234
+ for (choice, src, edge, tgt), texts in grouped.items():
235
+ common = set(texts)
236
+ if not common:
237
+ continue
238
+ for pred in common:
239
+ if choice == 'target':
240
+ new_edges.append({'source': pred, 'edge': None, 'target': new_node})
241
+ else:
242
+ new_edges.append({'source': new_node, 'edge': None, 'target': pred})
243
+
244
+ new_df = pd.DataFrame(new_edges).drop_duplicates()
245
+
246
+ relations = ['causes', 'prevents']
247
+ new_df = new_df.apply(lambda row: extract_edge_and_clean(row, relations), axis=1)
248
+
249
+ result = pd.concat([graph_df, new_df], ignore_index=True)
250
+
251
+ result['pair'] = result.apply(lambda x: tuple(sorted([x['source'], x['target']])), axis=1)
252
+ result = result.drop_duplicates(subset=['pair'])
253
+ result = result[result['source'] != result['target']]
254
+ result = result.drop(columns=['pair'])
255
+ # Remove duplicates based on plural/singular forms
256
+ result['source_singular'] = result['source'].apply(singularize)
257
+ result['target_singular'] = result['target'].apply(singularize)
258
+ result = result.drop_duplicates(subset=['source_singular', 'edge', 'target_singular'])
259
+ result = result[result['source'] != result['target']]
260
+ result = result.drop(columns=['source_singular', 'target_singular'])
261
+
262
+ return result
263
+
264
+ # Function to get a Dropbox client, refreshing the token if needed
265
+ def get_dropbox_client():
266
+ try:
267
+ # Create a Dropbox client using the refresh token
268
+ dbx = dropbox.Dropbox(
269
+ oauth2_refresh_token=REFRESH_TOKEN,
270
+ app_key=APP_KEY,
271
+ app_secret=APP_SECRET
272
+ )
273
+ return dbx
274
+ except Exception as e:
275
+ print(f"Error creating Dropbox client: {e}")
276
+ return None
277
+
278
+
279
+ client1 = OpenAI(
280
+ api_key=EMM_RETRIEVERS_OPENAI_API_KEY,
281
+ base_url="https://api-gpt.jrc.ec.europa.eu/v1",
282
+ )
283
+
284
+ df = pd.read_csv("https://jeodpp.jrc.ec.europa.eu/ftp/jrc-opendata/ETOHA/storylines/emdat2.csv", sep=',', header=0, dtype=str, encoding='utf-8')
285
+
286
+ world = gpd.read_file('https://jeodpp.jrc.ec.europa.eu/ftp/jrc-opendata/ETOHA/storylines/ne_110m_admin_0_countries.shp')
287
+
288
+
289
+ # Function to get fallback coordinates from GeoPandas
290
+ def get_country_centroid(country_name):
291
+ # Filter the world GeoDataFrame for the country
292
+ country = world[world['NAME'] == country_name]
293
+ if not country.empty:
294
+ # Get the centroid of the country's geometry
295
+ centroid = country.geometry.centroid.iloc[0]
296
+ return (centroid.y, centroid.x)
297
+ else:
298
+ # Default to (0, 0) if country not found
299
+ return (0, 0)
300
+
301
+ # Function to plot a geometry using Folium
302
+ def plot_geometry_folium(geometry, location_name='Location', country_name=None):
303
+ if geometry is not None:
304
+ # Get the centroid for initial map location
305
+ centroid = geometry.centroid
306
+ initial_coords = (centroid.y, centroid.x)
307
+ else:
308
+ # Use geopandas to get fallback coordinates
309
+ initial_coords = get_country_centroid(country_name)
310
+
311
+ # Create the map centered at initial_coords
312
+ m = folium.Map(location=initial_coords, zoom_start=6)
313
+
314
+ if geometry is not None:
315
+ # Convert to GeoJSON for Folium if geometry exists
316
+ geo_json = mapping(geometry)
317
+ # Add GeoJSON to the map
318
+ folium.GeoJson(geo_json, name=location_name).add_to(m)
319
+ else:
320
+ # Add a marker to indicate the country location
321
+ folium.Marker(initial_coords, popup=location_name).add_to(m)
322
+
323
+ # Return the HTML representation of the map object
324
+ return m._repr_html_()
325
+
326
+
327
+ def gpt_story(storyline):
328
+ prompt = (
329
+ "Use the information provided to create a short, clear, and useful narrative about a disaster event. "
330
+ "The goal is to help decision-makers (e.g. policy makers, disaster managers, civil protection) understand what happened, why, and what it caused. "
331
+ "Keep it short and focused.\n\n"
332
+ "Include all key information, but keep the text concise and easy to read. Avoid technical jargon.\n\n"
333
+ "Steps to Follow:\n"
334
+ "1. Start with what happened: Briefly describe the disaster event (what, where, when, who was affected).\n"
335
+ "2. Explain why it happened: Use the evidence provided to describe possible causes or triggers (e.g. heavy rainfall, poor infrastructure, heatwave).\n"
336
+ "3. Show the impacts: Highlight key impacts such as fatalities, displacement, health effects, or damage.\n"
337
+ "4. Connect the dots: Show how different factors are linked. Use simple cause-effect language (e.g. drought led to crop failure, which caused food insecurity).\n"
338
+ "5. Mention complexity if needed: If there were multiple contributing factors or reinforcing effects (e.g. climate + conflict), briefly explain them.\n"
339
+ "6. Keep it useful: Write with a decision-maker in mind. Focus on what matters: drivers, impacts, and lessons for preparedness or response.\n\n"
340
+ f"Information: {storyline}"
341
+ )
342
+
343
+ completion = client1.chat.completions.create(
344
+ model='gpt-4o',
345
+ messages=[
346
+ {"role": "system", "content": "You are a disaster manager expert in risk dynamics."},
347
+ {"role": "user", "content": prompt}
348
+ ]
349
+ )
350
+
351
+ # Extract the content from the response
352
+ message_content = completion.choices[0].message.content
353
+ return message_content
354
+
355
+
356
+ # DataFrame to store evaluation data
357
+ evaluation_df = pd.DataFrame(columns=["DisNo.", "TPN", "TPL", "FPN", "FPL", "FNN", "FNL", "User ID"])
358
+
359
+
360
+
361
+ def try_parse_date(y, m, d):
362
+ try:
363
+ if not y or not m or not d:
364
+ return None
365
+ return date(int(float(y)), int(float(m)), int(float(d)))
366
+ except (ValueError, TypeError):
367
+ return None
368
+
369
+ def plot_cgraph_pyvis(grp):
370
+ if not grp:
371
+ return "<div>No data available to plot.</div>"
372
+
373
+ net = Network(notebook=False, directed=True)
374
+ edge_colors_dict = {"causes": "red", "prevents": "green"}
375
+
376
+ for src, rel, tgt in grp:
377
+ src = str(src)
378
+ tgt = str(tgt)
379
+ rel = str(rel)
380
+ net.add_node(src, shape="circle", label=src)
381
+ net.add_node(tgt, shape="circle", label=tgt)
382
+ edge_color = edge_colors_dict.get(rel, 'black')
383
+ net.add_edge(src, tgt, title=rel, label=rel, color=edge_color)
384
+
385
+ net.repulsion(
386
+ node_distance=200,
387
+ central_gravity=0.2,
388
+ spring_length=200,
389
+ spring_strength=0.05,
390
+ damping=0.09
391
+ )
392
+ net.set_edge_smooth('dynamic')
393
+
394
+ html = net.generate_html()
395
+ html = html.replace("'", "\"")
396
+
397
+ # Adjust the iframe style to center the graph and fit the container
398
+ html_s = f"""
399
+ <div style="display: flex; justify-content: center; align-items: center;">
400
+ <iframe style="width: 90%; height: 800px; margin: 0 auto;" name="result" allow="midi; geolocation; microphone; camera;
401
+ display-capture; encrypted-media;" sandbox="allow-modals allow-forms
402
+ allow-scripts allow-same-origin allow-popups
403
+ allow-top-navigation-by-user-activation allow-downloads" allowfullscreen=""
404
+ allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>
405
+ </div>
406
+ """
407
+
408
+ return html_s
409
+
410
+ def generate_unique_user_id():
411
+ # Generate a timestamp string
412
+ timestamp_str = datetime.now().strftime("%Y%m%d%H%M%S")
413
+ # Generate a random string of 5 letters
414
+ random_str = ''.join(random.choices(string.ascii_letters, k=5))
415
+ # Combine both to form a unique User ID
416
+ return f"{timestamp_str}_{random_str}"
417
+
418
+ def load_initial_data():
419
+ dbx = dropbox.Dropbox(ACCESS_TOKEN)
420
+ try:
421
+ # Try to download the existing CSV file from Dropbox
422
+ metadata, res = dbx.files_download(DROPBOX_FILE_PATH)
423
+ csv_content = res.content.decode('utf-8')
424
+ # Read the CSV content into a DataFrame
425
+ df = pd.read_csv(io.StringIO(csv_content))
426
+ print("Loaded existing data from Dropbox.")
427
+ except ApiError as e:
428
+ # If file not found, initialize an empty DataFrame
429
+ if e.error.is_path() and e.error.get_path().is_not_found():
430
+ df = pd.DataFrame(columns=["DisNo.", "User Feedback", "User ID"])
431
+ print("No existing file found on Dropbox. Initialized an empty DataFrame.")
432
+ else:
433
+ print(f"Error downloading file: {e}")
434
+ df = pd.DataFrame(columns=["DisNo.", "User Feedback", "User ID"])
435
+ return df
436
+
437
+
438
+ def append_to_csv_on_dropbox(new_content, dropbox_path):
439
+ dbx = get_dropbox_client()
440
+ if not dbx:
441
+ print("Failed to create Dropbox client.")
442
+ return
443
+
444
+ try:
445
+ # Try to download existing file content
446
+ metadata, res = dbx.files_download(dropbox_path)
447
+ existing_content = res.content.decode('utf-8')
448
+ except ApiError as e:
449
+ # If file not found, start with empty content
450
+ if e.error.is_path() and e.error.get_path().is_not_found():
451
+ existing_content = ''
452
+ else:
453
+ print(f"Error downloading file: {e}")
454
+ return
455
+
456
+ # Append new content without header if file already exists
457
+ if existing_content:
458
+ new_content_lines = new_content.splitlines()
459
+ new_content_without_header = '\n'.join(new_content_lines[1:])
460
+ combined_content = existing_content.rstrip('\n') + '\n' + new_content_without_header
461
+ else:
462
+ combined_content = new_content
463
+
464
+ try:
465
+ # Upload combined content back to Dropbox (overwrite)
466
+ dbx.files_upload(combined_content.encode('utf-8'), dropbox_path, mode=dropbox.files.WriteMode.overwrite)
467
+ print(f"Appended and uploaded to {dropbox_path} successfully!")
468
+ except Exception as e:
469
+ print(f"Error uploading file: {e}")
470
+
471
+
472
+ #def append_to_csv_on_dropbox(new_content, dropbox_path):
473
+ # dbx = dropbox.Dropbox(ACCESS_TOKEN)
474
+
475
+ # try:
476
+ # Try to download existing file content
477
+ # metadata, res = dbx.files_download(dropbox_path)
478
+ # existing_content = res.content.decode('utf-8')
479
+ # except ApiError as e:
480
+ # If file not found, start with empty content
481
+ # if e.error.is_path() and e.error.get_path().is_not_found():
482
+ # existing_content = ''
483
+ # else:
484
+ # print(f"Error downloading file: {e}")
485
+ # return
486
+
487
+ # Append new content without header if file already exists
488
+ # if existing_content:
489
+ # new_content_lines = new_content.splitlines()
490
+ # new_content_without_header = '\n'.join(new_content_lines[1:])
491
+ # combined_content = existing_content.rstrip('\n') + '\n' + new_content_without_header
492
+ # else:
493
+ # combined_content = new_content
494
+
495
+ # try:
496
+ # Upload combined content back to Dropbox (overwrite)
497
+ # dbx.files_upload(combined_content.encode('utf-8'), dropbox_path, mode=dropbox.files.WriteMode.overwrite)
498
+ # print(f"Appended and uploaded to {dropbox_path} successfully!")
499
+ # except Exception as e:
500
+ # print(f"Error uploading file: {e}")
501
+
502
+ def save_data_to_dropbox():
503
+ # Convert DataFrame to CSV string
504
+ csv_content = evaluation_df.to_csv(index=False)
505
+ # Append the CSV content to the file on Dropbox
506
+ append_to_csv_on_dropbox(csv_content, DROPBOX_FILE_PATH)
507
+
508
+ def save_data(dis_no, user_feedback):
509
+ global evaluation_df
510
+
511
+ if not dis_no or dis_no == "Select a Disaster Event":
512
+ print("Invalid input. Ensure a disaster event is selected.")
513
+ return
514
+
515
+ user_id = generate_unique_user_id()
516
+ new_data = pd.DataFrame([[dis_no, user_feedback, user_id]],
517
+ columns=["DisNo.", "User Feedback", "User ID"])
518
+ evaluation_df = pd.concat([evaluation_df, new_data], ignore_index=True)
519
+ print("Updated DataFrame:")
520
+ print(evaluation_df)
521
+
522
+ save_data_to_dropbox()
523
+ print(f"Data saved: DisNo: {dis_no}, Feedback: {user_feedback}, User ID: {user_id}")
524
+
525
+
526
+ DROPBOX_FILE_PATH = '/evaluation_data.csv'
527
+ evaluation_df = load_initial_data()
528
+
529
+
530
+ def update_row_dropdown(disaster_type=None, country=None):
531
+ # Start with the entire dataframe
532
+ filtered_df = df
533
+
534
+ # Step 1: Filter by Disaster Type
535
+ if disaster_type:
536
+ filtered_df = filtered_df[filtered_df['Disaster Type'] == disaster_type]
537
+
538
+ # Step 2: Further filter by Country
539
+ if country:
540
+ filtered_df = filtered_df[filtered_df['Country'] == country]
541
+
542
+ # Step 3: Generate and sort the DisNo. choices based on the filtered DataFrame
543
+ choices = sorted(filtered_df['DisNo.'].tolist()) if not filtered_df.empty else []
544
+
545
+ # Add a placeholder option at the beginning
546
+ choices = ["Select a Disaster Event"] + choices
547
+
548
+ print(f"Available DisNo. for {disaster_type} in {country}: {choices}")
549
+
550
+ # Return the update for the dropdown, defaulting to the placeholder
551
+ return gr.update(choices=choices, value=choices[0] if choices else None)
552
+
553
+
554
+
555
+ def display_info(selected_row_str, country):
556
+ if not selected_row_str or selected_row_str == 'Select a Disaster Event':
557
+ print("No valid disaster event selected.")
558
+ return ('No valid event selected.', '<div>No graph available.</div>', '', '', '')
559
+
560
+ print(f"Selected Country: {country}, Selected Row: {selected_row_str}")
561
+
562
+ # Filter the dataframe for the selected disaster number
563
+ row_data = df[df['DisNo.'] == selected_row_str]
564
+
565
+ if not row_data.empty:
566
+ #print(f"Row data: {row_data}")
567
+ row_data["geometry"] = row_data.apply(get_geometries, axis=1)
568
+
569
+ row_data = row_data.squeeze()
570
+
571
+ # Combine the relevant columns into a single storyline with labels
572
+ storyline_parts = [
573
+ f"Key Information: {row_data.get('key information', '')}",
574
+ f"Severity: {row_data.get('severity', '')}",
575
+ f"Key Drivers: {row_data.get('key drivers', '')}",
576
+ f"Main Impacts, Exposure, and Vulnerability: {row_data.get('main impacts, exposure, and vulnerability', '')}",
577
+ f"Likelihood of Multi-Hazard Risks: {row_data.get('likelihood of multi-hazard risks', '')}",
578
+ f"Best Practices for Managing This Risk: {row_data.get('best practices for managing this risk', '')}",
579
+ f"Recommendations and Supportive Measures for Recovery: {row_data.get('recommendations and supportive measures for recovery', '')}"
580
+ ]
581
+ storyline = "\n\n".join(part for part in storyline_parts if part.split(': ')[1]) # Include only non-empty parts
582
+ cleaned_storyline = gpt_story(storyline)
583
+ causal_graph_caption = row_data.get('llama graph', '')
584
+ grp = ast.literal_eval(causal_graph_caption) if causal_graph_caption else []
585
+ causal_graph_html = plot_cgraph_pyvis(grp)
586
+
587
+ # Create the Folium map
588
+ geometry = row_data.get('geometry', None)
589
+ folium_map_html = plot_geometry_folium(geometry, location_name=country, country_name=country)
590
+
591
+ # Parse and format the start date
592
+ start_date_str = f"{row_data['Start Year']}-{row_data['Start Month']}-{row_data['Start Day']}"
593
+
594
+ # Parse and format the end date
595
+ end_date_str = f"{row_data['End Year']}-{row_data['End Month']}-{row_data['End Day']}"
596
+
597
+ return (
598
+ cleaned_storyline,
599
+ causal_graph_html,
600
+ folium_map_html,
601
+ start_date_str,
602
+ end_date_str
603
+ )
604
+ else:
605
+ print("No valid data found for the selection.")
606
+ return ('No valid data found.', '<div>No graph available.</div>', '', '', '')
607
+
608
+ def process_new_node(selected_row_str, new_node):
609
+ if not selected_row_str or selected_row_str == 'Select a Disaster Event':
610
+ print("No valid disaster event selected.")
611
+ return '<div>No graph available.</div>'
612
+
613
+ if not new_node:
614
+ print("No new node provided.")
615
+ return '<div>No graph available.</div>'
616
+
617
+ print(f"Selected Row: {selected_row_str}, New Node: {new_node}")
618
+
619
+ # Filter the dataframe for the selected disaster number
620
+ row_data = df[df['DisNo.'] == selected_row_str]
621
+
622
+ if not row_data.empty:
623
+ row_data = row_data.squeeze()
624
+ causal_graph_caption = row_data.get('llama graph', '')
625
+ grp = ast.literal_eval(causal_graph_caption) if causal_graph_caption else []
626
+ source, relations, target = list(zip(*grp))
627
+ kg_df = pd.DataFrame({'source': source, 'target': target, 'edge': relations})
628
+
629
+ # Call the generate_new_relations function
630
+ result_df = generate_new_relations(
631
+ graph_df=kg_df,
632
+ new_node=new_node,
633
+ max_combinations_fraction=0.1,
634
+ temperature=0.8, # Adjust temperature for diversity
635
+ top_k=50, # Top-k sampling
636
+ top_p=0.95, # Top-p (nucleus) sampling
637
+ seed=42, # Optional for reproducibility
638
+ verbose=False # Optional for debugging
639
+ )
640
+
641
+ # Plot the updated graph with the new relations
642
+ source = result_df['source'].astype(str)
643
+ relations = result_df['edge'].astype(str)
644
+ target = result_df['target'].astype(str)
645
+ grp = zip(source, relations, target)
646
+ causal_graph_html = plot_cgraph_pyvis(grp)
647
+ return causal_graph_html
648
+ else:
649
+ print("No valid data found for the selection.")
650
+ return '<div>No graph available.</div>'
651
+
652
+ def build_interface():
653
+ with gr.Blocks() as interface:
654
+ gr.Markdown(
655
+ """
656
+ # From Complexity to Clarity: Leveraging AI to Decode Interconnected Risks
657
+
658
+ Welcome to our Gradio application, developed and maintained by [JRC](https://joint-research-centre.ec.europa.eu/) Units: **E1**, **F7**, and **T5**. This is part of the **EMBRACE Portfolio on Risks**. <br><br>
659
+
660
+ **Overview**:
661
+ This application employs advanced AI techniques like Retrieval-Augmented Generation (RAG) on [EMM](https://emm.newsbrief.eu/) news. It extracts relevant media content on disaster events recorded in [EM-DAT](https://www.emdat.be/), including floods, wildfires, droughts, epidemics, and disease outbreaks. <br><br>
662
+
663
+ **How It Works**:
664
+ For each selected event (filterable by Disaster Type, Country, and Disaster Number), the app:
665
+ - Retrieves pertinent news chunks via the EMM RAG service.
666
+ - Uses multiple LLMs from the [GPT@JRC](https://gpt.jrc.ec.europa.eu/) portfolio to:
667
+ - Extract critical impact data (e.g., fatalities, affected populations).
668
+ - Transform unstructured news into coherent, structured storylines.
669
+ - Build causal knowledge graphs — *impact chains* — highlighting drivers, impacts, and interactions. <br><br>
670
+
671
+ **Explore Events**:
672
+ Use the selectors below to explore events by **Disaster Type**, **Country**, and **Disaster Number (DisNo)**. <br>
673
+ Once an event is selected, the app will display the **causal impact-chain graph**, illustrating key factors and their interrelationships. <br>
674
+ Below the graph, you'll find the **AI-generated narrative**, presenting a structured storyline of the event based on relevant news coverage. <br><br>
675
+
676
+ **Outcome**:
677
+ These outputs offer a deeper understanding of disaster dynamics, supporting practitioners, disaster managers, and policy-makers in identifying patterns, assessing risks, and enhancing preparedness and response strategies.
678
+ """
679
+ )
680
+
681
+ # Create dropdowns for Disaster Type, Country, and Disaster Event #
682
+ disaster_type_dropdown = gr.Dropdown(
683
+ choices=[''] + df['Disaster Type'].unique().tolist(),
684
+ label="Select Disaster Type"
685
+ )
686
+ country_dropdown = gr.Dropdown(
687
+ choices=[''], # Initially empty; will be populated based on disaster type
688
+ label="Select Country"
689
+ )
690
+ row_dropdown = gr.Dropdown(
691
+ choices=[],
692
+ label="Select Disaster Event #",
693
+ interactive=True
694
+ )
695
+
696
+ with gr.Column():
697
+ disaster_type_dropdown
698
+ country_dropdown
699
+ row_dropdown
700
+
701
+ gr.Markdown("### AI-Generated Storyline:") # Title
702
+ outputs = [
703
+ gr.Textbox(label="Storyline", interactive=False, lines=10),
704
+ gr.HTML(label="Original Causal Graph"), # Change from gr.Plot to gr.HTML
705
+ gr.HTML(label="Location Map"), # Add HTML output for Folium map
706
+ gr.HTML(label="Updated Causal Graph") # New HTML component for the updated graph
707
+ ]
708
+
709
+ # New Radio button for user feedback
710
+ feedback_radio = gr.Radio(
711
+ choices=["Yes", "No"],
712
+ label="According to your expert knowledge, does the graph capture the main relations?",
713
+ interactive=True
714
+ )
715
+
716
+ # New section for generating new scenarios
717
+ gr.Markdown("### Generate New Scenarios") # Subtitle for the new section
718
+ new_node_input = gr.Textbox(
719
+ label="Enter a new variable or factor that might interact with the current graph to generate a plausible scenario:",
720
+ placeholder="e.g., tornado",
721
+ interactive=True
722
+ )
723
+
724
+ # Button to save the data
725
+ save_button = gr.Button("Save Data")
726
+
727
+ # Button to process new node
728
+ process_button = gr.Button("Add New Node")
729
+
730
+ # Update country choices based on selected disaster type
731
+ disaster_type_dropdown.change(
732
+ fn=lambda disaster_type: gr.update(
733
+ choices=[''] + sorted(df[df['Disaster Type'] == disaster_type]['Country'].unique().tolist()),
734
+ value=''
735
+ ),
736
+ inputs=disaster_type_dropdown,
737
+ outputs=country_dropdown
738
+ )
739
+
740
+ # Update DisNo. choices based on selected disaster type and country
741
+ country_dropdown.change(
742
+ fn=update_row_dropdown,
743
+ inputs=[disaster_type_dropdown, country_dropdown],
744
+ outputs=row_dropdown
745
+ )
746
+
747
+ # Display information based on selected DisNo.
748
+ row_dropdown.change(
749
+ fn=display_info,
750
+ inputs=[row_dropdown, country_dropdown],
751
+ outputs=outputs[:3] # Do not overwrite the updated graph slot
752
+ )
753
+
754
+ # Handle saving data on button click
755
+ save_button.click(
756
+ fn=save_data,
757
+ inputs=[row_dropdown, feedback_radio],
758
+ outputs=[]
759
+ )
760
+
761
+ # Handle processing of the new node
762
+ process_button.click(
763
+ fn=process_new_node,
764
+ inputs=[row_dropdown, new_node_input],
765
+ outputs=[outputs[3]] # Update only the updated graph output
766
+ )
767
+
768
+ return interface
769
+
770
+ app = build_interface()
771
+ app.launch()