feat(jamie-journal): implement ComplianceChecker (의료법 제56조 pattern matching)

This commit is contained in:
2026-03-29 07:28:28 +09:00
parent 18bf611663
commit 1d236192b0

View File

@@ -172,8 +172,136 @@ class StructureValidator:
class ComplianceChecker: class ComplianceChecker:
"""Checks brand compliance rules for Jamie Clinic journal content.""" """Checks brand compliance rules for Jamie Clinic journal content."""
PROHIBITED_PATTERNS = {
'effect_guarantee': {
'severity': 'critical',
'patterns': [
r'100[%]\s*(?:만족|효과|성공)',
r'반드시\s+(?:효과|개선|만족)',
r'완벽한?\s+결과',
r'보장합니다',
r'확실한?\s+효과',
r'틀림없이?\s+(?:효과|개선)',
],
'message': '효과를 보장하는 표현은 의료법 제56조 제2항 제2호 위반입니다.',
'suggestion': '"개선에 도움을 줄 수 있습니다" 또는 "개인에 따라 결과가 다를 수 있습니다"로 변경하세요.',
},
'comparative_superiority': {
'severity': 'critical',
'patterns': [
r'최고의?',
r'(?:국내|강남|서울|압구정)\s*(?:최고|1위|최초)',
r'1위',
r'\s*병원보다',
r'다른\s*(?:병원|의원)보다\s*(?:우수|뛰어|좋)',
r'업계\s*최초',
r'독보적',
],
'message': '타 의료기관과의 비교 우위 주장은 의료법 제56조 제2항 제4호 위반입니다.',
'suggestion': '"풍부한 경험을 보유한" 또는 "저희만의 방법으로"와 같은 객관적 표현으로 변경하세요.',
},
'safety_guarantee': {
'severity': 'critical',
'patterns': [
r'부작용\s*(?:없|無|제로|zero)',
r'(?:100[%]|완전히?|절대)\s*안전',
r'위험\s*(?:없|無)',
r'흉터\s*(?:없|無|제로|zero)',
r'무통(?:증)?(?:\s|$|,)',
],
'message': '안전성을 보장하는 표현은 의료법 제56조 제2항 제7호 위반입니다.',
'suggestion': '부작용 가능성을 명시하고 "안전한 수술을 위해 최선을 다합니다"로 변경하세요.',
},
'patient_testimonial': {
'severity': 'critical',
'patterns': [
r'(?:환자|고객)\s*[가-힣A-Z]+\s*(?:씨|님)의?\s*(?:후기|경험)',
r'실제\s*(?:환자|고객)\s*(?:후기|리뷰|경험담)',
r'[""]\s*(?:정말|너무|진짜)\s+(?:만족|좋아요|감사)',
r'수술\s*후\s*\d+\s*(?:개월|주일|년)\s*(?:만족|경과)',
],
'message': '환자 치료경험담은 의료법 제56조 제2항 제2호 위반입니다.',
'suggestion': '환자 후기를 제거하고, 일반적인 수술 과정 설명으로 대체하세요.',
},
'exaggeration': {
'severity': 'high',
'patterns': [
r'(?:놀라운|대박|극적인)\s*(?:변화|효과|결과)',
r'마법같은?',
r'기적적인?',
r'다시\s*태어나',
r'완벽\s*변신',
],
'message': '과장된 표현은 의료법 제56조 제2항 제3호 위반 가능성이 있습니다.',
'suggestion': '"자연스러운 개선", "점진적인 효과"와 같은 절제된 표현으로 변경하세요.',
},
'specialty_claim': {
'severity': 'high',
'patterns': [
r'전문\s*병원',
r'특화\s*(?:병원|클리닉|센터)',
r'노하우',
],
'message': '전문병원 지정 없이 "전문", "특화" 표현 사용은 과장 광고에 해당합니다.',
'suggestion': '"중점진료", "집중진료", "풍부한 경험"으로 변경하세요.',
},
}
PROCEDURE_KEYWORDS = [
'수술', '시술', '이마거상', '쌍꺼풀', '리프팅', '보톡스', '필러',
'눈매교정', '안검하수', '지방이식', '절개',
]
def check(self, content: str) -> List[ValidationIssue]: def check(self, content: str) -> List[ValidationIssue]:
return [] issues: List[ValidationIssue] = []
lines = content.split('\n')
# Check prohibited patterns
for category, rule in self.PROHIBITED_PATTERNS.items():
for raw_pat in rule['patterns']:
compiled = re.compile(raw_pat)
for m in compiled.finditer(content):
line_number = content[:m.start()].count('\n') + 1
issues.append(ValidationIssue(
checker='ComplianceChecker',
issue_type=category,
severity=rule['severity'],
line_number=line_number,
matched_text=m.group(),
message=rule['message'],
suggestion=rule['suggestion'],
))
# Check for missing disclaimers when procedure keywords present
has_procedure = any(kw in content for kw in self.PROCEDURE_KEYWORDS)
if has_procedure:
side_effect_terms = ['부작용', '합병증', '붓기', '', '염증']
if not any(t in content for t in side_effect_terms):
total_lines = len(lines)
issues.append(ValidationIssue(
checker='ComplianceChecker',
issue_type='missing_side_effect_notice',
severity='high',
line_number=total_lines,
matched_text='',
message='시술/수술 내용이 있으나 부작용 안내가 없습니다.',
suggestion='"부작용(출혈, 감염, 염증 등)이 있을 수 있습니다" 형태의 안내를 추가하세요.',
))
individual_variation_pattern = re.compile(r'개인.{0,5}(?:차이|다를 수|다릅니다)')
if not individual_variation_pattern.search(content):
total_lines = len(lines)
issues.append(ValidationIssue(
checker='ComplianceChecker',
issue_type='missing_individual_variation_notice',
severity='medium',
line_number=total_lines,
matched_text='',
message='시술/수술 내용이 있으나 개인차 고지가 없습니다.',
suggestion='"개인에 따라 결과가 다를 수 있습니다" 문구를 추가하세요.',
))
return issues
class SpellingChecker: class SpellingChecker: