plebias commited on
Commit
9830691
·
1 Parent(s): a02d909

push submission ver0 commit

Browse files
Files changed (3) hide show
  1. app.py +331 -90
  2. app_v2.py +0 -636
  3. requirements.txt +2 -0
app.py CHANGED
@@ -1,20 +1,55 @@
1
  # -*- coding: utf-8 -*-
 
2
  # Default dependencies to run
3
  import os
4
  # from dotenv import load_dotenv
5
  # load_dotenv()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # @title ⚙️ Configure Tokens
 
 
8
 
9
- #oa_token_secret = 'oa_yewey' # @param {type: "string"}
10
- #lta_token_secret = 'lta_token' # @param {type: "string"}
11
 
12
- #os.environ["OPENAI_API_KEY"] = userdata.get(oa_token_secret)
13
- #os.environ["DATAMALL_API_KEY"] = userdata.get(lta_token_secret)
14
 
 
 
 
 
 
 
 
15
 
16
  import logging
17
- import docx
18
  import os
19
  import re
20
  import requests
@@ -48,18 +83,42 @@ import gradio as gr
48
 
49
  ########################## Initialise API keys ##############################
50
 
51
- OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
52
 
53
- if not OPENAI_API_KEY:
54
- raise Exception("No OpenAI API Key found!")
55
 
56
  DATAMALL_API_KEY = os.environ.get("DATAMALL_API_KEY")
57
 
58
  if not DATAMALL_API_KEY:
59
  raise Exception("No Datamall API Key found!")
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  ########################## Audio to Summary functions ##############################
62
  client = OpenAI(api_key=OPENAI_API_KEY)
 
63
 
64
  def get_transcript_from_audio(audio_file_path):
65
  """
@@ -84,9 +143,9 @@ def get_summary_from_transcript(transcript):
84
  raise Exception(f"Wrong type of transcript. Expected type str, got type {type(transcript)}")
85
 
86
  prompt = f"""You are provided with the following audio transcript of one or more calls between incident reporters and emergency responders in Singapore.
87
- Provide a concise and detailed summary (1) based on the transcript.
88
  Separately provide the following information (2) with labels in [] strictly following the format {{[label]: info}} (3) based on the generated audio transcript (2) [!important: Do not include details not found in the audio transcript]:
89
- [who], [what], ([where, direction]), closest single [landmark] phrase given by reporter, closest !single [road] phrase given by reporter, [when], [why], and [how] strictly based on the generated transcript. Example: {{[road]: road_name}}
90
  \n\n----------------\n\nTranscript:\n{transcript}\n\n(1)\n\n"""
91
 
92
  completion = client.completions.create(
@@ -129,6 +188,8 @@ def extract_location_from_summary(summary):
129
  landmark = re.search(landmark_pattern, summary).group(1)
130
 
131
  # Split the string by commas
 
 
132
  landmark_list = landmark.split(",")
133
 
134
  # Trim whitespace from each element and append to a list
@@ -139,8 +200,10 @@ def extract_location_from_summary(summary):
139
  try:
140
  road_pattern = r'\[road\]:\s*(.*?)\n'
141
  road = re.search(road_pattern, summary).group(1)
142
-
143
  # Split the string by commas
 
 
144
  road_list = road.split(",")
145
 
146
  # Trim whitespace from each element and append to a list
@@ -184,6 +247,7 @@ def get_latlong_from_summary(summary):
184
  lat, lng = get_latlong(location_list)
185
  print(f"Estimated lat, lng: ({lat}, {lng})\n")
186
 
 
187
  return lat, lng
188
 
189
  def call_api(api_url):
@@ -267,6 +331,7 @@ def get_nearest_camera(latlong, num):
267
 
268
  # Extract camera location and imagelink via Datamall API
269
  cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
 
270
  cameraimg_df['CameraID'] = cameraimg_df['CameraID'].astype('int64')
271
 
272
  # Extract additional camera information from database
@@ -282,25 +347,60 @@ def get_nearest_camera(latlong, num):
282
 
283
  # Append encoded image and time retrieved into dataframe
284
  img_list, camera_coords, encoded_img, datetime_list = [], [], [], []
 
285
 
286
  for idx in closest_cam.index:
287
  response = requests.get(closest_cam["ImageLink"][idx])
288
 
289
- # # Plot images of cameras
290
- # print(f"{nearest_cams['Description'][idx]} ({nearest_cams['Latitude'][idx]}, {nearest_cams['Longitude'][idx]})")
291
- # img = Image.open(BytesIO(response.content))
292
- # plt.imshow(img)
293
- # plt.show(img)
294
- # print("\n")
295
-
296
  img_list.append(closest_cam["ImageLink"][idx])
297
  encoded_img.append(encode_image_to_base64(response))
298
- datetime_list.append(datetime.now().strftime('%d/%m/%Y %I:%M %p'))
 
299
 
300
  closest_cam["encoded_img"] = encoded_img
301
  closest_cam["time_retrieved"] = datetime_list
 
302
 
303
- return closest_cam
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
  def get_firestation_from_latlong(latlong, num):
306
  """
@@ -313,7 +413,7 @@ def get_firestation_from_latlong(latlong, num):
313
  lat,lng = latlong
314
 
315
  civil_df = pd.read_excel("data/fire_hosp.xlsx")
316
- civil_df = civil_df[civil_df["category"].isin(["Firestation"])]
317
 
318
  # Calculate distances
319
  civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
@@ -331,7 +431,7 @@ def get_hospital_from_latlong(latlong, num):
331
  lat,lng = latlong
332
 
333
  civil_df = pd.read_excel("data/fire_hosp.xlsx")
334
- civil_df = civil_df[civil_df["category"].isin(["Hospital", "Firestation", "Firepost"])]
335
 
336
  # Calculate distances
337
  civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
@@ -345,32 +445,89 @@ def get_map_from_summary(summary_txt):
345
  Provide a Folium Map showing the location of the incident and the "num" nearest traffic
346
  cameras, fire stations and ambulance sites.
347
  """
348
-
349
  lat, lng = get_latlong_from_summary(summary_txt)
350
 
351
  if pd.isna(lat) and pd.isna(lng):
352
  print("Lat, Lng cannot be determined. Please try again")
353
  return None
354
  else:
355
- cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
 
 
 
 
 
 
356
 
357
  avg_lat = np.mean(cameraimg_df["Latitude"])
358
  avg_lng = np.mean(cameraimg_df["Longitude"])
359
-
360
  map = folium.Map(location=[avg_lat, avg_lng], zoom_start=12)
361
-
362
  folium.Marker(location=[float(lat), float(lng)],
363
  icon=folium.Icon(color='red'),
364
  popup="Incident"
365
  ).add_to(map)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
- nearest_cam_df = get_nearest_camera((lat,lng), 3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  nearest_fire_df = get_firestation_from_latlong((lat,lng), 3)
369
  nearest_hosp_df = get_hospital_from_latlong((lat,lng), 3)
370
 
371
- for idx in tqdm(cameraimg_df.index, desc="Processing Traffic Cameras"):
372
- if cameraimg_df["CameraID"][idx] in list(nearest_cam_df["CameraID"].astype(str)):
 
 
 
 
 
373
 
 
 
 
374
  html = '<h1>{}</h1><div>{}</div>&nbsp;<img style="width:100%; height:100%;" src="data:image/jpeg;base64,{}">'.format(
375
  nearest_cam_df["Description"][idx],
376
  nearest_cam_df["time_retrieved"][idx],
@@ -402,58 +559,53 @@ def get_map_from_summary(summary_txt):
402
  ########################## RAG to Recommendations functions ##############################
403
 
404
  action_prompt = """\
 
 
405
  You are a dispatching agent for traffic conditions. You will be provided with the Standard Operating Procedures (SOPs) of various departments, with a description of what each department's roles and responsibilities are. From these information, you are a well-versed dispatcher that can recommend the corresponding actions accurately for various scenarios.
406
 
407
- You will be provided the following information:
408
- 1. The stakeholder and its roles and responsibilities.
409
- 2. An incident report summary.
410
- 3. A list of the SOPs for the stakeholder {stakeholder}.
411
 
412
- Your task is to provide a short and sweet summary for the {stakeholder}. This should extract all the key points from the relevant SOPs, catered to the specific scenario.
 
 
 
 
413
 
414
  ----------------------------------------------------------------
415
-
416
  Here is the description of the stakeholder, and a description of its roles and responsibilities.
