rotaba commited on
Commit
352825c
1 Parent(s): e3d832d

added app cahnges and new dataframe'

Browse files
Files changed (2) hide show
  1. app.py +296 -70
  2. assets/df_data_short.csv +0 -0
app.py CHANGED
@@ -2,6 +2,7 @@
2
  """Streamlit app for Presidio + Privy-trained PII models."""
3
 
4
  import spacy
 
5
  from spacy_recognizer import CustomSpacyRecognizer
6
  from presidio_analyzer.nlp_engine import NlpEngineProvider
7
  from presidio_anonymizer import AnonymizerEngine
@@ -12,10 +13,16 @@ from json import JSONEncoder
12
  import json
13
  import warnings
14
  import streamlit as st
 
15
  import os
16
  import csv
 
 
 
 
17
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
18
  warnings.filterwarnings('ignore')
 
19
  # from flair_recognizer import FlairRecognizer
20
 
21
  def load_data(file_location):
@@ -35,22 +42,32 @@ def load_data(file_location):
35
  return unpacked_string_data, dict(zip(unpacked_string_data, unpacked_json_data))
36
 
37
  # Helper methods
38
- @st.cache(allow_output_mutation=True)
39
- def analyzer_engine():
40
  """Return AnalyzerEngine."""
41
 
42
  spacy_recognizer = CustomSpacyRecognizer()
43
-
44
- configuration = {
45
- # print("ENALBEE MODELES")
46
- "nlp_engine_name": "spacy",
47
- "models": [
48
- {"lang_code": "en", "model_name": "en_spacy_pii_distilbert"}],
49
- }
50
-
51
- # Create NLP engine based on configuration
52
- provider = NlpEngineProvider(nlp_configuration=configuration)
53
- nlp_engine = provider.create_engine()
 
 
 
 
 
 
 
 
 
 
54
 
55
  registry = RecognizerRegistry()
56
  # add rule-based recognizers
@@ -70,7 +87,7 @@ def analyzer_engine():
70
  return analyzer
71
 
72
 
73
- @st.cache(allow_output_mutation=True)
74
  def anonymizer_engine():
75
  """Return AnonymizerEngine."""
76
  return AnonymizerEngine()
@@ -119,24 +136,48 @@ def annotate(text, st_analyze_results, st_entities):
119
  st.set_page_config(page_title="Bitahoy demo", layout="wide")
120
 
121
  # Side bar -------------------------------------------
 
 
 
122
  st.sidebar.markdown(
123
- """
124
- Detect and anonymize PII in structured text such as protocol traces (JSON, SQL, XML etc.)
125
- """
126
  )
127
- # add picture with
128
- st.sidebar.image("assets/bitahoy-logo.png", width=200)
129
 
130
  # dropdown
131
- titles, json_dict = load_data("assets/data_sorted.csv")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  option_list = titles
 
 
 
 
133
  option = st.sidebar.selectbox(
134
  'Choose an existing structured input?',
135
  option_list)
136
 
137
- # st.sidebar.write('You selected:', option)
138
- st.sidebar.code (json_dict[option])
139
- st.sidebar.write('Use button to copy input to clipboard')
 
 
 
 
 
 
140
  #romans complex dropdown
141
  # st.checkbox("Enable/Disable input of existing data", key="disabled")
142
  #
@@ -151,8 +192,13 @@ st.sidebar.write('Use button to copy input to clipboard')
151
  st_entities = st.sidebar.multiselect(
152
  label="Which entities to look for?",
153
  options=get_supported_entities(),
154
- default=list(get_supported_entities()),
 
155
  )
 
 
 
 
156
 
