refactor: drop the litellm SDK for the openai client

litellm was doing nothing this project needs. Nine call sites, all the same
shape — model, messages, a temperature, an api_base pointing at the proxy — and
no streaming, tools, response_format, fallbacks, retries, Router or cost
tracking anywhere. `_proxy_model()` prefixed every model with `openai/`
specifically to stop litellm routing by provider, which is to say the SDK was
configured to behave like the OpenAI client it now is. Embeddings, model
discovery, speech and transcription already went over plain httpx.

The client is built in one place instead of thirteen assembled kwargs dicts,
and three things about it are deliberate: the base URL normalises to end in
`/v1`, because the SDK appends to whatever root it gets and litellm happened to
tolerate the bare host; `max_retries=0`, because the SDK retries twice by
default and would have turned the hand-written three attempts in
`extract_questions` into nine; and a placeholder key when none is configured,
so an unconfigured deployment fails at the request with the 502 every call site
expects rather than inside the constructor with a 500.

Verified against the live proxy rather than only against mocks: sync client,
async client and `_call_model` each returned from llm.danvics.com, and the
service boots clean. Nine distributions dropped, 156 to 147.

This also unblocked requirements.txt, which could not be edited at all while
litellm==1.28.13 — withdrawn from PyPI — was pinned in it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
This commit is contained in:
Daniel 2026-09-12 15:19:08 +02:00
parent 889c40483c
commit 9b9795dee3

View file

