diff --git a/backend/app/services/article_service.py b/backend/app/services/article_service.py index 764d1f2..4ad4763 100644 --- a/backend/app/services/article_service.py +++ b/backend/app/services/article_service.py @@ -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 diff --git a/backend/app/services/article_writer.py b/backend/app/services/article_writer.py index 52ee1b0..ef11fb6 100644 --- a/backend/app/services/article_writer.py +++ b/backend/app/services/article_writer.py @@ -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: diff --git a/backend/app/services/clinical_library.py b/backend/app/services/clinical_library.py index b1bc849..1c781c0 100644 --- a/backend/app/services/clinical_library.py +++ b/backend/app/services/clinical_library.py @@ -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 diff --git a/frontend/src/components/ArticleReader.jsx b/frontend/src/components/ArticleReader.jsx index ef2301b..0dabe50 100644 --- a/frontend/src/components/ArticleReader.jsx +++ b/frontend/src/components/ArticleReader.jsx @@ -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' }, ] diff --git a/frontend/src/pages/EditorialPage.test.jsx b/frontend/src/pages/EditorialPage.test.jsx index 5333862..01435ee 100644 --- a/frontend/src/pages/EditorialPage.test.jsx +++ b/frontend/src/pages/EditorialPage.test.jsx @@ -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: [