Wire up query history: localStorage session persistence + sidebar UI
This commit is contained in:
@@ -147,12 +147,27 @@ SECTION_PHRASES: dict[str, tuple[str, ...]] = {
|
||||
}
|
||||
|
||||
|
||||
# Book order of monograph sections (Hướng dẫn sử dụng, printed page 39). Used to
|
||||
# present a whole-drug overview when the query names the drug but no attribute —
|
||||
# typing "PARACETAMOL" should return the monograph, never a "specify an
|
||||
# attribute" dead-end.
|
||||
# Book order of monograph sections, verified 2026-08-14 against the actual
|
||||
# PDF (physical page 38 = printed page 39, "HƯỚNG DẪN SỬ DỤNG DƯỢC THƯ QUỐC
|
||||
# GIA VIỆT NAM"), not assumed from an earlier reading of this constant. The
|
||||
# guide numbers 19 items; item 1, "Tên chuyên luận thuốc", is the monograph's
|
||||
# own title/heading, not a content section with a `section_key` — items 2-19
|
||||
# are exactly these 18 keys, in exactly this order. Confirms this tuple was
|
||||
# already complete and correctly ordered for the book's own stated template.
|
||||
#
|
||||
# `ten_thuong_mai` (trade name) is real, present in the corpus (492/684
|
||||
# drugs) but is NOT one of the guide's 19 numbered items — the book's own
|
||||
# template never promises it, so there is no book-verified position to place
|
||||
# it at. Inserted right after `ten_chung_quoc_te` (generic/INN name) as the
|
||||
# most natural adjacency (same convention `understanding.py`'s `SECTION_KEYS`
|
||||
# already uses) — a judgment call, not a sourced fact, unlike the 18 above.
|
||||
#
|
||||
# Used to present a whole-drug overview when the query names the drug but no
|
||||
# attribute — typing "PARACETAMOL" should return the monograph, never a
|
||||
# "specify an attribute" dead-end.
|
||||
SECTION_ORDER: tuple[str, ...] = (
|
||||
"ten_chung_quoc_te",
|
||||
"ten_thuong_mai",
|
||||
"ma_atc",
|
||||
"loai_thuoc",
|
||||
"dang_thuoc_va_ham_luong",
|
||||
@@ -208,3 +223,37 @@ class SectionResolver:
|
||||
if f" {normalized_phrase} " in padded:
|
||||
return SectionMatch(section_key, phrase)
|
||||
return None
|
||||
|
||||
def resolve_all(self, query: str) -> tuple[SectionMatch, ...]:
|
||||
"""Every distinct section a question genuinely names, not just the
|
||||
first. Same longest-first order as `resolve()`, and the same
|
||||
span-claiming rule: once a phrase's occurrence is accepted, any
|
||||
shorter phrase whose only occurrence falls inside that already-
|
||||
claimed span is a substring of it, not a second section — e.g.
|
||||
"chỉ định" inside "chống chỉ định của X" must NOT count as a second,
|
||||
separate mention of `chi_dinh`. A phrase counts only when it has an
|
||||
occurrence that does not overlap any span already claimed by a
|
||||
longer, earlier-accepted phrase. Returns `()` for no match and
|
||||
exactly one item when the question names only one section — this is
|
||||
a superset of `resolve()`, not a replacement for it.
|
||||
"""
|
||||
normalized_query = normalize_name(query)
|
||||
if not normalized_query:
|
||||
return ()
|
||||
padded = f" {normalized_query} "
|
||||
claimed: list[tuple[int, int]] = []
|
||||
seen_sections: set[str] = set()
|
||||
matches: list[SectionMatch] = []
|
||||
for normalized_phrase, section_key, phrase in self._index:
|
||||
needle = f" {normalized_phrase} "
|
||||
idx = padded.find(needle)
|
||||
if idx == -1:
|
||||
continue
|
||||
span = (idx, idx + len(needle))
|
||||
if any(span[0] < c_end and c_start < span[1] for c_start, c_end in claimed):
|
||||
continue
|
||||
claimed.append(span)
|
||||
if section_key not in seen_sections:
|
||||
seen_sections.add(section_key)
|
||||
matches.append(SectionMatch(section_key, phrase))
|
||||
return tuple(matches)
|
||||
|
||||
Reference in New Issue
Block a user