417
 
418
- <stakeholder> {stakeholder} </stakeholder>
419
 
420
- <roles-and-responsibilities>
421
 
422
  {stakeholder_role}
423
 
424
- </roles-and-responsibilities>
425
-
426
  ----------------------------------------------------------------
427
  Below is the incident summary, and location of the incident.
428
 
429
- <location> {location} </location>
430
 
431
- <incident-summary>
432
 
433
  {summary}
434
 
435
- </incident-summary>
436
-
437
  ----------------------------------------------------------------
438
  Below is some relevant standard operating procedures.
439
- You will be provided with a list of SOPs, that might be relevant to the incident.. They will be split with ==============.
440
- The filename of the document and the document contents will be provided below.
441
-
442
- <standard-operating-procedures>
443
 
444
  {ref_content}
445
 
446
- </standard-operating-procedures>
447
-
448
  ----------------------------------------------------------------
449
 
450
  Given the situation above and the relevant SOPs, provide in detail the relevant procedure recommendations for the stakeholder {stakeholder}.
451
 
452
- ** Important: If there are no relevant actions for the stakeholder found, state that "No Relevant SOPs found for current incident". Recommend to redirect to a human agent for confirmation. **
453
-
454
- Remember to keep the action plan short and sweet, while incorporating only the necessary action plans from the relevant SOP.
455
 
456
- Your response:
457
 
458
  """
459
 
@@ -467,17 +619,16 @@ stakeholder_roles_gpt35 = {
467
 
468
  stakeholder_roles = stakeholder_roles_gpt35
469
 
470
- model_name = "bge-large-en-v1.5"
471
- model_kwargs = {"device": "cpu"}
472
- encode_kwargs = {"normalize_embeddings": True}
473
- bge = HuggingFaceBgeEmbeddings(
474
- model_name=model_name,
475
- model_kwargs = model_kwargs,
476
- encode_kwargs = encode_kwargs)
 
 
477
 
478
- def get_store(index_name, embeddings = bge):
479
- store = FAISS.load_local(index_name, embeddings, allow_dangerous_deserialization=True)
480
- return store
481
 
482
  def retrieve_sop_from_summary(summary_txt,
483
  stakeholders = ["SCDF", "LTA", "Traffic Police"],
@@ -485,16 +636,11 @@ def retrieve_sop_from_summary(summary_txt,
485
  ):
486
  sops_retrieved = {}
487
  for stakeholder in stakeholders:
488
- retriever = get_store(f"index/{stakeholder}").as_retriever(search_type="similarity", search_kwargs={"k":top_k})
489
- selected_sops = retriever.invoke(summary_txt)
490
-
491
- ref_content = [sop.page_content for sop in selected_sops]
492
- ref_filename = [str(sop.metadata) for sop in selected_sops]
493
-
494
- sops_retrieved[stakeholder] = (ref_content, ref_filename)
495
 
496
  return sops_retrieved
497
 
 
498
  def get_actions_from_summary(summary_txt, location = None,
499
  stakeholders = ["SCDF", "LTA", "Traffic Police"],
500
  top_k = 3):
@@ -518,25 +664,104 @@ def get_actions_from_summary(summary_txt, location = None,
518
  stakeholder=stakeholder,
519
  stakeholder_role = stakeholder_roles[stakeholder])
520
 
521
- # print(stakeholder_action_prompt)
522
-
523
- # completion = client.completions.create(
524
- # model="gpt-3.5-turbo-instruct",
525
- # max_tokens=1000,
526
- # prompt=stakeholder_action_prompt.format()
527
- # )
528
  completion = client.chat.completions.create(
529
  model = "gpt-3.5-turbo-1106",
 
530
  max_tokens=1000,
531
  messages=[{
532
  "role": "system",
533
  "content": stakeholder_action_prompt.format()}]
534
  )
535
 
536
- # print(stakeholder,"\n", completion.choices[0].text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538
  results[stakeholder] = ({
539
  "stakeholder": stakeholder,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  # "result_sop": completion.choices[0].text,
541
  "actionables": completion.choices[0].message.content,
542
  "ref_content": ref_content,
@@ -545,6 +770,18 @@ def get_actions_from_summary(summary_txt, location = None,
545
  # "ambulance_needed": "1",
546
  # "fire_truck_needed": "1",
547
  })
 
 
 
 
 
 
 
 
 
 
 
 
548
  return results
549
 
550
  ########################## Final Output function ##############################
@@ -565,13 +802,19 @@ def disseminate_actions(smry):
565
  # stakeholder = action.get('stakeholder')
566
  # ## TODO: Dissmeniate based on where the stakeholder is supposed to be
567
 
568
- ########################## gradio code ##############################
 
 
 
 
569
 
570
- def change_accordion(x):
 
 
571
  # print("debug: accordion")
572
  if len(x) >0 and x!= 'Summary':
573
  isOpen = True
574
- actionables, folium_map = disseminate_actions(x)
575
  if folium_map == None:
576
  return (gr.Accordion("Recommendations output (NOTE: unable to determine incident location)", visible=isOpen), gr.Textbox(visible=False) , \
577
  folium.Map(location=[1.2879, 103.8517], zoom_start=12), \
@@ -631,12 +874,10 @@ with gr.Blocks(js=js) as demo:
631
  fn=get_transcript_from_audio,
632
  inputs=input_audio,
633
  outputs=trans_textbox,
634
- examples=[
635
- "audio/call_711.mp3",
636
- "audio/ex1.m4a",
637
- "audio/ex2.m4a",
638
- "audio/ex3.m4a"
639
- ],
640
  cache_examples=True,
641
  allow_flagging = 'never'
642
  )
@@ -650,5 +891,5 @@ with gr.Blocks(js=js) as demo:
650
  # trans_textbox.change(lambda x: gr.Tab("Summarisation from Audio"), inputs=[trans_textbox], outputs=[tab2], scroll_to_output = True)
651
  summ_textbox.change(change_accordion, inputs=[summ_textbox], outputs=[img, outs_textbox, map_gr, recc_textbox1,recc_textbox2, recc_textbox3], scroll_to_output = True)
652
 
653
- if __name__ == "__main__":
654
- demo.launch()
 
1
  # -*- coding: utf-8 -*-
2
+
3
  # Default dependencies to run
4
  import os
5
  # from dotenv import load_dotenv
6
  # load_dotenv()
7
+ import logging
8
+ import logging
9
+ import os
10
+ import re
11
+ import requests
12
+ import math
13
+ import time
14
+ import folium
15
+ import base64
16
+ import pandas as pd
17
+ import numpy as np
18
+ import matplotlib.pyplot as plt
19
+
20
+ from PIL import Image
21
+ from io import BytesIO
22
+ from tqdm import tqdm
23
+ from datetime import datetime
24
+ from geopy.geocoders import Nominatim
25
+
26
+ import openai
27
+ from openai import OpenAI, AsyncOpenAI
28
+
29
+ import langchain_community.embeddings.huggingface
30
+ from langchain_community.embeddings.huggingface import HuggingFaceBgeEmbeddings
31
+ from langchain_community.vectorstores import FAISS
32
+ from langchain.docstore.document import Document
33
+
34
+ #from dotenv import load_dotenv
35
+ #load_dotenv()
36
 
37
+ ## new async
38
+ import asyncio
39
+ import aiohttp
40
 
 
 
41
 
 
 
42
 
43
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
44
+
45
+ if not OPENAI_API_KEY:
46
+ raise Exception("No OpenAI API Key found!")
47
+
48
+ client = OpenAI(api_key=OPENAI_API_KEY)
49
+ a_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
50
 
51
  import logging
52
+ # import docx
53
  import os
54
  import re
55
  import requests
 
83
 
84
  ########################## Initialise API keys ##############################
85
 
86
+ #OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
87
 
88
+ #if not OPENAI_API_KEY:
89
+ # raise Exception("No OpenAI API Key found!")
90
 
91
  DATAMALL_API_KEY = os.environ.get("DATAMALL_API_KEY")
92
 
93
  if not DATAMALL_API_KEY:
94
  raise Exception("No Datamall API Key found!")
95
 
96
+ ########################## init base variables ##############################
97
+
98
+ ## vector stores
99
+
100
+ model_name = "bge-large-en-v1.5"
101
+ model_kwargs = {"device": "cpu"}
102
+ encode_kwargs = {"normalize_embeddings": True}
103
+ bge = HuggingFaceBgeEmbeddings(
104
+ model_name=model_name,
105
+ model_kwargs = model_kwargs,
106
+ encode_kwargs = encode_kwargs)
107
+
108
+ store_dict = {}
109
+
110
+ def get_store(index_name, embeddings = bge, rerun = False):
111
+ if not store_dict.get(index_name, None) or rerun:
112
+ store_dict[index_name] = FAISS.load_local(index_name, embeddings, allow_dangerous_deserialization=True)
113
+ return store_dict[index_name]
114
+
115
+ # synchronous
116
+ for x in ["SCDF", "LTA", "Traffic Police"]:
117
+ get_store(f"index/{x}").as_retriever(search_type="similarity", search_kwargs={"k":3})
118
+
119
  ########################## Audio to Summary functions ##############################
120
  client = OpenAI(api_key=OPENAI_API_KEY)
121
+ a_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
122
 
123
  def get_transcript_from_audio(audio_file_path):
124
  """
 
