Files
homeclaw/scripts/fill_latin_terms.py
kimandClaude Sonnet 5 f014fec7e2 v4.0.0: 라이브 서비스 /srv/homeclaw로 이전 + Android 실기기 USB 연동 + 관리자 IP 차단 뷰어
- homeclaw 라이브 서비스를 /home/kim/homeclaw에서 /srv/homeclaw(480G 전용 파티션)로
  컷오버, 하드코딩된 절대경로 전부 치환 (config, mcp-servers, routes, scripts, web-ui)
- Android 에뮬레이터 컨테이너 리소스를 32스레드 Xeon 기준으로 상향(3g/3cpu → 6g/10cpu)
- 코드 에디터에 USB 실기기 패널 추가: 기기 감지, APK 설치, Logcat, scrcpy 화면 미러링
  (companion.py에 독립 프로세스로 scrcpy 스폰하는 로직 포함)
- 관리자 컨텍스트 뷰어에 차단된 IP 목록 탭 추가
- ollama primary 모델을 gemma4:31b-cloud로, 음성 TTS를 edge_tts로 전환

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 13:31:28 +09:00

421 lines
16 KiB
Python

#!/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 <i>Latin term</i>
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()