diff --git a/results.json b/results.json
index f84405a..5b035bf 100644
--- a/results.json
+++ b/results.json
@@ -1,583 +1,151 @@
[
{
- "path": "docs_md/articles/adrenal_082ca43c-db5c-4770-aeed-0c6ea317e8fc.md",
- "title": "Adrenal",
- "docid": "082ca43c-db5c-4770-aeed-0c6ea317e8fc",
+ "path": "docs_md/articles/intracranial-hemorrhage_3a9cbed6-aa2c-45a0-88bf-b39f1523ee85.md",
+ "title": "Intracranial Hemorrhage",
+ "docid": "3a9cbed6-aa2c-45a0-88bf-b39f1523ee85",
"breadcrumbs": [
- "Genitourinary",
- "Anatomy",
- "Adrenal"
+ "Pediatrics",
+ "Differential Diagnosis",
+ "Brain",
+ "Intracranial Hemorrhage"
],
"authors": [
{
- "key": "c1df94ab-4a9f-44c4-add7-1f174fb9ac45",
- "value": "Siva P. Raman, MD"
+ "key": "47381de4-c9fd-4999-8dd0-1808cd72db6b",
+ "value": "Luke L. Linscott, MD"
}
],
- "pageKeywords": "Genitourinary, Anatomy, Adrenal",
+ "pageKeywords": "Pediatrics, Differential Diagnosis, Brain, Intracranial Hemorrhage",
"reasons": [
"Content"
],
- "snippet": "...hrine) into bloodstream - **Vessels**,**nerves**, and **lymphatics** - Arteries - **Superior adrenal arteries**: Typic..."
- },
- {
- "path": "docs_md/articles/sensorineural-hearing-loss-in-child_08c895da-f2aa-4076-abf0-af9aca1677cd.md",
- "title": "Sensorineural Hearing Loss in Child",
- "docid": "08c895da-f2aa-4076-abf0-af9aca1677cd",
- "breadcrumbs": [
- "Head and Neck",
- "Differential Diagnosis",
- "CPA-IAC and Posterior Fossa",
- "Clinically Based Differentials",
- "Sensorineural Hearing Loss in Child"
- ],
- "authors": [
- {
- "key": "d19354f3-7ff2-495a-ad3f-064122e45602",
- "value": "Bernadette L. Koch, MD"
- }
- ],
- "pageKeywords": "Head and Neck, Differential Diagnosis, CPA-IAC and Posterior Fossa, Clinically Based Differentials, Sensorineural Hearing Loss in Child",
- "reasons": [
- "Content"
- ],
- "snippet": "...lar deficiency on CT - Look for large endolymphatic sac/duct ± cochlear malformation & modiolar deficiency on MR - **Conge..."
- },
- {
- "path": "docs_md/articles/segmental-colonic-narrowing_c193bc86-43d2-4932-8378-5db8001d755e.md",
- "title": "Segmental Colonic Narrowing",
- "docid": "c193bc86-43d2-4932-8378-5db8001d755e",
- "breadcrumbs": [
- "Gastrointestinal",
- "Differential Diagnosis",
- "Colon",
- "Generic Imaging Patterns",
- "Segmental Colonic Narrowing"
- ],
- "authors": [
- {
- "key": "6c5a9e0e-9dea-461b-9ad4-c00f5c4c2bbf",
- "value": "Atif Zaheer, MD, FSAR"
+ "snippet": "...illness, past medical history - Preceding trauma may be obvious [e.g., motor vehicle accident (MVA)] or occult (e.g., child abuse),...",
+ "linked_info": {
+ "anatomy": {
+ "exists": false,
+ "links": []
},
- {
- "key": "e987d3d3-1206-48d6-824b-3347c2968855",
- "value": "Michael P. Federle, MD, FACR"
+ "differential": {
+ "exists": false,
+ "links": []
}
- ],
- "pageKeywords": "Gastrointestinal, Differential Diagnosis, Colon, Generic Imaging Patterns, Segmental Colonic Narrowing",
- "reasons": [
- "Content"
- ],
- "snippet": "...etastases and Lymphoma** - Can be hematogenous or lymphatic spread or direct invasion - e.g., from duodenum, stomach, kidn..."
+ }
},
{
- "path": "docs_md/articles/normal-pressure-hydrocephalus_ba3f857d-58de-4f21-8463-1631b4cb9972.md",
- "title": "Normal-Pressure Hydrocephalus",
- "docid": "ba3f857d-58de-4f21-8463-1631b4cb9972",
+ "path": "docs_md/articles/epilepsy-child_a342e5b5-5b98-4003-a437-6d42a483b40e.md",
+ "title": "Epilepsy, Child",
+ "docid": "a342e5b5-5b98-4003-a437-6d42a483b40e",
"breadcrumbs": [
"Brain",
- "Diagnosis",
- "Anatomy-Based Diagnoses",
- "Ventricles and Cisterns",
- "Hydrocephalus",
- "Normal-Pressure Hydrocephalus"
- ],
- "authors": [
- {
- "key": "a25c450b-3d34-4f64-bba3-cc0834813df6",
- "value": "Miral D. Jhaveri, MD, MBA"
- }
- ],
- "pageKeywords": "Brain, Diagnosis, Anatomy-Based Diagnoses, Ventricles and Cisterns, Hydrocephalus, Normal-Pressure Hydrocephalus",
- "reasons": [
- "Content"
- ],
- "snippet": "...oval of neurotoxic compounds (β-amyloid & tau) - Glymphatic system - Facilitates brain fluid clearance & waste removal du..."
- },
- {
- "path": "docs_md/articles/peritoneal-cavity_6691f48d-ac34-477b-8ec1-b9dd731a14a8.md",
- "title": "Peritoneal Cavity",
- "docid": "6691f48d-ac34-477b-8ec1-b9dd731a14a8",
- "breadcrumbs": [
- "Gastrointestinal",
- "Anatomy",
- "Gastrointestinal Tract and Abdominal Cavity",
- "Peritoneal Cavity"
- ],
- "authors": [
- {
- "key": "6c5a9e0e-9dea-461b-9ad4-c00f5c4c2bbf",
- "value": "Atif Zaheer, MD, FSAR"
- },
- {
- "key": "c1df94ab-4a9f-44c4-add7-1f174fb9ac45",
- "value": "Siva P. Raman, MD"
- },
- {
- "key": "e987d3d3-1206-48d6-824b-3347c2968855",
- "value": "Michael P. Federle, MD, FACR"
- }
- ],
- "pageKeywords": "Gastrointestinal, Anatomy, Gastrointestinal Tract and Abdominal Cavity, Peritoneal Cavity",
- "reasons": [
- "Content"
- ],
- "snippet": "...ntains **superior mesenteric artery and vein**, nerves, and lymphatics - **Transverse mesocolon**crosses almost horizontally in front of pancreas,..."
- },
- {
- "path": "docs_md/articles/sensorineural-hearing-loss-in-adult_08d468da-fbc3-44f8-8212-6480e0a152c4.md",
- "title": "Sensorineural Hearing Loss in Adult",
- "docid": "08d468da-fbc3-44f8-8212-6480e0a152c4",
- "breadcrumbs": [
- "Head and Neck",
"Differential Diagnosis",
- "CPA-IAC and Posterior Fossa",
+ "Brain Parenchyma, General",
"Clinically Based Differentials",
- "Sensorineural Hearing Loss in Adult"
+ "Epilepsy, Child"
],
"authors": [
{
- "key": "eef2f839-5706-47b9-89c3-60d8315b2b3a",
- "value": "Nicholas A. Koontz, MD"
+ "key": "47381de4-c9fd-4999-8dd0-1808cd72db6b",
+ "value": "Luke L. Linscott, MD"
}
],
- "pageKeywords": "Head and Neck, Differential Diagnosis, CPA-IAC and Posterior Fossa, Clinically Based Differentials, Sensorineural Hearing Loss in Adult",
+ "pageKeywords": "Brain, Differential Diagnosis, Brain Parenchyma, General, Clinically Based Differentials, Epilepsy, Child",
"reasons": [
"Content"
],
- "snippet": "...ove; fat saturation suppresses fat signal - **Large Endolymphatic Sac Anomaly (IP-2)** - Most common lesion found in children with bilat..."
- },
- {
- "path": "docs_md/articles/abdominal-lymph-nodes_3ca98d42-20ef-48fe-9265-ebfe570ba54b.md",
- "title": "Abdominal Lymph Nodes",
- "docid": "3ca98d42-20ef-48fe-9265-ebfe570ba54b",
- "breadcrumbs": [
- "Ultrasound",
- "Anatomy",
- "Abdomen",
- "Abdominal Lymph Nodes"
- ],
- "authors": [
- {
- "key": "07469ec4-05aa-4d65-a788-08b4d64048af",
- "value": "Jade Wong-You-Cheong, MBChB, MRCP, FRCR, FSRU, FSAR"
- }
- ],
- "pageKeywords": "Ultrasound, Anatomy, Abdomen, Abdominal Lymph Nodes",
- "reasons": [
- "Content"
- ],
- "snippet": "# GROSS ANATOMY - ## Overview - Major lymphatic vessels and nodal chains lie along major blood vessels [aorta, inferior vena cav..."
- },
- {
- "path": "docs_md/articles/temporal-bone-facial-nerve-venous-malformation-hemangioma_dcd6a44e-cbe6-457c-9b03-598a2b874ece.md",
- "title": "Temporal Bone Facial Nerve Venous Malformation (Hemangioma)",
- "docid": "dcd6a44e-cbe6-457c-9b03-598a2b874ece",
- "breadcrumbs": [
- "Head and Neck",
- "Diagnosis",
- "Temporal Bone",
- "Intratemporal Facial Nerve",
- "Benign and Malignant Tumors",
- "Temporal Bone Facial Nerve Venous Malformation (Hemangioma)"
- ],
- "authors": [
- {
- "key": "07a2c087-6202-49e7-870b-7aa162d18f06",
- "value": "Bronwyn E. Hamilton, MD"
+ "snippet": "...tes without recurrence does not require imaging - Trauma, remote stroke, or infection results in encephalomalacia &/or gliosis, which may ca...",
+ "linked_info": {
+ "anatomy": {
+ "exists": false,
+ "links": []
},
- {
- "key": "33151213-01b2-4542-9105-342e006b3915",
- "value": "H. Ric Harnsberger, MD"
+ "differential": {
+ "exists": false,
+ "links": []
}
- ],
- "pageKeywords": "Head and Neck, Diagnosis, Temporal Bone, Intratemporal Facial Nerve, Benign and Malignant Tumors, Temporal Bone Facial Nerve Venous Malformation (Hemangioma)",
- "reasons": [
- "Content"
- ],
- "snippet": "...* staining utilizing D2-40 antibody **negativity** excludes lymphatic malformation - ## Clinical Issues - Intratemporal FNVM produces **peri..."
+ }
},
{
- "path": "docs_md/articles/colonic-wall-thickening_71ca9b30-6186-4129-957b-3b686b91b327.md",
- "title": "Colonic Wall Thickening",
- "docid": "71ca9b30-6186-4129-957b-3b686b91b327",
- "breadcrumbs": [
- "Gastrointestinal",
- "Differential Diagnosis",
- "Colon",
- "Generic Imaging Patterns",
- "Colonic Wall Thickening"
- ],
- "authors": [
- {
- "key": "6c5a9e0e-9dea-461b-9ad4-c00f5c4c2bbf",
- "value": "Atif Zaheer, MD, FSAR"
- },
- {
- "key": "e987d3d3-1206-48d6-824b-3347c2968855",
- "value": "Michael P. Federle, MD, FACR"
- }
- ],
- "pageKeywords": "Gastrointestinal, Differential Diagnosis, Colon, Generic Imaging Patterns, Colonic Wall Thickening",
- "reasons": [
- "Content"
- ],
- "snippet": "...n with regional adenopathy (strong predictor of cancer with lymphatic spread) - Look for hepatic and peritoneal metastases - **Portal Hy..."
- },
- {
- "path": "docs_md/articles/lymphangioma-mesenteric-cyst_bb42a128-c819-4368-a085-232b6db3434c.md",
- "title": "Lymphangioma (Mesenteric Cyst)",
- "docid": "bb42a128-c819-4368-a085-232b6db3434c",
- "breadcrumbs": [
- "Gastrointestinal",
- "Diagnosis",
- "Peritoneum, Mesentery, and Abdominal Wall",
- "Benign Neoplasms",
- "Lymphangioma (Mesenteric Cyst)"
- ],
- "authors": [
- {
- "key": "c1df94ab-4a9f-44c4-add7-1f174fb9ac45",
- "value": "Siva P. Raman, MD"
- }
- ],
- "pageKeywords": "Gastrointestinal, Diagnosis, Peritoneum, Mesentery, and Abdominal Wall, Benign Neoplasms, Lymphangioma (Mesenteric Cyst)",
- "reasons": [
- "Content"
- ],
- "snippet": "...ymphoepithelial cyst, cystic lymphangioma, mesenteric cyst, lymphatic malformation - ## Definitions - Generic descriptive term used for beni..."
- },
- {
- "path": "docs_md/articles/vertebral-body-t1-hypointense-signal-diffuse_5f4ceb8f-867f-4b4d-8149-57a906489453.md",
- "title": "Vertebral Body, T1-Hypointense Signal, Diffuse",
- "docid": "5f4ceb8f-867f-4b4d-8149-57a906489453",
- "breadcrumbs": [
- "Spine",
- "Differential Diagnosis",
- "Vertebral Body, Posterior Elements",
- "Modality-Specific Imaging Findings",
- "Vertebral Body, T1-Hypointense Signal, Diffuse"
- ],
- "authors": [
- {
- "key": "bee1f359-33fb-4cba-9e6b-ed1ca1842439",
- "value": "Jeffrey S. Ross, MD"
- },
- {
- "key": "99e1aff7-f42c-43a0-95ae-d89c8551aa01",
- "value": "Kevin R. Moore, MD"
- }
- ],
- "pageKeywords": "Spine, Differential Diagnosis, Vertebral Body, Posterior Elements, Modality-Specific Imaging Findings, Vertebral Body, T1-Hypointense Signal, Diffuse",
- "reasons": [
- "Content"
- ],
- "snippet": "...nation (arterial or venous via Batson plexus) > perineural, lymphatic, CSF spread - Marrow initially infiltrated, trabeculae destroy..."
- },
- {
- "path": "docs_md/articles/penetrating-atherosclerotic-ulcer_63f6cba2-2200-456a-8d03-ac3111e420c8.md",
- "title": "Penetrating Atherosclerotic Ulcer",
- "docid": "63f6cba2-2200-456a-8d03-ac3111e420c8",
- "breadcrumbs": [
- "Cardiac",
- "Diagnosis",
- "Aorta",
- "Penetrating Atherosclerotic Ulcer"
- ],
- "authors": [
- {
- "key": "9ad9af12-61a1-44d6-af52-0ee1f38eb298",
- "value": "Davis Vigneault, MD, DPhil"
- },
- {
- "key": "5de0df07-7b3e-4678-8767-1519e1153f29",
- "value": "Dominik Fleischmann, MD"
- },
- {
- "key": "10bb95ac-a27a-4ebe-833b-e59fea07734b",
- "value": "Santiago Martínez-Jiménez, MD, FACR"
- }
- ],
- "pageKeywords": "Cardiac, Diagnosis, Aorta, Penetrating Atherosclerotic Ulcer",
- "reasons": [
- "Content"
- ],
- "snippet": ".../ANATOMY:a7a252f0-2ac6-402a-8c87-cfce8adc799b ### Vessels, Lymphatic System, and Nerves, Abdominal Gastrointestinal/ANATOMY:0c38fd49-88e7-4272-960f-b..."
- },
- {
- "path": "docs_md/articles/mycotic-aneurysm_20616d0a-7e0b-48d9-9be9-1af29e3dd6da.md",
- "title": "Mycotic Aneurysm",
- "docid": "20616d0a-7e0b-48d9-9be9-1af29e3dd6da",
- "breadcrumbs": [
- "Cardiac",
- "Diagnosis",
- "Aorta",
- "Mycotic Aneurysm"
- ],
- "authors": [
- {
- "key": "ee6ece9d-ad74-458c-a8df-11628ae7f879",
- "value": "Arzu Canan, MD"
- },
- {
- "key": "3d1e4c57-c1cf-4c89-b0f0-5d82b29a31e1",
- "value": "Suhny Abbara, MD, FACR, MSCCT, FNASCI"
- },
- {
- "key": "501b57b9-1723-4b1e-b21e-d4516655fac8",
- "value": "Sanjeeva P. Kalva, MD, FSIR, FCIRSE"
- }
- ],
- "pageKeywords": "Cardiac, Diagnosis, Aorta, Mycotic Aneurysm",
- "reasons": [
- "Content"
- ],
- "snippet": "...nal infection or intraabdominal abscess - Lymphatic spread - *Burkholderia pseudomallei* (causing melioidosis), endemic in..."
- },
- {
- "path": "docs_md/articles/otosclerosis_ddc7b884-3c17-4834-9e96-d985c6b618a9.md",
- "title": "Otosclerosis",
- "docid": "ddc7b884-3c17-4834-9e96-d985c6b618a9",
- "breadcrumbs": [
- "Head and Neck",
- "Diagnosis",
- "Temporal Bone",
- "Inner Ear",
- "Infectious and Inflammatory Lesions",
- "Otosclerosis"
- ],
- "authors": [
- {
- "key": "07a2c087-6202-49e7-870b-7aa162d18f06",
- "value": "Bronwyn E. Hamilton, MD"
- },
- {
- "key": "4b6589b0-9b8d-4467-8a90-01a0a59742fc",
- "value": "Troy A. Hutchins, MD"
- }
- ],
- "pageKeywords": "Head and Neck, Diagnosis, Temporal Bone, Inner Ear, Infectious and Inflammatory Lesions, Otosclerosis",
- "reasons": [
- "Content"
- ],
- "snippet": "...kaida T et al: Magnetic resonance imaging evaluation of endolymphatic hydrops in cases with otosclerosis. Otol Neurotol. 36(7):1146-50, 2015](http://w..."
- },
- {
- "path": "docs_md/articles/adrenal-cyst_c5d717a3-3d6e-4e86-9efe-1ad0ec14740f.md",
- "title": "Adrenal Cyst",
- "docid": "c5d717a3-3d6e-4e86-9efe-1ad0ec14740f",
- "breadcrumbs": [
- "Genitourinary",
- "Diagnosis",
- "Adrenal",
- "Benign Neoplasms",
- "Adrenal Cyst"
- ],
- "authors": [
- {
- "key": "c3463c5c-31d3-4489-bbfe-6b895abdb86d",
- "value": "Mitchell Tublin, MD"
- }
- ],
- "pageKeywords": "Genitourinary, Diagnosis, Adrenal, Benign Neoplasms, Adrenal Cyst",
- "reasons": [
- "Content"
- ],
- "snippet": "...om preexisting vascular malformation or obstructed, ectatic lymphatic channels - Thin rim calcification typical - Epitheli..."
- },
- {
- "path": "docs_md/articles/hydrocephalus_e9481739-278e-4682-ab1e-4326a77c3d0c.md",
- "title": "Hydrocephalus",
- "docid": "e9481739-278e-4682-ab1e-4326a77c3d0c",
+ "path": "docs_md/articles/abusive-head-trauma_c57982e3-fa8f-4fd6-9184-04fd1d37a906.md",
+ "title": "Abusive Head Trauma",
+ "docid": "c57982e3-fa8f-4fd6-9184-04fd1d37a906",
"breadcrumbs": [
"Pediatrics",
"Diagnosis",
"Pediatric Neuroradiology",
"Brain",
- "Anatomy-Based Diagnoses",
- "Ventricles and Cisterns",
- "Hydrocephalus"
+ "Pathology-Based Diagnoses",
+ "Trauma",
+ "Abusive Head Trauma"
],
"authors": [
{
- "key": "838e1722-2479-4fbd-a5fe-d965980a1a2c",
- "value": "Blaise V. Jones, MD"
+ "key": "47381de4-c9fd-4999-8dd0-1808cd72db6b",
+ "value": "Luke L. Linscott, MD"
}
],
- "pageKeywords": "Pediatrics, Diagnosis, Pediatric Neuroradiology, Brain, Anatomy-Based Diagnoses, Ventricles and Cisterns, Hydrocephalus",
+ "pageKeywords": "Pediatrics, Diagnosis, Pediatric Neuroradiology, Brain, Pathology-Based Diagnoses, Trauma, Abusive Head Trauma",
"reasons": [
- "Content"
+ "Breadcrumbs",
+ "Content",
+ "Keywords",
+ "Title"
],
- "snippet": "...Extracellular, extravascular fluid in brain is managed by glymphatic system - CSF circulates from within ventricles → subarachnoid space →..."
- },
- {
- "path": "docs_md/articles/inner-ear-lesion-adult_e5bbf757-d77a-4546-a848-d1a1a64cb230.md",
- "title": "Inner Ear Lesion, Adult",
- "docid": "e5bbf757-d77a-4546-a848-d1a1a64cb230",
- "breadcrumbs": [
- "Head and Neck",
- "Differential Diagnosis",
- "Temporal Bone",
- "Anatomically Based Differentials",
- "Inner Ear Lesion, Adult"
- ],
- "authors": [
- {
- "key": "d19354f3-7ff2-495a-ad3f-064122e45602",
- "value": "Bernadette L. Koch, MD"
- }
- ],
- "pageKeywords": "Head and Neck, Differential Diagnosis, Temporal Bone, Anatomically Based Differentials, Inner Ear Lesion, Adult",
- "reasons": [
- "Content"
- ],
- "snippet": "...ochlear turns - Usually with enlarged endolymphatic sac/duct - Literature suggests dysplastic osseous spiral lamin..."
- },
- {
- "path": "docs_md/articles/ventricles-and-cisterns-overview_ad860c4f-fe9a-4469-8eca-a7ccd5cff70f.md",
- "title": "Ventricles and Cisterns Overview",
- "docid": "ad860c4f-fe9a-4469-8eca-a7ccd5cff70f",
- "breadcrumbs": [
- "Brain",
- "Diagnosis",
- "Anatomy-Based Diagnoses",
- "Ventricles and Cisterns",
- "Ventricles and Cisterns Overview"
- ],
- "authors": [
- {
- "key": "a25c450b-3d34-4f64-bba3-cc0834813df6",
- "value": "Miral D. Jhaveri, MD, MBA"
- }
- ],
- "pageKeywords": "Brain, Diagnosis, Anatomy-Based Diagnoses, Ventricles and Cisterns, Ventricles and Cisterns Overview",
- "reasons": [
- "Content"
- ],
- "snippet": "...lli along the superior sagittal sinus. CSF also drains into lymphatic vessels around the cranial cavity and spinal canal. - **Updated model of CSF and..."
- },
- {
- "path": "docs_md/articles/takayasu-arteritis_d35eb6f3-bfd3-4121-8781-325a93ccc197.md",
- "title": "Takayasu Arteritis",
- "docid": "d35eb6f3-bfd3-4121-8781-325a93ccc197",
- "breadcrumbs": [
- "Cardiac",
- "Diagnosis",
- "Aorta",
- "Takayasu Arteritis"
- ],
- "authors": [
- {
- "key": "ee6ece9d-ad74-458c-a8df-11628ae7f879",
- "value": "Arzu Canan, MD"
+ "snippet": "# KEY FACTS - ## Terminology - Abusive head trauma (AHT) - Traumatic injury inflicted on infants & children by adults...",
+ "linked_info": {
+ "anatomy": {
+ "exists": true,
+ "links": [
+ {
+ "title": "Visual Network",
+ "docid": "404625d9-3125-4923-9f9d-53d0f81c3542"
+ },
+ {
+ "title": "Anatomy Document",
+ "docid": "bf20450d-2629-4795-98e7-7788b665ad3c"
+ },
+ {
+ "title": "Anatomy Document",
+ "docid": "8fb2f541-799d-479f-b000-ab54f216199a"
+ }
+ ]
},
- {
- "key": "3d1e4c57-c1cf-4c89-b0f0-5d82b29a31e1",
- "value": "Suhny Abbara, MD, FACR, MSCCT, FNASCI"
+ "differential": {
+ "exists": true,
+ "links": [
+ {
+ "title": "Benign Enlarged Subarachnoid Spaces",
+ "docid": "3da4fec0-6e87-4bcc-bd66-b4a5d1984f6e"
+ },
+ {
+ "title": "Mitochondrial Encephalopathies",
+ "docid": "40004435-b768-4baf-a31e-651f8a174fe2"
+ },
+ {
+ "title": "CSF Shunts and Complications",
+ "docid": "1027d634-92ff-47c1-8266-a7fc3acd1529"
+ },
+ {
+ "title": "Subdural Empyema",
+ "docid": "30b1f367-f047-4664-b34b-69b2d13867e0"
+ },
+ {
+ "title": "Differential Diagnosis",
+ "docid": "f2d6806c-a267-4f64-ba16-b0fa89e229b6"
+ },
+ {
+ "title": "Differential Diagnosis",
+ "docid": "6083739e-ec7b-48ad-9b34-80434c3142f2"
+ },
+ {
+ "title": "Differential Diagnosis",
+ "docid": "17ae265f-b471-4ca0-bba4-75b73a9d76f6"
+ },
+ {
+ "title": "Differential Diagnosis",
+ "docid": "da3780c6-b627-47c3-912d-6f4c452a1acd"
+ }
+ ]
}
- ],
- "pageKeywords": "Cardiac, Diagnosis, Aorta, Takayasu Arteritis",
- "reasons": [
- "Content"
- ],
- "snippet": ".../ANATOMY:551f4b47-fac1-44f9-b800-09ce766fedd9 ### Vessels, Lymphatic System, and Nerves, Abdominal Gastrointestinal/ANATOMY:0c38fd49-88e7-4272-960f-b..."
- },
- {
- "path": "docs_md/articles/peritoneal-spaces-and-structures_9c50ad6a-e96b-44a1-93d1-a4e7de5212c2.md",
- "title": "Peritoneal Spaces and Structures",
- "docid": "9c50ad6a-e96b-44a1-93d1-a4e7de5212c2",
- "breadcrumbs": [
- "Ultrasound",
- "Anatomy",
- "Abdomen",
- "Peritoneal Spaces and Structures"
- ],
- "authors": [
- {
- "key": "07469ec4-05aa-4d65-a788-08b4d64048af",
- "value": "Jade Wong-You-Cheong, MBChB, MRCP, FRCR, FSRU, FSAR"
- }
- ],
- "pageKeywords": "Ultrasound, Anatomy, Abdomen, Peritoneal Spaces and Structures",
- "reasons": [
- "Content"
- ],
- "snippet": "...to right; contains superior mesenteric vessels, nerves, and lymphatics - Transverse mesocolon crosses almost horizontally in front of pancreas, du..."
- },
- {
- "path": "docs_md/articles/cpa-iac-lipoma_37ef2fb6-7d7e-48dc-b786-16ed4475b26e.md",
- "title": "CPA-IAC Lipoma",
- "docid": "37ef2fb6-7d7e-48dc-b786-16ed4475b26e",
- "breadcrumbs": [
- "Head and Neck",
- "Diagnosis",
- "CPA-IAC",
- "Congenital Lesions",
- "CPA-IAC Lipoma"
- ],
- "authors": [
- {
- "key": "318f80ab-6abb-4067-a809-2ebdaa5a30c9",
- "value": "Kalen Riley, MD, MBA"
- },
- {
- "key": "e0282a62-994d-4550-a127-1eb773b1e920",
- "value": "Blair A. Winegar, MD"
- },
- {
- "key": "94f835c8-fa13-4e8a-995b-53048e6b0605",
- "value": "Philip R. Chapman, MD"
- }
- ],
- "pageKeywords": "Head and Neck, Diagnosis, CPA-IAC, Congenital Lesions, CPA-IAC Lipoma",
- "reasons": [
- "Content"
- ],
- "snippet": "...ant CPA-IAC lipoma with inner ear second fatty focus in endolymphatic duct-sac.\\n\\nThree axial CT (#1-3) and 3 axial MR (#4-6) images reveal an obviou..."
- },
- {
- "path": "docs_md/articles/cystic-cpa-mass_6c60db6d-8093-4df5-8cbb-c6f6570ae167.md",
- "title": "Cystic CPA Mass",
- "docid": "6c60db6d-8093-4df5-8cbb-c6f6570ae167",
- "breadcrumbs": [
- "Head and Neck",
- "Differential Diagnosis",
- "CPA-IAC and Posterior Fossa",
- "Generic Imaging Patterns",
- "Cystic CPA Mass"
- ],
- "authors": [
- {
- "key": "07a2c087-6202-49e7-870b-7aa162d18f06",
- "value": "Bronwyn E. Hamilton, MD"
- }
- ],
- "pageKeywords": "Head and Neck, Differential Diagnosis, CPA-IAC and Posterior Fossa, Generic Imaging Patterns, Cystic CPA Mass",
- "reasons": [
- "Content"
- ],
- "snippet": "...chnoid, & neurenteric cysts; neurocysticercosis & large endolymphatic sac anomaly - Many solid CPA tumors may have either intramural cysts,..."
- },
- {
- "path": "docs_md/articles/cranial-meninges_3210cf7d-5be6-4741-a11d-dd7f8c1bc286.md",
- "title": "Cranial Meninges",
- "docid": "3210cf7d-5be6-4741-a11d-dd7f8c1bc286",
- "breadcrumbs": [
- "Brain",
- "Anatomy",
- "Scalp, Skull, and Meninges",
- "Cranial Meninges"
- ],
- "authors": [
- {
- "key": "5cff4116-3654-4b3a-bb75-5ebe0b8c9850",
- "value": "Anne G. Osborn, MD, FACR"
- }
- ],
- "pageKeywords": "Brain, Anatomy, Scalp, Skull, and Meninges, Cranial Meninges",
- "reasons": [
- "Content"
- ],
- "snippet": "...along penetrating arteries - Key part of brain \"glymphatic\" system ## GROSS ANATOMY - ### Overview - Brain encased by 3 meninge..."
+ }
}
]
\ No newline at end of file
diff --git a/tools/search_md.py b/tools/search_md.py
index 2630bc6..d56115e 100644
--- a/tools/search_md.py
+++ b/tools/search_md.py
@@ -20,7 +20,8 @@ import argparse
import csv
import json
import os
-from typing import List, Dict, Any
+import re
+from typing import List, Dict, Any, Tuple, Optional
import yaml
import fnmatch
@@ -249,7 +250,135 @@ def match_author(authors: List[Dict[str, Any]], q: str) -> bool:
return False
-def run_search(root: str, qkey: str, qval: str, mode: str = 'exact', targets: List[str] = None, or_queries: List[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
+_docid_map = {}
+
+def get_doc_by_id(root: str, identifier: str) -> Tuple[Optional[Dict[str, Any]], Optional[str], Optional[str]]:
+ global _docid_map
+ if not _docid_map:
+ for dirpath, dirnames, filenames in os.walk(root):
+ for fn in filenames:
+ if fn.endswith('.md'):
+ path = os.path.join(dirpath, fn)
+ fm, content = read_md_file(path)
+ basename = os.path.splitext(fn)[0]
+ if fm:
+ docid = fm.get('docid')
+ doc_info = (fm, content, path)
+ if docid:
+ _docid_map[docid] = doc_info
+ _docid_map[basename] = doc_info
+ return _docid_map.get(identifier, (None, None, None))
+
+
+def extract_section_content(content: str, section_type: str) -> str:
+ lines = content.split('\n')
+ section_content = []
+ in_section = False
+ section_level = 0
+
+ if section_type == 'anatomy':
+ pattern = re.compile(r'^#+\s+.*anatomy.*', re.IGNORECASE)
+ elif section_type == 'differential':
+ pattern = re.compile(r'^#+\s+.*differential.*', re.IGNORECASE)
+ else:
+ return ""
+
+ for line in lines:
+ if pattern.match(line):
+ in_section = True
+ section_level = len(line) - len(line.lstrip('#'))
+ continue
+
+ if in_section:
+ if line.startswith('#'):
+ current_level = len(line) - len(line.lstrip('#'))
+ if current_level <= section_level:
+ in_section = False
+ continue
+ section_content.append(line)
+
+ return '\n'.join(section_content)
+
+
+def find_links_in_text(text: str) -> List[Dict[str, str]]:
+ links = []
+ if not text:
+ return links
+
+ # 1. Standard markdown links [Label](/document/slug/uuid)
+ markdown_pattern = re.compile(r'\[([^\]]+)\]\((/document/[^/]+/([a-f0-9\-]{36}))\)')
+ for m in markdown_pattern.finditer(text):
+ links.append({
+ 'title': m.group(1),
+ 'docid': m.group(3)
+ })
+
+ # 2. Raw ANATOMY links (e.g. Brain/ANATOMY:uuid or ANATOMY:uuid)
+ anatomy_pattern = re.compile(r'(?:[a-zA-Z0-9\-]+/)?ANATOMY:([a-f0-9\-]{36})', re.IGNORECASE)
+ for m in anatomy_pattern.finditer(text):
+ docid = m.group(1)
+ if not any(l['docid'] == docid for l in links):
+ links.append({
+ 'title': 'Anatomy Document',
+ 'docid': docid
+ })
+
+ # 3. Raw DDX links (e.g. DDX:uuid)
+ ddx_pattern = re.compile(r'DDX:([a-f0-9\-]{36})', re.IGNORECASE)
+ for m in ddx_pattern.finditer(text):
+ docid = m.group(1)
+ if not any(l['docid'] == docid for l in links):
+ links.append({
+ 'title': 'Differential Diagnosis',
+ 'docid': docid
+ })
+
+ return links
+
+
+def check_linked_sections(content: str, root: str = 'docs_md/articles') -> Dict[str, Any]:
+ anatomy_text = extract_section_content(content, 'anatomy')
+ anatomy_exists = bool(re.search(r'(?i)^#+\s+.*anatomy.*', content, re.MULTILINE))
+
+ diff_text = extract_section_content(content, 'differential')
+ diff_exists = bool(re.search(r'(?i)^#+\s+.*differential.*', content, re.MULTILINE))
+
+ raw_anatomy_links = find_links_in_text(anatomy_text)
+ raw_diff_links = find_links_in_text(diff_text)
+
+ anatomy_links = []
+ for link in raw_anatomy_links:
+ docid = link['docid']
+ fm, _, _ = get_doc_by_id(root, docid)
+ title = fm.get('title') or fm.get('pageTitle') if fm else link['title']
+ anatomy_links.append({
+ 'title': title,
+ 'docid': docid
+ })
+
+ diff_links = []
+ for link in raw_diff_links:
+ docid = link['docid']
+ fm, _, _ = get_doc_by_id(root, docid)
+ title = fm.get('title') or fm.get('pageTitle') if fm else link['title']
+ diff_links.append({
+ 'title': title,
+ 'docid': docid
+ })
+
+ return {
+ 'anatomy': {
+ 'exists': anatomy_exists,
+ 'links': anatomy_links
+ },
+ 'differential': {
+ 'exists': diff_exists,
+ 'links': diff_links
+ }
+ }
+
+
+def run_search(root: str, qkey: str, qval: str, mode: str = 'exact', targets: List[str] = None, or_queries: List[Dict[str, Any]] = None, expand_links: bool = False) -> List[Dict[str, Any]]:
out = []
# Normalize queries list
@@ -456,7 +585,65 @@ def run_search(root: str, qkey: str, qval: str, mode: str = 'exact', targets: Li
'pageKeywords': fm.get('pageKeywords'),
'reasons': sorted(list(set(reasons))),
'snippet': snippet,
+ 'linked_info': check_linked_sections(content, root)
})
+
+ if expand_links:
+ seen_docids = {r.get('docid') for r in out if r.get('docid')}
+ seen_paths = {r['path'] for r in out}
+
+ expanded_results = []
+ for r in out:
+ info = r.get('linked_info')
+ if not info:
+ continue
+
+ parent_title = r['title']
+
+ # Anatomy links
+ for link in info['anatomy']['links']:
+ docid = link['docid']
+ if docid and docid not in seen_docids:
+ fm, content, path = get_doc_by_id(root, docid)
+ if fm:
+ linked_fm_title = fm.get('title') or fm.get('pageTitle') or link['title']
+ expanded_results.append({
+ 'path': path,
+ 'title': linked_fm_title,
+ 'docid': docid,
+ 'breadcrumbs': fm.get('breadcrumbs'),
+ 'authors': fm.get('authors'),
+ 'pageKeywords': fm.get('pageKeywords'),
+ 'reasons': [f'Linked (Anatomy of {parent_title})'],
+ 'snippet': content[:300] + '...' if content else '',
+ 'linked_info': check_linked_sections(content, root)
+ })
+ seen_docids.add(docid)
+ seen_paths.add(path)
+
+ # Differential links
+ for link in info['differential']['links']:
+ docid = link['docid']
+ if docid and docid not in seen_docids:
+ fm, content, path = get_doc_by_id(root, docid)
+ if fm:
+ linked_fm_title = fm.get('title') or fm.get('pageTitle') or link['title']
+ expanded_results.append({
+ 'path': path,
+ 'title': linked_fm_title,
+ 'docid': docid,
+ 'breadcrumbs': fm.get('breadcrumbs'),
+ 'authors': fm.get('authors'),
+ 'pageKeywords': fm.get('pageKeywords'),
+ 'reasons': [f'Linked (Diff Diag of {parent_title})'],
+ 'snippet': content[:300] + '...' if content else '',
+ 'linked_info': check_linked_sections(content, root)
+ })
+ seen_docids.add(docid)
+ seen_paths.add(path)
+
+ out.extend(expanded_results)
+
return out
diff --git a/tools/search_md_gui.py b/tools/search_md_gui.py
index 7f4eb55..648afd4 100644
--- a/tools/search_md_gui.py
+++ b/tools/search_md_gui.py
@@ -18,7 +18,11 @@ from typing import List, Dict, Any
import anyio
import asyncio
-from nicegui import ui
+from nicegui import ui, app
+import re
+
+app.add_static_files('/images', 'docs_md/articles/images')
+app.add_static_files('/document/images', 'docs_md/articles/images')
import shutil
import subprocess
@@ -156,6 +160,44 @@ def search_page() -> None: # build UI
color: #cbd5e1;
border: 1px solid rgba(148, 163, 184, 0.4);
}
+ .badge-status-exists {
+ background-color: rgba(16, 185, 129, 0.15);
+ color: #34d399;
+ border: 1px solid rgba(16, 185, 129, 0.35);
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 500;
+ display: inline-block;
+ }
+ .badge-status-exists.cursor-pointer {
+ cursor: pointer;
+ }
+ .badge-status-exists.cursor-pointer:hover {
+ background-color: rgba(16, 185, 129, 0.3) !important;
+ border-color: rgba(16, 185, 129, 0.6) !important;
+ text-decoration: underline;
+ }
+ .badge-status-missing {
+ background-color: rgba(239, 68, 68, 0.15);
+ color: #f87171;
+ border: 1px solid rgba(239, 68, 68, 0.35);
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 500;
+ display: inline-block;
+ }
+ .badge-status-zero {
+ background-color: rgba(59, 130, 246, 0.15);
+ color: #93c5fd;
+ border: 1px solid rgba(59, 130, 246, 0.35);
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 500;
+ display: inline-block;
+ }
''')
@@ -243,7 +285,9 @@ def search_page() -> None: # build UI
render_query_blocks()
with ui.row().classes('w-full justify-between items-center gap-4 mt-2'):
- ui.button('Add OR Query', icon='add', on_click=add_query_block).classes('text-blue-400 border border-blue-800').props('outline dense')
+ with ui.row().classes('items-center gap-4'):
+ ui.button('Add OR Query', icon='add', on_click=add_query_block).classes('text-blue-400 border border-blue-800').props('outline dense')
+ expand_chk = ui.checkbox('Expand with Linked Articles', value=False).classes('text-slate-300')
search_btn = ui.button('Search', icon='search', on_click=lambda: asyncio.create_task(run_search_handler())).classes('bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-8 rounded-lg shadow-md transition-all duration-300')
# Advanced Criteria Expansion Box
@@ -280,7 +324,8 @@ def search_page() -> None: # build UI
columns = [
{'name': 'title', 'label': 'Title', 'field': 'title', 'required': True, 'align': 'left', 'sortable': True},
{'name': 'reasons', 'label': 'Matched In', 'field': 'reasons', 'align': 'left', 'sortable': True},
- {'name': 'snippet', 'label': 'Snippet', 'field': 'snippet', 'align': 'left'},
+ {'name': 'anatomy', 'label': 'Anatomy', 'field': 'anatomy', 'align': 'center'},
+ {'name': 'differential', 'label': 'Diff Diagnosis', 'field': 'differential', 'align': 'center'},
{'name': 'docid', 'label': 'DocID', 'field': 'docid', 'align': 'left'},
{'name': 'path', 'label': 'Path', 'field': 'path', 'align': 'left', 'sortable': True},
]
@@ -300,10 +345,16 @@ def search_page() -> None: # build UI
# Add slot overrides to format table cells nicely
table.add_slot('body-cell-title', '''
-
- {{ props.row.title }}
-
- {{ props.row.title }}
+
''')
table.add_slot('body-cell-reasons', '''
@@ -315,6 +366,36 @@ def search_page() -> None: # build UI
''')
+ table.add_slot('body-cell-anatomy', '''
+
+
+ No Anatomy ❌
+
+
+ Anatomy ({{ props.row.linked_info.anatomy.links.length }}) 🔍
+
+
+ Anatomy (0)
+
+
+ ''')
+ table.add_slot('body-cell-differential', '''
+
+
+ No Diff Diag ❌
+
+
+ Diff Diag ({{ props.row.linked_info.differential.links.length }}) 🔍
+
+
+ Diff Diag (0)
+
+
+ ''')
table.add_slot('body-cell-path', '''
@@ -322,6 +403,205 @@ def search_page() -> None: # build UI
''')
+ table.on('expand_row_links', lambda msg: asyncio.create_task(expand_row_links_handler(msg.args)))
+ table.on('show_links_modal', lambda msg: show_links_modal_handler(msg.args))
+
+ async def expand_row_links_handler(path: str):
+ with table.client:
+ row = None
+ for r in table.rows:
+ if r['path'] == path:
+ row = r
+ break
+ if not row:
+ return
+
+ info = row.get('linked_info')
+ if not info:
+ ui.notify('No link metadata found for this article', color='warning')
+ return
+
+ docids = []
+ origins = {}
+ for link in info['anatomy']['links']:
+ docids.append(link['docid'])
+ origins[link['docid']] = f"Linked (Anatomy of {row['title']})"
+ for link in info['differential']['links']:
+ docids.append(link['docid'])
+ origins[link['docid']] = f"Linked (Diff Diag of {row['title']})"
+
+ if not docids:
+ ui.notify('No linked articles in Anatomy or Diff Diagnosis sections', color='info')
+ return
+
+ status.set_text('Expanding linked articles...')
+
+ try:
+ resolved = []
+ seen_paths = {r['path'] for r in table.rows}
+ seen_docids = {r.get('docid') for r in table.rows if r.get('docid')}
+
+ def fetch_linked():
+ res = []
+ for docid in docids:
+ if docid and docid not in seen_docids:
+ fm, content, doc_path = search_md.get_doc_by_id('docs_md/articles', docid)
+ if fm:
+ linked_fm_title = fm.get('title') or fm.get('pageTitle') or fm.get('docid')
+ res.append({
+ 'path': doc_path,
+ 'title': linked_fm_title,
+ 'docid': docid,
+ 'breadcrumbs': fm.get('breadcrumbs'),
+ 'authors': fm.get('authors'),
+ 'pageKeywords': fm.get('pageKeywords'),
+ 'reasons': [origins[docid]],
+ 'snippet': content[:300] + '...' if content else '',
+ 'linked_info': search_md.check_linked_sections(content, root_input.value)
+ })
+ seen_docids.add(docid)
+ seen_paths.add(doc_path)
+ return res
+
+ new_rows = await anyio.to_thread.run_sync(fetch_linked)
+ if new_rows:
+ table.rows.extend(new_rows)
+ table.rows = list(table.rows)
+ ui.notify(f'Added {len(new_rows)} linked articles to results', color='positive')
+ status.set_text(f'Added {len(new_rows)} linked articles. Total rows: {len(table.rows)}')
+ else:
+ ui.notify('All linked articles are already in the table', color='info')
+ status.set_text(f'All linked articles are already in the table. Total rows: {len(table.rows)}')
+
+ except Exception as e:
+ ui.notify(f'Failed to expand links: {e}', color='negative')
+ status.set_text(f'Expand failed: {e}')
+
+ def show_links_modal_handler(args):
+ with table.client:
+ path = args.get('path')
+ section_type = args.get('type')
+
+ row = None
+ for r in table.rows:
+ if r['path'] == path:
+ row = r
+ break
+ if not row:
+ return
+
+ info = row.get('linked_info', {})
+ sec_info = info.get(section_type, {})
+ links = sec_info.get('links', [])
+ if not links:
+ ui.notify('No links available to display', color='warning')
+ return
+
+ section_title = 'Anatomy' if section_type == 'anatomy' else 'Differential Diagnosis'
+
+ with ui.dialog() as dialog, ui.card().classes('w-[550px] p-6 bg-slate-900 text-white border border-slate-700 rounded-xl gap-4'):
+ ui.label(f'Linked {section_title}').classes('text-2xl font-bold text-blue-400')
+ ui.label(f'Source: {row["title"]}').classes('text-slate-400 text-sm')
+
+ with ui.column().classes('w-full gap-2 mt-2 max-h-[300px] overflow-y-auto'):
+ for link in links:
+ docid = link['docid']
+ linked_title = link['title']
+ already_added = any(r.get('docid') == docid for r in table.rows if r.get('docid'))
+
+ with ui.row().classes('w-full items-center justify-between p-2 rounded bg-slate-800 border border-slate-700 hover:border-blue-500 transition-all'):
+ with ui.row().classes('items-center gap-2 flex-grow'):
+ ui.icon('link', color='primary').classes('text-sm')
+ ui.link(linked_title, f'/document/{docid}', new_tab=True).classes('text-blue-300 hover:text-blue-200 font-semibold text-sm')
+
+ if already_added:
+ ui.label('Added').classes('text-green-400 text-xs font-semibold px-2 py-1 rounded bg-green-950/40 border border-green-800')
+ else:
+ def make_add_handler(d_id=docid, title=linked_title):
+ return lambda: asyncio.create_task(add_single_doc(d_id, title, row['title'], section_title, dialog))
+ ui.button(icon='add', on_click=make_add_handler()).classes('text-xs text-blue-400 hover:bg-blue-900/20').props('flat round dense')
+
+ with ui.row().classes('w-full justify-between items-center mt-4 pt-4 border-t border-slate-800'):
+ to_add = [l for l in links if not any(r.get('docid') == l['docid'] for r in table.rows if r.get('docid'))]
+ if to_add:
+ def add_all_handler():
+ return asyncio.create_task(add_all_docs(links, row['title'], section_title, dialog))
+ ui.button(f'Add All ({len(to_add)})', icon='playlist_add', on_click=add_all_handler, color='primary').classes('text-white font-semibold')
+ else:
+ ui.button('All Added', icon='check', color='positive').props('disabled').classes('text-slate-400')
+
+ ui.button('Close', on_click=dialog.close).props('outline').classes('text-slate-300 border-slate-700')
+
+ dialog.open()
+
+ async def add_single_doc(docid: str, title: str, parent_title: str, section_title: str, dialog: ui.dialog):
+ with table.client:
+ status.set_text(f'Adding {title}...')
+ try:
+ def fetch():
+ fm, content, doc_path = search_md.get_doc_by_id('docs_md/articles', docid)
+ if fm:
+ return {
+ 'path': doc_path,
+ 'title': fm.get('title') or fm.get('pageTitle') or title,
+ 'docid': docid,
+ 'breadcrumbs': fm.get('breadcrumbs'),
+ 'authors': fm.get('authors'),
+ 'pageKeywords': fm.get('pageKeywords'),
+ 'reasons': [f'Linked ({section_title} of {parent_title})'],
+ 'linked_info': search_md.check_linked_sections(content, root_input.value)
+ }
+ return None
+
+ res = await anyio.to_thread.run_sync(fetch)
+ if res:
+ table.rows.append(res)
+ table.rows = list(table.rows)
+ ui.notify(f'Added "{title}" to search results', color='positive')
+ status.set_text(f'Added "{title}". Total rows: {len(table.rows)}')
+ dialog.close()
+ else:
+ ui.notify(f'Could not load document for {title}', color='warning')
+ except Exception as e:
+ ui.notify(f'Error adding document: {e}', color='negative')
+
+ async def add_all_docs(links: List[Dict[str, str]], parent_title: str, section_title: str, dialog: ui.dialog):
+ with table.client:
+ status.set_text('Adding all linked documents...')
+ try:
+ seen_docids = {r.get('docid') for r in table.rows if r.get('docid')}
+
+ def fetch_all():
+ res = []
+ for link in links:
+ docid = link['docid']
+ if docid and docid not in seen_docids:
+ fm, content, doc_path = search_md.get_doc_by_id('docs_md/articles', docid)
+ if fm:
+ res.append({
+ 'path': doc_path,
+ 'title': fm.get('title') or fm.get('pageTitle') or link['title'],
+ 'docid': docid,
+ 'breadcrumbs': fm.get('breadcrumbs'),
+ 'authors': fm.get('authors'),
+ 'pageKeywords': fm.get('pageKeywords'),
+ 'reasons': [f'Linked ({section_title} of {parent_title})'],
+ 'linked_info': search_md.check_linked_sections(content, root_input.value)
+ })
+ seen_docids.add(docid)
+ return res
+
+ new_rows = await anyio.to_thread.run_sync(fetch_all)
+ if new_rows:
+ table.rows.extend(new_rows)
+ table.rows = list(table.rows)
+ ui.notify(f'Added {len(new_rows)} linked articles to results', color='positive')
+ status.set_text(f'Added {len(new_rows)} linked articles. Total rows: {len(table.rows)}')
+ dialog.close()
+ else:
+ ui.notify('No new articles were added', color='info')
+ except Exception as e:
+ ui.notify(f'Error adding documents: {e}', color='negative')
async def run_search_handler():
has_query = False
@@ -354,7 +634,8 @@ def search_page() -> None: # build UI
'',
'exact',
None,
- or_queries_param
+ or_queries_param,
+ expand_chk.value
)
table.rows = results
@@ -540,6 +821,129 @@ def search_page() -> None: # build UI
run_conv_button = ui.button('Run conversion', on_click=lambda: asyncio.create_task(run_dtm_handler()), color='primary')
+@ui.page('/document/{identifier}')
+def render_doc_page(identifier: str):
+ ui.add_head_html('''
+
+
+ ''')
+
+ fm, content, path = search_md.get_doc_by_id('docs_md/articles', identifier)
+ if not fm:
+ with ui.column().classes('w-full items-center justify-center p-12 gap-4'):
+ ui.icon('warning', size='4rem', color='negative')
+ ui.label(f'Document "{identifier}" not found.').classes('text-2xl text-red-400 font-bold')
+ ui.button('Go to Search', on_click=lambda: ui.navigate.to('/')).props('outline')
+ return
+
+ with ui.column().classes('container gap-6'):
+ ui.button('Back to Explorer', icon='arrow_back', on_click=lambda: ui.navigate.to('/')).classes('self-start text-blue-400 border border-blue-800').props('outline dense')
+
+ with ui.column().classes('header-card w-full gap-4'):
+ bcs = fm.get('breadcrumbs') or []
+ if bcs:
+ with ui.row().classes('gap-2 items-center flex-wrap'):
+ for b in bcs:
+ ui.label(b).classes('breadcrumb-chip')
+
+ title_text = fm.get('title') or fm.get('pageTitle') or "Untitled"
+ ui.label(title_text).classes('text-4xl font-extrabold text-white leading-tight')
+
+ with ui.row().classes('w-full gap-6 mt-2'):
+ if fm.get('category'):
+ with ui.column().classes('gap-0.5'):
+ ui.label('Category').classes('text-slate-400 text-xs uppercase font-semibold')
+ ui.label(fm.get('category')).classes('text-slate-200 font-medium')
+ if fm.get('type'):
+ with ui.column().classes('gap-0.5'):
+ ui.label('Type').classes('text-slate-400 text-xs uppercase font-semibold')
+ ui.label(fm.get('type')).classes('text-slate-200 font-medium')
+ if fm.get('lastUpdated'):
+ with ui.column().classes('gap-0.5'):
+ ui.label('Last Updated').classes('text-slate-400 text-xs uppercase font-semibold')
+ ui.label(fm.get('lastUpdated')).classes('text-slate-200 font-medium')
+
+ authors = fm.get('authors') or []
+ if authors:
+ with ui.column().classes('gap-1 mt-2'):
+ ui.label('Authors').classes('text-slate-400 text-xs uppercase font-semibold')
+ authors_text = ", ".join([a.get('value') if isinstance(a, dict) else str(a) for a in authors])
+ ui.label(authors_text).classes('text-blue-300 font-medium')
+
+ with ui.card().classes('w-full p-8 shadow-xl bg-slate-900/60 border border-slate-800 rounded-xl markdown-content'):
+ ui.markdown(content)
+
+
def main() -> None:
port = int(os.environ.get('PORT', '8081'))
# NiceGUI's ui.run will serve the app; mount the page at /search-md