143
  raise Exception(f"Wrong type of transcript. Expected type str, got type {type(transcript)}")
144
 
145
  prompt = f"""You are provided with the following audio transcript of one or more calls between incident reporters and emergency responders in Singapore.
146
+ Provide a concise and detailed summary (1) based on the transcript. Road names in the transcript may be wrong and should be edited to reflect real roads in Singapore.
147
  Separately provide the following information (2) with labels in [] strictly following the format {{[label]: info}} (3) based on the generated audio transcript (2) [!important: Do not include details not found in the audio transcript]:
148
+ [who], [what], ([where, direction]), closest single [landmark] phrase given by reporter, closest !single [road] phrase given by reporter, [when], [why], and [how] strictly based on the generated transcript. Example: {{[landmark]: landmark_name, \n [road]: road_name}}
149
  \n\n----------------\n\nTranscript:\n{transcript}\n\n(1)\n\n"""
150
 
151
  completion = client.completions.create(
 
188
  landmark = re.search(landmark_pattern, summary).group(1)
189
 
190
  # Split the string by commas
191
+ if " and " in landmark:
192
+ landmark= landmark.replace(" and ", ',')
193
  landmark_list = landmark.split(",")
194
 
195
  # Trim whitespace from each element and append to a list
 
200
  try:
201
  road_pattern = r'\[road\]:\s*(.*?)\n'
202
  road = re.search(road_pattern, summary).group(1)
203
+ print(road)
204
  # Split the string by commas
205
+ if " and " in road:
206
+ road= road.replace(" and ", ',')
207
  road_list = road.split(",")
208
 
209
  # Trim whitespace from each element and append to a list
 
247
  lat, lng = get_latlong(location_list)
248
  print(f"Estimated lat, lng: ({lat}, {lng})\n")
249
 
250
+
251
  return lat, lng
252
 
253
  def call_api(api_url):
 
331
 
332
  # Extract camera location and imagelink via Datamall API
333
  cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
334
+
335
  cameraimg_df['CameraID'] = cameraimg_df['CameraID'].astype('int64')
336
 
337
  # Extract additional camera information from database
 
347
 
348
  # Append encoded image and time retrieved into dataframe
349
  img_list, camera_coords, encoded_img, datetime_list = [], [], [], []
350
+ current_time = datetime.now().strftime('%d/%m/%Y %I:%M:%S:%f %p')
351
 
352
  for idx in closest_cam.index:
353
  response = requests.get(closest_cam["ImageLink"][idx])
354
 
 
 
 
 
 
 
 
355
  img_list.append(closest_cam["ImageLink"][idx])
356
  encoded_img.append(encode_image_to_base64(response))
357
+ print('time after embed image:', datetime.now().strftime('%I:%M:%S:%f %p'))
358
+ datetime_list.append(current_time)
359
 
360
  closest_cam["encoded_img"] = encoded_img
361
  closest_cam["time_retrieved"] = datetime_list
362
+ return closest_cam, cameraimg_df
363
 
364
+ async def a_get_nearest_camera(latlong, num):
365
+ """
366
+ Retrieve the information of "num" nearest Traffic Cameras based on specified lat, lng
367
+ """
368
+
369
+ if not num:
370
+ return
371
+ lat, lng = latlong
372
+ cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
373
+ cameraimg_df['CameraID'] = cameraimg_df['CameraID'].astype('int64')
374
+ camerainfo_df = pd.read_csv("data/traffic_images.csv")
375
+
376
+ merged_df = pd.merge(cameraimg_df, camerainfo_df[["CameraID", "Description", "Section"]], on='CameraID', how='inner')
377
+ cameraimg_df = merged_df
378
+
379
+ # Calculate distances
380
+ cameraimg_df['Distance'] = haversine(lat,lng, cameraimg_df['Latitude'], cameraimg_df['Longitude'])
381
+ closest_cam = cameraimg_df.sort_values(by='Distance').head(num)
382
+
383
+ # Append encoded image and time retrieved into dataframe
384
+ img_list, camera_coords, encoded_img, datetime_list = [], [], [], []
385
+ current_time = datetime.now().strftime('%d/%m/%Y %I:%M:%S:%f %p')
386
+
387
+ async def fetch_image_base64(session, url):
388
+ async with session.get(url) as response:
389
+ res = await response.read()
390
+ return base64.b64encode(res).decode('utf-8')
391
+
392
+ async with aiohttp.ClientSession() as session:
393
+ tasks = []
394
+ for idx in closest_cam.index:
395
+ task = fetch_image_base64(session, closest_cam["ImageLink"][idx])
396
+ tasks.append(task)
397
+
398
+ img_list.append(closest_cam["ImageLink"][idx])
399
+ datetime_list.append(current_time)
400
+ encoded_img = await asyncio.gather(*tasks)
401
+ closest_cam["encoded_img"] = encoded_img
402
+ closest_cam["time_retrieved"] = datetime_list
403
+ return closest_cam, cameraimg_df
404
 
405
  def get_firestation_from_latlong(latlong, num):
406
  """
 
413
  lat,lng = latlong
414
 
415
  civil_df = pd.read_excel("data/fire_hosp.xlsx")
416
+ civil_df = civil_df[civil_df["category"].isin(["Firestation", "Firepost"])]
417
 
418
  # Calculate distances
419
  civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
 
431
  lat,lng = latlong
432
 
433
  civil_df = pd.read_excel("data/fire_hosp.xlsx")
434
+ civil_df = civil_df[civil_df["category"].isin(["Hospital"])]
435
 
436
  # Calculate distances
437
  civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
 
445
  Provide a Folium Map showing the location of the incident and the "num" nearest traffic
446
  cameras, fire stations and ambulance sites.
447
  """
 
448
  lat, lng = get_latlong_from_summary(summary_txt)
449
 
450
  if pd.isna(lat) and pd.isna(lng):
451
  print("Lat, Lng cannot be determined. Please try again")
452
  return None
453
  else:
454
+ # cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
455
+ print('nearest cam')
456
+ nearest_cam_df, cameraimg_df= get_nearest_camera((lat,lng), 3)
457
+ print('ok. nearest fire')
458
+ nearest_fire_df = get_firestation_from_latlong((lat,lng), 3)
459
+ print('ok. nearest hosp')
460
+ nearest_hosp_df = get_hospital_from_latlong((lat,lng), 3)
461
 
462
  avg_lat = np.mean(cameraimg_df["Latitude"])
463
  avg_lng = np.mean(cameraimg_df["Longitude"])
464
+ print('ok. folium map')
465
  map = folium.Map(location=[avg_lat, avg_lng], zoom_start=12)
 
466
  folium.Marker(location=[float(lat), float(lng)],
467
  icon=folium.Icon(color='red'),
468
  popup="Incident"
469
  ).add_to(map)
470
+ print('ok')
471
+
472
+ for idx in tqdm(cameraimg_df.index, desc="Processing Traffic Cameras"):
473
+ if cameraimg_df["CameraID"][idx] in list(nearest_cam_df["CameraID"]):
474
+ print('added nearby camera', nearest_cam_df["Description"][idx])
475
+ html = '<h1>{}</h1><div>{}</div>&nbsp;<img style="width:100%; height:100%;" src="data:image/jpeg;base64,{}">'.format(
476
+ nearest_cam_df["Description"][idx],
477
+ nearest_cam_df["time_retrieved"][idx],
478
+ nearest_cam_df["encoded_img"][idx]
479
+ )
480
+ iframe = folium.IFrame(html, width=632+20, height=500+20)
481
+
482
+ popup = folium.Popup(iframe, max_width=2650)
483
+
484
+ folium.Marker(location=[nearest_cam_df["Latitude"][idx], nearest_cam_df["Longitude"][idx]],
485
+ icon=folium.Icon(color='blue'),
486
+ popup=popup).add_to(map)
487
+ else:
488
+ # Add marker for the camera with the specified color
489
+ folium.Marker(location=[cameraimg_df["Latitude"][idx], cameraimg_df["Longitude"][idx]], icon=folium.Icon(color='gray')).add_to(map)
490
+
491
+ for idx in tqdm(nearest_fire_df.index, desc="Processing Fire Stations"):
492
+ folium.Marker(location=[nearest_fire_df["lat"][idx], nearest_fire_df["long"][idx]],
493
+ icon=folium.Icon(color='orange'),
494
+ popup=nearest_fire_df["name"][idx]).add_to(map)
495
+
496
+ for idx in tqdm(nearest_hosp_df.index, desc="Processing Hospitals"):
497
+ folium.Marker(location=[nearest_hosp_df["lat"][idx], nearest_hosp_df["long"][idx]],
498
+ icon=folium.Icon(color='green'),
499
+ popup=nearest_hosp_df["name"][idx]).add_to(map)
500
 
501
+ return map
502
+
503
+ async def a_get_map_from_summary(summary_txt, get_num_cameras=3):
504
+ """
505
+ Provide a Folium Map showing the location of the incident and the "num" nearest traffic
506
+ cameras, fire stations and ambulance sites.
507
+ """
508
+ lat, lng = get_latlong_from_summary(summary_txt)
509
+
510
+ if pd.isna(lat) and pd.isna(lng):
511
+ print("Lat, Lng cannot be determined. Please try again")
512
+ return None
513
+ else:
514
+ # cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
515
+
516
+ nearest_cam_df, cameraimg_df= await a_get_nearest_camera((lat,lng), get_num_cameras)
517
  nearest_fire_df = get_firestation_from_latlong((lat,lng), 3)
518
  nearest_hosp_df = get_hospital_from_latlong((lat,lng), 3)
519
 
520
+ avg_lat = np.mean(cameraimg_df["Latitude"])
521
+ avg_lng = np.mean(cameraimg_df["Longitude"])
522
+ map = folium.Map(location=[avg_lat, avg_lng], zoom_start=12)
523
+ folium.Marker(location=[float(lat), float(lng)],
524
+ icon=folium.Icon(color='red'),
525
+ popup="Incident"
526
+ ).add_to(map)
527
 
528
+ for idx in tqdm(cameraimg_df.index, desc="Processing Traffic Cameras"):
529
+ if cameraimg_df["CameraID"][idx] in list(nearest_cam_df["CameraID"]):
530
+ print('added nearby camera', nearest_cam_df["Description"][idx])
531
  html = '<h1>{}</h1><div>{}</div>&nbsp;<img style="width:100%; height:100%;" src="data:image/jpeg;base64,{}">'.format(
532
  nearest_cam_df["Description"][idx],
533
  nearest_cam_df["time_retrieved"][idx],
 
559
  ########################## RAG to Recommendations functions ##############################
560
 
561
  action_prompt = """\
562
+ **Traffic Incident Response Assistant**
563
+
564
  You are a dispatching agent for traffic conditions. You will be provided with the Standard Operating Procedures (SOPs) of various departments, with a description of what each department's roles and responsibilities are. From these information, you are a well-versed dispatcher that can recommend the corresponding actions accurately for various scenarios.
565
 
566
+ **Your Task**
567
+ Your task is to analyze the provided information and generate a short and sweet summary for the stakeholder: {stakeholder}. This summary should extract key steps from the relevant SOPs, tailored to the specific incident scenario.
 
 
568
 
569
+ **Information Provided:**
570
+ You will be provided the following information:
571
+ 1. **Roles and Responsibilities**: The stakeholder and its roles and responsibilities.
572
+ 2. **Incident Report Summary**: A concise description of the incident location and nature.
573
+ 3. **Standard Operating Procedures**: A list of relevant sections from various SOPs for {stakeholder}.
574
 
575
  ----------------------------------------------------------------
576
+ **Roles and Responsibilities**
577
  Here is the description of the stakeholder, and a description of its roles and responsibilities.
578
 
579
+ Stakeholder: {stakeholder}
580
 
581
+ **Roles and Responsibilities**:
582
 
583
  {stakeholder_role}
584
 
 
 
585
  ----------------------------------------------------------------
586
  Below is the incident summary, and location of the incident.
587
 
588
+ Location: {location}
589
 
590
+ **Incident Summary**:
591
 
592
  {summary}
593
 
 
 
594
  ----------------------------------------------------------------
595
  Below is some relevant standard operating procedures.
596
+ You will be provided with a list of SOPs, that are possibly relevant to the incident. They will be split with ==============.
597
+ The filename of the document and the contents will be provided below.
 
 
598
 
599
  {ref_content}
600
 
 
 
601
  ----------------------------------------------------------------
602
 
603
  Given the situation above and the relevant SOPs, provide in detail the relevant procedure recommendations for the stakeholder {stakeholder}.
604
 
605
+ **Important**
606
+ * Remember to keep the action plan concise short and sweet. Incorporate only the necessary action plans from the relevant SOPs.
 
607
 
608
+ **Your Response**:
609
 
610
  """
611
 
 
619
 
620
  stakeholder_roles = stakeholder_roles_gpt35
621
 
622
+ def retrieve_stakeholder_sop_from_summary(stakeholder, summary_txt, top_k = 3):
623
+ # print('getting sop for', stakeholder)
624
+ retriever = get_store(f"index/{stakeholder}").as_retriever(search_type="similarity", search_kwargs={"k":top_k})
625
+ selected_sops = retriever.invoke(summary_txt)
626
+
627
+ ref_content = [sop.page_content for sop in selected_sops]
628
+ ref_filename = [str(sop.metadata) for sop in selected_sops]
629
+ # print('retrieved sop for', stakeholder)
630
+ return (ref_content, ref_filename)
631
 
 
 
 
632
 
633
  def retrieve_sop_from_summary(summary_txt,
634
  stakeholders = ["SCDF", "LTA", "Traffic Police"],
 
636
  ):
637
  sops_retrieved = {}
638
  for stakeholder in stakeholders:
639
+ sops_retrieved[stakeholder] = retrieve_stakeholder_sop_from_summary(stakeholder, summary_txt, top_k)
 
 
 
 
 
 
640
 
641
  return sops_retrieved
642
 
643
+
644
  def get_actions_from_summary(summary_txt, location = None,
645
  stakeholders = ["SCDF", "LTA", "Traffic Police"],
646
  top_k = 3):
 
664
  stakeholder=stakeholder,
665
  stakeholder_role = stakeholder_roles[stakeholder])
666
 
 
 
 
 
 
 
 
667
  completion = client.chat.completions.create(
668
  model = "gpt-3.5-turbo-1106",
669
+ temperature=0.,
670
  max_tokens=1000,
671
  messages=[{
672
  "role": "system",
673
  "content": stakeholder_action_prompt.format()}]
674
  )
675
 
676
+ results[stakeholder] = ({
677
+ "stakeholder": stakeholder,
678
+ # "result_sop": completion.choices[0].text,
679
+ "actionables": completion.choices[0].message.content,
680
+ "ref_content": ref_content,
681
+ "ref_filename" : ref_filename,
682
+ # "images": images,
683
+ # "ambulance_needed": "1",
684
+ # "fire_truck_needed": "1",
685
+ })
686
+ return results
687
+
688
+ async def a_retrieve_stakeholder_sop_from_summary(stakeholder, summary_txt, top_k = 3):
689
+ # print('getting sop for', stakeholder)
690
+ retriever = get_store(f"index/{stakeholder}").as_retriever(search_type="similarity", search_kwargs={"k":top_k})
691
+
692
+ ## async
693
+ selected_sops = await retriever.ainvoke(summary_txt)
694
+
695
+ ref_content = [sop.page_content for sop in selected_sops]
696
+ ref_filename = [str(sop.metadata) for sop in selected_sops]
697
+ # print('retrieved sop for', stakeholder)
698
+ return (ref_content, ref_filename)
699
+
700
+ async def a_retrieve_sop_from_summary(summary_txt,
701
+ stakeholders = ["SCDF", "LTA", "Traffic Police"],
702
+ top_k = 3
703
+ ):
704
+ sops_retrieved = {}
705
+ tasks = []
706
+ async def run_tasks():
707
+ for stakeholder in stakeholders:
708
+ tasks.append(a_retrieve_stakeholder_sop_from_summary(stakeholder, summary_txt, top_k))
709
+ results = await asyncio.gather(*tasks)
710
+ return results
711
 
712
+ results = await run_tasks()
713
+ for stakeholder, result in zip(stakeholders, results):
714
+ sops_retrieved[stakeholder] = result
715
+
716
+ return sops_retrieved
717
+
718
+ async def a_get_actions_from_summary(summary_txt, location = None,
719
+ stakeholders = ["SCDF", "LTA", "Traffic Police"],
720
+ top_k = 3):
721
+ """
722
+ Provides a json output of the SOPs for the relevant stakeholders based on the Summary + 5W1H
723
+ processed from the transcript.
724
+ """
725
+
726
+ # sops_retrieved = await a_retrieve_sop_from_summary(summary_txt, top_k = top_k)
727
+
728
+ results = {}
729
+ for stakeholder in stakeholders:
730
+ # ref_content, ref_filename = sops_retrieved[stakeholder]
731
  results[stakeholder] = ({
732
  "stakeholder": stakeholder,
733
+ # "actionables": completion.choices[0].message.content,
734
+ # "ref_content": ref_content,
735
+ # "ref_filename" : ref_filename,
736
+ })
737
+
738
+ # for stakeholder in stakeholders:
739
+ async def a_get_stakeholder_actions_from_summary(stakeholder):
740
+ # ref_content, ref_filename = sops_retrieved[stakeholder]
741
+ ref_content, ref_filename = await a_retrieve_stakeholder_sop_from_summary(stakeholder, summary_txt, top_k)
742
+
743
+ stakeholder_action_prompt = action_prompt.format(
744
+ summary=summary_txt,
745
+ location=location,
746
+ # ref_content=ref_content,
747
+ # ref_filename=ref_filename,
748
+ ref_content = ("\n"+'='*20+"\n").join(f"{i}" for i, j in zip(ref_content,ref_filename)),
749
+ stakeholder=stakeholder,
750
+ stakeholder_role = stakeholder_roles[stakeholder])
751
+
752
+ completion = await a_client.chat.completions.create(
753
+ model = "gpt-3.5-turbo-1106",
754
+ temperature=0.,
755
+ max_tokens=1000,
756
+ messages=[{
757
+ "role": "system",
758
+ "content": stakeholder_action_prompt.format()}]
759
+ )
760
+
761
+ # print(stakeholder,"\n", completion.choices[0].text)
762
+
763
+ return ({
764
+ # "stakeholder": stakeholder,
765
  # "result_sop": completion.choices[0].text,
766
  "actionables": completion.choices[0].message.content,
767
  "ref_content": ref_content,
 
770
  # "ambulance_needed": "1",
771
  # "fire_truck_needed": "1",
772
  })
773
+
774
+ tasks = []
775
+ async def run_tasks():
776
+ for stakeholder in stakeholders:
777
+ tasks.append(a_get_stakeholder_actions_from_summary(stakeholder))
778
+ return await asyncio.gather(*tasks)
779
+
780
+ actions_results = await run_tasks()
781
+ for stakeholder, action_result in zip(stakeholders, actions_results):
782
+ results[stakeholder].update(action_result)
783
+
784
+
785
  return results
786
 
787
  ########################## Final Output function ##############################
 
802
  # stakeholder = action.get('stakeholder')
803
  # ## TODO: Dissmeniate based on where the stakeholder is supposed to be
804
 
805
+ async def a_disseminate_actions(smry):
806
+ location = extract_location_for_prompt(smry)
807
+ tasks = [a_get_actions_from_summary(smry, location), a_get_map_from_summary(smry)]
808
+ actionables, folium_map = await asyncio.gather(*tasks)
809
+ return actionables, folium_map
810
 
811
+ ########################## gradio code ##############################
812
+ import gradio as gr
813
+ async def change_accordion(x):
814
  # print("debug: accordion")
815
  if len(x) >0 and x!= 'Summary':
816
  isOpen = True
817
+ actionables, folium_map = await a_disseminate_actions(x)
818
  if folium_map == None:
819
  return (gr.Accordion("Recommendations output (NOTE: unable to determine incident location)", visible=isOpen), gr.Textbox(visible=False) , \
820
  folium.Map(location=[1.2879, 103.8517], zoom_start=12), \
 
874
  fn=get_transcript_from_audio,
875
  inputs=input_audio,
876
  outputs=trans_textbox,
877
+ examples=[\
878
+ # ],
879
+ # "audio/call_711.mp3", \
880
+ "audio/ex1.m4a", "audio/ex2.m4a", "audio/ex3.m4a", "audio/ex3.m4a"], "audio/ex3.m4a"],
 
 
881
  cache_examples=True,
882
  allow_flagging = 'never'
883
  )
 
