Spaces:
Sleeping
Sleeping
Upload app.py
Browse files- src/app.py +137 -54
src/app.py
CHANGED
|
@@ -164,14 +164,80 @@ def load_data(data_dir_str: str):
|
|
| 164 |
cities_df, countries_df, fields_df, intents_df, journals_df)
|
| 165 |
|
| 166 |
|
| 167 |
-
# ββ KG
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
@st.cache_data(show_spinner=False)
|
| 169 |
-
def
|
|
|
|
| 170 |
d = None if HF_REPO_ID else Path(data_dir_str)
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
|
| 177 |
# ββ ν¬νΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -486,39 +552,49 @@ with tab_ontology:
|
|
| 486 |
components.html(pyvis_ontology(), height=820, scrolling=True)
|
| 487 |
|
| 488 |
|
| 489 |
-
# βββ 4. KNOWLEDGE GRAPH (μ€μ KG λ°μ΄ν°) ββββββββββββββββ
|
| 490 |
with tab_kg:
|
| 491 |
st.subheader("Knowledge Graph β Selected Seed Paper")
|
| 492 |
-
st.caption("kg_nodes + kg_edges
|
| 493 |
-
st.info("μλ λ²νΌμ λλ¬ KG λ°μ΄ν°λ₯Ό λ‘λνμΈμ
|
|
|
|
|
|
|
| 494 |
|
| 495 |
if st.button("KG λ°μ΄ν° λ‘λ", key="kg_load"):
|
| 496 |
-
with st.spinner("kg_nodes
|
| 497 |
st.session_state["kg_loaded"] = True
|
| 498 |
|
| 499 |
if st.session_state.get("kg_loaded"):
|
| 500 |
try:
|
| 501 |
-
kg_nodes
|
|
|
|
|
|
|
| 502 |
seed_doi = selected_seed["doi"]
|
| 503 |
if not seed_doi:
|
| 504 |
st.warning("μ νλ seed paperμ DOIκ° μμ΄ KG μ‘°νκ° λΆκ°ν©λλ€.")
|
| 505 |
else:
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
else:
|
| 510 |
-
|
|
|
|
|
|
|
| 511 |
c1, c2, c3 = st.columns(3)
|
| 512 |
c1.metric("Nodes", fmt_num(len(nodes_sub)))
|
| 513 |
c2.metric("Edges", fmt_num(len(edges_sub)))
|
| 514 |
c3.metric("Node types", fmt_num(nodes_sub["node_type"].nunique()))
|
| 515 |
|
| 516 |
type_counts = nodes_sub["node_type"].value_counts().reset_index()
|
| 517 |
-
type_counts.columns = ["node_type","count"]
|
| 518 |
st.plotly_chart(
|
| 519 |
px.bar(type_counts, x="node_type", y="count",
|
| 520 |
-
color="node_type",
|
| 521 |
-
color_discrete_map=NODE_TYPE_COLORS,
|
| 522 |
title="Node Type Distribution")
|
| 523 |
.update_layout(showlegend=False, xaxis_title="", yaxis_title="Count"),
|
| 524 |
use_container_width=True)
|
|
@@ -532,18 +608,21 @@ with tab_kg:
|
|
| 532 |
# βββ 5. KG EXPLORER βββββββββββββββββββββββββββββββββββββββββββββ
|
| 533 |
with tab_kg_exp:
|
| 534 |
st.subheader("KG Explorer")
|
| 535 |
-
st.caption("kg_nodes
|
| 536 |
-
st.info("μλ λ²νΌμ λλ¬ KG λ°μ΄ν°λ₯Ό λ‘λνμΈμ
|
| 537 |
|
| 538 |
if st.button("KG λ°μ΄ν° λ‘λ", key="kg_exp_load"):
|
| 539 |
-
with st.spinner("λ‘λ© μ€..."):
|
| 540 |
st.session_state["kg_loaded"] = True
|
| 541 |
|
| 542 |
if st.session_state.get("kg_loaded"):
|
| 543 |
try:
|
| 544 |
-
kg_nodes
|
|
|
|
|
|
|
|
|
|
| 545 |
|
| 546 |
-
# ββ μ 체 λ
Έλ νμ
λΆν¬
|
| 547 |
col_a, col_b = st.columns([1,2])
|
| 548 |
with col_a:
|
| 549 |
st.subheader("Node Type Counts")
|
|
@@ -551,9 +630,14 @@ with tab_kg_exp:
|
|
| 551 |
nt.columns = ["node_type","count"]
|
| 552 |
st.dataframe(nt, use_container_width=True, hide_index=True)
|
| 553 |
|
|
|
|
| 554 |
st.subheader("Edge Type Counts")
|
| 555 |
-
|
| 556 |
-
et
|
|
|
|
|
|
|
|
|
|
|
|
|
| 557 |
st.dataframe(et, use_container_width=True, hide_index=True)
|
| 558 |
|
| 559 |
with col_b:
|
|
@@ -584,8 +668,7 @@ with tab_kg_exp:
|
|
| 584 |
st.warning("κ²μ κ²°κ³Όκ° μμ΅λλ€.")
|
| 585 |
else:
|
| 586 |
sel_node_id = st.selectbox(
|
| 587 |
-
"Select node",
|
| 588 |
-
node_options,
|
| 589 |
format_func=lambda nid: sample.loc[sample["node_id"]==nid,"label"].iloc[0][:60],
|
| 590 |
)
|
| 591 |
sel_node_info = sample[sample["node_id"]==sel_node_id].iloc[0]
|
|
@@ -596,12 +679,14 @@ with tab_kg_exp:
|
|
| 596 |
st.markdown(f"**Cited by**: {fmt_num(sel_node_info.get('citedby_count',''))}")
|
| 597 |
|
| 598 |
max_e = st.slider("Max edges shown", 20, 150, 60, key="kg_exp_max")
|
| 599 |
-
|
| 600 |
if st.button("Show ego network", key="kg_exp_show"):
|
| 601 |
-
|
| 602 |
-
|
|
|
|
| 603 |
st.warning("μ°κ²°λ μ£μ§κ° μμ΅λλ€.")
|
| 604 |
else:
|
|
|
|
|
|
|
| 605 |
st.session_state["exp_nodes"] = exp_nodes
|
| 606 |
st.session_state["exp_edges"] = exp_edges
|
| 607 |
|
|
@@ -615,34 +700,32 @@ with tab_kg_exp:
|
|
| 615 |
else:
|
| 616 |
st.info("μΌμͺ½μμ λ
Έλλ₯Ό μ ννκ³ 'Show ego network'λ₯Ό ν΄λ¦νμΈμ.")
|
| 617 |
|
| 618 |
-
# ββ Enriched μΈμ¬μ΄νΈ
|
| 619 |
st.markdown("---")
|
| 620 |
st.subheader("Enriched Citation Insights")
|
| 621 |
-
st.caption("citation_events_enriched:
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
# λΆμΌλ³ semantic evidence λΉμ¨
|
| 633 |
-
if "field_folder" in enriched.columns:
|
| 634 |
-
field_sem = (enriched.groupby("field_folder")["has_semantic_evidence"]
|
| 635 |
-
.mean().reset_index()
|
| 636 |
-
.rename(columns={"has_semantic_evidence":"sem_ratio","field_folder":"field"})
|
| 637 |
-
.sort_values("sem_ratio", ascending=False).head(20))
|
| 638 |
st.plotly_chart(
|
| 639 |
-
px.
|
| 640 |
-
title="Semantic Evidence
|
| 641 |
-
|
| 642 |
-
.update_layout(xaxis_tickangle=-40),
|
| 643 |
use_container_width=True)
|
| 644 |
-
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
|
| 647 |
except Exception as e:
|
| 648 |
st.error(str(e))
|
|
|
|
| 164 |
cities_df, countries_df, fields_df, intents_df, journals_df)
|
| 165 |
|
| 166 |
|
| 167 |
+
# ββ KG λ°μ΄ν°: DuckDB λ°©μμΌλ‘ λΆλ¦¬ λ‘λ βββββββββββββββββββββ
|
| 168 |
+
# kg_nodes : pandas μ 체 λ‘λ (~160MB νμΌ, λ©λͺ¨λ¦¬ νμ© λ²μ)
|
| 169 |
+
# kg_edges : DuckDBλ‘ νμν λ
Έλμ μ£μ§λ§ 쿼리 (μ 체 λ‘λ μ ν¨)
|
| 170 |
+
# enriched : DuckDBλ‘ μ§κ³ ν΅κ³λ§ 쿼리 (μ 체 λ‘λ μ ν¨)
|
| 171 |
+
|
| 172 |
@st.cache_data(show_spinner=False)
|
| 173 |
+
def load_kg_nodes(data_dir_str: str) -> pd.DataFrame:
|
| 174 |
+
"""kg_nodes μ 체 λ‘λ (3.4M rows, ~160MB νμΌ)"""
|
| 175 |
d = None if HF_REPO_ID else Path(data_dir_str)
|
| 176 |
+
return _read("kg_nodes.parquet", d)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
@st.cache_data(show_spinner=False)
|
| 180 |
+
def get_parquet_path(filename: str, data_dir_str: str) -> str:
|
| 181 |
+
"""νμΌ κ²½λ‘ λ°ν (HFλ©΄ λ‘컬 μΊμμ λ€μ΄λ‘λ ν κ²½λ‘ λ°ν)"""
|
| 182 |
+
if HF_REPO_ID:
|
| 183 |
+
return _hf_download(filename)
|
| 184 |
+
# DuckDBμ©: μμ¬λμ β μ¬λμ λ³ν
|
| 185 |
+
return str(Path(data_dir_str) / filename).replace("\\", "/")
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
@st.cache_data(show_spinner=False)
|
| 189 |
+
def query_kg_edges_for_node(node_id: str, kg_edges_path: str, max_edges: int = 80) -> pd.DataFrame:
|
| 190 |
+
"""DuckDB: νΉμ λ
Έλμ μ£μ§λ§ parquetμμ λ°λ‘ 쿼리 (μ 체 λ‘λ μμ)"""
|
| 191 |
+
import duckdb
|
| 192 |
+
safe_path = kg_edges_path.replace("\\", "/")
|
| 193 |
+
safe_node = node_id.replace("'", "''")
|
| 194 |
+
q = f"""
|
| 195 |
+
SELECT source, target, edge_type
|
| 196 |
+
FROM read_parquet('{safe_path}')
|
| 197 |
+
WHERE source = '{safe_node}' OR target = '{safe_node}'
|
| 198 |
+
LIMIT {int(max_edges)}
|
| 199 |
+
"""
|
| 200 |
+
return duckdb.execute(q).df()
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
@st.cache_data(show_spinner=False)
|
| 204 |
+
def query_enriched_stats(enriched_path: str):
|
| 205 |
+
"""DuckDB: enriched μ 체 λ‘λ μμ΄ μ§κ³ ν΅κ³λ§ 쿼리"""
|
| 206 |
+
import duckdb
|
| 207 |
+
safe_path = enriched_path.replace("\\", "/")
|
| 208 |
+
|
| 209 |
+
sem_df = duckdb.execute(f"""
|
| 210 |
+
SELECT has_semantic_evidence, COUNT(*) AS count
|
| 211 |
+
FROM read_parquet('{safe_path}')
|
| 212 |
+
GROUP BY has_semantic_evidence
|
| 213 |
+
""").df()
|
| 214 |
+
|
| 215 |
+
field_df = duckdb.execute(f"""
|
| 216 |
+
SELECT field_folder AS field,
|
| 217 |
+
AVG(CAST(has_semantic_evidence AS INTEGER)) AS sem_ratio,
|
| 218 |
+
COUNT(*) AS event_count
|
| 219 |
+
FROM read_parquet('{safe_path}')
|
| 220 |
+
GROUP BY field_folder
|
| 221 |
+
ORDER BY sem_ratio DESC
|
| 222 |
+
LIMIT 20
|
| 223 |
+
""").df()
|
| 224 |
+
|
| 225 |
+
return sem_df, field_df
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
@st.cache_data(show_spinner=False)
|
| 229 |
+
def query_explorer_edges(node_id: str, kg_edges_path: str, max_edges: int = 60) -> pd.DataFrame:
|
| 230 |
+
"""DuckDB: KG Explorerμ© μμ λ
Έλ μ£μ§ 쿼리"""
|
| 231 |
+
import duckdb
|
| 232 |
+
safe_path = kg_edges_path.replace("\\", "/")
|
| 233 |
+
safe_node = node_id.replace("'", "''")
|
| 234 |
+
q = f"""
|
| 235 |
+
SELECT source, target, edge_type
|
| 236 |
+
FROM read_parquet('{safe_path}')
|
| 237 |
+
WHERE source = '{safe_node}' OR target = '{safe_node}'
|
| 238 |
+
LIMIT {int(max_edges)}
|
| 239 |
+
"""
|
| 240 |
+
return duckdb.execute(q).df()
|
| 241 |
|
| 242 |
|
| 243 |
# ββ ν¬νΌ βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 552 |
components.html(pyvis_ontology(), height=820, scrolling=True)
|
| 553 |
|
| 554 |
|
| 555 |
+
# βββ 4. KNOWLEDGE GRAPH (μ€μ KG λ°μ΄ν°, DuckDB) ββββββββββββββββ
|
| 556 |
with tab_kg:
|
| 557 |
st.subheader("Knowledge Graph β Selected Seed Paper")
|
| 558 |
+
st.caption("kg_nodes + kg_edgesμμ μ νλ seed paperμ 1-hop μλΈκ·Έλν (DuckDB λΆλΆ 쿼리)")
|
| 559 |
+
st.info("μλ λ²νΌμ λλ¬ KG λ°μ΄ν°λ₯Ό λ‘λνμΈμ. kg_nodesλ§ μ 체 λ‘λ, kg_edgesλ νμν λΆλΆλ§ 쿼리ν©λλ€.")
|
| 560 |
+
|
| 561 |
+
max_edges_kg = st.slider("Max edges", 20, 150, 80, key="kg_max_edges")
|
| 562 |
|
| 563 |
if st.button("KG λ°μ΄ν° λ‘λ", key="kg_load"):
|
| 564 |
+
with st.spinner("kg_nodes λ‘λ© + kg_edges κ²½λ‘ μ€λΉ μ€..."):
|
| 565 |
st.session_state["kg_loaded"] = True
|
| 566 |
|
| 567 |
if st.session_state.get("kg_loaded"):
|
| 568 |
try:
|
| 569 |
+
with st.spinner("kg_nodes λ‘λ© μ€..."):
|
| 570 |
+
kg_nodes = load_kg_nodes(data_dir_val)
|
| 571 |
+
|
| 572 |
seed_doi = selected_seed["doi"]
|
| 573 |
if not seed_doi:
|
| 574 |
st.warning("μ νλ seed paperμ DOIκ° μμ΄ KG μ‘°νκ° λΆκ°ν©λλ€.")
|
| 575 |
else:
|
| 576 |
+
node_id = f"seed:{seed_doi}"
|
| 577 |
+
|
| 578 |
+
with st.spinner("kg_edges 쿼리 μ€ (DuckDB)..."):
|
| 579 |
+
kg_edges_path = get_parquet_path("kg_edges.parquet", data_dir_val)
|
| 580 |
+
edges_sub = query_kg_edges_for_node(node_id, kg_edges_path, max_edges_kg)
|
| 581 |
+
|
| 582 |
+
if edges_sub.empty:
|
| 583 |
+
st.warning(f"KGμμ ν΄λΉ λ
Έλμ μ£μ§λ₯Ό μ°Ύμ μ μμ΅λλ€. (node_id: {node_id})")
|
| 584 |
else:
|
| 585 |
+
all_node_ids = set(edges_sub["source"].tolist()) | set(edges_sub["target"].tolist())
|
| 586 |
+
nodes_sub = kg_nodes[kg_nodes["node_id"].isin(all_node_ids)]
|
| 587 |
+
|
| 588 |
c1, c2, c3 = st.columns(3)
|
| 589 |
c1.metric("Nodes", fmt_num(len(nodes_sub)))
|
| 590 |
c2.metric("Edges", fmt_num(len(edges_sub)))
|
| 591 |
c3.metric("Node types", fmt_num(nodes_sub["node_type"].nunique()))
|
| 592 |
|
| 593 |
type_counts = nodes_sub["node_type"].value_counts().reset_index()
|
| 594 |
+
type_counts.columns = ["node_type", "count"]
|
| 595 |
st.plotly_chart(
|
| 596 |
px.bar(type_counts, x="node_type", y="count",
|
| 597 |
+
color="node_type", color_discrete_map=NODE_TYPE_COLORS,
|
|
|
|
| 598 |
title="Node Type Distribution")
|
| 599 |
.update_layout(showlegend=False, xaxis_title="", yaxis_title="Count"),
|
| 600 |
use_container_width=True)
|
|
|
|
| 608 |
# βββ 5. KG EXPLORER βββββββββββββββββββββββββββββββββββββββββββββ
|
| 609 |
with tab_kg_exp:
|
| 610 |
st.subheader("KG Explorer")
|
| 611 |
+
st.caption("kg_nodesλ₯Ό νμνκ³ μμ λ
Έλμ μ°κ²° κ΄κ³λ₯Ό μκ°νν©λλ€. kg_edgesλ DuckDBλ‘ νμν λΆλΆλ§ 쿼리ν©λλ€.")
|
| 612 |
+
st.info("μλ λ²νΌμ λλ¬ KG λ°μ΄ν°λ₯Ό λ‘λνμΈμ. kg_nodesλ§ μ 체 λ‘λλκ³ , kg_edges/enrichedλ DuckDBλ‘ μΏΌλ¦¬ν©λλ€.")
|
| 613 |
|
| 614 |
if st.button("KG λ°μ΄ν° λ‘λ", key="kg_exp_load"):
|
| 615 |
+
with st.spinner("kg_nodes λ‘λ© μ€..."):
|
| 616 |
st.session_state["kg_loaded"] = True
|
| 617 |
|
| 618 |
if st.session_state.get("kg_loaded"):
|
| 619 |
try:
|
| 620 |
+
with st.spinner("kg_nodes λ‘λ© μ€..."):
|
| 621 |
+
kg_nodes = load_kg_nodes(data_dir_val)
|
| 622 |
+
kg_edges_path = get_parquet_path("kg_edges.parquet", data_dir_val)
|
| 623 |
+
enriched_path = get_parquet_path("citation_events_enriched.parquet", data_dir_val)
|
| 624 |
|
| 625 |
+
# ββ μ 체 λ
Έλ νμ
λΆν¬ (kg_nodesλ§μΌλ‘ κ³μ°)
|
| 626 |
col_a, col_b = st.columns([1,2])
|
| 627 |
with col_a:
|
| 628 |
st.subheader("Node Type Counts")
|
|
|
|
| 630 |
nt.columns = ["node_type","count"]
|
| 631 |
st.dataframe(nt, use_container_width=True, hide_index=True)
|
| 632 |
|
| 633 |
+
# Edge type μ§κ³: DuckDBλ‘ λΉ λ₯΄κ² κ³μ°
|
| 634 |
st.subheader("Edge Type Counts")
|
| 635 |
+
import duckdb
|
| 636 |
+
et = duckdb.execute(f"""
|
| 637 |
+
SELECT edge_type, COUNT(*) AS count
|
| 638 |
+
FROM read_parquet('{kg_edges_path}')
|
| 639 |
+
GROUP BY edge_type ORDER BY count DESC
|
| 640 |
+
""").df()
|
| 641 |
st.dataframe(et, use_container_width=True, hide_index=True)
|
| 642 |
|
| 643 |
with col_b:
|
|
|
|
| 668 |
st.warning("κ²μ κ²°κ³Όκ° μμ΅λλ€.")
|
| 669 |
else:
|
| 670 |
sel_node_id = st.selectbox(
|
| 671 |
+
"Select node", node_options,
|
|
|
|
| 672 |
format_func=lambda nid: sample.loc[sample["node_id"]==nid,"label"].iloc[0][:60],
|
| 673 |
)
|
| 674 |
sel_node_info = sample[sample["node_id"]==sel_node_id].iloc[0]
|
|
|
|
| 679 |
st.markdown(f"**Cited by**: {fmt_num(sel_node_info.get('citedby_count',''))}")
|
| 680 |
|
| 681 |
max_e = st.slider("Max edges shown", 20, 150, 60, key="kg_exp_max")
|
|
|
|
| 682 |
if st.button("Show ego network", key="kg_exp_show"):
|
| 683 |
+
with st.spinner("DuckDBλ‘ μ£μ§ 쿼리 μ€..."):
|
| 684 |
+
exp_edges = query_explorer_edges(sel_node_id, kg_edges_path, max_e)
|
| 685 |
+
if exp_edges.empty:
|
| 686 |
st.warning("μ°κ²°λ μ£μ§κ° μμ΅λλ€.")
|
| 687 |
else:
|
| 688 |
+
all_ids = set(exp_edges["source"].tolist()) | set(exp_edges["target"].tolist())
|
| 689 |
+
exp_nodes = kg_nodes[kg_nodes["node_id"].isin(all_ids)]
|
| 690 |
st.session_state["exp_nodes"] = exp_nodes
|
| 691 |
st.session_state["exp_edges"] = exp_edges
|
| 692 |
|
|
|
|
| 700 |
else:
|
| 701 |
st.info("μΌμͺ½μμ λ
Έλλ₯Ό μ ννκ³ 'Show ego network'λ₯Ό ν΄λ¦νμΈμ.")
|
| 702 |
|
| 703 |
+
# ββ Enriched μΈμ¬μ΄νΈ (DuckDB μ§κ³λ§)
|
| 704 |
st.markdown("---")
|
| 705 |
st.subheader("Enriched Citation Insights")
|
| 706 |
+
st.caption("citation_events_enriched: DuckDBλ‘ μ§κ³ ν΅κ³λ§ 쿼리 (μ 체 λ‘λ μμ)")
|
| 707 |
+
with st.spinner("Enriched ν΅κ³ 쿼리 μ€ (DuckDB)..."):
|
| 708 |
+
sem_df, field_df = query_enriched_stats(enriched_path)
|
| 709 |
+
|
| 710 |
+
if not sem_df.empty:
|
| 711 |
+
sem_df["label"] = sem_df["has_semantic_evidence"].map(
|
| 712 |
+
{True:"With evidence", False:"Without evidence",
|
| 713 |
+
1:"With evidence", 0:"Without evidence"})
|
| 714 |
+
col_s1, col_s2 = st.columns(2)
|
| 715 |
+
with col_s1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 716 |
st.plotly_chart(
|
| 717 |
+
px.pie(sem_df, names="label", values="count",
|
| 718 |
+
title="Semantic Evidence Coverage")
|
| 719 |
+
.update_layout(legend_title=""),
|
|
|
|
| 720 |
use_container_width=True)
|
| 721 |
+
with col_s2:
|
| 722 |
+
if not field_df.empty:
|
| 723 |
+
st.plotly_chart(
|
| 724 |
+
px.bar(field_df, x="field", y="sem_ratio",
|
| 725 |
+
title="Semantic Evidence Rate by Field",
|
| 726 |
+
labels={"sem_ratio":"Evidence Rate","field":"Field"})
|
| 727 |
+
.update_layout(xaxis_tickangle=-40),
|
| 728 |
+
use_container_width=True)
|
| 729 |
|
| 730 |
except Exception as e:
|
| 731 |
st.error(str(e))
|