- 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>
421 lines
16 KiB
Python
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() |