216 lines
6.7 KiB
Python
216 lines
6.7 KiB
Python
import marimo
|
|
|
|
__generated_with = "0.23.13"
|
|
app = marimo.App(width="medium")
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _():
|
|
import marimo as mo
|
|
|
|
return (mo,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo):
|
|
mo.md("""
|
|
# Semantic search over a docket
|
|
|
|
A notebook-side look at what `LlmClient` (`llm.client`, #573) gives you: the
|
|
same pgvector similarity search the chat UI's `GET /search` endpoint runs,
|
|
called directly from a marimo cell over plain httpx — no `langchain`,
|
|
`sqlalchemy` or `psycopg` required, which matters because the notebooks
|
|
container installs the workspace without the `llm` extra (#720). Pick a
|
|
docket and a query below; the semantic-search hits sit beside the DuckDB
|
|
replica's own comment-analysis table for the same docket, so you can see
|
|
both views of "what commenters said" at once.
|
|
""")
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _():
|
|
# ── Setup ──
|
|
import polars as pl
|
|
|
|
from conf import connect
|
|
from llm.client import LlmClient
|
|
|
|
NOTES = {}
|
|
|
|
def _open_replica():
|
|
try:
|
|
return connect.duckdb("aco", read_only=True)
|
|
except Exception as e: # noqa: BLE001 — degrade, never crash the page
|
|
NOTES["replica"] = f"replica unavailable: {e}"
|
|
return None
|
|
|
|
def _open_bib():
|
|
try:
|
|
return connect.bib()
|
|
except Exception as e: # noqa: BLE001
|
|
NOTES["bib"] = f"bibliography unavailable: {e}"
|
|
return None
|
|
|
|
con = _open_replica()
|
|
store = _open_bib()
|
|
client = LlmClient()
|
|
|
|
def _service_up():
|
|
try:
|
|
return client.health()
|
|
except Exception: # noqa: BLE001 — health() never raises; belt & suspenders
|
|
return False
|
|
|
|
llm_up = _service_up()
|
|
if not llm_up:
|
|
NOTES["llm"] = f"llm service unreachable at {client.base_url}"
|
|
|
|
return NOTES, client, con, llm_up, pl, store
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(mo, store):
|
|
# ── Docket + query pickers ──
|
|
_DEFAULT_DOCKETS = ["CMS-2023-0121", "CMS-2025-0304"]
|
|
|
|
def _docket_options():
|
|
if store is not None:
|
|
try:
|
|
ids = sorted({d.id for d in store.dockets()})
|
|
except Exception: # noqa: BLE001 — fall back to the hard-coded list
|
|
ids = []
|
|
if ids:
|
|
return ids
|
|
return list(_DEFAULT_DOCKETS)
|
|
|
|
_options = _docket_options()
|
|
_default = "CMS-2025-0304" if "CMS-2025-0304" in _options else _options[0]
|
|
docket_picker = mo.ui.dropdown(options=_options, value=_default, label="Docket")
|
|
query_box = mo.ui.text(
|
|
value="telehealth",
|
|
label="Query",
|
|
full_width=True,
|
|
placeholder="a phrase to search for",
|
|
)
|
|
mo.vstack([docket_picker, query_box])
|
|
return docket_picker, query_box
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NOTES, client, docket_picker, llm_up, mo, pl, query_box):
|
|
# ── Semantic search (LlmClient.search) ──
|
|
_docket = docket_picker.value
|
|
_q = (query_box.value or "").strip()
|
|
|
|
search_results = pl.DataFrame()
|
|
if not llm_up:
|
|
search_view = mo.md(
|
|
f"_llm service unreachable at `{client.base_url}` — search skipped. "
|
|
"Set `LLM_URL` or run this inside the notebooks container "
|
|
"(`gateway` network) to reach it._"
|
|
)
|
|
elif not _q:
|
|
search_view = mo.md("_Type a query above to search._")
|
|
else:
|
|
try:
|
|
_hits = client.search(_q, collection="comments", docket=_docket, limit=10)
|
|
except Exception as e: # noqa: BLE001 — degrade, never crash the page
|
|
NOTES["search"] = f"search failed: {e}"
|
|
_hits = []
|
|
if _hits:
|
|
search_results = pl.DataFrame(
|
|
[
|
|
{
|
|
"label": h.get("label", ""),
|
|
"date": h.get("date", ""),
|
|
"snippet": h.get("snippet", ""),
|
|
"url": h.get("url", ""),
|
|
}
|
|
for h in _hits
|
|
]
|
|
)
|
|
search_view = mo.ui.table(
|
|
search_results, label=f"Semantic search — {_docket} ({len(_hits)} hits)"
|
|
)
|
|
else:
|
|
search_view = mo.md(
|
|
f"_No semantic-search hits for `{_q}` in {_docket} — either "
|
|
"nothing matches, or the docket isn't indexed yet "
|
|
"(`stack llm index`)._"
|
|
)
|
|
return search_results, search_view
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NOTES, con, docket_picker, mo, pl):
|
|
# ── DuckDB comments view (skin_subs.rulemaking_comments) ──
|
|
_docket = docket_picker.value
|
|
|
|
duckdb_comments = pl.DataFrame()
|
|
if con is None:
|
|
duckdb_view = mo.md("_replica unavailable — DuckDB comments view skipped._")
|
|
else:
|
|
try:
|
|
duckdb_comments = con.execute(
|
|
"SELECT comment_id, posted_date, organization, position, "
|
|
"text_length, is_form_letter FROM skin_subs.rulemaking_comments "
|
|
"WHERE docket_id = ? ORDER BY posted_date DESC",
|
|
[_docket],
|
|
).pl()
|
|
except Exception as e: # noqa: BLE001 — a missing table is "not built yet"
|
|
NOTES[f"duckdb:{_docket}"] = str(e)
|
|
if duckdb_comments.is_empty():
|
|
duckdb_view = mo.md(
|
|
f"_No `skin_subs.rulemaking_comments` rows for {_docket} yet — "
|
|
"this table only covers dockets run through "
|
|
"`uv run python dev/scripts/classify_comments.py --docket "
|
|
f"{_docket}` (the skin-substitute-relevant subset of a "
|
|
"docket's comments, not every comment on it)._"
|
|
)
|
|
else:
|
|
duckdb_view = mo.ui.table(
|
|
duckdb_comments,
|
|
label=(
|
|
f"DuckDB skin_subs.rulemaking_comments — {_docket} "
|
|
f"({duckdb_comments.height} rows)"
|
|
),
|
|
)
|
|
return (duckdb_view,)
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(docket_picker, duckdb_view, mo, search_view):
|
|
# ── Side by side ──
|
|
mo.hstack(
|
|
[
|
|
mo.vstack(
|
|
[mo.md(f"### Semantic search — {docket_picker.value}"), search_view]
|
|
),
|
|
mo.vstack(
|
|
[mo.md(f"### DuckDB comments — {docket_picker.value}"), duckdb_view]
|
|
),
|
|
],
|
|
widths="equal",
|
|
gap=2,
|
|
)
|
|
return
|
|
|
|
|
|
@app.cell(hide_code=True)
|
|
def _(NOTES, mo):
|
|
# ── Notes ──
|
|
mo.md(
|
|
"### Notes\n\n"
|
|
+ (
|
|
"\n".join(f"- {k}: {v}" for k, v in NOTES.items())
|
|
if NOTES
|
|
else "_All sources available._"
|
|
)
|
|
)
|
|
return
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|