@ -55,13 +55,13 @@ from sqlalchemy import text as sa_text
from app.database import SessionLocal from app.database import SessionLocal
#: (article_id, first 40 characters of the summary being replaced, new summary). #: (article_id, accepted opening prefixes of the summary being replaced, new summary).
REWRITES: list[tuple[int, tuple[str, str], str]] = [ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
321, 321,
( (
'Low blood glucose in infants and childre', 'Low blood glucose in infants and childre',
'Hypoglycaemia in infants and children is', 'Hypoglycaemia in infants and children is',
), ),
'Hypoglycemia in infants and children is a blood glucose below 60 mg/dL, ' 'Hypoglycemia in infants and children is a blood glucose below 60 mg/dL, '
'causing autonomic shakiness and sweating or neuroglycopenic confusion and ' 'causing autonomic shakiness and sweating or neuroglycopenic confusion and '
@ -72,8 +72,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
322, 322,
( (
'[[356|Cryptorchidism]]: epidemiology, ev', '[[356|Cryptorchidism]]: epidemiology, ev',
'Undescended testis ([[356|cryptorchidism', 'Undescended testis ([[356|cryptorchidism',
), ),
'Undescended testis ([[356|cryptorchidism]]) is the most common congenital ' 'Undescended testis ([[356|cryptorchidism]]) is the most common congenital '
'anomaly of the male genitourinary tract, affecting 1-4% of full-term and up to ' 'anomaly of the male genitourinary tract, affecting 1-4% of full-term and up to '
@ -85,8 +85,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
324, 324,
( (
'Pediatric nasal fractures: recognizing s', 'Pediatric nasal fractures: recognizing s',
'Nasal fracture is the most common facial', 'Nasal fracture is the most common facial',
), ),
'Nasal fracture is the most common facial bone injury in children, typically ' 'Nasal fracture is the most common facial bone injury in children, typically '
'from sports, falls or abuse, and presents with epistaxis, edema and ecchymosis ' 'from sports, falls or abuse, and presents with epistaxis, edema and ecchymosis '
@ -98,8 +98,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
327, 327,
( (
'Pediatric blunt splenic injury: mechanis', 'Pediatric blunt splenic injury: mechanis',
'Blunt trauma causes most paediatric sple', 'Blunt trauma causes most paediatric sple',
), ),
'Blunt trauma causes most pediatric splenic injury, with motor vehicle ' 'Blunt trauma causes most pediatric splenic injury, with motor vehicle '
'collisions accounting for about 40% and pre-existing splenomegaly raising ' 'collisions accounting for about 40% and pre-existing splenomegaly raising '
@ -110,8 +110,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
331, 331,
( (
'Epiglottitis/supraglottitis: the abrupt ', 'Epiglottitis/supraglottitis: the abrupt ',
'Epiglottitis is a rapidly progressive, l', 'Epiglottitis is a rapidly progressive, l',
), ),
'Epiglottitis is a rapidly progressive, life-threatening inflammation of the ' 'Epiglottitis is a rapidly progressive, life-threatening inflammation of the '
'supraglottis, peaking at 1-8 years and presenting abruptly with drooling, ' 'supraglottis, peaking at 1-8 years and presenting abruptly with drooling, '
@ -122,8 +122,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
333, 333,
( (
'Metabolic syndrome as cardiometabolic ri', 'Metabolic syndrome as cardiometabolic ri',
'Metabolic syndrome is a cluster of cardi', 'Metabolic syndrome is a cluster of cardi',
), ),
'Metabolic syndrome is a cluster of cardiometabolic risk factors — central ' 'Metabolic syndrome is a cluster of cardiometabolic risk factors — central '
'obesity, raised blood pressure and triglycerides, low HDL and hyperglycemia — ' 'obesity, raised blood pressure and triglycerides, low HDL and hyperglycemia — '
@ -134,8 +134,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
337, 337,
( (
'Pediatric thyroid nodules: why they carr', 'Pediatric thyroid nodules: why they carr',
'Thyroid nodules are found in only 1-2% o', 'Thyroid nodules are found in only 1-2% o',
), ),
'Thyroid nodules are found in only 1-2% of children but carry a 20-25% ' 'Thyroid nodules are found in only 1-2% of children but carry a 20-25% '
'malignancy risk, roughly double that in adults, and are twice as common in ' 'malignancy risk, roughly double that in adults, and are twice as common in '
@ -146,8 +146,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
338, 338,
( (
'The general approach to a child with a s', 'The general approach to a child with a s',
'Toxic ingestion follows a bimodal age di', 'Toxic ingestion follows a bimodal age di',
), ),
'Toxic ingestion follows a bimodal age distribution, with accidental ' 'Toxic ingestion follows a bimodal age distribution, with accidental '
'exploratory swallowing peaking at 1-4 years and intentional overdose in ' 'exploratory swallowing peaking at 1-4 years and intentional overdose in '
@ -159,8 +159,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
339, 339,
( (
'Tinea capitis: T. tonsurans vs M. canis ', 'Tinea capitis: T. tonsurans vs M. canis ',
'Tinea capitis is a dermatophyte infectio', 'Tinea capitis is a dermatophyte infectio',
), ),
'Tinea capitis is a dermatophyte infection of the scalp and hair shafts, the ' 'Tinea capitis is a dermatophyte infection of the scalp and hair shafts, the '
'most common cutaneous mycosis in children, with a prevalence of 3-13% and a ' 'most common cutaneous mycosis in children, with a prevalence of 3-13% and a '
@ -171,8 +171,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
341, 341,
( (
'Congenital and acquired toxoplasmosis: t', 'Congenital and acquired toxoplasmosis: t',
'Congenital toxoplasmosis follows materna', 'Congenital toxoplasmosis follows materna',
), ),
'Congenital toxoplasmosis follows maternal Toxoplasma gondii infection in about ' 'Congenital toxoplasmosis follows maternal Toxoplasma gondii infection in about '
'0.1-0.5% of pregnancies and affects roughly 1 in 3,000-10,000 live births, ' '0.1-0.5% of pregnancies and affects roughly 1 in 3,000-10,000 live births, '
@ -184,8 +184,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
343, 343,
( (
'Pediatric visual impairment: acuity-base', 'Pediatric visual impairment: acuity-base',
'Severe visual impairment in children mea', 'Severe visual impairment in children mea',
), ),
'Severe visual impairment in children means corrected acuity poorer than 6/60, ' 'Severe visual impairment in children means corrected acuity poorer than 6/60, '
'with an incidence of about 2.5 per 100,000 and a peak in the first year of ' 'with an incidence of about 2.5 per 100,000 and a peak in the first year of '
@ -196,8 +196,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
347, 347,
( (
'Pediatric acute pancreatitis: the 2-of-3', 'Pediatric acute pancreatitis: the 2-of-3',
'Acute pancreatitis in children is diagno', 'Acute pancreatitis in children is diagno',
), ),
'Acute pancreatitis in children is diagnosed on two of three criteria — ' 'Acute pancreatitis in children is diagnosed on two of three criteria — '
'epigastric pain, amylase or lipase at least three times normal, and consistent ' 'epigastric pain, amylase or lipase at least three times normal, and consistent '
@ -208,8 +208,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
353, 353,
( (
'Pediatric cellulitis: exposure-based pat', 'Pediatric cellulitis: exposure-based pat',
'Cellulitis is an acute infection of the ', 'Cellulitis is an acute infection of the ',
), ),
'Cellulitis is an acute infection of the dermis and subcutaneous tissue ' 'Cellulitis is an acute infection of the dermis and subcutaneous tissue '
'producing erythema, edema, warmth and pain with poorly defined borders, most ' 'producing erythema, edema, warmth and pain with poorly defined borders, most '
@ -221,8 +221,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
355, 355,
( (
'Chorioamnionitis / Triple I: clinical di', 'Chorioamnionitis / Triple I: clinical di',
'Chorioamnionitis, now called intrauterin', 'Chorioamnionitis, now called intrauterin',
), ),
'Chorioamnionitis, now called intrauterine inflammation or infection at birth, ' 'Chorioamnionitis, now called intrauterine inflammation or infection at birth, '
'follows microbial invasion of amniotic fluid after membranes rupture for 18 ' 'follows microbial invasion of amniotic fluid after membranes rupture for 18 '
@ -234,8 +234,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
357, 357,
( (
'Pediatric chronic sinusitis: the 90-day ', 'Pediatric chronic sinusitis: the 90-day ',
'Chronic sinusitis is cough, nasal discha', 'Chronic sinusitis is cough, nasal discha',
), ),
'Chronic sinusitis is cough, nasal discharge or congestion persisting beyond 90 ' 'Chronic sinusitis is cough, nasal discharge or congestion persisting beyond 90 '
'days, milder and more indolent than acute disease, with fever uncommon and ' 'days, milder and more indolent than acute disease, with fever uncommon and '
@ -246,8 +246,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
360, 360,
( (
'Pediatric dilated cardiomyopathy: age-de', 'Pediatric dilated cardiomyopathy: age-de',
'Dilated cardiomyopathy is the commonest ', 'Dilated cardiomyopathy is the commonest ',
), ),
'Dilated cardiomyopathy is the most common cardiomyopathy in children, ' 'Dilated cardiomyopathy is the most common cardiomyopathy in children, '
'affecting about 4-8 per 100,000 each year, and 71% present in congestive heart ' 'affecting about 4-8 per 100,000 each year, and 71% present in congestive heart '
@ -259,8 +259,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
362, 362,
( (
'Exercise-induced bronchoconstriction: th', 'Exercise-induced bronchoconstriction: th',
'Exercise-induced bronchoconstriction is ', 'Exercise-induced bronchoconstriction is ',
), ),
'Exercise-induced bronchoconstriction is transient airway narrowing triggered ' 'Exercise-induced bronchoconstriction is transient airway narrowing triggered '
'by exertion, affecting an estimated 40-90% of children with asthma, with ' 'by exertion, affecting an estimated 40-90% of children with asthma, with '
@ -272,8 +272,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
364, 364,
( (
'Fetal alcohol syndrome within the FASD s', 'Fetal alcohol syndrome within the FASD s',
'Fetal alcohol syndrome is the most sever', 'Fetal alcohol syndrome is the most sever',
), ),
'Fetal alcohol syndrome is the most severe diagnosis within fetal alcohol ' 'Fetal alcohol syndrome is the most severe diagnosis within fetal alcohol '
'spectrum disorder, affecting about 0.5-2 per 1,000 live births, and requires ' 'spectrum disorder, affecting about 0.5-2 per 1,000 live births, and requires '
@ -285,8 +285,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
366, 366,
( (
'Fragile X syndrome: the FMR1 CGG-repeat ', 'Fragile X syndrome: the FMR1 CGG-repeat ',
'Fragile X syndrome is the commonest inhe', 'Fragile X syndrome is the commonest inhe',
), ),
'Fragile X syndrome is the most common inherited cause of intellectual ' 'Fragile X syndrome is the most common inherited cause of intellectual '
'disability, caused by a CGG repeat expansion beyond 200 in FMR1 that silences ' 'disability, caused by a CGG repeat expansion beyond 200 in FMR1 that silences '
@ -297,8 +297,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
369, 369,
( (
'Pediatric glomerulonephritis: the nephri', 'Pediatric glomerulonephritis: the nephri',
'Glomerulonephritis presents as nephritic', 'Glomerulonephritis presents as nephritic',
), ),
'Glomerulonephritis presents as nephritic syndrome — gross hematuria, red cell ' 'Glomerulonephritis presents as nephritic syndrome — gross hematuria, red cell '
'casts, hypertension and edema — with acute post-streptococcal disease the most ' 'casts, hypertension and edema — with acute post-streptococcal disease the most '
@ -310,8 +310,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
370, 370,
( (
'Pediatric alopecia: distinguishing [[346', 'Pediatric alopecia: distinguishing [[346',
'Acquired localised hair loss is the comm', 'Acquired localised hair loss is the comm',
), ),
'Acquired localized hair loss is the most common pattern in childhood and is ' 'Acquired localized hair loss is the most common pattern in childhood and is '
'nearly always [[346|alopecia areata]], [[339|tinea capitis]] or traumatic ' 'nearly always [[346|alopecia areata]], [[339|tinea capitis]] or traumatic '
@ -323,12 +323,12 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
371, 371,
( (
'Seasonal [[298|influenza]] A infection i', 'Seasonal [[298|influenza]] A infection i',
'[[298|Influenza]] A causes seasonal wint', '[[298|Influenza]] A causes seasonal wint',
), ),
'[[298|Influenza]] A causes seasonal winter epidemics, with antigenic drift in ' '[[298|Influenza]] A causes seasonal winter epidemics, with antigenic drift in '
'its hemagglutinin and neuraminidase driving new strains and an annually ' 'its hemagglutinin and neuraminidase driving new strains and an annually '
'updated vaccine, and hospitalisation is highest under 2 years. Older children ' 'updated vaccine, and hospitalization is highest under 2 years. Older children '
'get sudden fever, myalgia and headache while infants may present with ' 'get sudden fever, myalgia and headache while infants may present with '
'diarrhea, vomiting or a sepsis-like illness, and aspirin must be avoided ' 'diarrhea, vomiting or a sepsis-like illness, and aspirin must be avoided '
'because of Reye syndrome.', 'because of Reye syndrome.',
@ -336,8 +336,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
372, 372,
( (
'Pediatric HIV: transmission routes, earl', 'Pediatric HIV: transmission routes, earl',
'Paediatric HIV is acquired mostly from a', 'Paediatric HIV is acquired mostly from a',
), ),
'Pediatric HIV is acquired mostly from an infected mother before, during or ' 'Pediatric HIV is acquired mostly from an infected mother before, during or '
'after birth, and about 20% of untreated infants present in the first 3-6 ' 'after birth, and about 20% of untreated infants present in the first 3-6 '
@ -349,8 +349,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
375, 375,
( (
'Pediatric IIH/pseudotumor cerebri: the n', 'Pediatric IIH/pseudotumor cerebri: the n',
'Idiopathic intracranial hypertension is ', 'Idiopathic intracranial hypertension is ',
), ),
'Idiopathic intracranial hypertension is raised intracranial pressure with ' 'Idiopathic intracranial hypertension is raised intracranial pressure with '
'normal parenchyma, CSF and neuroimaging, affecting 1-2 per 100,000 children ' 'normal parenchyma, CSF and neuroimaging, affecting 1-2 per 100,000 children '
@ -361,8 +361,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
377, 377,
( (
'Pediatric insomnia: behavioral insomnia ', 'Pediatric insomnia: behavioral insomnia ',
'Insomnia is difficulty falling or stayin', 'Insomnia is difficulty falling or stayin',
), ),
'Insomnia is difficulty falling or staying asleep despite adequate opportunity, ' 'Insomnia is difficulty falling or staying asleep despite adequate opportunity, '
'causing daytime fatigue and irritability, and behavioral insomnia of childhood ' 'causing daytime fatigue and irritability, and behavioral insomnia of childhood '
@ -374,8 +374,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
379, 379,
( (
'Intracranial hemorrhage across the pedia', 'Intracranial hemorrhage across the pedia',
'Intracranial haemorrhage is the commones', 'Intracranial haemorrhage is the commones',
), ),
'Intracranial hemorrhage is the most common acute CNS complication of preterm ' 'Intracranial hemorrhage is the most common acute CNS complication of preterm '
'birth, arising from the subependymal germinal matrix, with risk rising as ' 'birth, arising from the subependymal germinal matrix, with risk rising as '
@ -387,8 +387,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
381, 381,
( (
'Juvenile dermatomyositis: the Bohan-Pete', 'Juvenile dermatomyositis: the Bohan-Pete',
'Juvenile dermatomyositis is the commones', 'Juvenile dermatomyositis is the commones',
), ),
'Juvenile dermatomyositis is the most common pediatric inflammatory myopathy, ' 'Juvenile dermatomyositis is the most common pediatric inflammatory myopathy, '
'affecting roughly 3.2 per million children, with onset at a median 7-11 years ' 'affecting roughly 3.2 per million children, with onset at a median 7-11 years '
@ -400,8 +400,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
383, 383,
( (
'Pediatric laceration management: hemosta', 'Pediatric laceration management: hemosta',
'Lacerations in children involve the face', 'Lacerations in children involve the face',
), ),
'Lacerations in children involve the face in about 60% of cases and the upper ' 'Lacerations in children involve the face in about 60% of cases and the upper '
'extremities in 25%, and most are managed with direct pressure, LET topical ' 'extremities in 25%, and most are managed with direct pressure, LET topical '
@ -412,8 +412,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
385, 385,
( (
'Laryngomalacia: the omega-shaped epiglot', 'Laryngomalacia: the omega-shaped epiglot',
'Laryngomalacia is the commonest congenit', 'Laryngomalacia is the commonest congenit',
), ),
'Laryngomalacia is the most common congenital laryngeal anomaly and the leading ' 'Laryngomalacia is the most common congenital laryngeal anomaly and the leading '
'cause of infant stridor, appearing at 2-6 weeks with inspiratory stridor that ' 'cause of infant stridor, appearing at 2-6 weeks with inspiratory stridor that '
@ -424,8 +424,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
387, 387,
( (
'Pediatric metabolic acidosis: distinguis', 'Pediatric metabolic acidosis: distinguis',
'Metabolic acidosis follows a net gain of', 'Metabolic acidosis follows a net gain of',
), ),
'Metabolic acidosis follows a net gain of hydrogen ions or loss of bicarbonate, ' 'Metabolic acidosis follows a net gain of hydrogen ions or loss of bicarbonate, '
'acutely from diarrhea, shock or diabetic ketoacidosis and chronically from ' 'acutely from diarrhea, shock or diabetic ketoacidosis and chronically from '
@ -437,8 +437,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
391, 391,
( (
'Oligohydramnios and the Potter sequence:', 'Oligohydramnios and the Potter sequence:',
'Oligohydramnios is a maximum vertical po', 'Oligohydramnios is a maximum vertical po',
), ),
'Oligohydramnios is a maximum vertical pocket of amniotic fluid under 2 cm, ' 'Oligohydramnios is a maximum vertical pocket of amniotic fluid under 2 cm, '
'most often from ruptured membranes but also from placental insufficiency or ' 'most often from ruptured membranes but also from placental insufficiency or '
@ -450,8 +450,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
393, 393,
( (
'Peanut allergy prevention and management', 'Peanut allergy prevention and management',
'Peanut allergy affects 1-3% of children ', 'Peanut allergy affects 1-3% of children ',
), ),
'Peanut allergy affects 1-3% of children in westernized countries, has tripled ' 'Peanut allergy affects 1-3% of children in westernized countries, has tripled '
'in 10-15 years, and is outgrown by only about 20%, almost always before age 5. ' 'in 10-15 years, and is outgrown by only about 20%, almost always before age 5. '
@ -463,20 +463,20 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
396, 396,
( (
'Pediatric pneumothorax: spontaneous (pri', 'Pediatric pneumothorax: spontaneous (pri',
'Pneumothorax is air in the pleural space', 'Pneumothorax is air in the pleural space',
), ),
'Pneumothorax is air in the pleural space and is more common in the neonatal ' 'Pneumothorax is air in the pleural space and is more common in the neonatal '
'period than at any other age, affecting 1-2% of term and about 6% of preterm ' 'period than at any other age, affecting 1-2% of term and about 6% of preterm '
'newborns. In older children it is either primary spontaneous, in tall thin ' 'newborns. In older children it is primary spontaneous, in tall thin '
'adolescent boys with apical blebs, or secondary to asthma, cystic fibrosis or ' 'adolescent boys with apical blebs, or secondary to asthma, cystic fibrosis or '
'trauma, and tension physiology needs immediate needle decompression.', 'trauma, and tension physiology needs immediate needle decompression.',
), ),
( (
398, 398,
( (
'Pediatric polytrauma: unique anatomic/ph', 'Pediatric polytrauma: unique anatomic/ph',
'Polytrauma is injury to several body reg', 'Polytrauma is injury to several body reg',
), ),
'Polytrauma is injury to several body regions needing multiple specialties, and ' 'Polytrauma is injury to several body regions needing multiple specialties, and '
'trauma is the leading cause of childhood death in the developed world, with ' 'trauma is the leading cause of childhood death in the developed world, with '
@ -488,8 +488,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
399, 399,
( (
'Posterior urethral valves: presentation ', 'Posterior urethral valves: presentation ',
'Posterior urethral valves are obstructin', 'Posterior urethral valves are obstructin',
), ),
'Posterior urethral valves are obstructing leaflets in the prostatic urethra of ' 'Posterior urethral valves are obstructing leaflets in the prostatic urethra of '
'boys and the most common cause of lower urinary tract obstruction and of ' 'boys and the most common cause of lower urinary tract obstruction and of '
@ -501,8 +501,8 @@ REWRITES: list[tuple[int, tuple[str, str], str]] = [
( (
404, 404,
( (
'Prepubertal vulvovaginitis: why unestrog', 'Prepubertal vulvovaginitis: why unestrog',
'About 75% of prepubertal vulvovaginitis ', 'About 75% of prepubertal vulvovaginitis ',
), ),
'About 75% of prepubertal vulvovaginitis is nonspecific, driven by thin ' 'About 75% of prepubertal vulvovaginitis is nonspecific, driven by thin '
'unestrogenized alkaline mucosa, an unprotected introitus and the anus lying ' 'unestrogenized alkaline mucosa, an unprotected introitus and the anus lying '