"""Database β€” material-first browser over everything the agent's DB holds. Materials tab: one expandable entry per material (across Polymers, Fibers and Composites_materials), broken down into its properties β€” value, canonical SI, status, source and DOI. Raw tables tab: the classic row-level browser with filters, pagination and CSV export. Columns are discovered dynamically from information_schema, so the page works unchanged against the fresh `aim_agent` clone and against the team's `AIMDatabase` if DB_NAME is ever pointed back at it. """ import pandas as pd import streamlit as st from agent import ui_theme as T from agent.ui_common import status_banner, df_or_empty T.page_header("Database", "Every material the agent has ingested, expandable into its " "properties β€” plus a row-level browser over the raw tables.") status_banner() MATERIAL_TABLES = ("Polymers", "Fibers", "Composites_materials") BROWSABLE = MATERIAL_TABLES + ("sources", "figures") TABLE_ICON = {"Polymers": "πŸ§ͺ", "Fibers": "🧡", "Composites_materials": "🧱"} # Columns the union view exposes (aliased to NULL where a table lacks one). UNION_COLS = [ ("material_key", "text"), ("material_abbreviation", "text"), ("trade_grade", "text"), ("manufacturer", "text"), ("matrix", "text"), ("fiber", "text"), ("material_class", "text"), ("section", "text"), ("property_name", "text"), ("test_condition", "text"), ("value", "text"), ("unit", "text"), ("value_raw", "text"), ("unit_canonical", "text"), ("value_si", "double precision"), ("qualifier", "text"), ("status", "text"), ("source_pdf", "text"), ("page", "integer"), ("origin", "text"), ("figure_id", "text"), # processing route of the specimen (prompt 2.1; NULL on older rows) ("process_type", "text"), ("process_name", "text"), ("process_conditions", "text"), ("process_status", "text"), ("extracted_at", "text"), ] PREFERRED_COLS = [ "material_abbreviation", "material_key", "material_class", "trade_grade", "manufacturer", "matrix", "fiber", "section", "property_name", "test_condition", "process_type", "process_conditions", "value", "value_raw", "unit", "unit_canonical", "value_si", "qualifier", "status", "process_status", "origin", "source_pdf", "page", "doi_url", "extracted_at", ] SEARCH_COLS = [ "material_abbreviation", "material_key", "trade_grade", "manufacturer", "matrix", "fiber", "section", "property_name", "test_condition", "process_type", "process_name", "process_conditions", "source_pdf", "english", "value", "value_raw", "doi_url", "pdf_filename", "title", "doi", "url", "caption", "figure_kind", "figure_id", ] _q = lambda ident: '"' + ident.replace('"', "") + '"' # Markdown-special characters that must be backslash-escaped when a DB string # is placed in a widget label (labels are markdown, not HTML β€” a material # called ":red[x]" or "*a*" must render literally). _MD_SPECIAL = set("\\`*_{}[]()#+!|<>~:$^") def _md(x) -> str: """NaN/None-proof markdown-escaped string for widget labels.""" s = "" if x is None or (isinstance(x, float) and pd.isna(x)) else str(x) return "".join(("\\" + ch) if ch in _MD_SPECIAL else ch for ch in s) def _plural(n: int, word: str, plural: str = "") -> str: """'1 source' / '3 sources' / '2 properties'.""" n = int(n) return f"{n:,} {word if n == 1 else (plural or word + 's')}" def _count_line(n: int, noun: str, page: int, pages: int) -> None: """'4 materials match Β· page 1 of 1' as an inline count line.""" st.html(f'
{int(n):,} ' f'{noun if int(n) == 1 else noun + "s"} match Β· ' f'page {int(page):,} of {int(pages):,}
') # Property-table columns: fixed sensible widths (values stay exactly as built). PROP_COL_CONFIG = { "Section": st.column_config.TextColumn("Section", width=105), "Property": st.column_config.TextColumn("Property", width=215), "Condition": st.column_config.TextColumn("Condition", width=100), "Process": st.column_config.TextColumn( "Process", width=250, help="How the tested specimen was made (processing route, as the " "source states it). ⚠ = the sentence naming the route, or a " "number in its conditions, was not found in the PDF text."), "Process conditions": st.column_config.TextColumn( "Process conditions", width=230, help="Processing parameters as printed in the source"), "Value": st.column_config.TextColumn("Value", width=145), "SI": st.column_config.TextColumn("SI", width=125, help="Canonical SI value (when known)"), "": st.column_config.TextColumn("Status", width=70, alignment="center", help="βœ“ verified Β· 🚩 flagged (Review Queue) Β· " "πŸ“ˆ figure-derived estimate (quarantined)"), "Source": st.column_config.TextColumn("Source", width=205, help="Source PDF Β· page"), "DOI": st.column_config.LinkColumn("DOI", display_text=r"https://doi\.org/(.*)"), } @st.cache_data(ttl=60) def columns_of(table: str) -> list[str]: df = df_or_empty( "SELECT column_name FROM information_schema.columns " "WHERE table_schema = 'public' AND table_name = %s " "ORDER BY ordinal_position", (table,)) return df["column_name"].tolist() if not df.empty else [] def union_cte() -> str: """WITH u AS (...) β€” all material tables unified, mat_key computed.""" selects = [] for t in MATERIAL_TABLES: have = set(columns_of(t)) if not have: continue cols = ", ".join( _q(c) if c in have else f"NULL::{typ} AS {_q(c)}" for c, typ in UNION_COLS) selects.append(f"SELECT '{t}' AS tbl, {cols} FROM {_q(t)}") if not selects: return "" return ("WITH raw AS (" + " UNION ALL ".join(selects) + "), " "u AS (SELECT *, COALESCE(NULLIF(material_key, ''), " "NULLIF(material_abbreviation, ''), NULLIF(trade_grade, ''), " "'(unlabeled)') AS mat_key FROM raw)") CTE = union_cte() tab_mat, tab_raw = st.tabs(["πŸ§ͺ Materials", "πŸ“‹ Raw tables"]) # =========================================================================== # MATERIALS β€” one expandable entry per material, properties inside # =========================================================================== with tab_mat: if not CTE: st.error("No material tables found in this database.") st.stop() totals = df_or_empty( f"{CTE} SELECT count(DISTINCT (tbl, mat_key)) AS materials, " f"count(*) AS props, " f"count(*) FILTER (WHERE COALESCE(status,'ok')='ok') AS verified, " f"count(DISTINCT source_pdf) FILTER (WHERE source_pdf IS NOT NULL) AS docs, " f"count(*) FILTER (WHERE COALESCE(process_type,'') <> '') AS with_process " f"FROM u") n_mat = 0 if not totals.empty: n_mat = int(totals["materials"].iloc[0]) n_props = int(totals["props"].iloc[0]) n_verified = int(totals["verified"].iloc[0]) n_flagged = max(0, n_props - n_verified) T.kpi_row([ {"label": "Materials", "value": n_mat, "help": "Distinct materials across Polymers, Fibers and Composites"}, {"label": "Property rows", "value": n_props}, {"label": "Verified", "value": n_verified, "delta": (f"{n_flagged:,} flagged for review" if n_flagged else ("all rows verified" if n_props else None)), "delta_kind": "warn" if n_flagged else "up", "help": "Grounded in the PDF, unit-sane and plausible"}, {"label": "Source documents", "value": int(totals["docs"].iloc[0])}, {"label": "With processing route", "value": int(totals["with_process"].iloc[0]), "delta": (f"{100 * int(totals['with_process'].iloc[0]) / n_props:.0f}% of rows" if n_props else None), "delta_kind": "flat", "help": "Rows that carry the process type and conditions of the " "specimen. Extracted since prompt 2.1; a row stays empty " "when its source does not say how the material was made."}, ]) else: T.kpi_row([{"label": "Materials", "value": "β€”"}, {"label": "Property rows", "value": "β€”"}, {"label": "Verified", "value": "β€”"}, {"label": "Source documents", "value": "β€”"}, {"label": "With processing route", "value": "β€”"}]) with st.container(border=True): T.card_title("Find materials", "search, filter and sort the material list") f1, f2, f3, f5, f4 = st.columns([3, 2, 2, 2, 2]) m_search = f1.text_input( "Search materials", key="mat_search", placeholder="name, key, grade, manufacturer, matrix, fiber…") m_class = f2.selectbox("Type", ["(all)"] + list(MATERIAL_TABLES), key="mat_class") props_all = df_or_empty( f"{CTE} SELECT property_name, count(*) AS n FROM u " f"WHERE property_name IS NOT NULL AND property_name <> '' " f"GROUP BY 1 ORDER BY n DESC, 1 LIMIT 300") m_prop = f3.selectbox( "Has property", ["(any)"] + (props_all["property_name"].tolist() if not props_all.empty else []), key="mat_prop") procs_all = df_or_empty( f"{CTE} SELECT process_type, count(*) AS n FROM u " f"WHERE COALESCE(process_type, '') <> '' " f"GROUP BY 1 ORDER BY n DESC, 1 LIMIT 50") m_proc = f5.selectbox( "Process", ["(any)"] + (procs_all["process_type"].tolist() if not procs_all.empty else []), key="mat_proc", help="Materials with at least one property measured on a " "specimen made by this processing route") m_sort = f4.selectbox("Sort by", ["Most properties", "Name Aβ†’Z", "Recently updated"], key="mat_sort") where, params = [], [] if m_class != "(all)": where.append("tbl = %s") params.append(m_class) if m_search: name_cols = ["mat_key", "material_abbreviation", "trade_grade", "manufacturer", "matrix", "fiber"] where.append("(" + " OR ".join( f"COALESCE({c}, '') ILIKE %s" for c in name_cols) + ")") params.extend([f"%{m_search}%"] * len(name_cols)) if m_prop != "(any)": where.append("(tbl, mat_key) IN " "(SELECT tbl, mat_key FROM u WHERE property_name = %s)") params.append(m_prop) if m_proc != "(any)": where.append("(tbl, mat_key) IN " "(SELECT tbl, mat_key FROM u WHERE process_type = %s)") params.append(m_proc) where_sql = (" WHERE " + " AND ".join(where)) if where else "" order_sql = {"Most properties": "props DESC, display_name ASC", "Name Aβ†’Z": "display_name ASC", "Recently updated": "updated DESC NULLS LAST"}[m_sort] mat_count = df_or_empty( f"{CTE} SELECT count(DISTINCT (tbl, mat_key)) AS n FROM u{where_sql}", tuple(params)) n_materials = int(mat_count["n"].iloc[0]) if not mat_count.empty else 0 p1, p2, p3 = st.columns([1, 1, 4], vertical_alignment="bottom") m_psize = p1.selectbox("Materials / page", [10, 20, 50], index=1, key="mat_psize") m_pages = max(1, -(-n_materials // m_psize)) m_page = p2.number_input("Page", 1, m_pages, 1, key="mat_page") with p3: _count_line(n_materials, "material", m_page, m_pages) mats = df_or_empty( f"{CTE} SELECT tbl, mat_key, " f" min(COALESCE(NULLIF(material_abbreviation,''), NULLIF(trade_grade,''), mat_key)) AS display_name, " f" string_agg(DISTINCT NULLIF(material_class,''), ', ') AS mclass, " f" string_agg(DISTINCT NULLIF(trade_grade,''), ', ') AS grades, " f" string_agg(DISTINCT NULLIF(manufacturer,''), ', ') AS makers, " f" string_agg(DISTINCT NULLIF(matrix,''), ', ') AS matrices, " f" string_agg(DISTINCT NULLIF(fiber,''), ', ') AS fibers, " f" string_agg(DISTINCT NULLIF(process_type,''), ', ') AS processes, " f" count(*) AS props, count(DISTINCT property_name) AS distinct_props, " f" count(*) FILTER (WHERE COALESCE(status,'ok')='ok') AS verified, " f" count(DISTINCT source_pdf) FILTER (WHERE source_pdf IS NOT NULL) AS docs, " f" max(extracted_at) AS updated " f"FROM u{where_sql} GROUP BY tbl, mat_key " f"ORDER BY {order_sql} LIMIT %s OFFSET %s", tuple(params) + (m_psize, (m_page - 1) * m_psize)) if mats.empty: if n_mat == 0: T.empty_state("πŸ—„οΈ", "The database is empty so far", "Every material the agent ingests will appear here, " "expandable into its properties, values, SI " "conversions, sources and DOIs.") else: T.empty_state("πŸ”Ž", "No materials match the current filters", "Try a shorter search term, another type, or clear " "the property filter.") else: # ONE query for every property row on this page, split in pandas. pair_sql = " OR ".join(["(tbl = %s AND mat_key = %s)"] * len(mats)) pair_params = [x for _, r in mats.iterrows() for x in (r["tbl"], r["mat_key"])] detail = df_or_empty( f"{CTE} SELECT * FROM u WHERE {pair_sql} " f"ORDER BY section NULLS LAST, property_name, test_condition", tuple(pair_params)) prov = df_or_empty("SELECT filename, doi, title FROM agent_doi_seen") if not detail.empty and not prov.empty: detail = detail.merge(prov, how="left", left_on="source_pdf", right_on="filename") if "doi" not in detail.columns: detail["doi"] = "" def _s(x) -> str: """NaN/None-proof string.""" return "" if x is None or (isinstance(x, float) and pd.isna(x)) else str(x) for _, m in mats.iterrows(): icon = TABLE_ICON.get(m["tbl"], "πŸ”¬") mclass = _s(m["mclass"]) or m["tbl"].replace("_", " ").lower() n_dp, n_rows = int(m["distinct_props"]), int(m["props"]) n_ok, n_docs = int(m["verified"]), int(m["docs"]) n_flag = max(0, n_rows - n_ok) # Expander labels are markdown (colored text + badges), not HTML. bits = [_plural(n_dp, "property", "properties"), f"{n_ok} βœ“"] if n_flag: bits.append(f"{n_flag} 🚩") if n_docs: bits.append(_plural(n_docs, "source")) label = (f"{icon} **{_md(m['display_name'])}** " f":blue-background[{_md(mclass)}] " f":gray[{' Β· '.join(bits)}]") with st.expander(label): # Rich header row (HTML) β€” name, class, status, provenance. meta_bits = [f"{n_rows:,} property row{'s' if n_rows != 1 else ''}"] if n_docs: meta_bits.append(f"{n_docs:,} source document{'s' if n_docs != 1 else ''}") else: meta_bits.append("no source document recorded") if _s(m["updated"]): meta_bits.append("updated " + T.esc(_s(m["updated"])[:16].replace("T", " "))) head = [f'{T.esc(m["display_name"])}', T.chip_html(f"{n_ok:,} verified", "good" if n_ok else "muted", "grounded in the PDF, unit-sane, plausible")] if n_flag: head.append(T.chip_html(f"{n_flag:,} flagged", "warn", "quarantined in the Review Queue")) head.append(f'{" Β· ".join(meta_bits)}') st.html(f'
{"".join(head)}
') meta = [] if m["mat_key"] != m["display_name"]: meta.append(("key", _s(m["mat_key"]), "muted")) if _s(m["grades"]): meta.append(("grade", _s(m["grades"]), "muted")) if _s(m["makers"]): meta.append(("manufacturer", _s(m["makers"]), "muted")) if _s(m["matrices"]): meta.append(("matrix", _s(m["matrices"]), "muted")) if _s(m["fibers"]): meta.append(("fiber", _s(m["fibers"]), "muted")) if _s(m["processes"]): meta.append(("process", _s(m["processes"]), "muted")) if meta: st.html(T.chips_html(meta)) rows = detail[(detail["tbl"] == m["tbl"]) & (detail["mat_key"] == m["mat_key"])].copy() if rows.empty: st.html('
No property rows.
') continue raw_val = rows.get("value_raw", pd.Series("", index=rows.index)).fillna("") plain = (rows.get("value", pd.Series("", index=rows.index)).fillna("").astype(str) + " " + rows.get("unit", pd.Series("", index=rows.index)).fillna("").astype(str)).str.strip() value_col = raw_val.astype(str).where(raw_val.astype(str) != "", plain) qual = rows.get("qualifier", pd.Series("", index=rows.index)).fillna("") value_col = (qual.astype(str) + " " + value_col).str.strip() si = pd.Series("", index=rows.index, dtype=object) if "value_si" in rows and "unit_canonical" in rows: ok_si = rows["value_si"].notna() if ok_si.any(): # empty float64 selection would stay float64 si.loc[ok_si] = ( rows.loc[ok_si, "value_si"].astype(float).map(lambda v: f"{v:g}") + " " + rows.loc[ok_si, "unit_canonical"].fillna("").astype(str) ).str.strip() pg = rows.get("page", pd.Series(index=rows.index, dtype=object)) src = rows.get("source_pdf", pd.Series("", index=rows.index)).fillna("") src = src.astype(str) + pg.map( lambda p: f" Β· p.{int(p)}" if pd.notna(p) else "") # Processing route: normalized type, with the printed name # when it says more; ⚠ when the route did not ground. def _col(name): return rows.get(name, pd.Series("", index=rows.index)).fillna("").astype(str) p_type, p_name, p_stat = _col("process_type"), _col("process_name"), _col("process_status") proc_col = [ "" if not t_ else (t_ + (f" ({n_})" if n_ and n_.lower() not in t_.lower() else "") + ("" if s_ in ("grounded", "grounded_off_page") else " ⚠")) for t_, n_, s_ in zip(p_type, p_name, p_stat)] out = pd.DataFrame({ "Section": rows.get("section", pd.Series("", index=rows.index)).fillna(""), "Property": rows["property_name"].fillna(""), "Condition": rows.get("test_condition", pd.Series("", index=rows.index)).fillna(""), "Process": proc_col, "Process conditions": _col("process_conditions"), "Value": value_col, "SI": si, "": [("βœ“" if _s(st_) in ("", "ok") else ("πŸ“ˆ" if _s(og) == "figure" else "🚩")) for st_, og in zip( rows.get("status", pd.Series("ok", index=rows.index)).fillna("ok"), rows.get("origin", pd.Series("text", index=rows.index)).fillna("text"))], "Source": src, "DOI": rows["doi"].fillna("").map( lambda d: f"https://doi.org/{d}" if d else ""), }) if (out["Section"] == "").all(): out = out.drop(columns=["Section"]) # Rows from before prompt 2.1, or sources that state no route. if (out["Process"] == "").all(): out = out.drop(columns=["Process", "Process conditions"]) st.dataframe( out, column_config={k: v for k, v in PROP_COL_CONFIG.items() if k in out.columns}, hide_index=True, width="stretch", height=min(37 + 35 * len(out), 420)) st.html('
βœ“ = verified (grounded in the PDF, ' 'unit-sane, plausible) Β· 🚩 = flagged, quarantined in the ' 'Review Queue Β· πŸ“ˆ = figure-derived estimate (read off a ' 'plot/table image; quarantined until promoted). ' 'Process = how the tested specimen was made, with its ' 'processing conditions as printed in the source; empty when ' 'the source does not say.
') # =========================================================================== # RAW TABLES β€” classic row-level browser # =========================================================================== with tab_raw: counts = {} for t in BROWSABLE: df = df_or_empty(f"SELECT count(*) AS n FROM {_q(t)}") counts[t] = int(df["n"].iloc[0]) if not df.empty else 0 T.kpi_row([{"label": t.replace("_", " "), "value": counts[t]} for t in BROWSABLE], small=True) with st.container(border=True): T.card_title("Browse rows", "pick a table, then filter and page through it") tc, fc1, fc2, fc3, fc4 = st.columns([2, 3, 2, 2, 2]) table = tc.selectbox("Table", BROWSABLE, index=0, key="raw_table") have = columns_of(table) if not have: st.error(f"Table {table} not found in this database.") st.stop() search = fc1.text_input("Search", key="raw_search", placeholder="material, property, PDF, DOI…", help="Case-insensitive match across the text columns") where, params = [], [] if "property_name" in have: props = df_or_empty( f"SELECT property_name, count(*) AS n FROM {_q(table)} " f"WHERE property_name IS NOT NULL AND property_name <> '' " f"GROUP BY 1 ORDER BY n DESC, 1 LIMIT 300") prop_opts = ["(all)"] + props["property_name"].tolist() if not props.empty else ["(all)"] prop = fc2.selectbox("Property", prop_opts, index=0, key="raw_prop") if prop != "(all)": where.append("property_name = %s") params.append(prop) if "material_class" in have: classes = df_or_empty( f"SELECT DISTINCT material_class FROM {_q(table)} " f"WHERE material_class IS NOT NULL AND material_class <> '' " f"ORDER BY 1 LIMIT 100") cls_opts = ["(all)"] + classes["material_class"].tolist() if not classes.empty else ["(all)"] cls = fc3.selectbox("Class", cls_opts, index=0, key="raw_class") if cls != "(all)": where.append("material_class = %s") params.append(cls) if "status" in have: stat = fc4.selectbox("Status", ["(all)", "ok (verified)", "flagged"], index=0, key="raw_status") if stat == "ok (verified)": where.append("COALESCE(status, 'ok') = 'ok'") elif stat == "flagged": where.append("COALESCE(status, 'ok') <> 'ok'") searchable = [c for c in SEARCH_COLS if c in have] if search and searchable: where.append("(" + " OR ".join(f"{_q(c)}::text ILIKE %s" for c in searchable) + ")") params.extend([f"%{search}%"] * len(searchable)) where_sql = (" WHERE " + " AND ".join(where)) if where else "" total_df = df_or_empty(f"SELECT count(*) AS n FROM {_q(table)}{where_sql}", tuple(params)) total = int(total_df["n"].iloc[0]) if not total_df.empty else 0 pc1, pc2, pc3 = st.columns([1, 1, 4], vertical_alignment="bottom") page_size = pc1.selectbox("Rows / page", [25, 50, 100, 250], index=1, key="raw_psize") n_pages = max(1, -(-total // page_size)) page_no = pc2.number_input("Page", 1, n_pages, 1, key="raw_page") with pc3: _count_line(total, "row", page_no, n_pages) order = [f"{_q(c)} DESC NULLS LAST" for c in ("extracted_at",) if c in have] if "id" in have: order.append(f"{_q('id')} DESC") order_sql = (" ORDER BY " + ", ".join(order)) if order else "" # Binary columns (figure crop bytes) never enter the dataframe β€” show the # stored size instead. Everything else keeps SELECT * semantics. _BINARY_COLS = {"image_bytes"} if _BINARY_COLS & set(have): select_sql = ", ".join( f"(octet_length({_q(c)}) / 1024)::int AS {_q(c[:-6] + '_kb')}" if c in _BINARY_COLS else _q(c) for c in have) else: select_sql = "*" rows = df_or_empty( f"SELECT {select_sql} FROM {_q(table)}{where_sql}{order_sql} LIMIT %s OFFSET %s", tuple(params) + (page_size, (page_no - 1) * page_size)) if rows.empty: if counts.get(table, 0) == 0: T.empty_state("πŸ“‹", f"{table.replace('_', ' ')} has no rows yet", "Nothing has been ingested into this table so far β€” " "start a cycle from Run Control or check back after " "the next scheduled run.") else: T.empty_state("πŸ”Ž", "No rows match the current filters", "Try a shorter search term or reset the property, " "class and status filters.") else: _FIG_PREFERRED = ["source_pdf", "page", "figure_kind", "caption", "mining_status", "n_values", "material_key", "route", "image_kb", "width_px", "height_px", "figure_id"] _pref = _FIG_PREFERRED if table == "figures" else PREFERRED_COLS default_cols = [c for c in _pref if c in rows.columns] or list(rows.columns) with st.expander("Columns", expanded=False): chosen = st.multiselect("Show columns", list(rows.columns), default=default_cols, key="raw_cols") show = rows[chosen] if chosen else rows[default_cols] col_config = {} if "doi_url" in show.columns: col_config["doi_url"] = st.column_config.LinkColumn( "DOI", display_text=r"https://doi\.org/(.*)") if "extracted_at" in show.columns: show = show.assign( extracted_at=show["extracted_at"].astype(str).str.slice(0, 16).str.replace("T", " ")) st.dataframe(show, column_config=col_config, hide_index=True, width="stretch", height=min(37 + 35 * len(show), 520)) EXPORT_CAP = 5000 export = df_or_empty( f"SELECT {select_sql} FROM {_q(table)}{where_sql}{order_sql} LIMIT {EXPORT_CAP}", tuple(params)) st.download_button( f"⬇️ Download filtered CSV ({min(total, EXPORT_CAP):,} rows" + (f", capped at {EXPORT_CAP:,}" if total > EXPORT_CAP else "") + ")", export.to_csv(index=False).encode(), file_name=f"{table.lower()}_export.csv", mime="text/csv", key="raw_dl") if "property_name" in have: with st.expander("Property summary (current filters)"): summary = df_or_empty( f"SELECT property_name AS property, count(*) AS rows_, " f" count(*) FILTER (WHERE COALESCE(status,'ok')='ok') AS verified " f"FROM {_q(table)}{where_sql} GROUP BY 1 ORDER BY rows_ DESC LIMIT 200", tuple(params)) if summary.empty: st.html('
No properties yet.
') else: st.dataframe( summary.rename(columns={"rows_": "rows"}), column_config={ "property": st.column_config.TextColumn("Property", width="large"), "rows": st.column_config.NumberColumn("Rows", width="small", format="%d"), "verified": st.column_config.NumberColumn("Verified", width="small", format="%d"), }, hide_index=True, width="stretch") st.html('
Read-only view of the connected database ' '(the one the DB_NAME secret points at). Rows arrive via the ' 'agent\'s ingest step or any batch_ingest.py --pg run.
')