#!/usr/bin/env python3 """ Fill missing Latin terms in dental_dict.db using known medical Latin terminology and PubMed search for verification. Usage: python3 fill_latin_terms.py # Preview changes (dry-run) python3 fill_latin_terms.py --apply # Apply changes to DB python3 fill_latin_terms.py --category 임플란트 # Specific category only """ import sqlite3 import json import sys import time import urllib.request import urllib.parse import xml.etree.ElementTree as ET DB_PATH = '/srv/homeclaw/.smallclaw/databases/dental_dict.db' EUTILS = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils' # ── Known Latin translations for common dental/implant terms ── # These are well-established anatomical Latin terms from Nomina Anatomica KNOWN_LATIN = { # Anatomy 'alveolar bone loss': 'resorptio ossis alveolaris', 'alveolar ridge augmentation': 'augmentatio cristae alveolaris', 'alveolar crest': 'crista alveolaris', 'periosteum': 'periosteum', 'alveolar bone assessment': 'evaluatio ossis alveolaris', 'alveoloplasty': 'alveoloplastica', 'peri-implant mucosa': 'mucosa periimplantaris', 'peri-implant bone': 'os periimplantare', 'peri-implantitis': 'periimplantitis', 'peri-implant mucositis': 'mucositis periimplantaris', 'peri-implant bone loss': 'resorptio ossis periimplantaris', 'peri-implant health': 'salus periimplantaris', 'peri-implant probing': 'exploratio periimplantaris', # Bone grafts 'autogenous bone graft': 'transplantatio ossis autologi', 'allogenic bone graft': 'transplantatio ossis allogenici', 'xenogenic bone graft': 'transplantatio ossis xenogenici', 'alloplastic bone graft': 'transplantatio ossis alloplastici', # Implant components 'implant fixture': 'corpus implantatis', 'implant abutment': 'palum implantatis', 'implant crown': 'corona implantatis', 'cover screw': 'obturatorium screw', 'healing abutment': 'palum sanationis', 'custom abutment': 'palum customatum', 'scan body': 'corpus scanning', 'implant screw': 'stria implantatis', 'implant screw fracture': 'fractura striae implantatis', # Procedures 'implant placement': 'insertio implantatis', 'immediate implant placement': 'insertio implantatis immediata', 'immediate extraction and implant placement': 'extractio et insertio implantatis immediata', 'sinus floor elevation': 'elevatio soli sinus maxillaris', 'guided bone regeneration for implant': 'regeneratio ossis directae pro implantate', 'ridge augmentation': 'augmentatio cristae', 'implant positioning': 'positio implantatis', 'implant removal': 'remotio implantatis', 'implant re-placement': 'reinsertio implantatis', 'guided implant surgery': 'chirurgia implantatis dirigita', 'flapless surgery': 'chirurgia sine lamina', 'impression taking': 'impressio', 'digital impression': 'impressio digitalis', 'osteotomy': 'osteotomia', 'osteotomy drill': 'terebra osteotomiae', # Properties 'biocompatibility': 'biocompatibilitas', 'primary stability': 'stabilitas primaria', 'secondary stability': 'stabilitas secundaria', 'insertion torque': 'momentum insertionis', 'implant overloading': 'overloading implantatis', 'implant surface treatment': 'tractatio superficiei implantatis', 'implant failure': 'defectio implantatis', 'implant failure causes': 'causae defectonis implantatis', # Prosthesis 'implant prosthesis': 'prosthesis implantatis', 'implant-supported prosthesis': 'prosthesis implantatis supportata', 'screw-retained prosthesis': 'prosthesis stria retenta', 'cement-retained prosthesis': 'prosthesis cemento retenta', 'implant prosthesis retention': 'retentio prostheseos implantatis', 'implant-retained prosthesis': 'prosthesis implantatis retenta', 'implant-supported fixed prosthesis': 'prosthesis fixa implantatis supportata', 'implant provisional restoration': 'restauratio provisoria implantatis', 'implant prosthesis material': 'materia prostheseos implantatis', 'implant esthetics': 'aesthetica implantatis', # Types 'tapered implant': 'implantatum conicum', 'cylindrical implant': 'implantatum cylindricum', 'full-arch implant': 'implantatum totius arcus', 'early loaded implant': 'implantatum onere pramature accepto', 'delayed loaded implant': 'implantatum onere dilato accepto', # Connections 'internal connection': 'connexio interna', 'external connection': 'connexio externa', 'morse taper': 'conus Morse', 'implant-prosthesis connection': 'connexio implantatis et prostheseos', # Surface treatments 'sandblasting': 'abrasio arenaria', 'sla surface treatment': 'tractatio SLA superficiei', 'hydroxyapatite coating': 'coating hydroxyapatitis', # Occlusion/loading 'implant occlusion': 'occlusio implantatis', 'immediate loading': 'onere immediata', 'early loading': 'onere praecox', 'delayed loading': 'onere dilata', 'implant maintenance': 'manutentio implantatis', # Assessment 'implant radiographic assessment': 'evaluatio radiographica implantatis', 'implant design': 'designatio implantatis', 'implant diameter': 'diameter implantatis', 'implant length': 'longitudo implantatis', 'implant angulation': 'angulatio implantatis', # Other 'surface treatment': 'tractatio superficiei', 'taper': 'conus', 'post-extraction bone remodeling': 'remodelatio ossis post extractionem', 'soft tissue management': 'manusatio textus mollis', 'implant screw tightening': 'stringatio striae implantatis', 'surgical guide': 'dux chirurgicus', 'computer-assisted surgery': 'chirurgia computatore assista', 'socket shield': 'technica socket shield', # Categories outside 임플란트 - 방사선 'dental radiography': 'radiographia dentalis', 'radiograph': 'radiographia', 'periapical radiograph': 'radiographia periapicalis', 'bitewing radiograph': 'radiographia bitewing', 'panoramic radiograph': 'radiographia panoramica', 'cbct': 'tomographia computata conorum fasciculorum', 'cone beam computed tomography': 'tomographia computata conorum fasciculorum', 'cephalometric radiograph': 'radiographia cephalometrica', 'occlusal radiograph': 'radiographia occlusalis', 'radiation dose': 'dosim radiations', 'radiation protection': 'protectio radiations', 'x-ray tube': 'tubus radiographicus', 'image receptor': 'receptor imaginis', 'contrast': 'contrast', 'density': 'densitas', 'exposure': 'expositio', 'collimation': 'collimatio', 'filtration': 'filtratio', 'scattering': 'dispersio', # 소아치과 'primary tooth': 'dens deciduus', 'permanent tooth': 'dens permanens', 'deciduous tooth': 'dens deciduus', 'mixed dentition': 'dentitio mixta', 'pulpotomy': 'pulpotomia', 'sealant': 'sealant', 'space maintainer': 'mantenitor spatii', 'eruption': 'eruptio', 'early childhood caries': 'caries infantum praecox', 'nursing caries': 'caries lactis', # 예방치학 'dental prophylaxis': 'prophylaxis dentalis', 'fluoride': 'fluoridum', 'fluoride application': 'applicatio fluoridi', 'dental plaque': 'dentalis plack', 'oral hygiene': 'hygiena oris', 'toothbrushing': 'pulzatio dentium', 'flossing': 'flossing', 'dental scaling': 'scalatio dentalis', 'root planing': 'planatio radicis', # 구강내과 'oral medicine': 'medicina oralis', 'stomatitis': 'stomatitis', 'oral mucosa': 'mucosa oralis', 'xerostomia': 'xerostomia', 'burning mouth syndrome': 'syndroma ardens oris', 'halitosis': 'halitosis', # 치주 'periodontitis': 'periodontitis', 'gingivitis': 'gingivitis', 'periodontal pocket': 'sacculus periodontalis', 'periodontal ligament': 'ligamentum periodontale', 'cementum': 'cementum', 'alveolar bone': 'os alveolare', 'gingival recession': 'recessio gingivae', 'guided tissue regeneration': 'regeneratio textus directa', 'periodontal abscess': 'abscessus periodontalis', 'scaling and root planing': 'scalatio et planatio radicis', 'periodontal surgery': 'chirurgia periodontalis', # 보존 'dental caries': 'caries dentis', 'pulp': 'pulpa dentis', 'dentin': 'dentinum', 'enamel': 'enamelum', 'root canal': 'canales radicis', 'composite resin': 'resina composita', 'amalgam': 'amalgama', 'glass ionomer': 'cementum ionomerum vitreum', 'cavity preparation': 'preparatio cavitatis', 'pulp capping': 'operculatio pulpi', # 보철 'crown': 'corona', 'bridge': 'pons', 'denture': 'prosthesis mobilis', 'veneer': 'fornix laminaris', 'inlay': 'inlay', 'onlay': 'onlay', 'abutment': 'palum', 'pontic': 'corona pontis', 'retainer': 'retentor', 'porcelain': 'porcellana', # 교정 'orthodontic bracket': 'bracket orthodonticum', 'archwire': 'filarum arcus', 'orthodontic force': 'vis orthodontica', 'retainer orthodontic': 'retentor orthodonticus', 'malocclusion': 'malocclusio', 'overjet': 'overjet', 'overbite': 'overbite', 'crossbite': 'crossbite', 'open bite': 'apertura mordendi', # 구강외과 'extraction': 'extractio dentis', 'impacted tooth': 'dens impactus', 'wisdom tooth': 'dens serotinus', 'fracture mandible': 'fractura mandibulae', 'fracture maxilla': 'fractura maxillae', 'cyst oral': 'cystis oralis', 'tumor oral': 'tumor oralis', 'biopsy': 'biopsia', 'incisional biopsy': 'biopsia incisionalis', 'excisional biopsy': 'biopsia excisionalis', 'frozen section biopsy': 'biopsia cryosectionis', 'needle biopsy': 'biopsia per acus', 'oral mucosal flap': 'lamina mucosae oris', # 구강병리 'oral pathology': 'pathologia oralis', 'histopathology': 'histopathologia', 'lesion': 'lesio', 'neoplasm': 'neoplasma', 'dysplasia': 'dysplasia', 'hyperplasia': 'hyperplasia', 'metaplasia': 'metaplasia', # 기초치의학 'dental anatomy': 'anatomia dentalis', 'occlusion': 'occlusio', 'temporal muscle': 'musculus temporalis', 'masseter muscle': 'musculus masseter', 'salivary gland': 'glandula salivaria', 'trigeminal nerve': 'nervus trigeminus', 'facial nerve': 'nervus facialis', 'mandible': 'mandibula', 'maxilla': 'maxilla', 'tmj': 'articulatio temporomandibularis', } # ── PubMed search for Latin terms ── def search_pubmed(term: str, api_key: str = None) -> list: """Search PubMed for articles containing Latin terminology.""" params = { 'db': 'pubmed', 'term': f'{term} AND (latin OR terminology OR anatomy)', 'retmode': 'json', 'retmax': 3, } url = f"{EUTILS}/esearch.fcgi?{urllib.parse.urlencode(params)}" if api_key: url += f"&api_key={api_key}" try: req = urllib.request.Request(url) req.add_header('User-Agent', 'SmallClaw/1.0') with urllib.request.urlopen(req, timeout=10) as resp: data = json.loads(resp.read().decode()) return data.get('esearchresult', {}).get('idlist', []) except Exception as e: print(f" [PubMed search error] {e}", file=sys.stderr) return [] def fetch_pubmed_abstract(pmid: str, api_key: str = None) -> str: """Fetch abstract from PubMed to find Latin terminology.""" params = { 'db': 'pubmed', 'id': pmid, 'rettype': 'abstract', 'retmode': 'xml', } url = f"{EUTILS}/efetch.fcgi?{urllib.parse.urlencode(params)}" if api_key: url += f"&api_key={api_key}" try: req = urllib.request.Request(url) req.add_header('User-Agent', 'SmallClaw/1.0') with urllib.request.urlopen(req, timeout=15) as resp: return resp.read().decode() except Exception as e: print(f" [PubMed fetch error] {e}", file=sys.stderr) return '' def get_api_key(): """Read PubMed API key from config.""" try: with open('/srv/homeclaw/.smallclaw/config.json') as f: cfg = json.load(f) return cfg.get('pubmed', {}).get('api_key') or cfg.get('search', {}).get('pubmed_api_key') except: return None # ── Main ── def main(): apply = '--apply' in sys.argv category_filter = None for arg in sys.argv: if arg.startswith('--category='): category_filter = arg.split('=', 1)[1] if '--category' in sys.argv: idx = sys.argv.index('--category') if idx + 1 < len(sys.argv): category_filter = sys.argv[idx + 1] conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row query = 'SELECT id, korean, english, latin, category FROM terms WHERE (latin IS NULL OR latin = "")' if category_filter: query += f' AND category = "{category_filter}"' query += ' ORDER BY category, id' rows = conn.execute(query).fetchall() print(f"Missing Latin terms: {len(rows)} total") api_key = get_api_key() updated = 0 not_found = [] pubmed_verified = 0 for row in rows: english_lower = row['english'].lower().strip() # Try known dictionary first latin = KNOWN_LATIN.get(english_lower) if latin: action = "UPDATE" if apply else "WOULD UPDATE" print(f" [{action}] #{row['id']} {row['korean']} / {row['english']} → {latin} (dictionary)") if apply: conn.execute('UPDATE terms SET latin = ?, updated_at = datetime("now","localtime") WHERE id = ?', (latin, row['id'])) updated += 1 else: not_found.append(row) # For terms not in dictionary, try PubMed if not_found and api_key: print(f"\n--- PubMed search for {len(not_found)} remaining terms ---") for i, row in enumerate(not_found[:20]): # Limit to 20 to avoid rate limiting english_lower = row['english'].lower().strip() # Try to find Latin in PubMed abstracts pmids = search_pubmed(row['english'], api_key) if pmids: time.sleep(0.4) # Rate limit abstract_xml = fetch_pubmed_abstract(pmids[0], api_key) # Look for Latin patterns (italicized or parenthesized Latin) # Common patterns: (Latin term) or Latin term import re latin_patterns = re.findall(r'\(([A-Z][a-z]+(?: [a-z]+)*)\)', abstract_xml[:3000]) # Filter likely Latin terms (ending in -is, -um, -us, -a, -ae, etc.) for candidate in latin_patterns: if any(candidate.lower().endswith(s) for s in ['is', 'um', 'us', 'a', 'ae', 'ia', 'or', 'ix', 'as', 'es']): action = "UPDATE" if apply else "WOULD UPDATE" print(f" [{action}] #{row['id']} {row['korean']} / {row['english']} → {candidate} (PubMed)") if apply: conn.execute('UPDATE terms SET latin = ?, updated_at = datetime("now","localtime") WHERE id = ?', (candidate, row['id'])) updated += 1 pubmed_verified += 1 break else: not_found_remaining = not_found # stays in not_found continue time.sleep(0.4) if apply: conn.commit() print(f"\n✓ Updated {updated} terms in database") else: print(f"\n--- DRY RUN: {updated} terms would be updated ---") print(f" PubMed-verified: {pubmed_verified}") print(f" Still missing: {len(not_found) - pubmed_verified}") print(f"\n Run with --apply to update the database.") conn.close() if __name__ == '__main__': main()