Files changed (3) hide show
  1. README.md +3 -3
  2. app_pyvis_new.py +0 -792
  3. requirements.txt +1 -10
README.md CHANGED
@@ -1,7 +1,7 @@
1
  ---
2
  title: crisesStorylinesRAG
3
- app_file: app_pyvis_new.py
4
  sdk: gradio
5
- sdk_version: 5.33.1
6
  ---
7
- # crisesStorylinesRAG
 
1
  ---
2
  title: crisesStorylinesRAG
3
+ app_file: app_pyvis.py
4
  sdk: gradio
5
+ sdk_version: 5.0.1
6
  ---
7
+ # crisesStorylinesRAG
app_pyvis_new.py DELETED
@@ -1,792 +0,0 @@
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 string
9
- from datetime import datetime
10
- import random
11
- import geopandas as gpd
12
- import folium
13
- from shapely.geometry import mapping
14
- from shapely.geometry import Polygon
15
- from shapely.ops import unary_union
16
- import dropbox
17
- from dropbox.exceptions import ApiError
18
- import io
19
- from itertools import combinations
20
- from typing import Optional, Union
21
- import torch
22
- from transformers import T5ForConditionalGeneration, T5Tokenizer
23
- import inflect
24
- from itertools import combinations
25
- from typing import Optional, Union
26
- import torch
27
- import osmnx as osm
28
-
29
-
30
- # Replace these with your actual app key and secret
31
- APP_KEY = os.environ['APP_KEY']
32
- APP_SECRET = os.environ['APP_SECRET']
33
- REFRESH_TOKEN = os.environ['REFRESH_TOKEN']
34
-
35
-
36
- EMM_RETRIEVERS_OPENAI_API_BASE_URL="https://api-gpt.jrc.ec.europa.eu/v1"
37
- EMM_RETRIEVERS_OPENAI_API_KEY = os.environ['EMM_RETRIEVERS_OPENAI_API_KEY']
38
-
39
-
40
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
41
- usr_tkn_consose_read = os.environ['usr_tkn_consose_read']
42
-
43
- inflect_engine = inflect.engine()
44
-
45
- model_id = os.environ['ie_model_id']
46
- tokenizer = T5Tokenizer.from_pretrained(model_id)
47
- model = T5ForConditionalGeneration.from_pretrained(model_id)
48
-
49
-
50
- client1 = OpenAI(
51
- api_key=EMM_RETRIEVERS_OPENAI_API_KEY,
52
- base_url="https://api-gpt.jrc.ec.europa.eu/v1",
53
- )
54
-
55
-
56
- def geocode_emdat(location):
57
- def process_geocoding(location_to_geocode):
58
- try:
59
- return osm.geocode_to_gdf(location_to_geocode)["geometry"].iloc[0]
60
- except Exception:
61
- return None
62
-
63
- geocoded_location = process_geocoding(location)
64
-
65
- if geocoded_location is None:
66
- print(f"Error geocoding location '{location}'. Trying to correct with GPT-4.")
67
- response = client1.chat.completions.create(
68
- model="gpt-4o",
69
- stream=False,
70
- 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}'"}]
71
- )
72
- corrected_location = response.choices[0].message.content.strip()
73
- geocoded_location = process_geocoding(corrected_location)
74
-
75
- return geocoded_location
76
-
77
-
78
- def get_country_boundary(country_name):
79
- # Filter the world GeoDataFrame for the country
80
- country = world[world['NAME'] == country_name]
81
- if not country.empty:
82
- # Return the country's geometry
83
- return country.geometry.iloc[0]
84
- else:
85
- # Return None if country not found
86
- return None
87
-
88
- def get_geometries(row):
89
- country = row['Country']
90
- locations = row['Locations']
91
-
92
- # Return NaN if locations is NaN
93
- if pd.isna(locations):
94
- return None
95
-
96
- # Get the country's boundary
97
- country_boundary = get_country_boundary(country)
98
-
99
- # If no country boundary is found, return None
100
- if country_boundary is None:
101
- return None
102
-
103
- locations_list = locations.split(', ')
104
-
105
- # Get polygons for each location, ignoring None results
106
- polygons = [geocode_emdat(f"{country}, {location}") for location in locations_list]
107
- polygons = [polygon for polygon in polygons if polygon is not None]
108
-
109
- # Filter polygons to remove those outside the country boundary
110
- valid_polygons = [polygon for polygon in polygons if polygon.within(country_boundary)]
111
-
112
- # If there are no valid polygons, return None
113
- if not valid_polygons:
114
- return None
115
-
116
- # Combine them into a single geometry using unary_union
117
- combined_geometry = unary_union(valid_polygons)
118
- print(combined_geometry)
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 = get_dropbox_client()
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
- return "Thank you, your answer has been recorded! This will help make AI more reliable."
526
-
527
-
528
- DROPBOX_FILE_PATH = '/evaluation_data.csv'
529
- evaluation_df = load_initial_data()
530
-
531
-
532
- def update_row_dropdown(disaster_type=None, country=None):
533
- # Start with the entire dataframe
534
- filtered_df = df
535
-
536
- # Step 1: Filter by Disaster Type
537
- if disaster_type:
538
- filtered_df = filtered_df[filtered_df['Disaster Type'] == disaster_type]
539
-
540
- # Step 2: Further filter by Country
541
- if country:
542
- filtered_df = filtered_df[filtered_df['Country'] == country]
543
-
544
- # Step 3: Generate and sort the DisNo. choices based on the filtered DataFrame
545
- choices = sorted(filtered_df['DisNo.'].tolist()) if not filtered_df.empty else []
546
-
547
- # Add a placeholder option at the beginning
548
- choices = ["Select a Disaster Event"] + choices
549
-
550
- print(f"Available DisNo. for {disaster_type} in {country}: {choices}")
551
-
552
- # Return the update for the dropdown, defaulting to the placeholder
553
- return gr.update(choices=choices, value=choices[0] if choices else None)
554
-
555
-
556
-
557
- def display_info(selected_row_str, country):
558
- if not selected_row_str or selected_row_str == 'Select a Disaster Event':
559
- print("No valid disaster event selected.")
560
- return ('No valid event selected.', '<div>No graph available.</div>', '', '', '')
561
-
562
- print(f"Selected Country: {country}, Selected Row: {selected_row_str}")
563
-
564
- # Filter the dataframe for the selected disaster number
565
- row_data = df[df['DisNo.'] == selected_row_str]
566
-
567
- if not row_data.empty:
568
- #print(f"Row data: {row_data}")
569
- row_data["geometry"] = row_data.apply(get_geometries, axis=1)
570
-
571
- row_data = row_data.squeeze()
572
-
573
- # Combine the relevant columns into a single storyline with labels
574
- storyline_parts = [
575
- f"Key Information: {row_data.get('key information', '')}",
576
- f"Severity: {row_data.get('severity', '')}",
577
- f"Key Drivers: {row_data.get('key drivers', '')}",
578
- f"Main Impacts, Exposure, and Vulnerability: {row_data.get('main impacts, exposure, and vulnerability', '')}",
579
- f"Likelihood of Multi-Hazard Risks: {row_data.get('likelihood of multi-hazard risks', '')}",
580
- f"Best Practices for Managing This Risk: {row_data.get('best practices for managing this risk', '')}",
581
- f"Recommendations and Supportive Measures for Recovery: {row_data.get('recommendations and supportive measures for recovery', '')}"
582
- ]
583
- storyline = "\n\n".join(part for part in storyline_parts if part.split(': ')[1]) # Include only non-empty parts
584
- cleaned_storyline = gpt_story(storyline)
585
- causal_graph_caption = row_data.get('llama graph', '')
586
- grp = ast.literal_eval(causal_graph_caption) if causal_graph_caption else []
587
- causal_graph_html = plot_cgraph_pyvis(grp)
588
-
589
- # Create the Folium map
590
- geometry = row_data.get('geometry', None)
591
- folium_map_html = plot_geometry_folium(geometry, location_name=country, country_name=country)
592
-
593
- # Parse and format the start date
594
- start_date_str = f"{row_data['Start Year']}-{row_data['Start Month']}-{row_data['Start Day']}"
595
-
596
- # Parse and format the end date
597
- end_date_str = f"{row_data['End Year']}-{row_data['End Month']}-{row_data['End Day']}"
598
-
599
- return (
600
- cleaned_storyline,
601
- causal_graph_html,
602
- folium_map_html,
603
- start_date_str,
604
- end_date_str
605
- )
606
- else:
607
- print("No valid data found for the selection.")
608
- return ('No valid data found.', '<div>No graph available.</div>', '', '', '')
609
-
610
- def process_new_node(selected_row_str, new_node):
611
- if not selected_row_str or selected_row_str == 'Select a Disaster Event':
612
- print("No valid disaster event selected.")
613
- return '<div>No graph available.</div>'
614
-
615
- if not new_node:
616
- print("No new node provided.")
617
- return '<div>No graph available.</div>'
618
-
619
- print(f"Selected Row: {selected_row_str}, New Node: {new_node}")
620
-
621
- # Filter the dataframe for the selected disaster number
622
- row_data = df[df['DisNo.'] == selected_row_str]
623
-
624
- if not row_data.empty:
625
- row_data = row_data.squeeze()
626
- causal_graph_caption = row_data.get('llama graph', '')
627
- grp = ast.literal_eval(causal_graph_caption) if causal_graph_caption else []
628
- source, relations, target = list(zip(*grp))
629
- kg_df = pd.DataFrame({'source': source, 'target': target, 'edge': relations})
630
-
631
- # Call the generate_new_relations function
632
- result_df = generate_new_relations(
633
- graph_df=kg_df,
634
- new_node=new_node,
635
- max_combinations_fraction=0.1,
636
- temperature=0.8, # Adjust temperature for diversity
637
- top_k=50, # Top-k sampling
638
- top_p=0.95, # Top-p (nucleus) sampling
639
- seed=42, # Optional for reproducibility
640
- verbose=False # Optional for debugging
641
- )
642
-
643
- # Plot the updated graph with the new relations
644
- source = result_df['source'].astype(str)
645
- relations = result_df['edge'].astype(str)
646
- target = result_df['target'].astype(str)
647
- grp = zip(source, relations, target)
648
- causal_graph_html = plot_cgraph_pyvis(grp)
649
- return causal_graph_html
650
- else:
651
- print("No valid data found for the selection.")
652
- return '<div>No graph available.</div>'
653
-
654
- def build_interface():
655
- with gr.Blocks() as interface:
656
- gr.Markdown(
657
- """
658
- # From Complexity to Clarity: Leveraging AI to Decode Interconnected Risks
659
-
660
- 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>
661
-
662
- **Overview**:
663
- 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>
664
-
665
- **How It Works**:
666
- For each selected event (filterable by Disaster Type, Country, and Disaster Number), the app:
667
- - Retrieves pertinent news chunks via the EMM RAG service.
668
- - Uses multiple LLMs from the [GPT@JRC](https://gpt.jrc.ec.europa.eu/) portfolio to:
669
- - Extract critical impact data (e.g., fatalities, affected populations).
670
- - Transform unstructured news into coherent, structured storylines.
671
- - Build causal knowledge graphs — *impact chains* — highlighting drivers, impacts, and interactions. <br><br>
672
-
673
- **Explore Events**:
674
- Use the selectors below to explore events by **Disaster Type**, **Country**, and **Disaster Number (DisNo)**. <br>
675
- Once an event is selected, the app will display the **causal impact-chain graph**, illustrating key factors and their interrelationships. <br>
676
- Below the graph, you'll find the **AI-generated narrative**, presenting a structured storyline of the event based on relevant news coverage. <br><br>
677
-
678
- **Outcome**:
679
- 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.
680
- """
681
- )
682
-
683
- # Create dropdowns for Disaster Type, Country, and Disaster Event #
684
- disaster_type_dropdown = gr.Dropdown(
685
- choices=[''] + df['Disaster Type'].unique().tolist(),
686
- label="Select Disaster Type"
687
- )
688
- country_dropdown = gr.Dropdown(
689
- choices=[''], # Initially empty; will be populated based on disaster type
690
- label="Select Country"
691
- )
692
- row_dropdown = gr.Dropdown(
693
- choices=[],
694
- label="Select Disaster Event #",
695
- interactive=True
696
- )
697
-
698
- with gr.Column():
699
- disaster_type_dropdown
700
- country_dropdown
701
- row_dropdown
702
-
703
- gr.Markdown("### AI-Generated Storyline:") # Title
704
- outputs = [
705
- gr.Textbox(label="Storyline", interactive=False, lines=10),
706
- gr.HTML(label="Original Causal Graph"), # Change from gr.Plot to gr.HTML
707
- gr.HTML(label="Location Map"), # Add HTML output for Folium map
708
- gr.HTML(label="Updated Causal Graph") # New HTML component for the updated graph
709
- ]
710
-
711
- # New Radio button for user feedback
712
- feedback_radio = gr.Radio(
713
- choices=[
714
- "Fully Correct",
715
- "Mostly Correct",
716
- "Partially Incorrect",
717
- "Incorrect",
718
- "Difficult to Judge"
719
- ],
720
- label="According to your expert knowledge, evaluate the graph based on the following descriptions:",
721
- interactive=True
722
- )
723
-
724
- # Add descriptions for each option
725
- gr.Markdown("""
726
- - **Fully Correct**: All relevant nodes are accurately identified. The relationships among these nodes, including their directions (causes or prevents), are correct.
727
- - **Mostly Correct**: The majority of relevant nodes are identified. However, some relationships may be missing or incorrect, such as a missing link or an inaccurate parent-child relationship. Despite these minor issues, the overall structure is largely accurate.
728
- - **Partially Incorrect**: Some important nodes are missing and there are errors in the directions of relationships. However, the graph still captures some of the main characteristics of the intended relationships.
729
- - **Incorrect**: The majority of links are incorrect, and many relevant nodes are either missing or inaccurately represented.
730
- - **Difficult to Judge**: The graph is ambiguous or lacks sufficient context for accurate assessment.
731
- """)
732
-
733
- # New section for generating new scenarios
734
- gr.Markdown("### Generate New Scenarios") # Subtitle for the new section
735
- new_node_input = gr.Textbox(
736
- label="Enter a new variable or factor that might interact with the current graph to generate a plausible scenario:",
737
- placeholder="e.g., tornado",
738
- interactive=True
739
- )
740
-
741
- # Button to save the data
742
- save_button = gr.Button("Save Your Answer")
743
-
744
- # Feedback message
745
- feedback_message = gr.Markdown("", visible=True)
746
-
747
- # Button to process new node
748
- process_button = gr.Button("Add New Node")
749
-
750
- # Update country choices based on selected disaster type
751
- disaster_type_dropdown.change(
752
- fn=lambda disaster_type: gr.update(
753
- choices=[''] + sorted(df[df['Disaster Type'] == disaster_type]['Country'].unique().tolist()),
754
- value=''
755
- ),
756
- inputs=disaster_type_dropdown,
757
- outputs=country_dropdown
758
- )
759
-
760
- # Update DisNo. choices based on selected disaster type and country
761
- country_dropdown.change(
762
- fn=update_row_dropdown,
763
- inputs=[disaster_type_dropdown, country_dropdown],
764
- outputs=row_dropdown
765
- )
766
-
767
- # Display information based on selected DisNo.
768
- row_dropdown.change(
769
- fn=display_info,
770
- inputs=[row_dropdown, country_dropdown],
771
- outputs=outputs[:3] # Do not overwrite the updated graph slot
772
- )
773
-
774
- # Handle saving data on button click
775
- save_button.click(
776
- fn=save_data,
777
- inputs=[row_dropdown, feedback_radio],
778
- outputs=[feedback_message],
779
- #api_name="Save Answer"
780
- )
781
-
782
- # Handle processing of the new node
783
- process_button.click(
784
- fn=process_new_node,
785
- inputs=[row_dropdown, new_node_input],
786
- outputs=[outputs[3]] # Update only the updated graph output
787
- )
788
-
789
- return interface
790
-
791
- app = build_interface()
792
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -3,13 +3,4 @@ pandas
3
  pyvis
4
  matplotlib
5
  networkx
6
- openai
7
- dropbox
8
- geopandas
9
- shapely
10
- osmnx
11
- folium
12
- torch
13
- transformers
14
- inflect
15
- sentencepiece
 
3
  pyvis
4
  matplotlib
5
  networkx
6
+ openai