feat: short / long / clinical, and stop broad topics retrieving index lines
Renames the middle view to Short and puts it first: it is the quickest way to tell whether this is the article you wanted, and the full text is one click away. Existing generated articles were migrated in place. The prompt now asks for bullets that each carry a fact, because "X is important to recognise" is a bullet that survives revision and teaches nothing. The retrieval bug that made the last run mostly skips The prose filter — drop chunks under 200 characters, since they are headings and index lines — ran *after* taking the top fourteen hits. A broad query like "Immunodeficiency" or a specialty name matches chapter titles first, so all fourteen were index lines and the filter left nothing: the topic was skipped as having no source material when the library holds plenty. Retrieval now asks for five times what it needs and keeps the first passages that are actually prose. Immunodeficiency went from 0 passages to 14, Pediatric Cardiology 0 to 14. That is the same mistake the folder filter has a comment warning about — filter inside the ranking, not after it — made two functions later. Two things I got wrong and corrected rather than worked around: a `LIKE '%key_points%'` check reported the migration had failed, when `_` is a single-character wildcard and it was matching the title "Key points"; and a variant count showing no Short sections was taken against the old image, where Short was not yet a known variant and was being coerced to Long. 203 backend, 234 frontend green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
parent
3980ecb7f8
commit
e1b580386c
5 changed files with 29 additions and 16 deletions
|
|
@ -3,8 +3,8 @@
|
|||
Three things live here because they are decisions rather than plumbing, and each
|
||||
one has a failure mode worth naming.
|
||||
|
||||
**Variants.** One article is read three ways — the full text, the key points, and
|
||||
the clinical view that carries management and doses. They are one article rather
|
||||
**Variants.** One article is read three ways — short, long, and the clinical view
|
||||
that carries management and doses. They are one article rather
|
||||
than three because they describe one condition: split into three rows they drift
|
||||
apart, and a question linked to "Bronchiolitis" would have to pick which of the
|
||||
three it meant. Each section carries the variant it belongs to.
|
||||
|
|
@ -28,7 +28,10 @@ from app.models.article import Article, ArticleRevision, ArticleSlug
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VARIANTS = ("long", "key_points", "clinical")
|
||||
# Short first: it is the fastest way to tell whether this is the article you
|
||||
# wanted. Long is one click away and is what an article written before variants
|
||||
# existed becomes, since that is what it was.
|
||||
VARIANTS = ("short", "long", "clinical")
|
||||
DEFAULT_VARIANT = "long"
|
||||
|
||||
# [[7|Febrile seizures]] — id first, because the id is the part that must not
|
||||
|
|
|
|||
|
|
@ -66,8 +66,9 @@ Produce three views of the same topic:
|
|||
Typical headings: Definition, Epidemiology, Etiology, Pathophysiology,
|
||||
Clinical features, Diagnostics, Differential diagnosis, Treatment,
|
||||
Complications, Prevention. Use only the ones the passages support.
|
||||
2. "key_points" — one section titled "Key points", 6 to 10 short bullets a
|
||||
learner could revise from the night before an exam.
|
||||
2. "short" — one section titled "In short", 6 to 10 tight bullets a learner
|
||||
could revise from the night before an exam. Every bullet must carry a fact:
|
||||
no bullet that only says a topic is important.
|
||||
3. "clinical" — one or two sections covering management at the bedside:
|
||||
what to do, in what order, with drug doses and routes where the passages give
|
||||
them. Include units and per-kilogram dosing exactly as stated. If a dose is
|
||||
|
|
@ -78,7 +79,7 @@ Return ONLY valid JSON, no markdown fence:
|
|||
{{
|
||||
"summary": "one or two sentences, no heading",
|
||||
"long": [{{"title": "Definition", "content": "markdown"}}],
|
||||
"key_points": [{{"title": "Key points", "content": "- bullet\\n- bullet"}}],
|
||||
"short": [{{"title": "In short", "content": "- bullet\\n- bullet"}}],
|
||||
"clinical": [{{"title": "Management", "content": "markdown"}}]
|
||||
}}
|
||||
|
||||
|
|
@ -161,7 +162,7 @@ def write_article(db: Session, topic: str, category_id: int | None = None,
|
|||
|
||||
sections = _unique_slugs([
|
||||
*_sections(data.get("long"), "long"),
|
||||
*_sections(data.get("key_points"), "key_points"),
|
||||
*_sections(data.get("short"), "short"),
|
||||
*_sections(data.get("clinical"), "clinical"),
|
||||
])
|
||||
if not sections:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,11 @@ DEFAULT_LIMIT = 12
|
|||
# They rank well against a query that looks like a chapter title and then fill
|
||||
# the shortlist with nothing — a topic named after a shelf retrieves ten of them.
|
||||
MIN_PROSE_CHARS = 200
|
||||
# Ask for more than we need, because the prose filter runs after ranking. A
|
||||
# broad query — "Immunodeficiency", a specialty name — matches chapter titles
|
||||
# and index lines first, and filtering inside a shortlist of fourteen left
|
||||
# nothing at all.
|
||||
POOL_MULTIPLIER = 5
|
||||
|
||||
|
||||
TIMEOUT = 20
|
||||
|
|
@ -119,7 +124,7 @@ def search(query: str, limit: int = DEFAULT_LIMIT,
|
|||
"collectionName": settings.CLINICAL_MILVUS_COLLECTION,
|
||||
"data": [vector],
|
||||
"annsField": "dense",
|
||||
"limit": limit,
|
||||
"limit": limit * POOL_MULTIPLIER,
|
||||
"outputFields": ["payload", "folder_path", "file_path", "chunk_index"],
|
||||
}
|
||||
# Filtering in the query is what keeps the shelf constraint honest: taking
|
||||
|
|
@ -150,6 +155,8 @@ def search(query: str, limit: int = DEFAULT_LIMIT,
|
|||
"source": _source_of(raw),
|
||||
"folder": _tidy(hit.get("folder_path")),
|
||||
})
|
||||
if len(results) >= limit:
|
||||
break
|
||||
return results
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,14 +46,16 @@ export function Markdown({ children, attemptId }) {
|
|||
* links and the pane keeps it to itself; everything else — what is open, the
|
||||
* rail, the mobile drawer — belongs to this reader alone.
|
||||
*/
|
||||
// One topic, three readings. The full article is what you study from; the key
|
||||
// points are what you revise from; the clinical view is what you act from at the
|
||||
// bedside. They are views of one article rather than three articles, so the
|
||||
// numbers cannot drift apart and a question linked to the topic still means one
|
||||
// thing.
|
||||
// One topic, three readings. Short is what you revise from, long is what you
|
||||
// study from, clinical is what you act from at the bedside. They are views of
|
||||
// one article rather than three articles, so the numbers cannot drift apart and
|
||||
// a question linked to the topic still means one thing.
|
||||
//
|
||||
// Short leads because it is the quickest way to tell whether this is the article
|
||||
// you wanted; the full text is one click away.
|
||||
const VIEWS = [
|
||||
{ key: 'long', label: 'Article' },
|
||||
{ key: 'key_points', label: 'Key points' },
|
||||
{ key: 'short', label: 'Short' },
|
||||
{ key: 'long', label: 'Long' },
|
||||
{ key: 'clinical', label: 'Clinical' },
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const queue = {
|
|||
],
|
||||
machine_drafts: [
|
||||
{ id: 2, slug: 'intussusception', title: 'Intussusception', status: 'draft',
|
||||
generated_by: 'clinical-library:claude', variants: ['long', 'key_points', 'clinical'] },
|
||||
generated_by: 'clinical-library:claude', variants: ['short', 'long', 'clinical'] },
|
||||
],
|
||||
published_without_references: [],
|
||||
published_without_questions: [
|
||||
|
|
|
|||
Loading…
Reference in a new issue