Gilmullin Almaz commited on
Commit
41887bd
·
1 Parent(s): 72a3513

Enhance planning results display and download functionality with improved HTML report generation

Browse files
Files changed (1) hide show
  1. app.py +185 -71
app.py CHANGED
@@ -532,8 +532,8 @@ def display_planning_results():
532
  )
533
  image_counter += 1
534
  reactions = tree.synthesis_route(route_id)
535
- for reaction in reactions:
536
- st.write(reaction)
537
  else:
538
  st.warning(
539
  f"Could not generate SVG for route {route_id}."
@@ -541,83 +541,192 @@ def display_planning_results():
541
  if image_counter >= 20:
542
  break
543
 
544
- # st.warning(
545
- # "No reaction path found for the target molecule with the current settings."
546
- # )
547
- # st.write(
548
- # "Consider adjusting planning options (e.g., increase iterations, adjust depth, check molecule validity)."
549
- # )
550
- # stat_col, _ = st.columns(2)
551
- # with stat_col:
552
- # st.subheader("Run Statistics (No Solution)")
553
- # try:
554
- # if (
555
- # "target_smiles" not in res
556
- # and "target_smiles" in st.session_state
557
- # ):
558
- # res["target_smiles"] = st.session_state.target_smiles
559
- # cols_to_show = [
560
- # col
561
- # for col in [
562
- # "target_smiles",
563
- # "num_nodes",
564
- # "num_iter",
565
- # "search_time",
566
- # ]
567
- # if col in res
568
- # ]
569
- # if cols_to_show:
570
- # df = pd.DataFrame(res, index=[0])[cols_to_show]
571
- # st.dataframe(df)
572
- # else:
573
- # st.write("No statistics to display for the unsuccessful run.")
574
- # except Exception as e:
575
- # st.error(f"Error displaying statistics: {e}")
576
- # st.write(res)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
 
 
 
 
578
 
579
  def download_planning_results():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  """6. Planning Results Download: Providing functionality to download."""
581
  if (
582
- st.session_state.get("planning_done", False)
583
- and st.session_state.res
584
- and st.session_state.res.get("solved", False)
585
  ):
586
- res = st.session_state.res
587
- tree = st.session_state.tree
588
- # This section is usually placed within a column in the original script
589
- # We'll assume it's called after display_planning_results and can use a new column or area.
590
- # For proper layout, this should be integrated with display_planning_results' columns.
591
- # For now, creating a placeholder or separate section for downloads:
592
- # st.subheader("Downloads") # This might be redundant if called within a layout context.
593
-
594
- # The original code places downloads in the second column of planning results.
595
- # To replicate, we'd need to pass the column object or call this within that context.
596
- # Simulating this by just creating the download links:
597
- try:
598
- html_body = generate_results_html(tree, html_path=None, extended=True)
599
- dl_html = download_button(
600
- html_body,
601
- f"results_synplanner_{st.session_state.target_smiles}.html",
602
- "Download results (HTML)",
603
- )
604
- if dl_html:
605
- st.markdown(dl_html, unsafe_allow_html=True)
606
 
607
- try:
608
- res_df = pd.DataFrame(res, index=[0])
609
- dl_csv = download_button(
610
- res_df,
611
- f"stats_synplanner_{st.session_state.target_smiles}.csv",
612
- "Download statistics (CSV)",
613
- )
614
- if dl_csv:
615
- st.markdown(dl_csv, unsafe_allow_html=True)
616
- except Exception as e:
617
- st.error(f"Could not prepare statistics CSV for download: {e}")
 
 
 
 
 
 
618
 
619
- except Exception as e:
620
- st.error(f"Error generating download links for planning results: {e}")
621
 
622
 
623
  def setup_clustering():
@@ -1281,6 +1390,11 @@ def main():
1281
  with download_col:
1282
  st.subheader("Planning Downloads") # Adding a subheader for clarity
1283
  download_planning_results()
 
 
 
 
 
1284
 
1285
  # Clustering section (setup button, display, download)
1286
  if (
 
532
  )
533
  image_counter += 1
534
  reactions = tree.synthesis_route(route_id)
535
+ for step in range(len(reactions)):
536
+ st.markdown(f"Step {step+1} - {reactions[step]}")
537
  else:
538
  st.warning(
539
  f"Could not generate SVG for route {route_id}."
 
541
  if image_counter >= 20:
542
  break
543
 
544
+ def generate_results_html_mod(tree: Tree,
545
+ html_path: str = None,
546
+ aam: bool = False,
547
+ extended: bool = False):
548
+ """Either writes out a full report (if html_path given),
549
+ or returns a single HTML string while showing a Streamlit
550
+ progress bar (if html_path is None)."""
551
+
552
+ # 1) Prepare routes
553
+ MoleculeContainer.depict_settings(aam=aam)
554
+ if not extended:
555
+ routes = list(tree.nodes.keys())[1:tree.config.max_iterations:50]
556
+ else:
557
+ routes = list(tree.nodes.keys())
558
+ total = len(routes)
559
+
560
+ # 2) Exact same header, summary and footer as your original
561
+ header = """<!doctype html>
562
+ <html lang="en">
563
+ <head>
564
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
565
+ rel="stylesheet"
566
+ integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
567
+ crossorigin="anonymous">
568
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
569
+ integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
570
+ crossorigin="anonymous"></script>
571
+ <meta charset="utf-8">
572
+ <meta name="viewport" content="width=device-width, initial-scale=1">
573
+ <title>Predicted Paths Report</title>
574
+ </head>
575
+ <body>
576
+ <table class="table table-striped table-hover caption-top">
577
+ <caption><h3>Retrosynthetic Routes Report</h3></caption>
578
+ <tbody>
579
+ """
580
+ td = '<td style="text-align:left; border:1px solid black; border-spacing:0">'
581
+ legend = f"""
582
+ <tr>{td}
583
+ <div>
584
+ <svg width="30" height="30" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">
585
+ <circle cx="0.5" cy="0.5" r="0.5" fill="rgb(152,238,255)" fill-opacity="0.35"/>
586
+ </svg> Target Molecule
587
+ <svg width="30" height="30" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">
588
+ <circle cx="0.5" cy="0.5" r="0.5" fill="rgb(240,171,144)" fill-opacity="0.35"/>
589
+ </svg> Molecule Not In Stock
590
+ <svg width="30" height="30" viewBox="0 0 1 1" xmlns="http://www.w3.org/2000/svg">
591
+ <circle cx="0.5" cy="0.5" r="0.5" fill="rgb(155,250,179)" fill-opacity="0.35"/>
592
+ </svg> Molecule In Stock
593
+ </div>
594
+ </td></tr>
595
+ """
596
+ summary = (
597
+ f"<tr>{td}Target: {tree.nodes[1].curr_precursor}</td></tr>\n"
598
+ f"<tr>{td}Tree size: {len(tree)} nodes</td></tr>\n"
599
+ f"<tr>{td}Visited nodes: {len(tree.visited_nodes)}</td></tr>\n"
600
+ f"<tr>{td}Found paths: {total}</td></tr>\n"
601
+ f"<tr>{td}Time: {tree.curr_time:.4f} s</td></tr>\n"
602
+ + legend
603
+ )
604
+ footer = """
605
+ </tbody>
606
+ </table>
607
+ </body>
608
+ </html>
609
+ """
610
+
611
+ # 3) If writing to disk, just stream without UI
612
+ if html_path:
613
+ with open(html_path, "w", encoding="utf-8") as out:
614
+ out.write(header)
615
+ out.write(summary)
616
+ for route_id in routes:
617
+ # build each route exactly as before
618
+ score = round(tree.route_score(route_id), 3)
619
+ steps = tree.synthesis_route(route_id)
620
+ svg = get_route_svg_mod(tree, route_id)
621
+
622
+ # one-shot build
623
+ block = (
624
+ f"<tr style='line-height:250%'>{td}"
625
+ f"<b style='font-size:18px'>Route {route_id}; "
626
+ f"Steps: {len(steps)}; Score: {score}</b>"
627
+ f"</td></tr>\n"
628
+ f"<tr><td>{svg}</td></tr>\n"
629
+ "<tr><td>"
630
+ + "".join(f"<b>Step {i+1}:</b> {s}<br>\n"
631
+ for i, s in enumerate(steps))
632
+ + "</td></tr>\n"
633
+ )
634
+ out.write(block)
635
+ out.write(footer)
636
+ return
637
+
638
+ # 4) Otherwise, show Streamlit bar as we build in-memory
639
+ prog_text = "Rendering routes"
640
+ prog_bar = st.progress(0.0, text=prog_text)
641
+
642
+ html_chunks = [header, summary]
643
+ for i, route_id in enumerate(routes, start=1):
644
+ score = round(tree.route_score(route_id), 3)
645
+ steps = tree.synthesis_route(route_id)
646
+ svg = get_route_svg_mod(tree, route_id)
647
+
648
+ block = (
649
+ f"<tr style='line-height:250%'>{td}"
650
+ f"<b style='font-size:18px'>Route {route_id}; "
651
+ f"Steps: {len(steps)}; Score: {score}</b>"
652
+ f"</td></tr>\n"
653
+ f"<tr><td>{svg}</td></tr>\n"
654
+ "<tr><td>"
655
+ + "".join(f"<b>Step {j+1}:</b> {s}<br>\n"
656
+ for j, s in enumerate(steps))
657
+ + "</td></tr>\n"
658
+ )
659
+ html_chunks.append(block)
660
+
661
+ # update Streamlit bar exactly like your MCTS example
662
+ frac = min(1.0, i / total)
663
+ prog_bar.progress(frac, text=f"{prog_text} ({i}/{total})")
664
 
665
+ html_chunks.append(footer)
666
+ prog_bar.empty()
667
+
668
+ return "".join(html_chunks)
669
 
670
  def download_planning_results():
671
+ """6. Planning Results Download: Deferring computation until button press."""
672
+ if st.session_state.get("planning_done") and st.session_state.res and st.session_state.res.get("solved"):
673
+
674
+ # --- Full HTML Report Download ---
675
+ if st.button("Generate Full HTML Report", key="gen_plan_html"):
676
+ with st.spinner("Generating HTML report..."):
677
+ st.session_state.planning_report_html = generate_results_html(st.session_state.tree, html_path=None, extended=True)
678
+
679
+ if st.session_state.get("planning_report_html"):
680
+ st.download_button(
681
+ label="Download Full Report (HTML)",
682
+ data=st.session_state.planning_report_html,
683
+ file_name=f"full_report_{st.session_state.target_smiles}.html",
684
+ mime="text/html",
685
+ )
686
+
687
+ # --- Statistics CSV Download (fast, so no deferral needed) ---
688
+ try:
689
+ res_df = pd.DataFrame(st.session_state.res, index=[0])
690
+ csv_data = res_df.to_csv(index=False).encode('utf-8')
691
+ st.download_button(
692
+ label="Download Statistics (CSV)",
693
+ data=csv_data,
694
+ file_name=f"stats_synplanner_{st.session_state.target_smiles}.csv",
695
+ mime="text/csv",
696
+ )
697
+ except Exception as e:
698
+ st.error(f"Could not prepare statistics CSV for download: {e}")
699
+
700
+
701
+ def download_planning_results_mod():
702
  """6. Planning Results Download: Providing functionality to download."""
703
  if (
704
+ st.session_state.get("planning_done", True)
 
 
705
  ):
706
+ if st.button("Generate Full HTML Report", key="gen_plan_html"):
707
+ with st.spinner("Generating HTML report..."):
708
+ planning_report_html = generate_results_html_mod(st.session_state.tree, html_path=None, extended=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
 
710
+ st.download_button(
711
+ label=f"Download Full Report (HTML) max {len(st.session_state.tree.nodes)} routes",
712
+ data=planning_report_html,
713
+ file_name=f"full_report_{st.session_state.target_smiles}.html",
714
+ mime="text/html",
715
+ )
716
+
717
+ if st.button("Generate Short HTML Report", key="gen_plan_html_short"):
718
+ with st.spinner("Generating HTML report..."):
719
+ short_planning_report_html = generate_results_html_mod(st.session_state.tree, html_path=None, extended=False)
720
+ num_short = len(list(st.session_state.tree.nodes.keys())[1:st.session_state.tree.config.max_iterations:50])
721
+ st.download_button(
722
+ label=f"Download Short Report (HTML) max {num_short} routes",
723
+ data=short_planning_report_html,
724
+ file_name=f"short_report_{st.session_state.target_smiles}.html",
725
+ mime="text/html",
726
+ )
727
 
728
+ # except Exception as e:
729
+ # st.error(f"Error generating download links for planning results: {e}")
730
 
731
 
732
  def setup_clustering():
 
1390
  with download_col:
1391
  st.subheader("Planning Downloads") # Adding a subheader for clarity
1392
  download_planning_results()
1393
+ else:
1394
+ download_col, _ = st.columns(2, gap="medium") # Placeholder for download column
1395
+ with download_col:
1396
+ st.subheader("Planning Downloads") # Adding a subheader for clarity
1397
+ download_planning_results_mod()
1398
 
1399
  # Clustering section (setup button, display, download)
1400
  if (