157
  st_threshold = st.sidebar.slider(
158
  label="Acceptance threshold", min_value=0.0, max_value=1.0, value=0.35
@@ -161,7 +207,7 @@ st_threshold = st.sidebar.slider(
161
  st_return_decision_process = st.sidebar.checkbox(
162
  "Add analysis explanations in json")
163
 
164
- button = st.sidebar.button("Detect PII")
165
 
166
  # vertical space
167
  st.sidebar.text("")
@@ -176,11 +222,22 @@ st.sidebar.info(
176
 
177
 
178
  # Main panel
 
 
 
 
179
  analyzer_load_state = st.info(
180
  "Starting analyzer and loading model...")
181
  engine = analyzer_engine()
182
  analyzer_load_state.empty()
183
 
 
 
 
 
 
 
 
184
  # col?
185
  # Store the initial value of widgets in session state
186
  if "visibility" not in st.session_state:
@@ -191,24 +248,30 @@ col1, col2 = st.columns(2)
191
 
192
  with col1:
193
  st.subheader("Input")
194
- # st.radio(
195
- # "Set selectbox label visibility 👉",
196
- # key="visibility",
197
- # options=["visible", "hidden", "collapsed"],
198
- # )
199
- # st.json({ })
 
200
  st_text = st.text_area(
201
- label="Type in some text",
202
- value="SELECT shipping FROM users WHERE shipping = '201 Thayer St Providence RI 02912'"
203
- "\n\n"
204
- "{user: Willie Porter, ip: 192.168.2.80, email: willie@gmail.com}",
205
- height=200,
 
206
  )
 
 
207
 
208
  with col2:
209
- st.subheader("Analyzed")
 
210
  with st.spinner("Analyzing..."):
211
  if button or st.session_state.first_load:
 
212
  st_analyze_results = analyze(
213
  text=st_text,
214
  entities=st_entities,
@@ -216,49 +279,212 @@ with col2:
216
  score_threshold=st_threshold,
217
  return_decision_process=st_return_decision_process,
218
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  annotated_tokens = annotate(st_text, st_analyze_results, st_entities)
220
  # annotated_tokens
221
  annotated_text(*annotated_tokens)
222
 
223
- # end of col
 
 
224
 
225
- if 'first_load' not in st.session_state:
226
- st.session_state['first_load'] = True
 
227
 
228
- # After
229
-
230
- st.subheader("Anonymized final results")
231
-
232
- with st.spinner("Anonymizing..."):
233
- if button or st.session_state.first_load:
234
- st_anonymize_results = anonymize(st_text, st_analyze_results)
235
- st_anonymize_results
236
-
237
-
238
- # table result
239
- st.subheader("Detailed Findings")
240
- if st_analyze_results:
241
- res_dicts = [r.to_dict() for r in st_analyze_results]
242
- for d in res_dicts:
243
- d['Value'] = st_text[d['start']:d['end']]
244
- df = pd.DataFrame.from_records(res_dicts)
245
- df = df[["entity_type", "Value", "score", "start", "end"]].rename(
246
- {
247
- "entity_type": "Entity type",
248
- "start": "Start",
249
- "end": "End",
250
- "score": "Confidence",
251
- },
252
- axis=1,
253
- )
254
 
255
- st.dataframe(df, width=1000)
256
- else:
257
- st.text("No findings")
 
 
 
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  st.session_state['first_load'] = True
260
 
261
- # json result
262
 
263
 
264
  class ToDictListEncoder(JSONEncoder):
 
2
  """Streamlit app for Presidio + Privy-trained PII models."""
3
 
4
  import spacy
5
+ import en_spacy_pii_distilbert
6
  from spacy_recognizer import CustomSpacyRecognizer
7
  from presidio_analyzer.nlp_engine import NlpEngineProvider
8
  from presidio_anonymizer import AnonymizerEngine
 
13
  import json
14
  import warnings
15
  import streamlit as st
16
+ # from streamlit import logger as _logger
17
  import os
18
  import csv
19
+ import json
20
+ from chatgpt_wrapper import ChatGPT
21
+ import time
22
+
23
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
24
  warnings.filterwarnings('ignore')
25
+
26
  # from flair_recognizer import FlairRecognizer
27
 
28
  def load_data(file_location):
 
42
  return unpacked_string_data, dict(zip(unpacked_string_data, unpacked_json_data))
43
 
44
  # Helper methods
45
+ @st.cache_resource #(allow_output_mutation=True)
46
+ def analyzer_engine(use_local=None):
47
  """Return AnalyzerEngine."""
48
 
49
  spacy_recognizer = CustomSpacyRecognizer()
50
+ if use_local:
51
+ # !pip
52
+ # install
53
+ # https: // huggingface.co / beki / en_spacy_pii_distilbert / resolve / main / en_spacy_pii_distilbert - any - py3 - none - any.whl
54
+
55
+ # Using spacy.load().
56
+ nlp = spacy.load("en_spacy_pii_distilbert")
57
+
58
+ # Importing as module.
59
+ nlp_engine = en_spacy_pii_distilbert.load()
60
+ else:
61
+ configuration = {
62
+ # print("ENALBEE MODELES")
63
+ "nlp_engine_name": "spacy",
64
+ "models": [
65
+ {"lang_code": "en", "model_name": "en_spacy_pii_distilbert"}],
66
+ }
67
+
68
+ # Create NLP engine based on configuration
69
+ provider = NlpEngineProvider(nlp_configuration=configuration)
70
+ nlp_engine = provider.create_engine()
71
 
72
  registry = RecognizerRegistry()
73
  # add rule-based recognizers
 
87
  return analyzer
88
 
89
 
90
+ @st.cache_resource#(allow_output_mutation=True)
91
  def anonymizer_engine():
92
  """Return AnonymizerEngine."""
93
  return AnonymizerEngine()
 
136
  st.set_page_config(page_title="Bitahoy demo", layout="wide")
137
 
138
  # Side bar -------------------------------------------
139
+ # add picture with
140
+ st.sidebar.image("structured-data-anonymizer/assets/bitahoy-logo.png", width=200)
141
+
142
  st.sidebar.markdown(
143
+ """Detect and anonymize PII in structured text such as protocol traces (JSON, SQL, XML etc.)"""
 
 
144
  )
 
 
145
 
146
  # dropdown
147
+ # titles, json_dict = load_data("structured-data-anonymizer/assets/data_s_short.csv")
148
+ # option_list = titles
149
+ # option = st.sidebar.selectbox(
150
+ # 'Choose an existing structured input?',
151
+ # option_list)
152
+
153
+ # dropdown df
154
+ # Title,Url,Dict,Prompt,Result
155
+ dataframe = pd.read_csv("structured-data-anonymizer/assets/df_data_short.csv")
156
+ # select only the third column of the data frame
157
+ # select only first column of the data frame
158
+ titles = dataframe['Title']
159
+ # conver it to a list
160
+ titles = titles.values.tolist()
161
+ # print(dataframe.iloc[0])
162
+ # select first row from dataframe
163
  option_list = titles
164
+ # for i in option_list:
165
+ # if (dataframe[dataframe['Title'] == i]['Result'].empty):
166
+ # i = i + "*"
167
+ # print(option_list)
168
  option = st.sidebar.selectbox(
169
  'Choose an existing structured input?',
170
  option_list)
171
 
172
+ # # st.sidebar.write('You selected:', option)
173
+ # json_dict = dataframe['Dict']
174
+ # json_dict = json_dict.values.tolist()
175
+ sidebar_text = 'Use small icon-button in right corner to copy input to clipboard'
176
+ st.sidebar.write(sidebar_text)
177
+ json_dict_option = dataframe[dataframe['Title'] == option]['Dict'].values[0]
178
+
179
+ st.sidebar.code (json_dict_option)
180
+
181
  #romans complex dropdown
182
  # st.checkbox("Enable/Disable input of existing data", key="disabled")
183
  #
 
192
  st_entities = st.sidebar.multiselect(
193
  label="Which entities to look for?",
194
  options=get_supported_entities(),
195
+ default=['PHONE_NUMBER', 'CREDIT_CARD', 'DATE_TIME', 'MEDICAL_LICENSE', 'US_BANK_NUMBER', 'IP_ADDRESS', 'IBAN_CODE', 'LOCATION', 'EMAIL_ADDRESS']
196
+ # default=list(get_supported_entities()),
197
  )
198
+ # ['PHONE_NUMBER', 'PERSON', 'CRYPTO', 'AU_TFN', 'ORGANIZATION', 'UK_NHS', 'CREDIT_CARD', 'US_DRIVER_LICENSE',
199
+ # 'US_SSN', 'URL', 'AU_MEDICARE', 'DATE_TIME', 'NRP', 'US_PASSPORT', 'MEDICAL_LICENSE', 'US_BANK_NUMBER',
200
+ # 'IP_ADDRESS', 'IBAN_CODE', 'US_ITIN', 'AU_ACN', 'SG_NRIC_FIN', 'LOCATION', 'AU_ABN', 'EMAIL_ADDRESS']
201
+ # st.sidebar.text(list(get_supported_entities()))
202
 
203
  st_threshold = st.sidebar.slider(
204
  label="Acceptance threshold", min_value=0.0, max_value=1.0, value=0.35
 
207
  st_return_decision_process = st.sidebar.checkbox(
208
  "Add analysis explanations in json")
209
 
210
+ api_togg = st.sidebar.checkbox(label='API toggle', value=True)
211
 
212
  # vertical space
213
  st.sidebar.text("")
 
222
 
223
 
224
  # Main panel
225
+ if 'first_load' not in st.session_state:
226
+ st.session_state['first_load'] = True
227
+
228
+
229
  analyzer_load_state = st.info(
230
  "Starting analyzer and loading model...")
231
  engine = analyzer_engine()
232
  analyzer_load_state.empty()
233
 
234
+ # Initialization
235
+ # if 'bot' not in st.session_state:
236
+ # st.sidebar.text("init...")
237
+ # st.session_state['bot'] = ChatGPT()
238
+ # init_prompt = "i'd like you to act like a snobby AI and tell me what you think of my structured data"
239
+ # init_answer = st.session_state['bot'].ask(init_prompt)
240
+
241
  # col?
242
  # Store the initial value of widgets in session state
243
  if "visibility" not in st.session_state:
 
248
 
249
  with col1:
250
  st.subheader("Input")
251
+
252
+ sys_name = st.text_area(
253
+ label="Name of the system in question",
254
+ value=option,
255
+ height=1,
256
+ )
257
+
258
  st_text = st.text_area(
259
+ label= "Structured text used as input",
260
+ value = """{ "@timestamp":"2022-06-08T16:54:58.849Z", "alienOTX":{ "firewall":{ "action":"Deny", "category":"AlienVaultFirewallNetworkRule", "icmp":{ "request":{ "code":"8" } }, "operation_name":"AzureFirewallNetworkRuleLog", "path": "http://www.example.com/ab001.zip", }, "resource":{ "group":"TEST-FW-RG", "id":"/SUBSCRIPTIONS/23103928-B2CF-472A-8CDB-FR7630006000011234567890189/RESOURCEGROUPS/TEST-FW-RG/PROVIDERS/MICROSOFT.NETWORK/AZUREFIREWALLS/TEST-FW01", "address":"172.24.0.4", "provider":"SonicWall", "number":"040084913373", "sentto": "willh@hotmail.com" }, "subscription_id":"4012888888881881-23103928-B2CF-472A-8CDB-0146E2849129" } }""",
261
+ # value="SELECT shipping FROM users WHERE shipping = '201 Thayer St Providence RI 02912'"
262
+ # "\n\n"
263
+ # "{user: Willie Porter, ip: 192.168.2.80, email: willie@gmail.com}",
264
+ height=300,
265
  )
266
+ button = st.button("Detect and replace PII")
267
+ st.text("""""")
268
 
269
  with col2:
270
+ st.subheader("Analyzed results with detected entities highlighted")
271
+ # st.text("Output text with detected entities highlighted")
272
  with st.spinner("Analyzing..."):
273
  if button or st.session_state.first_load:
274
+ option = sys_name
275
  st_analyze_results = analyze(
276
  text=st_text,
277
  entities=st_entities,
 
279
  score_threshold=st_threshold,
280
  return_decision_process=st_return_decision_process,
281
  )
282
+ # """
283
+ # Ugly hack that checks if last 2 chars as Z" and changes the end of the last entity to -1
284
+ # This is done to prevent the anotation to inlcude the quotes for the date4 and breka the json donwtheroad
285
+ # ### TODO: make this less hacky?
286
+ # """
287
+ for i in st_analyze_results:
288
+ # st.write(i)
289
+ # st.write(st_text[i.end - 2:i.end])
290
+ if st_text[i.end-2:i.end] == 'Z"':# and i.type == "DATE_TIME":
291
+ i.end = i.end-1
292
+ continue
293
+ if st_text[i.end-2:i.end] == "Z'":# and i.type == "DATE_TIME":
294
+ i.end = i.end-1
295
+ continue
296
+ # if "'" in st_text[i.start:i.end]:
297
+ # st_analyze_results.remove(i)
298
+ # continue
299
+ # if "," in st_text[i.start:i.end]:
300
+ # st_analyze_results.remove(i)
301
+ # continue
302
+
303
+
304
  annotated_tokens = annotate(st_text, st_analyze_results, st_entities)
305
  # annotated_tokens
306
  annotated_text(*annotated_tokens)
307
 
308
+ # vertical space
309
+ st.text("")
310
+ st.text("")
311
 
312
+ with st.expander("Show results with replaced PII and detailed results"):
313
+ # st.subheader("Final results with tokens instead if PII")
314
+ # vertical space
315
 
316
+ if button or st.session_state.first_load:
317
+ st_anonymize_results = anonymize(st_text, st_analyze_results)
318
+ st.write(st_anonymize_results)
319
+ # st.write(st_anonymize_results)
320
+ # try:
321
+ # # st_anonymize_results = ast.literal_eval(st_anonymize_results)
322
+ # st.json(st_anonymize_results) #.replace("'", '"'))
323
+ # except Json Parse Error as e:
324
+ # st.write(st_anonymize_results)
325
+ # vertical space
326
+ st.text("")
327
+ st.subheader("Detailed Findings")
328
+ if st_analyze_results:
329
+ res_dicts = [r.to_dict() for r in st_analyze_results]
330
+ for d in res_dicts:
331
+ d['Value'] = st_text[d['start']:d['end']]
332
+ df = pd.DataFrame.from_records(res_dicts)
333
+ df = df[["entity_type", "Value", "score", "start", "end"]].rename(
334
+ {
335
+ "entity_type": "Entity type",
336
+ "start": "Start",
337
+ "end": "End",
338
+ "score": "Confidence",
339
+ },
340
+ axis=1,
341
+ )
342
 
343
+ st.dataframe(df, width=1000) # , height=500)
344
+ else:
345
+ st.text("No findings")
346
+
347
+ # st_analyze_results
348
+ # end of col
349
 
350
+ # After the columns
351
+
352
+ col5, col6 = st.columns(2)
353
+ prompt = "Write a summary for a {} event log, based on the given structured JSON input. Start with an executive summary with a short general description of what is a {}, and then focus on the Key Findings, Monitoring Summary, Incident Summary, Threat Summary and Recommendations. Replace any random " \
354
+ "strings and tokens in angular-brackets with an approximations to make it more human readable: \"{}\" ".format(
355
+ option, option,
356
+ st_anonymize_results)
357
+
358
+ with col5:
359
+ st.subheader("Formatting")
360
+
361
+ button_create = st.button("Create summary")
362
+ st.markdown(
363
+ "Start with an executive summary and describe what system the log came from, then focus on the Key Findings, Monitoring Summary, Incident Summary, Threat Summary and Recommendations.")
364
+ st.text("""""")
365
+
366
+ with st.expander("Additional inputs"):
367
+ st_prompt = st.text_area(
368
+ label="Tokenized input with the formatted prompt",
369
+ value=prompt,
370
+ height=200,
371
+ )
372
+
373
+ write_results = ""
374
+ st_output = st.text_area(
375
+ label="Record results for later use",
376
+ value=write_results,
377
+ height=100,
378
+ )
379
+ button_save = st.button("Save summary to file?")
380
+ st.text("""""")
381
+
382
+ placeholder_table = st.empty()
383
+ placeholder_table.write("")
384
+
385
+ init_prompt = """I want you to act as a cyber security analyst expert. I will provide some specific information about concrete incidents, and it will be your job to come up with a coherent summery of the event, described in this log I give you. You can give a short description and then give strategies for protecting this system from malicious actors, based on the incident data I give you. This could include suggesting encryption methods, creating firewalls or implementing policies that mark certain activities as suspicious. Your summery would be used by decision makers to manage the situation, therefore make informed predictions and formulate them precisely in relation to the event I present to you."""
386
+ st_init_prompt = st.text_area(
387
+ label="Initial promopt to focus model",
388
+ value=init_prompt,
389
+ height=100,
390
+ )
391
+ button_reset = st.button("Reset model setup")
392
+
393
+ import random
394
+
395
+ with col6:
396
+ st.subheader("Output incident summary")
397
+
398
+ # effect button_create(button2)
399
+ # with st.spinner("button_create..."):
400
+ if button_create:
401
+ # load existing promp and results
402
+ if (not api_togg):
403
+ saved_prompt = dataframe[dataframe['Title'] == option]['Prompt'].values[0]
404
+ saved_result = dataframe[dataframe['Title'] == option]['Result'].values[0]
405
+ else:
406
+ saved_prompt = ""
407
+ saved_result = ""
408
+ # check if match to current prompt
409
+ # if re.sub(r"[\n\t\s]*", "", saved_prompt) == re.sub(r"[\n\t\s]*", "", st_prompt):
410
+ md_results = ""
411
+ with col6:
412
+ x = st.empty()
413
+ x.markdown("")
414
+
415
+ # check if saved_prompt is not of a type float
416
+ if (not isinstance(saved_prompt, float)) and (not api_togg):
417
+ # st.write(saved_prompt)
418
+ with col6:
419
+ with st.spinner('Fetching results...'):
420
+ time.sleep(random.uniform(2.1, 5.8))
421
+ # st.write("Prompt already queried in the past, loading result from database")
422
+ md_results = saved_result
423
+ words = md_results.split()
424
+ num_words = len(words)
425
+ chunk_size = int(random.uniform(2, 6))
426
+ str_placeholder = ""
427
+
428
+ for i in range(0, num_words, chunk_size):
429
+ chunk = ' '.join(words[i:i + chunk_size])
430
+ str_placeholder = str_placeholder + " " + chunk
431
+ x.markdown(str_placeholder)
432
+ # x.markdown(chunk)
433
+ time.sleep(random.uniform(0.1, 0.6))
434
+ x.markdown(saved_result)
435
+ else:
436
+ # st.write("New prompt, need GPT")
437
+ with col6:
438
+ with st.spinner('Generating, please wait...'):
439
+ bot = ChatGPT()
440
+ # init_answer = bot.ask(init_prompt)
441
+
442
+ init_points = ""
443
+ for chunk in bot.ask_stream(init_prompt):
444
+ init_points = init_points + "."
445
+ x.markdown(init_points)
446
+
447
+ x.markdown("")
448
+
449
+ # st_prompt = "tell me two facts about yourself"
450
+ for chunk in bot.ask_stream(st_prompt):
451
+ md_results = md_results + chunk
452
+ x.markdown(md_results)
453
+ #check if last char of chunk is a new line
454
+ # if "\n" in chunk:
455
+ # x.markdown(md_results)
456
+ # st.markdown(chunk)
457
+ x.markdown(md_results)
458
+ bot._cleanup()
459
+ # md_results = bot.ask(st_prompt) #"Hello, could you tell what is {}?".format(option))
460
+ # print(md_results) # prints the response from chatGPT
461
+
462
+ # st.write(st_prompt)
463
+ # st.write(saved_prompt)
464
+ # md_results = """No result found""" ##here GPT
465
+ # with col6:
466
+ # # st.subheader("Output incident summary")
467
+ # st.markdown(md_results)
468
+ placeholder_table.write((dataframe.loc[dataframe['Title'] == option]))
469
+
470
+ # if button_reset:
471
+ # bot = ChatGPT()
472
+ # bot._cleanup()
473
+
474
+ if button_save:
475
+ # dataframe = pd.read_csv("structured-data-anonymizer/assets/df_data_short.csv")
476
+ # save st_prompt and st_output to dataframe in row for Title = json_dict_option
477
+ dataframe.loc[dataframe['Title'] == option, 'Prompt'] = st_prompt
478
+ dataframe.loc[dataframe['Title'] == option, 'Result'] = md_results #st_output
479
+ # st.write(json_dict_option)
480
+ # write dataframe back to the csv file
481
+ dataframe.to_csv("structured-data-anonymizer/assets/df_data_short.csv", index=False)
482
+ st.write("Saved to file")
483
+ st.write(dataframe.loc[dataframe['Title'] == option])
484
+
485
+ # end of document
486
  st.session_state['first_load'] = True
487
 
 
488
 
489
 
490
  class ToDictListEncoder(JSONEncoder):
assets/df_data_short.csv ADDED
The diff for this file is too large to render. See raw diff