891
  # trans_textbox.change(lambda x: gr.Tab("Summarisation from Audio"), inputs=[trans_textbox], outputs=[tab2], scroll_to_output = True)
892
  summ_textbox.change(change_accordion, inputs=[summ_textbox], outputs=[img, outs_textbox, map_gr, recc_textbox1,recc_textbox2, recc_textbox3], scroll_to_output = True)
893
 
894
+ demo.launch(debug = True)
895
+
app_v2.py DELETED
@@ -1,636 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # Default dependencies to run
3
- import os
4
- # from dotenv import load_dotenv
5
- # load_dotenv()
6
-
7
- import logging
8
- import docx
9
- import os
10
- import re
11
- import requests
12
- import math
13
- import time
14
- import base64
15
- import pandas as pd
16
- import numpy as np
17
- import matplotlib.pyplot as plt
18
-
19
- from PIL import Image
20
- from io import BytesIO
21
- from tqdm import tqdm
22
- from datetime import datetime
23
- from geopy.geocoders import Nominatim
24
- import folium
25
-
26
- import openai
27
- from openai import OpenAI
28
-
29
- import langchain_community.embeddings.huggingface
30
- from langchain_community.embeddings.huggingface import HuggingFaceBgeEmbeddings
31
- from langchain_community.vectorstores import FAISS
32
- from langchain.docstore.document import Document
33
-
34
- from gradio_folium import Folium
35
- import gradio as gr
36
-
37
- # from dotenv import load_dotenv
38
- # load_dotenv()
39
-
40
- ########################## Initialise API keys ##############################
41
-
42
- OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
43
-
44
- if not OPENAI_API_KEY:
45
- raise Exception("No OpenAI API Key found!")
46
-
47
- DATAMALL_API_KEY = os.environ.get("DATAMALL_API_KEY")
48
-
49
- if not DATAMALL_API_KEY:
50
- raise Exception("No Datamall API Key found!")
51
-
52
- ########################## init base variables ##############################
53
-
54
- ## vector stores
55
- model_name = "bge-large-en-v1.5"
56
- model_kwargs = {"device": "cpu"}
57
- encode_kwargs = {"normalize_embeddings": True}
58
- bge = HuggingFaceBgeEmbeddings(
59
- model_name=model_name,
60
- model_kwargs = model_kwargs,
61
- encode_kwargs = encode_kwargs)
62
-
63
-
64
- store_dict = {}
65
-
66
- def get_store(index_name, embeddings = bge, rerun = False):
67
- if not store_dict.get(index_name, None) or rerun:
68
- store_dict[index_name] = FAISS.load_local(index_name, embeddings, allow_dangerous_deserialization=True)
69
- return store_dict[index_name]
70
-
71
- for x in ["SCDF", "LTA", "Traffic Police"]:
72
- get_store(f"index/{x}").as_retriever(search_type="similarity", search_kwargs={"k":3})
73
-
74
- ########################## Audio to Summary functions ##############################
75
- client = OpenAI(api_key=OPENAI_API_KEY)
76
-
77
- def get_transcript_from_audio(audio_file_path):
78
- """
79
- Provides transcript from audio file.
80
- """
81
-
82
- with open(audio_file_path, "rb") as f:
83
- transcript = client.audio.translations.create(
84
- model="whisper-1",
85
- file=f
86
- )
87
- return transcript.text
88
-
89
- def get_summary_from_transcript(transcript):
90
- """
91
- Provides summary with 5W1H from transcripted text.
92
- """
93
-
94
- if type(transcript) == openai.types.audio.translation.Translation:
95
- transcript = transcript.text
96
- if type(transcript) is not str:
97
- raise Exception(f"Wrong type of transcript. Expected type str, got type {type(transcript)}")
98
-
99
- prompt = f"""You are provided with the following audio transcript of one or more calls between incident reporters and emergency responders in Singapore.
100
- Provide a concise and detailed summary (1) based on the transcript. Road names in the transcript may be wrong and should be edited to reflect real roads in Singapore.
101
- Separately provide the following information (2) with labels in [] strictly following the format {{[label]: info}} (3) based on the generated audio transcript (2) [!important: Do not include details not found in the audio transcript]:
102
- [who], [what], ([where, direction]), closest single [landmark] phrase given by reporter, closest !single [road] phrase given by reporter, [when], [why], and [how] strictly based on the generated transcript. Example: {{[landmark]: landmark_name, [road]: road_name}}
103
- \n\n----------------\n\nTranscript:\n{transcript}\n\n(1)\n\n"""
104
-
105
- completion = client.completions.create(
106
- model="gpt-3.5-turbo-instruct",
107
- max_tokens=1000,
108
- prompt=prompt,
109
- temperature=0
110
- )
111
- summary = completion.choices[0].text
112
- return summary
113
-
114
- ########################## Summary to Location retrieval (fire station, images, hospitals) functions ##############################
115
-
116
- def extract_location_for_prompt(summary):
117
- """
118
- Provides location for GPT prompt
119
- """
120
-
121
- try:
122
- location_pattern = r'\[where, direction]:\s*(.*?)\n'
123
- location = re.search(location_pattern, summary).group(1)
124
-
125
- # Split the string by commas
126
- location_list = location.split(",")
127
-
128
- # Trim whitespace from each element and append to a list
129
- location_list = [item.strip() for item in location_list]
130
- except:
131
- location_list = extract_location_from_summary(summary)
132
-
133
- return location_list
134
-
135
- def extract_location_from_summary(summary):
136
- """
137
- Provides a list of places identified from Summary + 5W1H
138
- """
139
-
140
- try:
141
- landmark_pattern = r'\[landmark\]:\s*(.*?)\n'
142
- landmark = re.search(landmark_pattern, summary).group(1)
143
-
144
- # Split the string by commas
145
- landmark_list = landmark.split(",")
146
-
147
- # Trim whitespace from each element and append to a list
148
- landmark_list = [item.strip() for item in landmark_list]
149
- except:
150
- landmark_list = []
151
-
152
- try:
153
- road_pattern = r'\[road\]:\s*(.*?)\n'
154
- road = re.search(road_pattern, summary).group(1)
155
-
156
- # Split the string by commas
157
- road_list = road.split(",")
158
-
159
- # Trim whitespace from each element and append to a list
160
- road_list = [item.strip() for item in road_list]
161
- except:
162
- road_list = []
163
-
164
- return landmark_list + road_list
165
-
166
- def get_latlong(location_list):
167
- """
168
- Approximates the location based on a list of places.
169
- """
170
-
171
- geolocator = Nominatim(user_agent="user_agent")
172
-
173
- lat_list, lng_list = [], []
174
-
175
- for location in location_list:
176
- try:
177
- identified_location = geolocator.geocode(f"{location}, Singapore")
178
- print(f"- Identified '{identified_location.address}' from '{location}'")
179
-
180
- lat_list.append(identified_location.latitude)
181
- lng_list.append(identified_location.longitude)
182
- except:
183
- print(f"- Unable to identify '{location}'")
184
-
185
- return np.mean(lat_list), np.mean(lng_list)
186
-
187
- def get_latlong_from_summary(summary):
188
- """
189
- Gets the approximated location of the incident based on Summary + 5W1H
190
- """
191
-
192
- # Get a list of locations from the summary
193
- location_list = extract_location_from_summary(summary)
194
- print(f"\nLocations identified: {location_list}")
195
-
196
- # Get approximated location of the incident
197
- lat, lng = get_latlong(location_list)
198
- print(f"Estimated lat, lng: ({lat}, {lng})\n")
199
-
200
-
201
- return lat, lng
202
-
203
- def call_api(api_url):
204
- """
205
- Makes Datamall API request
206
- """
207
-
208
- # Make sure to add any necessary headers for your API request here
209
- headers = {
210
- 'AccountKey': DATAMALL_API_KEY,
211
- 'accept': 'application/json' # Example header, adjust as necessary
212
- }
213
-
214
- # Call the API
215
- response = requests.get(api_url, headers=headers)
216
-
217
- # Check if the response was successful
218
- if response.status_code == 200:
219
- # Parse the JSON response
220
- data = response.json()
221
-
222
- # Extracting the list of incidents from the 'value' key
223
- df = pd.DataFrame(data['value'])
224
- else:
225
- print("Failed to retrieve data. Status code:", response.status_code)
226
-
227
- return df
228
-
229
- # Function to calculate distance using Haversine formula
230
- def haversine(lat1, lon1, lat2, lon2):
231
- """
232
- Calculates the distance between 2 entities.
233
- """
234
-
235
- # Radius of the Earth in km
236
- R = 6371.0
237
-
238
- # Convert latitude and longitude from degrees to radians
239
- lat1 = np.radians(lat1)
240
- lon1 = np.radians(lon1)
241
- lat2 = np.radians(lat2)
242
- lon2 = np.radians(lon2)
243
-
244
- # Calculate the change in coordinates
245
- dlat = lat2 - lat1
246
- dlon = lon2 - lon1
247
-
248
- # Haversine formula
249
- a = np.sin(dlat / 2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2)**2
250
- c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
251
-
252
- # Distance
253
- distance = R * c
254
-
255
- return distance
256
-
257
- def encode_image_to_base64(response):
258
- """
259
- Encodes HTTP request and decodes it as a UTF-8 encoded string.
260
- """
261
-
262
- encoded_string = base64.b64encode(response.content).decode('utf-8')
263
- return encoded_string
264
-
265
- def decode_base64_to_image(encoded_string):
266
- """
267
- Decodes an encoded string into binary data.
268
- """
269
-
270
- return base64.b64decode(encoded_string)
271
-
272
- def get_nearest_camera(latlong, num, cameraimg_df):
273
- """
274
- Retrieve the information of "num" nearest Traffic Cameras based on specified lat, lng
275
- """
276
-
277
- if not num:
278
- return
279
-
280
- lat, lng = latlong
281
-
282
- # Extract camera location and imagelink via Datamall API
283
- # cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
284
-
285
- cameraimg_df['CameraID'] = cameraimg_df['CameraID'].astype('int64')
286
-
287
- # Extract additional camera information from database
288
- camerainfo_df = pd.read_csv("data/traffic_images.csv")
289
-
290
- # Update cameraimg_df
291
- merged_df = pd.merge(cameraimg_df, camerainfo_df[["CameraID", "Description", "Section"]], on='CameraID', how='inner')
292
- cameraimg_df = merged_df
293
-
294
- # Calculate distances
295
- cameraimg_df['Distance'] = haversine(lat,lng, cameraimg_df['Latitude'], cameraimg_df['Longitude'])
296
- closest_cam = cameraimg_df.sort_values(by='Distance').head(num)
297
-
298
- # Append encoded image and time retrieved into dataframe
299
- img_list, camera_coords, encoded_img, datetime_list = [], [], [], []
300
-
301
- for idx in closest_cam.index:
302
- response = requests.get(closest_cam["ImageLink"][idx])
303
- # print('time after request image:', datetime.now().strftime('%I:%M:%S:%f %p'))
304
-
305
- # # Plot images of cameras
306
- # print(f"{nearest_cams['Description'][idx]} ({nearest_cams['Latitude'][idx]}, {nearest_cams['Longitude'][idx]})")
307
- # img = Image.open(BytesIO(response.content))
308
- # plt.imshow(img)
309
- # plt.show(img)
310
- # print("\n")
311
-
312
- img_list.append(closest_cam["ImageLink"][idx])
313
- encoded_img.append(encode_image_to_base64(response))
314
- # print('time after embed image:', datetime.now().strftime('%I:%M:%S:%f %p'))
315
- datetime_list.append(datetime.now().strftime('%d/%m/%Y %I:%M:%S:%f %p'))
316
-
317
- closest_cam["encoded_img"] = encoded_img
318
- closest_cam["time_retrieved"] = datetime_list
319
- return closest_cam
320
-
321
- def get_firestation_from_latlong(latlong, num):
322
- """
323
- Retrieves the "num" nearest firestation based on specified lat, lng
324
- """
325
-
326
- if not num:
327
- return
328
-
329
- lat,lng = latlong
330
-
331
- civil_df = pd.read_excel("data/fire_hosp.xlsx")
332
- civil_df = civil_df[civil_df["category"].isin(["Firestation"])]
333
-
334
- # Calculate distances
335
- civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
336
- closest_fire = civil_df.sort_values(by='Distance').head(num)
337
-
338
- return closest_fire
339
-
340
- def get_hospital_from_latlong(latlong, num):
341
- """
342
- Retrieves the "num" nearest firestation based on specified lat, lng
343
- """
344
- if not num:
345
- return
346
-
347
- lat,lng = latlong
348
-
349
- civil_df = pd.read_excel("data/fire_hosp.xlsx")
350
- civil_df = civil_df[civil_df["category"].isin(["Hospital", "Firestation", "Firepost"])]
351
-
352
- # Calculate distances
353
- civil_df['Distance'] = haversine(lat,lng, civil_df['lat'], civil_df['long'])
354
- closest_hosp = civil_df.sort_values(by='Distance').head(num)
355
-
356
- return closest_hosp
357
-
358
- ########################## Location to Map generator functions ##############################
359
- def get_map_from_summary(summary_txt):
360
- """
361
- Provide a Folium Map showing the location of the incident and the "num" nearest traffic
362
- cameras, fire stations and ambulance sites.
363
- """
364
- lat, lng = get_latlong_from_summary(summary_txt)
365
-
366
- if pd.isna(lat) and pd.isna(lng):
367
- print("Lat, Lng cannot be determined. Please try again")
368
- return None
369
- else:
370
- cameraimg_df = call_api('http://datamall2.mytransport.sg/ltaodataservice/Traffic-Imagesv2?long=')
371
-
372
- avg_lat = np.mean(cameraimg_df["Latitude"])
373
- avg_lng = np.mean(cameraimg_df["Longitude"])
374
-
375
- map = folium.Map(location=[avg_lat, avg_lng], zoom_start=12)
376
-
377
- folium.Marker(location=[float(lat), float(lng)],
378
- icon=folium.Icon(color='red'),
379
- popup="Incident"
380
- ).add_to(map)
381
-
382
- nearest_cam_df = get_nearest_camera((lat,lng), 3, cameraimg_df)
383
- nearest_fire_df = get_firestation_from_latlong((lat,lng), 3)
384
- nearest_hosp_df = get_hospital_from_latlong((lat,lng), 3)
385
-
386
- for idx in tqdm(cameraimg_df.index, desc="Processing Traffic Cameras"):
387
- if cameraimg_df["CameraID"][idx] in list(nearest_cam_df["CameraID"].astype(str)):
388
-
389
- html = '<h1>{}</h1><div>{}</div>&nbsp;<img style="width:100%; height:100%;" src="data:image/jpeg;base64,{}">'.format(
390
- nearest_cam_df["Description"][idx],
391
- nearest_cam_df["time_retrieved"][idx],
392
- nearest_cam_df["encoded_img"][idx]
393
- )
394
- iframe = folium.IFrame(html, width=632+20, height=500+20)
395
-
396
- popup = folium.Popup(iframe, max_width=2650)
397
-
398
- folium.Marker(location=[nearest_cam_df["Latitude"][idx], nearest_cam_df["Longitude"][idx]],
399
- icon=folium.Icon(color='blue'),
400
- popup=popup).add_to(map)
401
- else:
402
- # Add marker for the camera with the specified color
403
- folium.Marker(location=[cameraimg_df["Latitude"][idx], cameraimg_df["Longitude"][idx]], icon=folium.Icon(color='gray')).add_to(map)
404
-
405
- for idx in tqdm(nearest_fire_df.index, desc="Processing Fire Stations"):
406
- folium.Marker(location=[nearest_fire_df["lat"][idx], nearest_fire_df["long"][idx]],
407
- icon=folium.Icon(color='orange'),
408
- popup=nearest_fire_df["name"][idx]).add_to(map)
409
-
410
- for idx in tqdm(nearest_hosp_df.index, desc="Processing Hospitals"):
411
- folium.Marker(location=[nearest_hosp_df["lat"][idx], nearest_hosp_df["long"][idx]],
412
- icon=folium.Icon(color='green'),
413
- popup=nearest_hosp_df["name"][idx]).add_to(map)
414
-
415
- return map
416
-
417
- ########################## RAG to Recommendations functions ##############################
418
-
419
- action_prompt = """\
420
- **Traffic Incident Response Assistant**
421
-
422
- You are a dispatching agent for traffic conditions. You will be provided with the Standard Operating Procedures (SOPs) of various departments, with a description of what each department's roles and responsibilities are. From these information, you are a well-versed dispatcher that can recommend the corresponding actions accurately for various scenarios.
423
-
424
- **Your Task**
425
- Your task is to analyze the provided information and generate a short and sweet summary for the stakeholder: {stakeholder}. This summary should extract key steps from the relevant SOPs, tailored to the specific incident scenario.
426
-
427
- **Information Provided:**
428
- You will be provided the following information:
429
- 1. **Roles and Responsibilities**: The stakeholder and its roles and responsibilities.
430
- 2. **Incident Report Summary**: A concise description of the incident location and nature.
431
- 3. **Standard Operating Procedures**: A list of relevant sections from various SOPs for {stakeholder}.
432
-
433
- ----------------------------------------------------------------
434
- **Roles and Responsibilities**
435
- Here is the description of the stakeholder, and a description of its roles and responsibilities.
436
-
437
- Stakeholder: {stakeholder}
438
-
439
- **Roles and Responsibilities**:
440
-
441
- {stakeholder_role}
442
-
443
- ----------------------------------------------------------------
444
- Below is the incident summary, and location of the incident.
445
-
446
- Location: {location}
447
-
448
- **Incident Summary**:
449
-
450
- {summary}
451
-
452
- ----------------------------------------------------------------
453
- Below is some relevant standard operating procedures.
454
- You will be provided with a list of SOPs, that are possibly relevant to the incident. They will be split with ==============.
455
- The filename of the document and the contents will be provided below.
456
-
457
- {ref_content}
458
-
459
- ----------------------------------------------------------------
460
-
461
- Given the situation above and the relevant SOPs, provide in detail the relevant procedure recommendations for the stakeholder {stakeholder}.
462
-
463
- **Important**
464
- * Remember to keep the action plan concise short and sweet. Incorporate only the necessary action plans from the relevant SOPs.
465
-
466
- **Your Response**:
467
-
468
- """
469
-
470
- stakeholder_roles_gpt35 = {
471
- "SCDF": "The Singapore Civil Defence Force (SCDF) plays a crucial role in managing traffic incidents, including accidents, vehicle breakdowns, and road blockages. Their responsibilities include providing emergency medical services, extrication of trapped individuals, and ensuring public safety during such incidents. \n\nThe SCDF is mandated to respond to emergencies and protect lives and property. Traffic incidents often involve casualties and pose risks to public safety. SCDF's expertise in emergency medical services and rescue operations enables them to provide timely assistance, including medical care, extrication of trapped individuals, and clearing obstructions to restore traffic flow swiftly. Their swift response helps minimize casualties, alleviate traffic congestion, and ensure smooth coordination with other agencies for effective incident management.",
472
-
473
- "LTA": "The Land Transport Authority (LTA) in Singapore is responsible for managing and regulating various aspects of the transportation system, including responding to traffic incidents. Their roles involve coordinating with other agencies, managing traffic flow, implementing road safety measures, and providing real-time information to the public during incidents. \n\nLTA is tasked with ensuring smooth and safe transportation operations. During traffic incidents, LTA's role becomes crucial in managing traffic flow, implementing diversions, and coordinating with relevant agencies to clear obstructions promptly. They leverage technology and infrastructure such as traffic lights, CCTV cameras, and electronic signages to monitor and manage traffic effectively. Additionally, LTA disseminates real-time updates to the public to facilitate informed decision-making and minimize disruptions caused by incidents.",
474
-
475
- "Traffic Police": "The Traffic Police in Singapore are tasked with managing traffic incidents, including accidents, road obstructions, and heavy traffic. Their responsibilities involve ensuring road safety, managing traffic flow, conducting investigations, and enforcing traffic laws to prevent further incidents and maintain order on the roads. \n\nTraffic Police are essential for maintaining order and safety on Singapore's roads. When incidents occur, they must promptly respond to manage traffic, ensure the safety of motorists and pedestrians, and investigate the causes to prevent recurrence. Their enforcement of traffic laws deters reckless behavior and promotes compliance, contributing to overall road safety. Through effective coordination with other agencies, Traffic Police play a vital role in minimizing disruptions and ensuring smooth traffic flow during incidents."
476
- }
477
-
478
- stakeholder_roles = stakeholder_roles_gpt35
479
-
480
- def retrieve_sop_from_summary(summary_txt,
481
- stakeholders = ["SCDF", "LTA", "Traffic Police"],
482
- top_k = 3
483
- ):
484
- sops_retrieved = {}
485
- for stakeholder in stakeholders:
486
- retriever = get_store(f"index/{stakeholder}").as_retriever(search_type="similarity", search_kwargs={"k":top_k})
487
- selected_sops = retriever.invoke(summary_txt)
488
-
489
- ref_content = [sop.page_content for sop in selected_sops]
490
- ref_filename = [str(sop.metadata) for sop in selected_sops]
491
-
492
- sops_retrieved[stakeholder] = (ref_content, ref_filename)
493
-
494
- return sops_retrieved
495
-
496
- def get_actions_from_summary(summary_txt, location = None,
497
- stakeholders = ["SCDF", "LTA", "Traffic Police"],
498
- top_k = 3):
499
- """
500
- Provides a json output of the SOPs for the relevant stakeholders based on the Summary + 5W1H
501
- processed from the transcript.
502
- """
503
-
504
- sops_retrieved = retrieve_sop_from_summary(summary_txt, top_k = top_k)
505
-
506
- results = {}
507
-
508
- for stakeholder in stakeholders:
509
- ref_content, ref_filename = sops_retrieved[stakeholder]
510
- stakeholder_action_prompt = action_prompt.format(
511
- summary=summary_txt,
512
- location=location,
513
- # ref_content=ref_content,
514
- # ref_filename=ref_filename,
515
- ref_content = ("\n"+'='*20+"\n").join(f"{i}" for i, j in zip(ref_content,ref_filename)),
516
- stakeholder=stakeholder,
517
- stakeholder_role = stakeholder_roles[stakeholder])
518
-
519
- completion = client.chat.completions.create(
520
- model = "gpt-3.5-turbo-1106",
521
- max_tokens=1000,
522
- messages=[{
523
- "role": "system",
524
- "content": stakeholder_action_prompt.format()}]
525
- )
526
-
527
- results[stakeholder] = ({
528
- "stakeholder": stakeholder,
529
- # "result_sop": completion.choices[0].text,
530
- "actionables": completion.choices[0].message.content,
531
- "ref_content": ref_content,
532
- "ref_filename" : ref_filename,
533
- # "images": images,
534
- # "ambulance_needed": "1",
535
- # "fire_truck_needed": "1",
536
- })
537
- return results
538
-
539
- ########################## Final Output function ##############################
540
-
541
- def disseminate_actions(smry):
542
- """
543
- Provides relevant information and recommended actions based on the Summary + 5W1H processed
544
- from the transcript.
545
- """
546
- location = extract_location_for_prompt(smry)
547
-
548
- actionables = get_actions_from_summary(smry, location)
549
- folium_map = get_map_from_summary(smry)
550
-
551
- return actionables, folium_map
552
-
553
- ########################## gradio code ##############################
554
-
555
- def change_accordion(x):
556
- # print("debug: accordion")
557
- if len(x) >0 and x!= 'Summary':
558
- isOpen = True
559
- actionables, folium_map = disseminate_actions(x)
560
- if folium_map == None:
561
- return (gr.Accordion("Recommendations output (NOTE: unable to determine incident location)", visible=isOpen), gr.Textbox(visible=False) , \
562
- folium.Map(location=[1.2879, 103.8517], zoom_start=12), \
563
- actionables['SCDF']["actionables"], actionables['LTA']["actionables"], actionables['Traffic Police']["actionables"])
564
- # return gr.Accordion("Image Location", open=isOpen), map_out
565
- return (gr.Accordion("Recommendations output and Locations on Map", visible=isOpen), gr.Textbox(visible=False) ,folium_map, \
566
- actionables['SCDF']["actionables"], actionables['LTA']["actionables"], actionables['Traffic Police']["actionables"])
567
- else:
568
- isOpen = False
569
- # return gr.Accordion("Image Location", open=isOpen), x[1]
570
- return (gr.Accordion("Recommendations output and Locations on Map", visible=isOpen), gr.Textbox(visible=False), \
571
- folium.Map(location=[1.2879, 103.8517], zoom_start=12), recc_textbox1,recc_textbox2, recc_textbox3)
572
-
573
- js = '''
574
- function refresh() {
575
- const url = new URL(window.location);
576
-
577
- if (url.searchParams.get('__theme') !== 'dark') {
578
- url.searchParams.set('__theme', 'dark');
579
- window.location.href = url.href;
580
- }
581
- }
582
- '''
583
-
584
- trans_textbox = gr.Textbox('Transcript',lines=10,max_lines=19, autoscroll = False)
585
- summ_textbox = gr.Textbox('Summary',lines=10,max_lines=19, autoscroll = False)
586
- # recc_textbox1 = gr.Textbox(label="SCDF", max_lines=8, show_copy_button=True)
587
- # recc_textbox2 = gr.Textbox(label="LTA", max_lines=8, show_copy_button=True)
588
- # recc_textbox3 = gr.Textbox(label="Traffic Police", max_lines=8, show_copy_button=True)
589
- # map_gr = Folium(value=map, height=400)
590
-
591
- with gr.Blocks(js=js) as demo:
592
- # summ_textbox = gr.Textbox('Summary')
593
- gr.Markdown("Emergency Responder Copilot Demonstrator")
594
- isOpen = False
595
- outs_textbox = gr.Textbox("Recommendations outputs will appear here once summary is generated from audio transcript.",lines=1,max_lines=1, autoscroll = False, show_label= False)
596
- with gr.Accordion("Recommendations output and Locations on Map", open=True, visible = isOpen) as img:
597
- with gr.Row():
598
- recc_textbox1 = gr.Textbox(label="SCDF", max_lines=8, show_copy_button=True, autoscroll = False)
599
- recc_textbox2 = gr.Textbox(label="LTA", max_lines=8, show_copy_button=True, autoscroll = False)
600
- recc_textbox3 = gr.Textbox(label="Traffic Police", max_lines=8, show_copy_button=True, autoscroll = False)
601
- # gr.Markdown(loc)
602
- map_gr = Folium(value=folium.Map(location=[1.2879, 103.8517], zoom_start=12), height=400)
603
-
604
- with gr.Tab("Transcript Generator from Audio"):
605
- input_audio = gr.Audio(
606
- sources=["microphone"],
607
- type = 'filepath',
608
- waveform_options=gr.WaveformOptions(
609
- waveform_color="#01C6FF",
610
- waveform_progress_color="#0066B4",
611
- skip_length=2,
612
- show_controls=False,
613
- ),
614
- )
615
- audio2trans = gr.Interface(
616
- fn=get_transcript_from_audio,
617
- inputs=input_audio,
618
- outputs=trans_textbox,
619
- examples=[\
620
- # "audio/call_711.mp3", \
621
- "audio/ex1.m4a", "audio/ex2.m4a", "audio/ex3.m4a"],
622
- cache_examples=True,
623
- allow_flagging = 'never'
624
- )
625
- with gr.Tab("Summarisation from Audio") as tab2:
626
- trans2summary = gr.Interface(
627
- fn=get_summary_from_transcript,
628
- inputs=trans_textbox,
629
- outputs=summ_textbox,
630
- allow_flagging = 'never'
631
- )
632
- # trans_textbox.change(lambda x: gr.Tab("Summarisation from Audio"), inputs=[trans_textbox], outputs=[tab2], scroll_to_output = True)
633
- summ_textbox.change(change_accordion, inputs=[summ_textbox], outputs=[img, outs_textbox, map_gr, recc_textbox1,recc_textbox2, recc_textbox3], scroll_to_output = True)
634
-
635
- if __name__ == "__main__":
636
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,3 +1,5 @@
 
 
1
  gradio==4.17
2
  gradio-folium
3
  python-docx==1.1.0
 
1
+ asyncio
2
+ aiohttp
3
  gradio==4.17
4
  gradio-folium
5
  python-docx==1.1.0