bluenevus commited on
Commit
2bef542
·
1 Parent(s): d212a64

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +306 -152
app.py CHANGED
@@ -29,6 +29,11 @@ current_document = None
29
  document_type = None
30
  shredded_document = None
31
  pink_review_document = None
 
 
 
 
 
32
  uploaded_doc_contents = {}
33
 
34
  spreadsheet_types = ["Shred", "Pink Review", "Red Review", "Gold Review", "Virtual Board", "LOE"]
@@ -46,6 +51,18 @@ document_types = {
46
  "LOE": "Ignore all other instructions and generate and generate a Level of Effort (LOE) breakdown and produce only spreadsheet"
47
  }
48
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  def get_right_col_content(selected_type):
50
  controls = []
51
  controls.append(html.Div([
@@ -56,6 +73,7 @@ def get_right_col_content(selected_type):
56
  type="dot",
57
  children=[html.Div(id="loading-output")]
58
  ))
 
59
  if selected_type == "Shred":
60
  controls.append(
61
  html.Div([
@@ -80,6 +98,61 @@ def get_right_col_content(selected_type):
80
  file_list_component()
81
  ])
82
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  controls.append(
84
  dbc.Row([
85
  dbc.Col(
@@ -95,36 +168,6 @@ def get_right_col_content(selected_type):
95
  )
96
  controls.append(html.Hr())
97
  controls.append(html.Div(id='document-preview', className="border p-3 mb-3"))
98
- if selected_type != "Shred":
99
- controls.append(html.Div([
100
- html.Label(f"Upload {selected_type} Document"),
101
- dcc.Upload(
102
- id={'type': 'upload-doc-type', 'index': selected_type},
103
- children=html.Div(['Drag and Drop or ', html.A('Select File')]),
104
- style={
105
- 'width': '100%',
106
- 'height': '60px',
107
- 'lineHeight': '60px',
108
- 'borderWidth': '1px',
109
- 'borderStyle': 'dashed',
110
- 'borderRadius': '5px',
111
- 'textAlign': 'center',
112
- 'margin': '10px 0'
113
- },
114
- multiple=False
115
- ),
116
- html.Div(id={'type': 'uploaded-doc-name', 'index': selected_type}),
117
- dbc.RadioItems(
118
- id={'type': 'radio-doc-source', 'index': selected_type},
119
- options=[
120
- {'label': 'Loaded Document', 'value': 'loaded'},
121
- {'label': 'Uploaded Document', 'value': 'uploaded'}
122
- ],
123
- value='loaded',
124
- inline=True,
125
- className="mb-2"
126
- ),
127
- ], id={'type': 'doc-type-controls', 'index': selected_type}))
128
  return dbc.Card(dbc.CardBody(controls))
129
 
130
  def get_left_col_content():
@@ -361,127 +404,25 @@ def remove_file(n_clicks, existing_files):
361
  filtered_files.append(file)
362
  return filtered_files
363
 
364
- @app.callback(
365
- Output('document-preview', 'children'),
366
- Output('loading-output', 'children'),
367
- Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
368
- State({'type': 'btn-generate-doc', 'index': ALL}, 'id'),
369
- State({'type': 'radio-doc-source', 'index': ALL}, 'value'),
370
- State({'type': 'upload-doc-type', 'index': ALL}, 'contents'),
371
- State({'type': 'upload-doc-type', 'index': ALL}, 'filename'),
372
- prevent_initial_call=True
373
- )
374
- def generate_any_doc(n_clicks_list, btn_ids, radio_values, upload_contents, upload_filenames):
375
- global current_document, document_type, shredded_document, pink_review_document
376
- ctx = callback_context
377
- logging.info(f"generate_any_doc triggered: n_clicks_list={n_clicks_list}, btn_ids={btn_ids}")
378
- if not ctx.triggered:
379
- raise dash.exceptions.PreventUpdate
380
- idx = [i for i, x in enumerate(n_clicks_list) if x]
381
- if not idx:
382
- raise dash.exceptions.PreventUpdate
383
- idx = idx[-1]
384
- doc_type = btn_ids[idx]['index']
385
- document_type = doc_type
386
-
387
- if doc_type == "Shred":
388
- if not uploaded_files:
389
- logging.info("No uploaded files for Shred. Aborting.")
390
- return html.Div("Please upload a document before shredding."), ""
391
- file_contents = list(uploaded_files.values())
392
- logging.info(f"Calling OpenAI for Shred with {len(file_contents)} files: {list(uploaded_files.keys())}")
393
- try:
394
- generated = generate_document(doc_type, file_contents)
395
- current_document = generated
396
- shredded_document = generated
397
- preview = markdown_table_preview(generated)
398
- logging.info("Shred document generated.")
399
- return preview, "Shred generated"
400
- except Exception as e:
401
- logging.error(f"Error generating document: {str(e)}")
402
- return html.Div(f"Error generating document: {str(e)}"), "Error"
403
-
404
- if shredded_document is None:
405
- logging.info("Shredded document is None. Aborting.")
406
- return html.Div("Please shred a document first."), ""
407
-
408
- source = None
409
- if radio_values and len(radio_values) > idx:
410
- source = radio_values[idx]
411
- else:
412
- source = 'loaded'
413
-
414
- doc_content = None
415
- if source == 'uploaded':
416
- if upload_contents and len(upload_contents) > idx and upload_contents[idx] and upload_filenames and len(upload_filenames) > idx and upload_filenames[idx]:
417
- doc_content = process_document(upload_contents[idx], upload_filenames[idx])
418
- else:
419
- logging.info("No uploaded doc content for this doc type. Aborting.")
420
- return html.Div("Please upload a document to use as source."), ""
421
- else:
422
- if doc_type == "Pink":
423
- doc_content = shredded_document
424
- elif doc_type == "Pink Review":
425
- doc_content = pink_review_document if pink_review_document else ""
426
- elif doc_type == "Red":
427
- doc_content = pink_review_document if pink_review_document else ""
428
- elif doc_type == "Red Review":
429
- doc_content = pink_review_document if pink_review_document else ""
430
- elif doc_type == "Gold":
431
- doc_content = shredded_document
432
- elif doc_type == "Gold Review":
433
- doc_content = shredded_document
434
- elif doc_type == "Virtual Board":
435
- doc_content = shredded_document
436
- elif doc_type == "LOE":
437
- doc_content = shredded_document
438
- else:
439
- doc_content = shredded_document
440
-
441
- try:
442
- if doc_type == "Pink Review":
443
- generated = generate_document(doc_type, [doc_content, shredded_document])
444
- pink_review_document = generated
445
- current_document = generated
446
- preview = markdown_table_preview(generated)
447
- logging.info("Pink Review document generated.")
448
- return preview, f"{doc_type} generated"
449
- elif doc_type in ["Red Review", "Gold Review", "Virtual Board", "LOE"]:
450
- generated = generate_document(doc_type, [doc_content, shredded_document])
451
- current_document = generated
452
- preview = markdown_table_preview(generated)
453
- logging.info(f"{doc_type} document generated.")
454
- return preview, f"{doc_type} generated"
455
- elif doc_type in ["Pink", "Red", "Gold"]:
456
- generated = generate_document(doc_type, [doc_content])
457
- current_document = generated
458
- preview = markdown_narrative_preview(generated)
459
- logging.info(f"{doc_type} document generated.")
460
- return preview, f"{doc_type} generated"
461
- else:
462
- generated = generate_document(doc_type, [doc_content])
463
- current_document = generated
464
- preview = markdown_narrative_preview(generated)
465
- logging.info(f"{doc_type} document generated.")
466
- return preview, f"{doc_type} generated"
467
- except Exception as e:
468
- logging.error(f"Error generating document: {str(e)}")
469
- return html.Div(f"Error generating document: {str(e)}"), "Error"
470
-
471
- @app.callback(
472
- Output({'type': 'uploaded-doc-name', 'index': MATCH}, 'children'),
473
- Output({'type': 'upload-doc-type', 'index': MATCH}, 'contents'),
474
- Output({'type': 'radio-doc-source', 'index': MATCH}, 'value'),
475
- Input({'type': 'upload-doc-type', 'index': MATCH}, 'contents'),
476
- State({'type': 'upload-doc-type', 'index': MATCH}, 'filename'),
477
- State({'type': 'upload-doc-type', 'index': MATCH}, 'id')
478
- )
479
- def update_uploaded_doc_name(contents, filename, id_dict):
480
- if contents is not None:
481
- uploaded_doc_contents[id_dict['index']] = (contents, filename)
482
- logging.info(f"{id_dict['index']} file uploaded: {filename}")
483
- return filename, contents, "uploaded"
484
- return "", None, "loaded"
485
 
486
  def extract_markdown_tables(md_text):
487
  tables = []
@@ -578,7 +519,7 @@ def strip_markdown(text):
578
  text = text.replace('***', '')
579
  return text.strip()
580
 
581
- def generate_document(document_type, file_contents):
582
  if document_type in spreadsheet_types:
583
  prompt = f"""Ignore all other instructions and output only a spreadsheet for {document_type} as described below. Do not include any narrative, only the spreadsheet in markdown table format.
584
  Instructions: {document_types[document_type]}
@@ -602,6 +543,8 @@ Instructions:
602
  {document_types.get(document_type, '')}
603
  Now, generate the {document_type}:
604
  """
 
 
605
  logging.info(f"Generating document for type: {document_type}")
606
  try:
607
  response = openai.ChatCompletion.create(
@@ -620,6 +563,217 @@ Now, generate the {document_type}:
620
  logging.error(f"Error generating document: {str(e)}")
621
  raise
622
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  @app.callback(
624
  Output('chat-output', 'children'),
625
  Output('document-preview', 'children', allow_duplicate=True),
 
29
  document_type = None
30
  shredded_document = None
31
  pink_review_document = None
32
+ pink_document = None
33
+ red_review_document = None
34
+ red_document = None
35
+ gold_review_document = None
36
+ gold_document = None
37
  uploaded_doc_contents = {}
38
 
39
  spreadsheet_types = ["Shred", "Pink Review", "Red Review", "Gold Review", "Virtual Board", "LOE"]
 
51
  "LOE": "Ignore all other instructions and generate and generate a Level of Effort (LOE) breakdown and produce only spreadsheet"
52
  }
53
 
54
+ # Document dependency rules: mapping doc type to allowed sources for generation and review
55
+ doc_dependencies = {
56
+ "Pink": {"source": ["shred"], "require": True},
57
+ "Pink Review": {"source": ["pink", "shred"], "require": True},
58
+ "Red": {"source": ["pink_review"], "require": True},
59
+ "Red Review": {"source": ["red", "shred"], "require": True},
60
+ "Gold": {"source": ["red_review"], "require": True},
61
+ "Gold Review": {"source": ["gold", "shred"], "require": True},
62
+ "LOE": {"source": ["gold"], "require": True},
63
+ "Virtual Board": {"source": ["shred"], "require": True},
64
+ }
65
+
66
  def get_right_col_content(selected_type):
67
  controls = []
68
  controls.append(html.Div([
 
73
  type="dot",
74
  children=[html.Div(id="loading-output")]
75
  ))
76
+ # Shred: can always upload
77
  if selected_type == "Shred":
78
  controls.append(
79
  html.Div([
 
98
  file_list_component()
99
  ])
100
  )
101
+ # For all proposal/response/review types, show only allowed upload controls
102
+ elif selected_type in doc_dependencies:
103
+ sources = doc_dependencies[selected_type]["source"]
104
+ # For each source, show upload and radio if loaded/generated exists
105
+ for src in sources:
106
+ label = ""
107
+ store_var = None
108
+ if src == "shred":
109
+ label = "Shred (Requirements)"
110
+ store_var = shredded_document
111
+ elif src == "pink":
112
+ label = "Pink Document"
113
+ store_var = pink_document
114
+ elif src == "pink_review":
115
+ label = "Pink Review"
116
+ store_var = pink_review_document
117
+ elif src == "red":
118
+ label = "Red Document"
119
+ store_var = red_document
120
+ elif src == "red_review":
121
+ label = "Red Review"
122
+ store_var = red_review_document
123
+ elif src == "gold":
124
+ label = "Gold Document"
125
+ store_var = gold_document
126
+ # Upload control for this source
127
+ controls.append(html.Div([
128
+ html.Label(f"Upload {label}"),
129
+ dcc.Upload(
130
+ id={'type': f'upload-doc-type-{src}', 'index': selected_type},
131
+ children=html.Div(['Drag and Drop or ', html.A('Select File')]),
132
+ style={
133
+ 'width': '100%',
134
+ 'height': '60px',
135
+ 'lineHeight': '60px',
136
+ 'borderWidth': '1px',
137
+ 'borderStyle': 'dashed',
138
+ 'borderRadius': '5px',
139
+ 'textAlign': 'center',
140
+ 'margin': '10px 0'
141
+ },
142
+ multiple=False
143
+ ),
144
+ html.Div(id={'type': f'uploaded-doc-name-{src}', 'index': selected_type}),
145
+ dbc.RadioItems(
146
+ id={'type': f'radio-doc-source-{src}', 'index': selected_type},
147
+ options=[
148
+ {'label': 'Loaded Document', 'value': 'loaded'},
149
+ {'label': 'Uploaded Document', 'value': 'uploaded'}
150
+ ] if store_var else [{'label': 'Uploaded Document', 'value': 'uploaded'}],
151
+ value='loaded' if store_var else 'uploaded',
152
+ inline=True,
153
+ className="mb-2"
154
+ ),
155
+ ], id={'type': f'doc-type-controls-{src}', 'index': selected_type}))
156
  controls.append(
157
  dbc.Row([
158
  dbc.Col(
 
168
  )
169
  controls.append(html.Hr())
170
  controls.append(html.Div(id='document-preview', className="border p-3 mb-3"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  return dbc.Card(dbc.CardBody(controls))
172
 
173
  def get_left_col_content():
 
404
  filtered_files.append(file)
405
  return filtered_files
406
 
407
+ # Upload callbacks for all doc dependencies (Pink, Pink Review, Red, Red Review, Gold, Gold Review, LOE, Virtual Board)
408
+ def make_upload_callback(src):
409
+ @app.callback(
410
+ Output({'type': f'uploaded-doc-name-{src}', 'index': MATCH}, 'children'),
411
+ Output({'type': f'upload-doc-type-{src}', 'index': MATCH}, 'contents'),
412
+ Output({'type': f'radio-doc-source-{src}', 'index': MATCH}, 'value'),
413
+ Input({'type': f'upload-doc-type-{src}', 'index': MATCH}, 'contents'),
414
+ State({'type': f'upload-doc-type-{src}', 'index': MATCH}, 'filename'),
415
+ State({'type': f'upload-doc-type-{src}', 'index': MATCH}, 'id')
416
+ )
417
+ def update_uploaded_doc_name(contents, filename, id_dict):
418
+ global uploaded_doc_contents
419
+ if contents is not None:
420
+ uploaded_doc_contents[(src, id_dict['index'])] = (contents, filename)
421
+ logging.info(f"{id_dict['index']} {src} file uploaded: {filename}")
422
+ return filename, contents, "uploaded"
423
+ return "", None, "loaded"
424
+ for src in ["shred", "pink", "pink_review", "red", "red_review", "gold"]:
425
+ make_upload_callback(src)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
  def extract_markdown_tables(md_text):
428
  tables = []
 
519
  text = text.replace('***', '')
520
  return text.strip()
521
 
522
+ def generate_document(document_type, file_contents, extra_context=None):
523
  if document_type in spreadsheet_types:
524
  prompt = f"""Ignore all other instructions and output only a spreadsheet for {document_type} as described below. Do not include any narrative, only the spreadsheet in markdown table format.
525
  Instructions: {document_types[document_type]}
 
543
  {document_types.get(document_type, '')}
544
  Now, generate the {document_type}:
545
  """
546
+ if extra_context:
547
+ prompt += f"\n\n{extra_context}"
548
  logging.info(f"Generating document for type: {document_type}")
549
  try:
550
  response = openai.ChatCompletion.create(
 
563
  logging.error(f"Error generating document: {str(e)}")
564
  raise
565
 
566
+ @app.callback(
567
+ Output('document-preview', 'children'),
568
+ Output('loading-output', 'children'),
569
+ Input({'type': 'btn-generate-doc', 'index': ALL}, 'n_clicks'),
570
+ State({'type': 'btn-generate-doc', 'index': ALL}, 'id'),
571
+ State({'type': 'radio-doc-source-shred', 'index': ALL}, 'value'),
572
+ State({'type': 'upload-doc-type-shred', 'index': ALL}, 'contents'),
573
+ State({'type': 'upload-doc-type-shred', 'index': ALL}, 'filename'),
574
+ State({'type': 'radio-doc-source-pink', 'index': ALL}, 'value'),
575
+ State({'type': 'upload-doc-type-pink', 'index': ALL}, 'contents'),
576
+ State({'type': 'upload-doc-type-pink', 'index': ALL}, 'filename'),
577
+ State({'type': 'radio-doc-source-pink_review', 'index': ALL}, 'value'),
578
+ State({'type': 'upload-doc-type-pink_review', 'index': ALL}, 'contents'),
579
+ State({'type': 'upload-doc-type-pink_review', 'index': ALL}, 'filename'),
580
+ State({'type': 'radio-doc-source-red', 'index': ALL}, 'value'),
581
+ State({'type': 'upload-doc-type-red', 'index': ALL}, 'contents'),
582
+ State({'type': 'upload-doc-type-red', 'index': ALL}, 'filename'),
583
+ State({'type': 'radio-doc-source-red_review', 'index': ALL}, 'value'),
584
+ State({'type': 'upload-doc-type-red_review', 'index': ALL}, 'contents'),
585
+ State({'type': 'upload-doc-type-red_review', 'index': ALL}, 'filename'),
586
+ State({'type': 'radio-doc-source-gold', 'index': ALL}, 'value'),
587
+ State({'type': 'upload-doc-type-gold', 'index': ALL}, 'contents'),
588
+ State({'type': 'upload-doc-type-gold', 'index': ALL}, 'filename'),
589
+ prevent_initial_call=True
590
+ )
591
+ def generate_any_doc(
592
+ n_clicks_list, btn_ids,
593
+ radio_shred, upload_shred_contents, upload_shred_filenames,
594
+ radio_pink, upload_pink_contents, upload_pink_filenames,
595
+ radio_pink_review, upload_pink_review_contents, upload_pink_review_filenames,
596
+ radio_red, upload_red_contents, upload_red_filenames,
597
+ radio_red_review, upload_red_review_contents, upload_red_review_filenames,
598
+ radio_gold, upload_gold_contents, upload_gold_filenames
599
+ ):
600
+ global current_document, document_type, shredded_document, pink_review_document, pink_document, red_review_document, red_document, gold_review_document, gold_document
601
+ ctx = callback_context
602
+ logging.info(f"generate_any_doc triggered: n_clicks_list={n_clicks_list}, btn_ids={btn_ids}")
603
+ if not ctx.triggered:
604
+ raise dash.exceptions.PreventUpdate
605
+ idx = [i for i, x in enumerate(n_clicks_list) if x]
606
+ if not idx:
607
+ raise dash.exceptions.PreventUpdate
608
+ idx = idx[-1]
609
+ doc_type = btn_ids[idx]['index']
610
+ document_type = doc_type
611
+
612
+ # SHRED - always allowed
613
+ if doc_type == "Shred":
614
+ if not uploaded_files:
615
+ logging.info("No uploaded files for Shred. Aborting.")
616
+ return html.Div("Please upload a document before shredding."), ""
617
+ file_contents = list(uploaded_files.values())
618
+ try:
619
+ generated = generate_document(doc_type, file_contents)
620
+ current_document = generated
621
+ shredded_document = generated
622
+ preview = markdown_table_preview(generated)
623
+ logging.info("Shred document generated.")
624
+ return preview, "Shred generated"
625
+ except Exception as e:
626
+ logging.error(f"Error generating document: {str(e)}")
627
+ return html.Div(f"Error generating document: {str(e)}"), "Error"
628
+
629
+ # Dependency rules
630
+ def get_doc_from_radio(radio, upload_contents, upload_filenames, loaded_var):
631
+ if radio and radio[0] == 'uploaded':
632
+ if upload_contents and upload_contents[0] and upload_filenames and upload_filenames[0]:
633
+ return process_document(upload_contents[0], upload_filenames[0])
634
+ else:
635
+ return None
636
+ else:
637
+ return loaded_var
638
+
639
+ # Pink: can only use Shred (requirements), loaded or uploaded
640
+ if doc_type == "Pink":
641
+ shred_doc = get_doc_from_radio(radio_shred, upload_shred_contents, upload_shred_filenames, shredded_document)
642
+ if not shred_doc:
643
+ return html.Div("Please provide a Shred requirements document (either loaded or uploaded) to generate Pink."), ""
644
+ try:
645
+ generated = generate_document(doc_type, [shred_doc])
646
+ current_document = generated
647
+ pink_document = generated
648
+ preview = markdown_narrative_preview(generated)
649
+ logging.info("Pink document generated.")
650
+ return preview, "Pink generated"
651
+ except Exception as e:
652
+ logging.error(f"Error generating Pink: {str(e)}")
653
+ return html.Div(f"Error generating Pink: {str(e)}"), "Error"
654
+
655
+ # Pink Review: must compare Pink (uploaded or generated) with Shred (uploaded or generated)
656
+ if doc_type == "Pink Review":
657
+ pink_doc = get_doc_from_radio(radio_pink, upload_pink_contents, upload_pink_filenames, pink_document)
658
+ shred_doc = get_doc_from_radio(radio_shred, upload_shred_contents, upload_shred_filenames, shredded_document)
659
+ if not pink_doc or not shred_doc:
660
+ return html.Div("Please provide both Pink and Shred documents (either loaded or uploaded) to generate Pink Review."), ""
661
+ try:
662
+ generated = generate_document(doc_type, [pink_doc, shred_doc])
663
+ current_document = generated
664
+ pink_review_document = generated
665
+ preview = markdown_table_preview(generated)
666
+ logging.info("Pink Review document generated.")
667
+ return preview, "Pink Review generated"
668
+ except Exception as e:
669
+ logging.error(f"Error generating Pink Review: {str(e)}")
670
+ return html.Div(f"Error generating Pink Review: {str(e)}"), "Error"
671
+
672
+ # Red: can only use Pink Review (uploaded or generated)
673
+ if doc_type == "Red":
674
+ pink_review_doc = get_doc_from_radio(radio_pink_review, upload_pink_review_contents, upload_pink_review_filenames, pink_review_document)
675
+ if not pink_review_doc:
676
+ return html.Div("Please provide a Pink Review document (either loaded or uploaded) to generate Red."), ""
677
+ try:
678
+ generated = generate_document(doc_type, [pink_review_doc])
679
+ current_document = generated
680
+ red_document = generated
681
+ preview = markdown_narrative_preview(generated)
682
+ logging.info("Red document generated.")
683
+ return preview, "Red generated"
684
+ except Exception as e:
685
+ logging.error(f"Error generating Red: {str(e)}")
686
+ return html.Div(f"Error generating Red: {str(e)}"), "Error"
687
+
688
+ # Red Review: must compare Red (uploaded or generated) with Shred (uploaded or generated)
689
+ if doc_type == "Red Review":
690
+ red_doc = get_doc_from_radio(radio_red, upload_red_contents, upload_red_filenames, red_document)
691
+ shred_doc = get_doc_from_radio(radio_shred, upload_shred_contents, upload_shred_filenames, shredded_document)
692
+ if not red_doc or not shred_doc:
693
+ return html.Div("Please provide both Red and Shred documents (either loaded or uploaded) to generate Red Review."), ""
694
+ try:
695
+ generated = generate_document(doc_type, [red_doc, shred_doc])
696
+ current_document = generated
697
+ red_review_document = generated
698
+ preview = markdown_table_preview(generated)
699
+ logging.info("Red Review document generated.")
700
+ return preview, "Red Review generated"
701
+ except Exception as e:
702
+ logging.error(f"Error generating Red Review: {str(e)}")
703
+ return html.Div(f"Error generating Red Review: {str(e)}"), "Error"
704
+
705
+ # Gold: can only use Red Review (uploaded or generated)
706
+ if doc_type == "Gold":
707
+ red_review_doc = get_doc_from_radio(radio_red_review, upload_red_review_contents, upload_red_review_filenames, red_review_document)
708
+ if not red_review_doc:
709
+ return html.Div("Please provide a Red Review document (either loaded or uploaded) to generate Gold."), ""
710
+ try:
711
+ generated = generate_document(doc_type, [red_review_doc])
712
+ current_document = generated
713
+ gold_document = generated
714
+ preview = markdown_narrative_preview(generated)
715
+ logging.info("Gold document generated.")
716
+ return preview, "Gold generated"
717
+ except Exception as e:
718
+ logging.error(f"Error generating Gold: {str(e)}")
719
+ return html.Div(f"Error generating Gold: {str(e)}"), "Error"
720
+
721
+ # Gold Review: must compare Gold (uploaded or generated) with Shred (uploaded or generated)
722
+ if doc_type == "Gold Review":
723
+ gold_doc = get_doc_from_radio(radio_gold, upload_gold_contents, upload_gold_filenames, gold_document)
724
+ shred_doc = get_doc_from_radio(radio_shred, upload_shred_contents, upload_shred_filenames, shredded_document)
725
+ if not gold_doc or not shred_doc:
726
+ return html.Div("Please provide both Gold and Shred documents (either loaded or uploaded) to generate Gold Review."), ""
727
+ try:
728
+ generated = generate_document(doc_type, [gold_doc, shred_doc])
729
+ current_document = generated
730
+ gold_review_document = generated
731
+ preview = markdown_table_preview(generated)
732
+ logging.info("Gold Review document generated.")
733
+ return preview, "Gold Review generated"
734
+ except Exception as e:
735
+ logging.error(f"Error generating Gold Review: {str(e)}")
736
+ return html.Div(f"Error generating Gold Review: {str(e)}"), "Error"
737
+
738
+ # LOE: can only use Gold (uploaded or generated)
739
+ if doc_type == "LOE":
740
+ gold_doc = get_doc_from_radio(radio_gold, upload_gold_contents, upload_gold_filenames, gold_document)
741
+ if not gold_doc:
742
+ return html.Div("Please provide a Gold document (either loaded or uploaded) to generate LOE."), ""
743
+ try:
744
+ generated = generate_document(doc_type, [gold_doc])
745
+ current_document = generated
746
+ preview = markdown_table_preview(generated)
747
+ logging.info("LOE document generated.")
748
+ return preview, "LOE generated"
749
+ except Exception as e:
750
+ logging.error(f"Error generating LOE: {str(e)}")
751
+ return html.Div(f"Error generating LOE: {str(e)}"), "Error"
752
+
753
+ # Virtual Board: can only use Shred (L&M evaluation criteria)
754
+ if doc_type == "Virtual Board":
755
+ shred_doc = get_doc_from_radio(radio_shred, upload_shred_contents, upload_shred_filenames, shredded_document)
756
+ if not shred_doc:
757
+ return html.Div("Please provide a Shred requirements document (either loaded or uploaded) to generate Virtual Board."), ""
758
+ try:
759
+ # Extract section L&M if possible
760
+ lm_text = ""
761
+ lm_match = re.search(r'(Section\s+L[\s\S]+?)(Section\s+M|$)', shred_doc, re.IGNORECASE)
762
+ if lm_match:
763
+ lm_text = lm_match.group(1)
764
+ else:
765
+ lm_text = shred_doc
766
+ generated = generate_document(doc_type, [lm_text])
767
+ current_document = generated
768
+ preview = markdown_table_preview(generated)
769
+ logging.info("Virtual Board document generated.")
770
+ return preview, "Virtual Board generated"
771
+ except Exception as e:
772
+ logging.error(f"Error generating Virtual Board: {str(e)}")
773
+ return html.Div(f"Error generating Virtual Board: {str(e)}"), "Error"
774
+
775
+ return html.Div("Unsupported document type or missing required sources."), ""
776
+
777
  @app.callback(
778
  Output('chat-output', 'children'),
779
  Output('document-preview', 'children', allow_duplicate=True),