seawolf2357 commited on
Commit
8aa5999
ยท
verified ยท
1 Parent(s): b3ca1d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +376 -395
app.py CHANGED
@@ -1,8 +1,8 @@
1
  """
2
- AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0 โ€” AI ํƒ์ง€ + ํ’ˆ์งˆ + AIโ†’์ธ๊ฐ„ ๋ณ€ํ™˜ + ํ‘œ์ ˆ ๊ฒ€์‚ฌ + ๋ฌธ์„œ ๋ถ„์„
3
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
4
  5์ถ• AI ํƒ์ง€ | 6ํ•ญ๋ชฉ ํ’ˆ์งˆ | LLM ๊ต์ฐจ๊ฒ€์ฆ (GPT-OSS-120B ยท Qwen3-32B ยท Kimi-K2)
5
- โ˜… AIโ†’์ธ๊ฐ„: Adversarial Humanizer v2 (๋ฐ˜๋ณต ์ž๊ธฐ๋Œ€์ „ ๋ฃจํ”„)
6
  โ˜… ํ‘œ์ ˆ: Brave Search ๋ณ‘๋ ฌ(์ตœ๋Œ€20) + KCI/RISS/ARXIV + Gemini + CopyKiller ๋ณด๊ณ ์„œ
7
  โ˜… ๋ฌธ์„œ: PDFยทDOCXยทHWPยทHWPXยทTXT ์—…๋กœ๋“œ โ†’ ์„น์…˜๋ณ„ ํžˆํŠธ๋งต + PDF ๋ณด๊ณ ์„œ
8
  """
@@ -319,140 +319,344 @@ def call_groq(model, prompt, max_tokens=800, temperature=0.1):
319
  except Exception as e: return None, str(e)[:150]
320
 
321
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
322
- # โ˜… ํ†ตํ•ฉ ๋ฌธ์žฅ ์ ์ˆ˜ (ํƒญ1 + ํƒญ2 ๊ณต์œ )
323
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
324
  AI_ENDINGS = ['ํ•ฉ๋‹ˆ๋‹ค','์ž…๋‹ˆ๋‹ค','๋ฉ๋‹ˆ๋‹ค','์Šต๋‹ˆ๋‹ค','์žˆ์Šต๋‹ˆ๋‹ค','ํ–ˆ์Šต๋‹ˆ๋‹ค','๊ฒ ์Šต๋‹ˆ๋‹ค']
325
- AI_CONNS = ['๋˜ํ•œ','๋”ฐ๋ผ์„œ','๊ทธ๋Ÿฌ๋ฏ€๋กœ','์ด์— ๋”ฐ๋ผ','ํ•œํŽธ','๋”๋ถˆ์–ด','์•„์šธ๋Ÿฌ','๋ฟ๋งŒ ์•„๋‹ˆ๋ผ','์ด๋ฅผ ํ†ตํ•ด','์ด์—','๊ฒฐ๊ณผ์ ์œผ๋กœ','๊ถ๊ทน์ ์œผ๋กœ','ํŠนํžˆ','๋‚˜์•„๊ฐ€','์ด๋Ÿฌํ•œ']
326
- AI_FILLER = ['๊ฒƒ์œผ๋กœ ๋ณด','๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€','๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ','ํ•  ์ˆ˜ ์žˆ','๋ณผ ์ˆ˜ ์žˆ','์ฃผ๋ชฉํ•  ๋งŒ','์ค‘์š”ํ•œ ์—ญํ• ','์ค‘์š”ํ•œ ์˜๋ฏธ','๊ธ์ •์ ์ธ ์˜ํ–ฅ','๋ถ€์ •์ ์ธ ์˜ํ–ฅ','ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค','ํ•„์š”ํ•˜๋‹ค','์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค','์ค‘์š”ํ•˜๋‹ค','์—ญํ• ์„ ํ•˜','์˜ํ–ฅ์„ ๋ฏธ','๊ธฐ๋Œ€๋œ๋‹ค','์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค','๋ถ€๊ฐ๋˜๊ณ ','๋Œ€๋‘๋˜๊ณ ','๋‹ค์–‘ํ•œ ๋ถ„์•ผ','๋‹ค์–‘ํ•œ ์‚ฐ์—…','๋ˆˆ๋ถ€์‹  ์„ฑ๊ณผ','ํš๊ธฐ์ ์ธ ๋ณ€ํ™”','ํ˜์‹ ์ ์ธ','์ ์—์„œ','์ธก๋ฉด์—์„œ','๊ด€์ ์—์„œ']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  HUMAN_MARKERS = {
328
- 'ใ…‹ใ…Žใ… ': re.compile(r'([ใ…‹ใ…Žใ… ใ…œใ„ทใ„ฑ])\1{2,}'),
329
  '์ด๋ชจํ‹ฐ์ฝ˜': re.compile(r'[;:]-?[)(DPp]|\^[_\-]?\^|ใ…กใ…ก|;;'),
330
- '์ค„์ž„': re.compile(r'ใ„นใ…‡|ใ…‡ใ…‡|ใ„ดใ„ด|ใ…‡ใ…‹'),
331
  '๋А๋‚Œํ‘œ': re.compile(r'[!?]{2,}'),
332
- '๋น„๊ฒฉ์‹': re.compile(r'(๊ฑฐ๋“ |์ž–์•„|์ธ๋ฐ|์ธ๊ฑธ|๊ฐ™์Œ|๋А๋‚Œ|์•„๋‹˜|๋Œ€๋ฐ•|๋ฏธ์ณค)'),
 
 
 
333
  }
334
  FP = {
335
- "GPT": {"m":['๋ฌผ๋ก ์ด์ฃ ','๋„์›€์ด ๋˜์…จ๊ธฐ๋ฅผ','์„ค๋ช…ํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค','์ถ”๊ฐ€ ์งˆ๋ฌธ','๋„์›€์ด ํ•„์š”ํ•˜์‹œ๋ฉด'],"e":['์Šต๋‹ˆ๋‹ค','๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค'],"lp":re.compile(r'^\d+\.\s|^[-โ€ข]\s',re.M)},
336
- "Claude": {"m":['๋ง์”€ํ•˜์‹ ','์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค','๊ท ํ˜• ์žกํžŒ','๋งฅ๋ฝ์—์„œ','ํ•œ ๊ฐ€์ง€ ์ฃผ์˜ํ• ','๋‰˜์•™์Šค'],"e":['๋„ค์š”','๊ฑฐ์˜ˆ์š”'],"lp":re.compile(r'^\*\*.*\*\*|^#+\s',re.M)},
337
- "Gemini": {"m":['๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค','์ •๋ฆฌํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค','ํ•ต์‹ฌ ๋‚ด์šฉ์„','๋” ์•Œ๊ณ  ์‹ถ์œผ์‹œ๋ฉด'],"e":['๊ฒ ์Šต๋‹ˆ๋‹ค','๋ณด์„ธ์š”'],"lp":re.compile(r'^\*\s|^-\s\*\*',re.M)},
338
- "Perplexity": {"m":['๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅด๋ฉด','๋ณด๋„์— ๋”ฐ๋ฅด๋ฉด','์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด','๋ฐํ˜”๋‹ค','์ „ํ–ˆ๋‹ค'],"e":['๋ฐํ˜”๋‹ค','๋‚˜ํƒ€๋‚ฌ๋‹ค'],"lp":re.compile(r'\[\d+\]',re.M)},
 
 
 
 
339
  }
340
 
341
  def score_sentence(sent):
342
- """๋‹จ์ผ ๋ฌธ์žฅ AI ์ ์ˆ˜ (0~100). ํƒญ1ยทํƒญ2 ๊ณต์œ ."""
343
  sc = 0; reasons = []
344
- # ๊ฒฉ์‹ ์ข…๊ฒฐ์–ด๋ฏธ
 
 
 
345
  for e in AI_ENDINGS:
346
- if sent.rstrip('.').endswith(e): sc += 25; reasons.append(f"๊ฒฉ์‹์–ด๋ฏธ(-{e})"); break
347
- # ๋ฌธ๋‘ ์ ‘์†์‚ฌ
 
 
 
 
 
 
 
348
  for c in AI_CONNS:
349
- if sent.strip().startswith(c): sc += 20; reasons.append(f"AI์ ‘์†์‚ฌ({c})"); break
350
- # ์ƒํˆฌ์  ํ‘œํ˜„
351
- filler_found = 0
352
- for f in AI_FILLER:
353
- if f in sent: filler_found += 1
354
- if filler_found >= 2: sc += 25; reasons.append(f"์ƒํˆฌํ‘œํ˜„ร—{filler_found}")
355
- elif filler_found == 1: sc += 15; reasons.append("์ƒํˆฌํ‘œํ˜„ร—1")
356
- # ๋ชจ๋ธ ์ง€๋ฌธ
 
 
 
 
 
 
 
 
 
 
357
  for mn, fp in FP.items():
358
  for m in fp["m"]:
359
- if m in sent: sc += 10; reasons.append(f"{mn}์ง€๋ฌธ({m})"); break
360
- # ์ธ๊ฐ„ ๋งˆ์ปค (๊ฐ์ )
 
 
 
 
 
 
 
 
 
 
 
 
 
361
  for n, p in HUMAN_MARKERS.items():
362
- if p.search(sent): sc -= 30; reasons.append(f"์ธ๊ฐ„๋งˆ์ปค({n})")
 
 
 
 
 
 
363
  return max(0, min(100, sc)), reasons
364
 
365
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
366
- # ์ถ•โ‘  ํ†ต๊ณ„
367
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
368
  def analyze_statistics(text, sentences, words):
369
  sl = [len(s) for s in sentences]
370
  if len(sl) < 2: return {"score":50}
 
 
371
  avg = sum(sl)/len(sl); std = math.sqrt(sum((l-avg)**2 for l in sl)/len(sl))
372
  cv = std/avg if avg > 0 else 0
373
- burst = 90 if cv<0.25 else 70 if cv<0.35 else 50 if cv<0.50 else 30 if cv<0.65 else 15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  wf = Counter(words); t = len(words)
375
- ne = 0
376
- if t > 0:
377
- ent = -sum((c/t)*math.log2(c/t) for c in wf.values() if c>0)
378
- mx = math.log2(len(wf)) if len(wf)>1 else 1
379
- ne = ent/mx if mx>0 else 0
380
- es = 75 if ne>0.92 else 55 if ne>0.85 else 30
381
  ttr = len(wf)/t if t>0 else 0
382
- vs = 70 if ttr<0.45 else 50 if ttr<0.55 else 25
383
- se = []
384
- for s in sentences:
385
- sw = split_words(s)
386
- if len(sw)<3: continue
387
- sf = Counter(sw); st = len(sw)
388
- se.append(-sum((c/st)*math.log2(c/st) for c in sf.values() if c>0))
389
- ps = 50
390
- if len(se)>=2:
391
- sa = sum(se)/len(se); scv = math.sqrt(sum((e-sa)**2 for e in se)/len(se))/(sa if sa else 1)
392
- ps = 80 if scv<0.15 else 55 if scv<0.25 else 25
393
- return {"score":int(burst*0.35+es*0.2+vs*0.2+ps*0.25),"cv":round(cv,3),"ttr":round(ttr,3)}
 
394
 
395
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
396
- # ์ถ•โ‘ก ๋ฌธ์ฒด
397
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
398
  def analyze_korean_style(text, sentences, morphemes):
399
- fc = sum(1 for s in sentences if any(s.rstrip('.').endswith(e) for e in AI_ENDINGS))
400
- fr = fc/len(sentences) if sentences else 0
401
- es = 80 if fr>0.8 else 60 if fr>0.6 else 40 if fr>0.4 else 20
402
- cc = sum(1 for c in AI_CONNS if c in text); cd = cc/len(sentences) if sentences else 0
403
- cs = 85 if cd>0.5 else 65 if cd>0.3 else 40 if cd>0.15 else 15
404
- flc = sum(1 for f in AI_FILLER if f in text)
405
- fs = 90 if flc>=5 else 70 if flc>=3 else 45 if flc>=1 else 10
406
- hs = sum(len(p.findall(text)) for p in HUMAN_MARKERS.values())
407
- hp = min(30, hs*10)
408
- ps = 50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  if morphemes:
410
  pc = Counter(t for _,t in morphemes); tm = sum(pc.values())
411
- nr = sum(pc.get(t,0) for t in ['NNG','NNP','NNB','NR','NP'])/tm if tm else 0
412
- ps = 70 if nr>0.45 else 55 if nr>0.40 else 30
413
- return {"score":max(5,int(es*0.25+cs*0.25+fs*0.25+ps*0.25)-hp),"formal":f"{fr:.0%}","conn":f"{cd:.2f}","filler":flc,"human":hs}
 
 
 
414
 
415
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
416
- # ์ถ•โ‘ข ๋ฐ˜๋ณต
417
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
418
  def analyze_repetition(text, sentences, words):
 
 
 
419
  tr = 0
420
- if len(words)>=3:
421
  tg = Counter(tuple(words[i:i+3]) for i in range(len(words)-2))
422
  tr = sum(1 for c in tg.values() if c>1)/len(tg) if tg else 0
423
- ns = 75 if tr>0.15 else 55 if tr>0.08 else 25
424
- fws = 50
425
- if len(sentences)>=3:
426
- fw = [split_words(s)[0] for s in sentences if split_words(s)]
427
- if fw: r = Counter(fw).most_common(1)[0][1]/len(fw); fws = 70 if r>0.4 else 50 if r>0.25 else 20
428
- # ์ ‘์†์‚ฌ ๋ฌธ๋‘ ๋ฐ˜๋ณต (์ˆœ์ˆ˜ AI ์ ‘์†์‚ฌ๋งŒ โ€” ์ž์—ฐ์–ด ์ ‘์†์‚ฌ ์ œ์™ธ)
 
 
 
 
 
 
 
 
 
 
429
  ai_only_conns = ['๋˜ํ•œ','๋”ฐ๋ผ์„œ','๊ทธ๋Ÿฌ๋ฏ€๋กœ','์ด์— ๋”ฐ๋ผ','๋”๋ถˆ์–ด','์•„์šธ๋Ÿฌ','๋ฟ๋งŒ ์•„๋‹ˆ๋ผ',
430
  '์ด๋ฅผ ํ†ตํ•ด','์ด์—','๊ฒฐ๊ณผ์ ์œผ๋กœ','๊ถ๊ทน์ ์œผ๋กœ','๋‚˜์•„๊ฐ€','์ด๋Ÿฌํ•œ']
431
  cr = sum(1 for s in sentences if any(s.strip().startswith(c) for c in ai_only_conns))
432
  crr = cr/len(sentences) if sentences else 0
433
- css = 85 if crr>0.4 else 60 if crr>0.25 else 35 if crr>0.1 else 15
434
- return {"score":int(ns*0.3+fws*0.3+css*0.4)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
437
- # ์ถ•โ‘ฃ ๊ตฌ์กฐ
438
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
 
 
 
 
 
 
439
  def analyze_structure(text, sentences):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  paras = [p.strip() for p in text.split('\n\n') if p.strip()]
441
- psc = 40
442
- if len(paras)>1:
443
- pl = [len(split_sentences(p)) for p in paras]; ap = sum(pl)/len(pl)
444
- sp = math.sqrt(sum((l-ap)**2 for l in pl)/len(pl)) if len(pl)>1 else 0
445
- cv = sp/ap if ap>0 else 0
446
- psc = 75 if cv<0.2 and len(paras)>=3 else 50 if cv<0.4 else 25
447
- lt = len(re.findall(r'^\d+[.)]\s',text,re.M))+len(re.findall(r'^[-โ€ข*]\s',text,re.M))+len(re.findall(r'^#+\s',text,re.M))+len(re.findall(r'\*\*[^*]+\*\*',text))
448
- lsc = 85 if lt>=5 else 60 if lt>=2 else 15
449
- return {"score":int(psc*0.4+lsc*0.6)}
 
 
 
450
 
451
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
452
- # ์ถ•โ‘ค ์ง€๋ฌธ
453
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
454
  def analyze_model_fingerprint(text, sentences):
455
  ms = {}
 
456
  for mn, fp in FP.items():
457
  sc = sum(min(15,text.count(m)*5) for m in fp["m"] if text.count(m)>0)
458
  lm = fp["lp"].findall(text)
@@ -460,8 +664,29 @@ def analyze_model_fingerprint(text, sentences):
460
  em = sum(1 for s in sentences if any(s.rstrip('.!?').endswith(e) for e in fp.get("e",[])))
461
  if sentences: sc += int((em/len(sentences))*20)
462
  ms[mn] = min(100,sc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  mx = max(ms.values()) if ms else 0
464
- return {"score":85 if mx>=50 else 65 if mx>=30 else 40 if mx>=15 else 15,"model_scores":ms}
 
 
 
 
 
465
 
466
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
467
  # ํ’ˆ์งˆ
@@ -610,14 +835,34 @@ AIํ™•๋ฅ : 75%
610
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
611
  # ์ข…ํ•ฉ ํŒ์ • (์ผ๊ด€๋œ ๊ธฐ์ค€)
612
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
613
- def compute_verdict(scores, llm_score=-1):
614
- w={"ํ†ต๊ณ„":.25,"๋ฌธ์ฒด":.30,"๋ฐ˜๋ณต์„ฑ":.15,"๊ตฌ์กฐ":.15,"์ง€๋ฌธ":.15}
615
  ws=sum(scores[k]*w[k] for k in w)
 
 
 
 
 
 
 
 
 
 
 
 
 
616
  hi=sum(1 for v in scores.values() if v>=50)
617
- if hi>=4: ws+=12
618
- elif hi>=3: ws+=8
619
- elif hi>=2: ws+=4
620
- if sum(1 for v in scores.values() if v<25)>=3: ws-=8
 
 
 
 
 
 
 
621
  if llm_score>=0: ws=ws*0.70+llm_score*0.30
622
  fs=max(0,min(100,int(ws)))
623
  if fs>=75: return fs,"AI ์ž‘์„ฑ ํ™•์‹ ","ai_high"
@@ -631,275 +876,14 @@ def quick_score(text):
631
  sc={"ํ†ต๊ณ„":analyze_statistics(text,sents,words)["score"],"๋ฌธ์ฒด":analyze_korean_style(text,sents,morphs)["score"],
632
  "๋ฐ˜๋ณต์„ฑ":analyze_repetition(text,sents,words)["score"],"๊ตฌ์กฐ":analyze_structure(text,sents)["score"],
633
  "์ง€๋ฌธ":analyze_model_fingerprint(text,sents)["score"]}
634
- fs,v,lv=compute_verdict(sc); return fs,v,lv,sc
 
 
 
635
 
636
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
637
- # โ˜… AIโ†’์ธ๊ฐ„ ๋ณ€ํ™˜ (๋Œ€ํญ ๊ฐ•ํ™”)
638
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
639
- CONN_MAP = {'๋˜ํ•œ':['๊ทธ๋ฆฌ๊ณ ','์ด ๋ฐ–์—๋„','๊ฒŒ๋‹ค๊ฐ€'],'๋”ฐ๋ผ์„œ':['๊ทธ๋ž˜์„œ','์ด๋Ÿฐ ์ด์œ ๋กœ','๊ทธ๋Ÿฌ๋‹ˆ๊นŒ'],
640
- '์ด์— ๋”ฐ๋ผ':['๊ทธ๋ž˜์„œ','๊ทธ๋Ÿฌ๋‹ˆ๊นŒ'],'ํ•œํŽธ':['๋ฐ˜๋ฉด์—','๋ฐ˜๋Œ€๋กœ'],
641
- '๋”๋ถˆ์–ด':['ํ•จ๊ป˜','๊ฐ™์ด'],'๊ฒฐ๊ณผ์ ์œผ๋กœ':['๊ฒฐ๊ตญ','๋๋‚ด๋Š”'],
642
- '๊ถ๊ทน์ ์œผ๋กœ':['๊ฒฐ๊ตญ์—๋Š”','๋งˆ์ง€๋ง‰์—๋Š”'],'๋‚˜์•„๊ฐ€':['๋” ๋‚˜์•„๊ฐ€๋ฉด','์—ฌ๊ธฐ์„œ ๋”'],
643
- '์ด๋Ÿฌํ•œ':['์ด๋Ÿฐ','์ด์™€ ๊ฐ™์€'],'ํŠนํžˆ':['๊ทธ์ค‘์—์„œ๋„','๋ฌด์—‡๋ณด๋‹ค','ํŠน๋ณ„ํžˆ'],
644
- '๋ฟ๋งŒ ์•„๋‹ˆ๋ผ':['๊ฑฐ๊ธฐ์—๋‹ค','๊ทธ๊ฒƒ๋ฟ ์•„๋‹ˆ๋ผ'],'์ด๋ฅผ ํ†ตํ•ด':['๋•๋ถ„์—','์ด ๋•์—'],
645
- '์ด์—':['๊ทธ๋ž˜์„œ','์ด๊ฑธ๋กœ'],'์•„์šธ๋Ÿฌ':['๊ทธ๋ฆฌ๊ณ ','ํ•จ๊ป˜'],'๊ทธ๋Ÿฌ๋ฏ€๋กœ':['๊ทธ๋ž˜์„œ','๊ทธ๋Ÿฌ๋‹ˆ']}
646
- FILL_MAP = {
647
- '์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜๊ณ ':'ํฐ ๋ชซ์„ ์ฐจ์ง€ํ•˜๊ณ ','์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•˜':'ํฐ ๋ชซ์„ ์ฐจ์ง€ํ•˜',
648
- '์ค‘์š”ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€':'ํฐ ์˜๋ฏธ๋ฅผ ์ง€๋‹ˆ',
649
- '๊ธ์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ณ ':'์ข‹์€ ์ชฝ์œผ๋กœ ์ž‘์šฉํ•˜๊ณ ','๊ธ์ •์ ์ธ ์˜ํ–ฅ':'์ข‹์€ ์ชฝ ์ž‘์šฉ',
650
- '๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„':'๋‚˜์œ ์ชฝ์œผ๋กœ','์˜ํ–ฅ์„ ๋ฏธ์น˜':'์ž‘์šฉ์„ ํ•˜',
651
- '๋ˆˆ๋ถ€์‹  ์„ฑ๊ณผ๋ฅผ ๊ฑฐ๋‘':'๋Œ€๋‹จํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด','๋ˆˆ๋ถ€์‹  ์„ฑ๊ณผ':'๋Œ€๋‹จํ•œ ๊ฒฐ๊ณผ',
652
- '๊ด„๋ชฉํ•  ๋งŒํ•œ':'๋ˆˆ์— ๋„๋Š”','ํš๊ธฐ์ ์ธ ๋ณ€ํ™”':'ํฐ ์ „ํ™˜์ ','ํ˜์‹ ์ ์ธ':'์ƒˆ๋กœ์šด',
653
- '๋‹ค์–‘ํ•œ ๋ถ„์•ผ':'์—ฌ๋Ÿฌ ๋ถ„์•ผ','๋‹ค์–‘ํ•œ ์‚ฐ์—… ๋ถ„์•ผ':'์—ฌ๋Ÿฌ ์‚ฐ์—…','๋‹ค์–‘ํ•œ ์‚ฐ์—…':'์—ฌ๋Ÿฌ ์‚ฐ์—…',
654
- '๋‹ค์–‘ํ•œ ์ฐฝ์ž‘':'์—ฌ๋Ÿฌ ์ฐฝ์ž‘','๋‹ค์–‘ํ•œ ์ธก๋ฉด':'์—ฌ๋Ÿฌ ๋ฉด',
655
- '๋ถ€๊ฐ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค':'๋‘๋“œ๋Ÿฌ์ง€๊ณ  ์žˆ๋‹ค','๋ถ€๊ฐ๋˜๊ณ ':'๋‘๋“œ๋Ÿฌ์ง€๊ณ ',
656
- '๋Œ€๋‘๋˜๊ณ ':'๋– ์˜ค๋ฅด๊ณ ','ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ':'์“ธ ์ˆ˜ ์žˆ๊ฒŒ',
657
- 'ํ™œ๋ฐœํžˆ ์ง„ํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค':'ํ™œ๋ฐœํ•˜๊ฒŒ ์ด๋ค„์ง€๊ณ  ์žˆ๋‹ค',
658
- '๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค':'๊ฒƒ ๊ฐ™๋‹ค','๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค':'๊ฒƒ ๊ฐ™๋‹ค',
659
- '๊ฒƒ์œผ๋กœ ํŒ๋‹จ๋ฉ๋‹ˆ๋‹ค':'๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค','๊ฒƒ์œผ๋กœ ๋ถ„์„๋ฉ๋‹ˆ๋‹ค':'๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค',
660
- '๊ฒƒ์œผ๋กœ ๋ณด์ด':'๊ฒƒ ๊ฐ™','๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€':'๊ฒƒ์œผ๋กœ ๋“œ๋Ÿฌ๋‚˜',
661
- 'ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค':'ํ•  ์ˆ˜ ์žˆ๋‹ค','๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค':'๋ณผ ์ˆ˜ ์žˆ๋‹ค',
662
- '์ฃผ๋ชฉํ•  ๋งŒํ•œ':'๋ˆˆ์—ฌ๊ฒจ๋ณผ','์ฃผ๋ชฉํ•  ๋งŒ':'๋ˆˆ์—ฌ๊ฒจ๋ณผ ๋งŒ',
663
- 'ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค':'ํ•„์š”ํ•˜๋‹ค','์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค':'์ค‘์š”ํ•˜๋‹ค',
664
- '์—ญํ• ์„ ํ•˜๊ณ ':'๋ชซ์„ ํ•˜๊ณ ','์—ญํ• ์„ ํ•˜':'๋ชซ์„ ํ•˜',
665
- '์ ์—์„œ':'๋ฉด์—์„œ','์ธก๋ฉด์—์„œ':'๋ถ€๋ถ„์—์„œ','๊ด€์ ์—์„œ':'์‹œ๊ฐ์—์„œ',
666
- }
667
- INLINE_CONN = {'์ด๋ฅผ ํ†ตํ•ด ':'์ด๊ฑธ๋กœ ','์ด์— ๋Œ€ํ•œ ':'์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ','๋”ฐ๋ผ์„œ ':'๊ทธ๋ž˜์„œ ',
668
- '๊ฒฐ๊ณผ์ ์œผ๋กœ ':'๊ฒฐ๊ตญ ','์ด๋Ÿฌํ•œ ':'์ด๋Ÿฐ ','์ด์— ':'์ด๊ฑธ๋กœ ','๋‚˜์•„๊ฐ€ ':'๋” ๋‚˜์•„๊ฐ€๋ฉด '}
669
- END_RULES = [
670
- ('ํ™œ๋ฐœํžˆ ์ง„ํ–‰๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค','ํ™œ๋ฐœํ•˜๊ฒŒ ์ด๋ค„์ง€๊ณ  ์žˆ๋‹ค'),
671
- ('๊ฑฐ๋‘๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค','๊ฑฐ๋‘๊ณ  ์žˆ๋‹ค'),('๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค','๋ฐ”๋€Œ๊ณ  ์žˆ๋‹ค'),
672
- ('์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค','์žˆ๊ฒŒ ๋๋‹ค'),('ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค','ํ•˜๊ณ  ์žˆ๋‹ค'),
673
- ('๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค','๋˜๊ณ  ์žˆ๋‹ค'),('ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค','ํ•  ์ˆ˜ ์žˆ๋‹ค'),
674
- ('๋ฏธ์น˜๊ณ  ์žˆ์œผ๋ฉฐ','์ฃผ๊ณ  ์žˆ๊ณ '),('๊ฐ€๋Šฅํ•ด์กŒ์œผ๋ฉฐ','๊ฐ€๋Šฅํ•ด์กŒ๊ณ '),
675
- ('ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค','ํ•„์š”ํ•˜๋‹ค'),('์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค','์ค‘์š”ํ•˜๋‹ค'),
676
- ('์žˆ์Šต๋‹ˆ๋‹ค','์žˆ๋‹ค'),('๋ฉ๋‹ˆ๋‹ค','๋œ๋‹ค'),('ํ–ˆ์Šต๋‹ˆ๋‹ค','ํ–ˆ๋‹ค'),
677
- ('๊ฒ ์Šต๋‹ˆ๋‹ค','๊ฒƒ์ด๋‹ค'),('์ž…๋‹ˆ๋‹ค','์ด๋‹ค'),
678
- ('๊ฐ€์ง€๋ฉฐ','๊ฐ€์ง€๊ณ '),('์ด๋ฃจ๋Š” ๊ฒƒ์ด','์ด๋ฃจ๋Š” ๊ฒŒ'),
679
- ]
680
- # ๋ฌธ์žฅ ์žฌ๊ตฌ์„ฑ์šฉ ํŒจํ„ด
681
- RESTRUCTURE = [
682
- (r'(\S+)์€ (\S+)์—์„œ (.+)', lambda m: f"{m.group(2)}์—์„œ {m.group(1)}์€ {m.group(3)}" if random.random()<0.3 else m.group()),
683
- (r'(.+)ํ•˜๊ณ  ์žˆ๋‹ค\.', lambda m: f"{m.group(1)}ํ•˜๋Š” ์ค‘์ด๋‹ค." if random.random()<0.3 else m.group()),
684
- ]
685
-
686
- def rule_humanize(text):
687
- r=text; ch=[]
688
- # 1. ๋ฌธ๋‘ ์ ‘์†์‚ฌ (100% ๊ต์ฒด โ€” 50%๋Š” ๋Œ€์ฒด, 50%๋Š” ์‚ญ์ œ)
689
- for ac,alts in CONN_MAP.items():
690
- pat=re.compile(r'(?:^|\n)(\s*)('+re.escape(ac)+r')(\s)',re.M)
691
- for m in reversed(list(pat.finditer(r))):
692
- if random.random()<0.6:
693
- alt=random.choice(alts); r=r[:m.start(2)]+alt+r[m.end(2):]; ch.append(f"์ ‘์†์‚ฌ '{ac}'โ†’'{alt}'")
694
- else: r=r[:m.start(2)]+r[m.end(2):]; ch.append(f"์ ‘์†์‚ฌ์ œ๊ฑฐ '{ac}'")
695
- # 2. ๋ฌธ์žฅ ๋‚ด ์ ‘์†์‚ฌ
696
- for ai,hu in INLINE_CONN.items():
697
- if ai in r: r=r.replace(ai,hu,1); ch.append(f"๋‚ด๋ถ€์ ‘์† '{ai.strip()}'")
698
- # 3. ์ƒํˆฌ (FILL_MAP โ€” AI_FILLER ์™„์ „ ๋ฌด๋ ฅํ™”)
699
- for ai in sorted(FILL_MAP.keys(), key=len, reverse=True):
700
- hu = FILL_MAP[ai]
701
- if ai in r: r=r.replace(ai,hu,1); ch.append(f"์ƒํˆฌ '{ai}'")
702
- # 4. ์ข…๊ฒฐ์–ด๋ฏธ ์ „๋ฉด ๋ณ€ํ™˜
703
- for ai,hu in END_RULES:
704
- cnt=r.count(ai)
705
- if cnt>0: r=r.replace(ai,hu); ch.append(f"์ข…๊ฒฐ '{ai}'โ†’'{hu}' ร—{cnt}")
706
- # 5. ๋งˆํฌ๋‹ค์šด ์ œ๊ฑฐ
707
- r=re.sub(r'^\d+\.\s+','',r,flags=re.M)
708
- r=re.sub(r'^[-โ€ข*]\s+','',r,flags=re.M)
709
- r=re.sub(r'\*\*([^*]+)\*\*',r'\1',r)
710
- r=re.sub(r'^#+\s+','',r,flags=re.M)
711
- # 6. ๋ฌธ์žฅ ์žฌ๊ตฌ์„ฑ
712
- sents = split_sentences(r)
713
- rebuilt = []
714
- for i, s in enumerate(sents):
715
- ns = s
716
- for pat, repl in RESTRUCTURE:
717
- ns = re.sub(pat, repl, ns)
718
- rebuilt.append(ns)
719
- # 7. ๋ฌธ์žฅ ๊ธธ์ด ๋ณ€๋™ ์ฃผ์ž… (ํ†ต๊ณ„์ถ• ๊ฐœ์„ ) โ€” BUG3 FIX
720
- if len(rebuilt) >= 4:
721
- sl = [len(s) for s in rebuilt]
722
- avg = sum(sl)/len(sl)
723
- cv = math.sqrt(sum((l-avg)**2 for l in sl)/len(sl))/(avg if avg>0 else 1)
724
- if cv < 0.35: # CV๊ฐ€ ๋‚ฎ์œผ๋ฉด = ๋ฌธ์žฅ ๊ธธ์ด๊ฐ€ ๋„ˆ๋ฌด ๊ท ์ผ = AI์ 
725
- # ๋žœ๋ค ์œ„์น˜์— ์งง์€ ๋ฌธ์žฅ ์‚ฝ์ž…
726
- short_comments = [
727
- "์ข€ ๋†€๋ž๋‹ค.","์ด๊ฒŒ ํ•ต์‹ฌ์ด๋‹ค.","๊ฝค ์˜๋ฏธ ์žˆ๋‹ค.","๋ณ€ํ™”๊ฐ€ ํฌ๋‹ค.",
728
- "์‰ฝ์ง€ ์•Š์€ ๋ฌธ์ œ๋‹ค.","์ƒ๊ฐํ•ด๋ณผ ๋ถ€๋ถ„์ด๋‹ค.","๋ฌด์‹œ ๋ชป ํ•  ํ๋ฆ„์ด๋‹ค."
729
- ]
730
- # 2~3๊ฐœ ์‚ฝ์ž… (์ค‘๋ณต ๋ฐฉ์ง€)
731
- used = set()
732
- for _ in range(min(3, len(rebuilt)//3)):
733
- pos = random.randint(1, len(rebuilt)-1)
734
- cmt = random.choice([c for c in short_comments if c not in used])
735
- used.add(cmt)
736
- rebuilt.insert(pos, cmt)
737
- ch.append(f"์งง์€ ๋ฌธ์žฅ {len(used)}๊ฐœ ์‚ฝ์ž… (CV ๊ฐœ์„ )")
738
- # ๊ฐ€๋” ๊ธด ๋ฌธ์žฅ๋„ ๋งŒ๋“ค๊ธฐ (์งง์€ 2๊ฐœ ํ•ฉ์น˜๊ธฐ)
739
- if len(rebuilt) >= 6 and random.random() < 0.5:
740
- idx = random.randint(0, len(rebuilt)-2)
741
- if 15 < len(rebuilt[idx]) < 35 and 15 < len(rebuilt[idx+1]) < 35:
742
- merged = rebuilt[idx].rstrip('.') + ', ' + rebuilt[idx+1][0].lower() + rebuilt[idx+1][1:] if rebuilt[idx+1] else rebuilt[idx]
743
- rebuilt[idx] = merged
744
- rebuilt.pop(idx+1)
745
- ch.append("๊ธด ๋ฌธ์žฅ ํ•ฉ๋ณ‘ (CV ๊ฐœ์„ )")
746
- # 8. ๋ฌธ๋‹จ ๋‚˜๋ˆ„๊ธฐ โ€” ๊ตฌ์กฐ์ถ• ๊ฐœ์„ 
747
- if len(rebuilt) >= 6 and '\n\n' not in ' '.join(rebuilt):
748
- mid = len(rebuilt)//2
749
- rebuilt.insert(mid, '\n')
750
- ch.append("๋ฌธ๋‹จ ๋ถ„๋ฆฌ ์‚ฝ์ž…")
751
- r = ' '.join(rebuilt)
752
- r = r.replace(' \n ', '\n\n')
753
- return r.strip(), ch
754
-
755
- def llm_humanize(text):
756
- if not GROQ_KEY: return text, ["LLM: APIํ‚ค ์—†์Œ"]
757
- prompt = f"""๋‹น์‹ ์€ AI ํ…์ŠคํŠธ ํƒ์ง€๊ธฐ๋ฅผ ์šฐํšŒํ•˜๋Š” ๋ฆฌ๋ผ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ํ…์ŠคํŠธ๋ฅผ ์ž์—ฐ์Šค๋Ÿฌ์šด ์‚ฌ๋žŒ์ด ์“ด ๊ธ€๋กœ ์™„์ „ํžˆ ์žฌ์ž‘์„ฑํ•˜์„ธ์š”.
758
-
759
- [ํ•„์ˆ˜ ๊ทœ์น™]
760
- 1. "-์Šต๋‹ˆ๋‹ค/-ํ•ฉ๋‹ˆ๋‹ค/-์ž…๋‹ˆ๋‹ค" ์ข…๊ฒฐ์–ด๋ฏธ ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€. ์ „๋ถ€ "-๋‹ค/-์ด๋‹ค/-ํ–ˆ๋‹ค/-์ธ ์…ˆ์ด๋‹ค" ๋“ฑ ๋น„๊ฒฉ์‹์ฒด๋กœ
761
- 2. "๋˜ํ•œ/๋”ฐ๋ผ์„œ/๋‚˜์•„๊ฐ€/์ด๋Ÿฌํ•œ/์ด์—/๊ฒฐ๊ณผ์ ์œผ๋กœ" ์ ˆ๋Œ€ ์‚ฌ์šฉ ๊ธˆ์ง€
762
- 3. "์ค‘์š”ํ•œ ์—ญํ• /๊ธ์ •์ ์ธ ์˜ํ–ฅ/๋‹ค์–‘ํ•œ ๋ถ„์•ผ/ํš๊ธฐ์ ์ธ" ์ „๋ถ€ ์ผ์ƒ์–ด๋กœ
763
- 4. ๋ฌธ์žฅ ๊ธธ์ด๋ฅผ ์‹ฌํ•˜๊ฒŒ ๋ถˆ๊ทœ์น™ํ•˜๊ฒŒ: 5~10์ž ์งง์€ ๋ฌธ์žฅ๊ณผ 50~80์ž ๊ธด ๋ฌธ์žฅ์„ ๋ฐ˜๋“œ์‹œ ์„ž์–ด๋ผ
764
- 5. ๋ฐ˜๋“œ์‹œ 3~4๊ฐœ ๊ฐœ์ธ์  ๊ฐ์ƒ/์˜๊ฒฌ ์‚ฝ์ž… ("์†”์งํžˆ", "์‚ฌ์‹ค", "๊ทผ๋ฐ ์ƒ๊ฐํ•ด๋ณด๋ฉด", "๋‚ด ์ƒ๊ฐ์—”")
765
- 6. ์›๋ž˜ ์˜๋ฏธยท์ •๋ณด๋Š” 100% ๋ณด์กด
766
- 7. ๋งˆํฌ๋‹ค์šด/๋ฆฌ์ŠคํŠธ ์—†์ด ์™„์ „ํ•œ ์‚ฐ๋ฌธ์œผ๋กœ
767
- 8. ๋ฌธ์žฅ ์ˆœ์„œ๋ฅผ ์ผ๋ถ€ ๋ฐ”๊ฟ”๋„ ๋จ
768
- 9. ํ”ผ๋™ ํ‘œํ˜„ ์ค„์ด๊ณ  ๋Šฅ๋™ํƒœ ์œ„์ฃผ
769
- 10. "~๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค/์˜ˆ์ƒ๋œ๋‹ค" ๊ฐ™์€ ํšŒํ”ผ ํ‘œํ˜„ โ†’ "~์ผ ๊ฑฐ๋‹ค/~ํ•  ๊ฒƒ ๊ฐ™๋‹ค"
770
-
771
- [์›๋ฌธ]
772
- {text[:2500]}
773
-
774
- [๋ณ€ํ™˜ ๊ฒฐ๊ณผ๋งŒ ์ถœ๋ ฅ - ์„ค๋ช… ์—†์ด]"""
775
- resp, err = call_groq("qwen/qwen3-32b", prompt, max_tokens=2000, temperature=0.85)
776
- if resp:
777
- cleaned = re.sub(r'<think>.*?</think>', '', resp, flags=re.S).strip()
778
- if len(cleaned) > 50: return cleaned, [f"LLM ๋ฆฌ๋ผ์ดํŒ… ({len(cleaned)}์ž)"]
779
- return text, [f"LLM ์‹คํŒจ: {err}"]
780
-
781
- def run_humanizer(text, progress=gr.Progress()):
782
- if not text or len(text.strip())<50: return "","","",""
783
- text=text.strip()
784
- progress(0.05,"์›๋ณธ ๋ถ„์„...")
785
- b_score,b_verdict,_,b_axes=quick_score(text)
786
- bq=analyze_quality(text,split_sentences(text),split_words(text),get_morphemes(text))
787
- if b_score<25: return text,"์ด๋ฏธ ์ธ๊ฐ„์ ์ธ ํ…์ŠคํŠธ (AI์ ์ˆ˜ 25๋ฏธ๋งŒ).","",""
788
-
789
- # โ•โ•โ• Adversarial Humanizer v2.1: ๋ฐ˜๋ณต ์ž๊ธฐ๋Œ€์ „ (์—ดํ™” ๋ฐฉ์ง€) โ•โ•โ•
790
- MAX_ROUNDS = 3
791
- TARGET_SCORE = 25
792
- best_text = text
793
- best_score = b_score
794
- best_method = "์›๋ณธ"
795
- all_ch = []
796
- round_log = []
797
- original_text = text # LLM์€ ํ•ญ์ƒ ์›๋ณธ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜ธ์ถœ (์—ดํ™” ๋ฐฉ์ง€)
798
-
799
- for rnd in range(1, MAX_ROUNDS + 1):
800
- if best_score <= TARGET_SCORE:
801
- round_log.append(f"๐Ÿ† Round {rnd} ์Šคํ‚ต โ€” ์ด๋ฏธ ๋ชฉํ‘œ ๋‹ฌ์„ฑ (์ ์ˆ˜ {best_score})")
802
- break
803
-
804
- pct = 0.05 + (rnd / MAX_ROUNDS) * 0.65
805
- progress(pct, f"โš”๏ธ Round {rnd}/{MAX_ROUNDS} โ€” ํ˜„์žฌ ์ ์ˆ˜ {best_score}...")
806
-
807
- candidates = []
808
-
809
- # ๊ทœ์น™ ๋ณ€ํ™˜: ํ˜„์žฌ best์— ์ ์šฉ (Round 1์€ ์›๋ณธ, Round 2+๋Š” best)
810
- rule_text, rule_ch = rule_humanize(best_text)
811
- r_score, _, _, _ = quick_score(rule_text)
812
- candidates.append((rule_text, r_score, f"R{rnd}-๊ทœ์น™", rule_ch))
813
-
814
- if GROQ_KEY:
815
- # LLM: ํ•ญ์ƒ ์›๋ณธ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ (Round 2+์—์„œ ์—ดํ™”๋œ ํ…์ŠคํŠธ ์žฌLLM ๋ฐฉ์ง€)
816
- llm_text, llm_ch = llm_humanize(original_text)
817
- l_score, _, _, _ = quick_score(llm_text)
818
- candidates.append((llm_text, l_score, f"R{rnd}-LLM", llm_ch))
819
-
820
- # LLM โ†’ ๊ทœ์น™ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ
821
- llm_rule, lr_ch = rule_humanize(llm_text)
822
- lr_score, _, _, _ = quick_score(llm_rule)
823
- candidates.append((llm_rule, lr_score, f"R{rnd}-LLM+๊ทœ์น™", llm_ch + lr_ch))
824
-
825
- # ์ตœ์  ์„ ํƒ
826
- winner = min(candidates, key=lambda x: x[1])
827
- w_text, w_score, w_method, w_changes = winner
828
- score_strs = ', '.join(f"{c[2]}:{c[1]}" for c in candidates)
829
- round_log.append(f"Round {rnd}: {score_strs} โ†’ {w_method} ์ฑ„ํƒ (ฮ”{best_score - w_score})")
830
-
831
- # ์•…ํ™” ๋ฐฉ์ง€: ์ด์ „๋ณด๋‹ค 2์  ์ด์ƒ ๊ฐœ์„ ๋œ ๊ฒฝ์šฐ๋งŒ ์ฑ„ํƒ
832
- if w_score < best_score - 1:
833
- best_text, best_score, best_method = w_text, w_score, w_method
834
- all_ch.extend(w_changes)
835
- else:
836
- round_log.append(f" โ†ณ ์œ ์˜๋ฏธํ•œ ๊ฐœ์„  ์—†์Œ (ฮ”{best_score - w_score}), ์ด์ „ ๊ฒฐ๊ณผ ์œ ์ง€")
837
- break
838
-
839
- final_text = best_text
840
- method = best_method
841
-
842
- progress(0.75, "์ตœ์ข… ๊ฒ€์ฆ...")
843
- a_score, a_verdict, a_level, a_axes = quick_score(final_text)
844
- aq = analyze_quality(final_text, split_sentences(final_text), split_words(final_text), get_morphemes(final_text))
845
-
846
- progress(0.85, "LLM ๊ต์ฐจ๊ฒ€์ฆ...")
847
- llm_v = llm_cross_check(final_text)
848
- if llm_v["score"] >= 0:
849
- a_score_f, a_verdict_f, _ = compute_verdict(a_axes, llm_v["score"])
850
- else:
851
- a_score_f, a_verdict_f = a_score, a_verdict
852
-
853
- delta = b_score - a_score_f
854
- passed = a_score_f < 30
855
- change_log = f"โš”๏ธ Adversarial v2 ({len(round_log)}๋ผ์šด๋“œ)\n"
856
- change_log += '\n'.join(f" {r}" for r in round_log)
857
- change_log += f"\n\n์ด {len(all_ch)}๊ฑด ๋ณ€ํ™˜:\n" + '\n'.join(f" โ€ข {c}" for c in all_ch[:20])
858
- if len(all_ch) > 20: change_log += f"\n ... +{len(all_ch)-20}๊ฑด"
859
-
860
- # ๋น„๊ต HTML
861
- def cbar(lbl,bv,av):
862
- bc="#FF4444" if bv>=50 else "#DDAA00" if bv>=35 else "#22AA44"
863
- ac="#FF4444" if av>=50 else "#DDAA00" if av>=35 else "#22AA44"
864
- d=bv-av; ds=f"<span style='color:#22AA44;font-weight:700;'>โ†“{d}</span>" if d>0 else f"<span style='color:#FF4444;'>โ†‘{abs(d)}</span>" if d<0 else "="
865
- return f"<div style='margin:3px 0;display:grid;grid-template-columns:70px 1fr 20px 1fr 36px;align-items:center;gap:3px;font-size:11px;'><span style='font-weight:600;'>{lbl}</span><div style='background:#E8E8E8;border-radius:3px;height:6px;'><div style='background:{bc};height:100%;width:{bv}%;border-radius:3px;'></div></div><span style='text-align:center;color:#CCC;'>โ†’</span><div style='background:#E8E8E8;border-radius:3px;height:6px;'><div style='background:{ac};height:100%;width:{av}%;border-radius:3px;'></div></div><span style='text-align:right;'>{ds}</span></div>"
866
-
867
- bfg="#FF4444" if b_score>=60 else "#FF8800" if b_score>=45 else "#DDAA00" if b_score>=30 else "#22AA44"
868
- bbg="#FFE0E0" if b_score>=60 else "#FFF0DD" if b_score>=45 else "#FFFBE0" if b_score>=30 else "#E0FFE8"
869
- afg="#FF4444" if a_score_f>=60 else "#FF8800" if a_score_f>=45 else "#DDAA00" if a_score_f>=30 else "#22AA44"
870
- abg="#FFE0E0" if a_score_f>=60 else "#FFF0DD" if a_score_f>=45 else "#FFFBE0" if a_score_f>=30 else "#E0FFE8"
871
- dc="#22AA44" if delta>0 else "#FF4444"
872
- badge='<span style="background:#22AA44;color:white;padding:4px 14px;border-radius:20px;font-weight:700;">โœ… ๊ฒ€์ฆ ํ†ต๊ณผ</span>' if passed else '<span style="background:#FF8800;color:white;padding:4px 14px;border-radius:20px;font-weight:700;">โš ๏ธ ์ถ”๊ฐ€ ์ˆ˜์ • ๊ถŒ์žฅ (AI์ ์ˆ˜ {})'.format(a_score_f)+'</span>'
873
-
874
- html=f"""<div style="font-family:'Pretendard',sans-serif;max-width:700px;margin:0 auto;">
875
- <div style="text-align:center;margin-bottom:12px;">{badge}</div>
876
- <div style="display:grid;grid-template-columns:1fr 50px 1fr;gap:6px;margin-bottom:12px;">
877
- <div style="background:{bbg};border:2px solid {bfg};border-radius:12px;padding:14px;text-align:center;">
878
- <div style="font-size:10px;color:#888;">BEFORE</div>
879
- <div style="font-size:32px;font-weight:900;color:{bfg};">{b_score}</div>
880
- <div style="font-size:10px;color:{bfg};">{b_verdict}</div>
881
- </div>
882
- <div style="display:flex;align-items:center;justify-content:center;">
883
- <div style="font-size:22px;color:{dc};font-weight:900;">{"โ†“" if delta>0 else "โ†‘"}{abs(delta)}</div>
884
- </div>
885
- <div style="background:{abg};border:2px solid {afg};border-radius:12px;padding:14px;text-align:center;">
886
- <div style="font-size:10px;color:#888;">AFTER ({method})</div>
887
- <div style="font-size:32px;font-weight:900;color:{afg};">{a_score_f}</div>
888
- <div style="font-size:10px;color:{afg};">{a_verdict_f}</div>
889
- </div>
890
- </div>
891
- <div style="background:#FAFAFA;border-radius:8px;padding:10px;margin-bottom:8px;">
892
- <div style="font-size:11px;font-weight:700;margin-bottom:4px;">๐Ÿ“Š ์ถ•๋ณ„ ๋น„๊ต</div>
893
- {cbar("ํ†ต๊ณ„",b_axes["ํ†ต๊ณ„"],a_axes["ํ†ต๊ณ„"])}{cbar("๋ฌธ์ฒด",b_axes["๋ฌธ์ฒด"],a_axes["๋ฌธ์ฒด"])}{cbar("๋ฐ˜๋ณต",b_axes["๋ฐ˜๋ณต์„ฑ"],a_axes["๋ฐ˜๋ณต์„ฑ"])}{cbar("๊ตฌ์กฐ",b_axes["๊ตฌ์กฐ"],a_axes["๊ตฌ์กฐ"])}{cbar("์ง€๋ฌธ",b_axes["์ง€๋ฌธ"],a_axes["์ง€๋ฌธ"])}
894
- </div>
895
- <div style="background:#F0F4FF;border-radius:6px;padding:6px 10px;font-size:11px;">
896
- <b>ํ’ˆ์งˆ:</b> {bq['grade']}({bq['score']}) โ†’ {aq['grade']}({aq['score']})
897
- </div></div>"""
898
- return final_text, change_log, html, ""
899
-
900
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
901
  # โ˜… ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Brave Search ๋ณ‘๋ ฌ + KCI/RISS/ARXIV + Gemini)
902
- # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
903
  def brave_search(query, count=5):
904
  """Brave Search API โ€” ๋‹จ์ผ ์ฟผ๋ฆฌ"""
905
  if not BRAVE_KEY: return []
@@ -1457,15 +1441,16 @@ def run_detection(text, progress=gr.Progress()):
1457
  progress(0.62); qr=analyze_quality(text,sents,words,morphs)
1458
  progress(0.75); lr=llm_cross_check(text)
1459
  sc={"ํ†ต๊ณ„":s1["score"],"๋ฌธ์ฒด":s2["score"],"๋ฐ˜๋ณต์„ฑ":s3["score"],"๊ตฌ์กฐ":s4["score"],"์ง€๋ฌธ":s5["score"]}
1460
- fs,verdict,level=compute_verdict(sc,lr["score"])
 
 
 
1461
  progress(0.95)
1462
  cm={"ai_high":("#FF4444","#FFE0E0","๋†’์Œ"),"ai_medium":("#FF8800","#FFF0DD","์ค‘๊ฐ„~๋†’์Œ"),"ai_low":("#DDAA00","#FFFBE0","์ค‘๊ฐ„"),"uncertain":("#888","#F0F0F0","๋‚ฎ์Œ"),"human":("#22AA44","#E0FFE8","๋งค์šฐ ๋‚ฎ์Œ")}
1463
  fg,bg,conf=cm.get(level,("#888","#F0F0F0","?"))
1464
  ms=s5.get("model_scores",{}); tm=max(ms,key=ms.get) if ms else "N/A"; tms=ms.get(tm,0)
1465
  mt=f"{tm} ({tms}์ )" if tms>=15 else "ํŠน์ • ๋ถˆ๊ฐ€"
1466
 
1467
- # ๋ฌธ์žฅ๋ณ„ ์ ์ˆ˜ (ํƒญ2์™€ ๋™์ผ ๊ธฐ์ค€)
1468
- sent_scores = [score_sentence(s)[0] for s in sents]
1469
  ai_sents = sum(1 for s in sent_scores if s >= 40)
1470
  human_sents = sum(1 for s in sent_scores if s < 20)
1471
 
@@ -1563,28 +1548,43 @@ def run_highlight(text):
1563
  sents=split_sentences(text)
1564
  hl=[]
1565
  for s in sents:
1566
- sc, reasons = score_sentence(s) # โ† ๋™์ผ ํ•จ์ˆ˜ ์‚ฌ์šฉ
1567
- bg=f"rgba(255,68,68,{sc/150})" if sc>=50 else f"rgba(255,170,0,{sc/150})" if sc>=25 else f"rgba(34,170,68,{max(0.05,(100-sc)/300)})"
1568
- tt = ' | '.join(reasons) if reasons else '์ธ๊ฐ„์  ํ‘œํ˜„'
1569
- level = "AI" if sc >= 50 else "์˜์‹ฌ" if sc >= 25 else "์ธ๊ฐ„"
1570
- hl.append(f'<span style="background:{bg};padding:2px 4px;border-radius:3px;display:inline;line-height:2;" title="[{level}] {tt} ({sc}์ )">{s}</span>')
 
 
 
 
 
 
 
 
 
 
 
 
 
1571
 
1572
  total_scores = [score_sentence(s)[0] for s in sents]
1573
  avg_sc = sum(total_scores)/len(total_scores) if total_scores else 0
1574
- ai_cnt = sum(1 for s in total_scores if s >= 50)
 
1575
  human_cnt = sum(1 for s in total_scores if s < 25)
1576
 
1577
  return f"""<div style='font-family:Pretendard,sans-serif;'>
1578
  <div style='margin-bottom:10px;padding:10px;background:#F8F8FF;border-radius:8px;'>
1579
- <div style='display:flex;gap:12px;align-items:center;font-size:11px;margin-bottom:6px;'>
1580
- <span style='background:rgba(255,68,68,0.4);padding:2px 8px;border-radius:3px;'>๐Ÿ”ด AI ({ai_cnt}๋ฌธ์žฅ)</span>
1581
- <span style='background:rgba(255,170,0,0.3);padding:2px 8px;border-radius:3px;'>๐ŸŸ  ์˜์‹ฌ</span>
1582
- <span style='background:rgba(34,170,68,0.15);padding:2px 8px;border-radius:3px;'>๐ŸŸข ์ธ๊ฐ„ ({human_cnt}๋ฌธ์žฅ)</span>
1583
- <span style='color:#888;'>ํ‰๊ท  {avg_sc:.0f}์  | ๋งˆ์šฐ์Šค ์˜ค๋ฒ„โ†’๊ทผ๊ฑฐ</span>
 
1584
  </div>
1585
- <div style='font-size:10px;color:#888;'>๐Ÿ’ก ์ ์ˆ˜ ๊ธฐ์ค€: ๊ฒฉ์‹์–ด๋ฏธ(25์ ) + AI์ ‘์†์‚ฌ(20์ ) + ์ƒํˆฌํ‘œํ˜„(15~25์ ) + ๋ชจ๋ธ์ง€๋ฌธ(10์ ) โˆ’ ์ธ๊ฐ„๋งˆ์ปค(30์ )</div>
1586
  </div>
1587
- <div style='line-height:2.2;font-size:14px;'>{' '.join(hl)}</div>
1588
  </div>"""
1589
 
1590
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -1637,7 +1637,9 @@ def run_document_analysis(file, progress=gr.Progress()):
1637
  progress(0.30, "LLM ๊ต์ฐจ๊ฒ€์ฆ...")
1638
  llm_result = llm_cross_check(full_text[:3000])
1639
  if llm_result["score"] >= 0:
1640
- total_score, total_verdict, total_level = compute_verdict(total_axes, llm_result["score"])
 
 
1641
 
1642
  # ์„น์…˜๋ณ„ ๋ถ„์„
1643
  progress(0.45, f"{len(sections)}๊ฐœ ์„น์…˜ ๋ถ„์„...")
@@ -1884,7 +1886,7 @@ def extract_file_text_api(file):
1884
 
1885
 
1886
  with gr.Blocks(title="AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0") as demo:
1887
- gr.Markdown("# ๐Ÿ”Ž AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0\n**5์ถ• AI ํƒ์ง€ ยท ํ’ˆ์งˆ ์ธก์ • ยท LLM ๊ต์ฐจ๊ฒ€์ฆ ยท Adversarial Humanizer v2 ยท ํ‘œ์ ˆ ๊ฒ€์‚ฌ ยท ํŒŒ์ผ ์—…๋กœ๋“œ**")
1888
  with gr.Tab("๐Ÿ” ๋ถ„์„"):
1889
  gr.Markdown("ํ…์ŠคํŠธ๊ฐ€ AI์— ์˜ํ•ด ์ž‘์„ฑ๋˜์—ˆ๋Š”์ง€ 5๊ฐœ ์ถ•์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. 0~100์  (๋†’์„์ˆ˜๋ก AI ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ)")
1890
  inp=gr.Textbox(label="๋ถ„์„ํ•  ํ…์ŠคํŠธ",placeholder="์ตœ์†Œ 50์ž ์ด์ƒ...",lines=10)
@@ -1898,15 +1900,6 @@ with gr.Blocks(title="AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0") as demo:
1898
  gr.Markdown("๋ฌธ์žฅ๋ณ„๋กœ AI ํ™•๋ฅ ์„ ์ƒ‰์ƒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. **ํƒญ1๊ณผ ๋™์ผํ•œ ๊ธฐ์ค€**์œผ๋กœ ํŒ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ๊ทผ๊ฑฐ ํ™•์ธ.")
1899
  ih=gr.Textbox(label="ํ…์ŠคํŠธ",lines=8); bh=gr.Button("๐ŸŽจ ํ•˜์ด๋ผ์ดํŠธ ๋ถ„์„",variant="primary"); hr=gr.HTML()
1900
  bh.click(run_highlight,[ih],[hr],api_name="run_highlight")
1901
- with gr.Tab("๐Ÿ”„ AIโ†’์ธ๊ฐ„ ๋ณ€ํ™˜"):
1902
- gr.Markdown("**Adversarial Humanizer v2** โ€” ํƒ์ง€๊ธฐ์™€ ๋ณ€ํ™˜๊ธฐ์˜ ์ž๊ธฐ๋Œ€์ „ ๋ฃจํ”„. ์ตœ๋Œ€ 3๋ผ์šด๋“œ ๋ฐ˜๋ณตํ•˜๋ฉฐ AI ์ ์ˆ˜๋ฅผ ์ตœ์ €๋กœ ๋Œ์–ด๋‚ด๋ฆฝ๋‹ˆ๋‹ค.")
1903
- ihm=gr.Textbox(label="์›๋ณธ (AI ํ…์ŠคํŠธ)",lines=8)
1904
- with gr.Row():
1905
- bhm=gr.Button("๐Ÿ”„ ์ž๋™ ๋ณ€ํ™˜ + ๊ฒ€์ฆ",variant="primary",size="lg"); bhs=gr.Button("๐Ÿ“ AI ์˜ˆ์‹œ",size="sm")
1906
- ohm=gr.Textbox(label="โœ… ๋ณ€ํ™˜ ๊ฒฐ๊ณผ",lines=8)
1907
- och=gr.Textbox(label="๐Ÿ“‹ ๋ณ€ํ™˜ ๋‚ด์—ญ",lines=5,elem_classes=["mono"])
1908
- ocp=gr.HTML(); oex=gr.Textbox(visible=False)
1909
- bhm.click(run_humanizer,[ihm],[ohm,och,ocp,oex],api_name="run_humanizer"); bhs.click(lambda:SAMPLE_AI,outputs=[ihm])
1910
  with gr.Tab("๐Ÿ” ํ‘œ์ ˆ ๊ฒ€์‚ฌ"):
1911
  gr.Markdown("**Brave Search ๋ณ‘๋ ฌ(์ตœ๋Œ€20) + KCI ยท RISS ยท arXiv + Gemini Google Search** ๊ธฐ๋ฐ˜ ํ‘œ์ ˆ ๊ฒ€์‚ฌ. CopyKiller ์Šคํƒ€์ผ ๋ณด๊ณ ์„œ.")
1912
  inp_plag=gr.Textbox(label="๊ฒ€์‚ฌํ•  ํ…์ŠคํŠธ",placeholder="ํ‘œ์ ˆ ๊ฒ€์‚ฌํ•  ํ…์ŠคํŠธ (์ตœ์†Œ 50์ž)...",lines=10)
@@ -1918,7 +1911,7 @@ with gr.Blocks(title="AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0") as demo:
1918
  btn_ps.click(lambda:SAMPLE_AI,outputs=[inp_plag])
1919
  with gr.Tab("๐Ÿ“– ์„ค๋ช…"):
1920
  gr.Markdown("""
1921
- ### ์•„ํ‚คํ…์ฒ˜ v4.0
1922
  - **ํƒ์ง€ 5์ถ•:** ํ†ต๊ณ„(25%)ยท๋ฌธ์ฒด(30%)ยท๋ฐ˜๋ณต(15%)ยท๊ตฌ์กฐ(15%)ยท์ง€๋ฌธ(15%)
1923
  - **ํ’ˆ์งˆ 6ํ•ญ๋ชฉ:** ๊ฐ€๋…์„ฑยท์–ดํœ˜ยท๋…ผ๋ฆฌยท์ •ํ™•์„ฑยทํ‘œํ˜„ยท์ •๋ณด๋ฐ€๋„
1924
  - **LLM ๊ต์ฐจ๊ฒ€์ฆ:** GPT-OSS-120BยทQwen3-32BยทKimi-K2 (GROQ)
@@ -1927,26 +1920,14 @@ with gr.Blocks(title="AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0") as demo:
1927
  - `score_sentence()` ํ†ตํ•ฉ ํ•จ์ˆ˜๋กœ ๋™์ผ ๊ธฐ์ค€ ํŒ์ •
1928
  - ๊ฒฉ์‹์–ด๋ฏธ(25์ ) + AI์ ‘์†์‚ฌ(20์ ) + ์ƒํˆฌํ‘œํ˜„(15~25์ ) + ๋ชจ๋ธ์ง€๋ฌธ(10์ ) โˆ’ ์ธ๊ฐ„๋งˆ์ปค(30์ )
1929
 
1930
- ### AIโ†’์ธ๊ฐ„ ๋ณ€ํ™˜ (Adversarial v2)
1931
- 1. **์ž๊ธฐ๋Œ€์ „ ๋ฃจํ”„**: ๋ณ€ํ™˜โ†’ํƒ์ง€โ†’์žฌ๋ณ€ํ™˜ ์ตœ๋Œ€ 3๋ผ์šด๋“œ
1932
- 2. **๋ผ์šด๋“œ๋ณ„**: ๊ทœ์น™ / LLM / LLM+๊ทœ์น™ 3ํ›„๋ณด ๊ฒฝ์Ÿ
1933
- 3. **์ž๋™ ์ข…๋ฃŒ**: ๋ชฉํ‘œ ์ ์ˆ˜(25์ ) ์ดํ•˜ ๋‹ฌ์„ฑ ์‹œ ์ข…๋ฃŒ
1934
- 4. **ํ”ผ๋“œ๋ฐฑ**: ์ด์ „ ๋ผ์šด๋“œ ์ตœ์  ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์Œ ๋ผ์šด๋“œ ์ž…๋ ฅ์œผ๋กœ
1935
-
1936
  ### ํ‘œ์ ˆ ๊ฒ€์‚ฌ
1937
  - **Brave Search**: ๋ณ‘๋ ฌ 20๊ฐœ ๋™์‹œ ์›น๊ฒ€์ƒ‰
1938
  - **ํ•™์ˆ  DB**: KCI(ํ•œ๊ตญํ•™์ˆ ์ง€์ธ์šฉ์ƒ‰์ธ), RISS(ํ•™์ˆ ์—ฐ๊ตฌ์ •๋ณด), arXiv
1939
  - **Gemini**: Google Search Grounding
1940
  - **๋ณด๊ณ ์„œ**: CopyKiller ์Šคํƒ€์ผ โ€” ์œ ์‚ฌ๋„%, ์ถœ์ฒ˜ํ‘œ, ๋ฌธ์žฅ๋ณ„ ํ•˜์ด๋ผ์ดํŠธ
1941
 
1942
- ### ๐Ÿ“„ ๋ฌธ์„œ ๋ถ„์„ (NEW)
1943
- - **์ง€์› ํ˜•์‹**: PDF ยท DOCX ยท HWP ยท HWPX ยท TXT ยท MD
1944
- - **์„น์…˜๋ณ„ ํžˆํŠธ๋งต**: ํŽ˜์ด์ง€/๋ฌธ๋‹จ๋ณ„ AI ํ™•๋ฅ  ์ƒ‰์ƒ ์‹œ๊ฐํ™”
1945
- - **๋ฌธ์žฅ๋ณ„ ํ•˜์ด๋ผ์ดํŠธ**: ๊ฐ ๋ฌธ์žฅ AI ํ™•๋ฅ  score_sentence() ๊ธฐ๋ฐ˜
1946
- - **PDF ๋ณด๊ณ ์„œ**: ์ข…ํ•ฉ๊ฒฐ๊ณผ + 5์ถ• ๋ถ„์„ + ์„น์…˜๋ณ„ ์ƒ์„ธ ๋‹ค์šด๋กœ๋“œ
1947
-
1948
  ### ํ™˜๊ฒฝ๋ณ€์ˆ˜
1949
- - `GROQ_API_KEY` โ€” LLM ๊ต์ฐจ๊ฒ€์ฆ + AIโ†’์ธ๊ฐ„ ๋ฆฌ๋ผ์ดํŒ…
1950
  - `GEMINI_API_KEY` โ€” ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Google Search Grounding)
1951
  - `BRAVE_API_KEY` โ€” ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Brave Search ๋ณ‘๋ ฌ)
1952
  """)
 
1
  """
2
+ AI ๊ธ€ ํŒ๋ณ„๊ธฐ v5.0 โ€” 5์ถ• AI ํƒ์ง€ + ํ’ˆ์งˆ ์ธก์ • + LLM ๊ต์ฐจ๊ฒ€์ฆ + ํ‘œ์ ˆ ๊ฒ€์‚ฌ
3
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
4
  5์ถ• AI ํƒ์ง€ | 6ํ•ญ๋ชฉ ํ’ˆ์งˆ | LLM ๊ต์ฐจ๊ฒ€์ฆ (GPT-OSS-120B ยท Qwen3-32B ยท Kimi-K2)
5
+ โ˜… LLM ๊ต์ฐจ๊ฒ€์ฆ: 3๋ชจ๋ธ (GPT-OSS/Qwen3/Kimi-K2) ํˆฌํ‘œ + ๊ฐ•๊ฑดํ•œ ํŒŒ์‹ฑ
6
  โ˜… ํ‘œ์ ˆ: Brave Search ๋ณ‘๋ ฌ(์ตœ๋Œ€20) + KCI/RISS/ARXIV + Gemini + CopyKiller ๋ณด๊ณ ์„œ
7
  โ˜… ๋ฌธ์„œ: PDFยทDOCXยทHWPยทHWPXยทTXT ์—…๋กœ๋“œ โ†’ ์„น์…˜๋ณ„ ํžˆํŠธ๋งต + PDF ๋ณด๊ณ ์„œ
8
  """
 
319
  except Exception as e: return None, str(e)[:150]
320
 
321
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
322
+ # โ˜… ํ†ตํ•ฉ ๋ฌธ์žฅ ์ ์ˆ˜ (ํƒญ1 + ํƒญ2 ๊ณต์œ ) โ€” v5.0 ๋Œ€ํญ ๊ฐ•ํ™”
323
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
324
  AI_ENDINGS = ['ํ•ฉ๋‹ˆ๋‹ค','์ž…๋‹ˆ๋‹ค','๋ฉ๋‹ˆ๋‹ค','์Šต๋‹ˆ๋‹ค','์žˆ์Šต๋‹ˆ๋‹ค','ํ–ˆ์Šต๋‹ˆ๋‹ค','๊ฒ ์Šต๋‹ˆ๋‹ค']
325
+ # ๋น„๊ฒฉ์‹์ด์ง€๋งŒ AI์ ์ธ ์ข…๊ฒฐ ํŒจํ„ด
326
+ AI_CASUAL_ENDINGS = ['๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค','๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค','๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค','๋‹ค๊ณ  ํŒ๋‹จ๋œ๋‹ค',
327
+ '์ธ ์…ˆ์ด๋‹ค','์ธ ๊ฒƒ์ด๋‹ค','๋Š” ๊ฒƒ์ด๋‹ค','๋Š” ์…ˆ์ด๋‹ค','ใ„น ๊ฒƒ์ด๋‹ค','์„ ๊ฒƒ์ด๋‹ค',
328
+ '๋ผ ํ•  ์ˆ˜ ์žˆ๋‹ค','๋กœ ๋ณด์ธ๋‹ค','๋กœ ํŒ๋‹จ๋œ๋‹ค','๊ณ  ์žˆ๋‹ค','๋Š” ์ถ”์„ธ๋‹ค','๋Š” ์ƒํ™ฉ์ด๋‹ค',
329
+ '์ง€ ์•Š์„ ์ˆ˜ ์—†๋‹ค','๋ผ ํ•˜๊ฒ ๋‹ค','์Œ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค','ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค']
330
+ AI_CONNS = ['๋˜ํ•œ','๋”ฐ๋ผ์„œ','๊ทธ๋Ÿฌ๋ฏ€๋กœ','์ด์— ๋”ฐ๋ผ','ํ•œํŽธ','๋”๋ถˆ์–ด','์•„์šธ๋Ÿฌ','๋ฟ๋งŒ ์•„๋‹ˆ๋ผ',
331
+ '์ด๋ฅผ ํ†ตํ•ด','์ด์—','๊ฒฐ๊ณผ์ ์œผ๋กœ','๊ถ๊ทน์ ์œผ๋กœ','ํŠนํžˆ','๋‚˜์•„๊ฐ€','์ด๋Ÿฌํ•œ']
332
+ # ์ž์—ฐ์–ด์—์„œ๋„ ์“ฐ์ด์ง€๋งŒ AI๊ฐ€ ๊ณผ๋„ํ•˜๊ฒŒ ์“ฐ๋Š” ์ ‘์†์‚ฌ
333
+ AI_SOFT_CONNS = ['๋ฌผ๋ก ','๊ทธ๋Ÿฌ๋‚˜','ํ•˜์ง€๋งŒ','์ด์ฒ˜๋Ÿผ','์ด์™€ ๊ฐ™์ด','์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ']
334
+ AI_FILLER = ['๊ฒƒ์œผ๋กœ ๋ณด','๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€','๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ','ํ•  ์ˆ˜ ์žˆ','๋ณผ ์ˆ˜ ์žˆ','์ฃผ๋ชฉํ•  ๋งŒ',
335
+ '์ค‘์š”ํ•œ ์—ญํ• ','์ค‘์š”ํ•œ ์˜๋ฏธ','๊ธ์ •์ ์ธ ์˜ํ–ฅ','๋ถ€์ •์ ์ธ ์˜ํ–ฅ','ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค','ํ•„์š”ํ•˜๋‹ค',
336
+ '์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค','์ค‘์š”ํ•˜๋‹ค','์—ญํ• ์„ ํ•˜','์˜ํ–ฅ์„ ๋ฏธ','๊ธฐ๋Œ€๋œ๋‹ค','์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค','๋ถ€๊ฐ๋˜๊ณ ',
337
+ '๋Œ€๋‘๋˜๊ณ ','๋‹ค์–‘ํ•œ ๋ถ„์•ผ','๋‹ค์–‘ํ•œ ์‚ฐ์—…','๋ˆˆ๋ถ€์‹  ์„ฑ๊ณผ','ํš๊ธฐ์ ์ธ ๋ณ€ํ™”','ํ˜์‹ ์ ์ธ',
338
+ '์ ์—์„œ','์ธก๋ฉด์—์„œ','๊ด€์ ์—์„œ']
339
+ # ๋น„๊ฒฉ์‹ AI ์ƒํˆฌํ‘œํ˜„ (๋น„๊ฒฉ์‹์ฒด๋กœ ์จ๋„ AI์ )
340
+ AI_CASUAL_FILLER = ['๋ฌด๊ถ๋ฌด์ง„ํ•˜๋‹ค','๋ฌด๊ถ๋ฌด์ง„ํ•œ','๊ณผ์–ธ์ด ์•„๋‹ˆ','๋ŒํŒŒ๊ตฌ๊ฐ€ ๋ ','์ „ํ™˜์ ์ด ๋ ',
341
+ '๊ธฐ๋ฐ˜์œผ๋กœ','๋ฐœํŒ์œผ๋กœ','์›๋™๋ ฅ์ด','์ดˆ์„์ด ๋ ','๊ฐ€์†ํ™”๋˜','๊ธ‰๋ถ€์ƒ','ํŒจ๋Ÿฌ๋‹ค์ž„',
342
+ '์ง€ํ‰์„ ์—ด','์ƒˆ๋กœ์šด ์žฅ์„','๋Œ€์ „ํ™˜','๋ณธ๊ฒฉํ™”๋˜','๊ณ ๋„ํ™”','์ด์ •ํ‘œ']
343
+ # ์–‘๋ณด-์ฃผ์žฅ ํŒจํ„ด (AI ํŠน์œ : "๋ฌผ๋ก  Xํ•˜์ง€๋งŒ, Y" ๊ตฌ์กฐ)
344
+ AI_CONCESSION = re.compile(r'๋ฌผ๋ก .{2,20}(ํ•˜์ง€๋งŒ|๊ทธ๋Ÿฌ๋‚˜|๊ทธ๋ ‡์ง€๋งŒ|๋‹ค๋งŒ)|.{2,15}(์ด๊ธด ํ•˜์ง€๋งŒ|๊ธฐ๋Š” ํ•˜์ง€๋งŒ|์ˆ˜ ์žˆ์ง€๋งŒ|์ˆ˜๋Š” ์žˆ์ง€๋งŒ)')
345
+ # ์˜์–ด AI ํŒจํ„ด
346
+ EN_AI_MARKERS = ['furthermore','additionally','moreover','it is worth noting','in conclusion',
347
+ 'it is important to','plays a crucial role','significant impact','various aspects',
348
+ 'in this regard','consequently','nevertheless','integral part of','led to remarkable',
349
+ 'fundamentally transformed','has become increasingly','it should be noted',
350
+ 'in the context of','paradigm shift','landscape of','methodologies',
351
+ 'transformative impact','unprecedented','in various domains']
352
  HUMAN_MARKERS = {
353
+ 'ใ…‹ใ…Žใ… ': re.compile(r'([ใ…‹ใ…Žใ… ใ…œใ„ทใ„ฑ])\1{1,}'),
354
  '์ด๋ชจํ‹ฐ์ฝ˜': re.compile(r'[;:]-?[)(DPp]|\^[_\-]?\^|ใ…กใ…ก|;;'),
355
+ '์ค„์ž„': re.compile(r'ใ„นใ…‡|ใ…‡ใ…‡|ใ„ดใ„ด|ใ…‡ใ…‹|ใ„ทใ„ท|ใ…‚ใ…‚'),
356
  '๋А๋‚Œํ‘œ': re.compile(r'[!?]{2,}'),
357
+ '๋น„๊ฒฉ์‹์ข…๊ฒฐ': re.compile(r'(๊ฑฐ๋“ |์ž–์•„|์ธ๋ฐ|์ธ๊ฑธ|๊ฐ™์Œ|๋А๋‚Œ|์•„๋‹˜|๋Œ€๋ฐ•|๋ฏธ์ณค|ํ—|ใ…‹$|ใ…Ž$|์ž„$|์Œ$|๋“ฏ$)'),
358
+ '๊ตฌ์–ด์ถ•์•ฝ': re.compile(r'(๊ฑ|์ข€|๋ง‰|์™„์ „|์ง„์งœ|๋ ˆ์•Œ|์กด๋‚˜|๊ฐœ|์กธ๋ผ|์กด๋ง›|๊ฒ๋‚˜)'),
359
+ '๋งž์ถค๋ฒ•์˜ค๋ฅ˜': re.compile(r'๋ฌ|๋ช‡์ผ|๊ธˆ์ƒˆ|ํ• ์ˆ˜์žˆ|๊ฒƒ๊ฐ™[์€๋‹ค]|๋˜๊ฐ€|๋˜์„œ|์•ˆ๋€|ํ–‡๋‹ค'),
360
+ '๋ง์ค„์ž„ํ‘œ': re.compile(r'\.{3,}|โ€ฆ'),
361
  }
362
  FP = {
363
+ "GPT": {"m":['๋ฌผ๋ก ์ด์ฃ ','๋„์›€์ด ๋˜์…จ๊ธฐ๋ฅผ','์„ค๋ช…ํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค','์ถ”๊ฐ€ ์งˆ๋ฌธ','๋„์›€์ด ํ•„์š”ํ•˜์‹œ๋ฉด',
364
+ '์š”์•ฝํ•˜์ž๋ฉด','๊ฐ„๋žตํžˆ ์ •๋ฆฌํ•˜๋ฉด','ํ•ต์‹ฌ์€'],"e":['์Šต๋‹ˆ๋‹ค','๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค'],"lp":re.compile(r'^\d+\.\s|^[-โ€ข]\s',re.M)},
365
+ "Claude": {"m":['๋ง์”€ํ•˜์‹ ','์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค','๊ท ํ˜• ์žกํžŒ','๋งฅ๋ฝ์—์„œ','ํ•œ ๊ฐ€์ง€ ์ฃผ์˜ํ• ','๋‰˜์•™์Šค',
366
+ 'ํฅ๋ฏธ๋กœ์šด ์งˆ๋ฌธ','๋ณต์žกํ•œ ์ฃผ์ œ'],"e":['๋„ค์š”','๊ฑฐ์˜ˆ์š”'],"lp":re.compile(r'^\*\*.*\*\*|^#+\s',re.M)},
367
+ "Gemini": {"m":['๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค','์ •๋ฆฌํ•ด ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค','ํ•ต์‹ฌ ๋‚ด์šฉ์„','๋” ์•Œ๊ณ  ์‹ถ์œผ์‹œ๋ฉด',
368
+ '์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค'],"e":['๊ฒ ์Šต๋‹ˆ๋‹ค','๋ณด์„ธ์š”'],"lp":re.compile(r'^\*\s|^-\s\*\*',re.M)},
369
+ "Perplexity": {"m":['๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅด๋ฉด','๋ณด๋„์— ๋”ฐ๋ฅด๋ฉด','์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด','๋ฐํ˜”๋‹ค','์ „ํ–ˆ๋‹ค',
370
+ '๊ฒƒ์œผ๋กœ ๋‚˜ํƒ€๋‚ฌ๋‹ค','๊ฒƒ์œผ๋กœ ์กฐ์‚ฌ๋๋‹ค','๊ฒƒ์œผ๋กœ ์ง‘๊ณ„๋๋‹ค','๋ฐœํ‘œํ–ˆ๋‹ค'],"e":['๋ฐํ˜”๋‹ค','๋‚˜ํƒ€๋‚ฌ๋‹ค','์ „ํ–ˆ๋‹ค'],"lp":re.compile(r'\[\d+\]',re.M)},
371
  }
372
 
373
  def score_sentence(sent):
374
+ """๋‹จ์ผ ๋ฌธ์žฅ AI ์ ์ˆ˜ (0~100). ํƒญ1ยทํƒญ2 ๊ณต์œ . v5.0 ๋Œ€ํญ ๊ฐ•ํ™”."""
375
  sc = 0; reasons = []
376
+ sl = sent.lower().strip()
377
+ sr = sent.rstrip('.!?ใ€‚')
378
+
379
+ # โ”€โ”€ ๊ฒฉ์‹ ์ข…๊ฒฐ์–ด๋ฏธ โ”€โ”€
380
  for e in AI_ENDINGS:
381
+ if sr.endswith(e): sc += 22; reasons.append(f"๊ฒฉ์‹์–ด๋ฏธ(-{e})"); break
382
+
383
+ # โ”€โ”€ ๋น„๊ฒฉ์‹ AI ์ข…๊ฒฐ ํŒจํ„ด โ”€โ”€
384
+ if sc == 0: # ๊ฒฉ์‹์ด ์•„๋‹Œ ๊ฒฝ์šฐ๋งŒ
385
+ for e in AI_CASUAL_ENDINGS:
386
+ if sr.endswith(e): sc += 15; reasons.append(f"๋น„๊ฒฉ์‹AI(-{e})"); break
387
+
388
+ # โ”€โ”€ ๋ฌธ๋‘ ์ ‘์†์‚ฌ โ”€โ”€
389
+ stripped = sent.strip()
390
  for c in AI_CONNS:
391
+ if stripped.startswith(c):
392
+ sc += 18; reasons.append(f"AI์ ‘์†์‚ฌ({c})"); break
393
+ else:
394
+ for c in AI_SOFT_CONNS:
395
+ if stripped.startswith(c): sc += 8; reasons.append(f"์•ฝํ•œ์ ‘์†์‚ฌ({c})"); break
396
+
397
+ # โ”€โ”€ ์ƒํˆฌ์  ํ‘œํ˜„ (ํด๋ž˜์‹ + ๋น„๊ฒฉ์‹) โ”€โ”€
398
+ filler_found = sum(1 for f in AI_FILLER if f in sent)
399
+ casual_filler = sum(1 for f in AI_CASUAL_FILLER if f in sent)
400
+ total_filler = filler_found + casual_filler
401
+ if total_filler >= 3: sc += 25; reasons.append(f"์ƒํˆฌํ‘œํ˜„ร—{total_filler}")
402
+ elif total_filler == 2: sc += 18; reasons.append(f"์ƒํˆฌํ‘œํ˜„ร—2")
403
+ elif total_filler == 1: sc += 10; reasons.append(f"์ƒํˆฌํ‘œํ˜„ร—1")
404
+
405
+ # โ”€โ”€ ์–‘๋ณด-์ฃผ์žฅ ํŒจํ„ด (Claude/GPT ํŠน์œ ) โ”€โ”€
406
+ if AI_CONCESSION.search(sent): sc += 10; reasons.append("์–‘๋ณดํŒจํ„ด")
407
+
408
+ # โ”€โ”€ ๋ชจ๋ธ ์ง€๋ฌธ โ”€โ”€
409
  for mn, fp in FP.items():
410
  for m in fp["m"]:
411
+ if m in sent: sc += 8; reasons.append(f"{mn}์ง€๋ฌธ"); break
412
+
413
+ # โ”€โ”€ ์˜์–ด AI ํŒจํ„ด (๋ณต์ˆ˜ ๋ˆ„์ ) โ”€โ”€
414
+ en_count = sum(1 for em in EN_AI_MARKERS if em in sl)
415
+ if en_count >= 3: sc += 25; reasons.append(f"์˜์–ดAIร—{en_count}")
416
+ elif en_count >= 2: sc += 18; reasons.append(f"์˜์–ดAIร—{en_count}")
417
+ elif en_count >= 1: sc += 12; reasons.append(f"์˜์–ดAIร—1")
418
+
419
+ # โ”€โ”€ ๋ณตํ•ฉ ๋ณด๋„ˆ์Šค: ๊ฒฉ์‹+์ƒํˆฌ+์ ‘์† ๋™์‹œ โ†’ ๊ฑฐ์˜ ํ™•์‹ค AI โ”€โ”€
420
+ has_formal = any(sr.endswith(e) for e in AI_ENDINGS)
421
+ has_conn = any(stripped.startswith(c) for c in AI_CONNS)
422
+ if has_formal and total_filler >= 1 and has_conn: sc += 8; reasons.append("๋ณตํ•ฉAI")
423
+ elif has_formal and total_filler >= 2: sc += 5; reasons.append("๊ฒฉ์‹+์ƒํˆฌ")
424
+
425
+ # โ”€โ”€ ์ธ๊ฐ„ ๋งˆ์ปค (๊ฐ์ ) โ€” ์„ธ๋ถ„ํ™” โ”€โ”€
426
  for n, p in HUMAN_MARKERS.items():
427
+ matches = p.findall(sent)
428
+ if matches:
429
+ if n in ('ใ…‹ใ…Žใ… ','์ด๋ชจํ‹ฐ์ฝ˜','์ค„์ž„'): sc -= 25; reasons.append(f"์ธ๊ฐ„({n})")
430
+ elif n in ('๋น„๊ฒฉ์‹์ข…๊ฒฐ','๊ตฌ์–ด์ถ•์•ฝ'): sc -= 18; reasons.append(f"๊ตฌ์–ด์ฒด({n})")
431
+ elif n == '๋งž์ถค๋ฒ•์˜ค๋ฅ˜': sc -= 12; reasons.append("๋งž์ถค๋ฒ•์˜ค๋ฅ˜")
432
+ elif n in ('๋А๋‚Œํ‘œ','๋ง์ค„์ž„ํ‘œ'): sc -= 10; reasons.append(f"์ธ๊ฐ„({n})")
433
+
434
  return max(0, min(100, sc)), reasons
435
 
436
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
437
+ # ์ถ•โ‘  ํ†ต๊ณ„ โ€” v5.0: Burstiness + ์กฐ๊ฑด๋ถ€ ์—”ํŠธ๋กœํ”ผ ์ถ”๊ฐ€
438
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
439
  def analyze_statistics(text, sentences, words):
440
  sl = [len(s) for s in sentences]
441
  if len(sl) < 2: return {"score":50}
442
+
443
+ # 1. CV (๋ฌธ์žฅ ๊ธธ์ด ๋ณ€๋™ ๊ณ„์ˆ˜)
444
  avg = sum(sl)/len(sl); std = math.sqrt(sum((l-avg)**2 for l in sl)/len(sl))
445
  cv = std/avg if avg > 0 else 0
446
+ cv_score = 85 if cv<0.20 else 70 if cv<0.30 else 50 if cv<0.45 else 30 if cv<0.60 else 15
447
+
448
+ # 2. Burstiness โ€” ์—ฐ์† ๋ฌธ์žฅ ๊ธธ์ด์ฐจ ๋ณ€๋™
449
+ diffs = [abs(sl[i]-sl[i-1]) for i in range(1,len(sl))]
450
+ burst_score = 50
451
+ if diffs:
452
+ avg_d = sum(diffs)/len(diffs)
453
+ max_d = max(diffs)
454
+ burst_ratio = max_d / (avg_d + 1)
455
+ burst_score = 85 if burst_ratio < 1.8 else 65 if burst_ratio < 2.5 else 40 if burst_ratio < 3.5 else 20
456
+
457
+ # 3. โ˜… ํ‘œ์ค€ ๊ธธ์ด ๋น„์œจ โ€” AI๋Š” ๋Œ€๋ถ€๋ถ„ 25~60์ž, ์ธ๊ฐ„์€ ๊ทน๋‹จ outlier ์žˆ์Œ
458
+ standard_ratio = sum(1 for l in sl if 20 <= l <= 60) / len(sl)
459
+ std_score = 80 if standard_ratio > 0.8 else 60 if standard_ratio > 0.6 else 40 if standard_ratio > 0.4 else 20
460
+ # ๊ทน๋‹จ ๋ฌธ์žฅ(10์ž ๋ฏธ๋งŒ or 80์ž ์ดˆ๊ณผ) ์žˆ์œผ๋ฉด ์ธ๊ฐ„์ 
461
+ extreme = sum(1 for l in sl if l < 10 or l > 80)
462
+ if extreme >= 2: std_score = max(10, std_score - 20)
463
+ elif extreme >= 1: std_score = max(15, std_score - 10)
464
+
465
+ # 4. ์–ดํœ˜ ๋‹ค์–‘์„ฑ
466
  wf = Counter(words); t = len(words)
 
 
 
 
 
 
467
  ttr = len(wf)/t if t>0 else 0
468
+ vocab_score = 70 if ttr<0.45 else 55 if ttr<0.55 else 35 if ttr<0.65 else 20
469
+
470
+ # 5. ๋ฌธ์žฅ ๋ณต์žก๋„ ๊ท ์ผ์„ฑ
471
+ wpc = [len(split_words(s)) for s in sentences]
472
+ complex_score = 50
473
+ if len(wpc) >= 3:
474
+ wpc_avg = sum(wpc)/len(wpc)
475
+ wpc_std = math.sqrt(sum((w-wpc_avg)**2 for w in wpc)/len(wpc))
476
+ wpc_cv = wpc_std/wpc_avg if wpc_avg > 0 else 0
477
+ complex_score = 80 if wpc_cv < 0.20 else 60 if wpc_cv < 0.35 else 35 if wpc_cv < 0.50 else 15
478
+
479
+ final = int(cv_score*0.20 + burst_score*0.20 + std_score*0.25 + vocab_score*0.15 + complex_score*0.20)
480
+ return {"score":final,"cv":round(cv,3),"ttr":round(ttr,3)}
481
 
482
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
483
+ # ์ถ•โ‘ก ๋ฌธ์ฒด โ€” v5.0: ์–‘๋ณดํŒจํ„ด + ๋น„๊ฒฉ์‹AI + ์ ‘์†์‚ฌ ์œ„์น˜ํŒจํ„ด
484
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
485
  def analyze_korean_style(text, sentences, morphemes):
486
+ if not sentences: return {"score":50}
487
+
488
+ # 1. ์ข…๊ฒฐ์–ด๋ฏธ ๋ถ„์„ (๊ฒฉ์‹ + ๋น„๊ฒฉ์‹AI)
489
+ formal_cnt = sum(1 for s in sentences if any(s.rstrip('.!?').endswith(e) for e in AI_ENDINGS))
490
+ casual_ai = sum(1 for s in sentences if any(s.rstrip('.!?').endswith(e) for e in AI_CASUAL_ENDINGS))
491
+ fr = formal_cnt/len(sentences)
492
+ car = casual_ai/len(sentences)
493
+ # ๊ฒฉ์‹ ๋น„์œจ ๋†’์œผ๋ฉด AI์ , ๋น„๊ฒฉ์‹AI๋„ ๊ฐ€์‚ฐ
494
+ ending_score = 85 if fr>0.7 else 65 if fr>0.5 else 45 if fr>0.3 else 25 if fr>0.1 else 10
495
+ ending_score = min(90, ending_score + int(car * 25)) # ๋น„๊ฒฉ์‹AI ๋ณด๋„ˆ์Šค
496
+
497
+ # 2. ์ ‘์†์‚ฌ ๋ฐ€๋„ + โ˜… ์œ„์น˜ ํŒจํ„ด
498
+ conn_positions = []
499
+ for i, s in enumerate(sentences):
500
+ for c in AI_CONNS:
501
+ if s.strip().startswith(c): conn_positions.append(i); break
502
+ conn_density = len(conn_positions)/len(sentences) if sentences else 0
503
+ conn_score = 85 if conn_density>0.4 else 65 if conn_density>0.25 else 40 if conn_density>0.1 else 15
504
+ # AI๋Š” ์ ‘์†์‚ฌ๋ฅผ ๊ทœ์น™์  ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐฐ์น˜ (2-3๋ฌธ์žฅ๋งˆ๋‹ค)
505
+ if len(conn_positions) >= 2:
506
+ gaps = [conn_positions[i]-conn_positions[i-1] for i in range(1,len(conn_positions))]
507
+ gap_cv = (math.sqrt(sum((g-sum(gaps)/len(gaps))**2 for g in gaps)/len(gaps))/(sum(gaps)/len(gaps)+0.01))
508
+ if gap_cv < 0.5: conn_score = min(90, conn_score + 10) # ๋งค์šฐ ๊ทœ์น™์  โ†’ AI ๋ณด๋„ˆ์Šค
509
+
510
+ # 3. ์ƒํˆฌํ‘œํ˜„ (ํด๋ž˜์‹ + ๋น„๊ฒฉ์‹)
511
+ filler_cnt = sum(1 for f in AI_FILLER if f in text) + sum(1 for f in AI_CASUAL_FILLER if f in text)
512
+ filler_score = 90 if filler_cnt>=6 else 75 if filler_cnt>=4 else 55 if filler_cnt>=2 else 30 if filler_cnt>=1 else 10
513
+
514
+ # 4. โ˜… ์–‘๋ณด-์ฃผ์žฅ ๊ตฌ๋ฌธ (AI ํŠน์œ : "๋ฌผ๋ก ~ํ•˜์ง€๋งŒ~" ํŒจํ„ด)
515
+ concession_cnt = len(AI_CONCESSION.findall(text))
516
+ conc_score = 80 if concession_cnt >= 2 else 55 if concession_cnt >= 1 else 20
517
+
518
+ # 5. ์ธ๊ฐ„ ๋งˆ์ปค ๊ฐ์ 
519
+ human_count = sum(len(p.findall(text)) for p in HUMAN_MARKERS.values())
520
+ human_penalty = min(35, human_count * 8)
521
+
522
+ # 6. ํ’ˆ์‚ฌ ๋น„์œจ (๋ช…์‚ฌ ๊ณผ๋‹ค = AI์ )
523
+ pos_score = 45
524
  if morphemes:
525
  pc = Counter(t for _,t in morphemes); tm = sum(pc.values())
526
+ noun_r = sum(pc.get(t,0) for t in ['NNG','NNP','NNB','NR'])/tm if tm else 0
527
+ pos_score = 70 if noun_r>0.42 else 55 if noun_r>0.38 else 35 if noun_r>0.32 else 20
528
+
529
+ final = max(5, int(ending_score*0.25 + conn_score*0.20 + filler_score*0.20 +
530
+ conc_score*0.10 + pos_score*0.15 + 10*0.10) - human_penalty)
531
+ return {"score":final,"formal":f"{fr:.0%}","conn":f"{conn_density:.2f}","filler":filler_cnt,"human":human_count}
532
 
533
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
534
+ # ์ถ•โ‘ข ๋ฐ˜๋ณต โ€” v5.0: ๋ฌธ๋‘ N-์–ด์ ˆ + ๊ตฌ๋ฌธ ํ…œํ”Œ๋ฆฟ + ์˜๋ฏธ ๋ฐ˜๋ณต
535
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
536
  def analyze_repetition(text, sentences, words):
537
+ if not sentences or len(sentences) < 2: return {"score":35}
538
+
539
+ # 1. N-gram ๋ฐ˜๋ณต (3-gram)
540
  tr = 0
541
+ if len(words)>=5:
542
  tg = Counter(tuple(words[i:i+3]) for i in range(len(words)-2))
543
  tr = sum(1 for c in tg.values() if c>1)/len(tg) if tg else 0
544
+ ngram_score = 80 if tr>0.15 else 60 if tr>0.08 else 35 if tr>0.03 else 15
545
+
546
+ # 2. โ˜… ๋ฌธ๋‘ 2-3์–ด์ ˆ ๋‹ค์–‘์„ฑ (์ฒซ ๋‹จ์–ด๋งŒ์ด ์•„๋‹Œ ์ฒซ 2-3์–ด์ ˆ)
547
+ openers_2 = []
548
+ openers_3 = []
549
+ for s in sentences:
550
+ ws = split_words(s)
551
+ if len(ws) >= 2: openers_2.append(tuple(ws[:2]))
552
+ if len(ws) >= 3: openers_3.append(tuple(ws[:3]))
553
+
554
+ opener2_score = 50
555
+ if openers_2:
556
+ unique2 = len(set(openers_2))/len(openers_2)
557
+ opener2_score = 80 if unique2 < 0.5 else 60 if unique2 < 0.7 else 35 if unique2 < 0.85 else 15
558
+
559
+ # 3. AI ์ ‘์†์‚ฌ ๋ฌธ๋‘ ๋ฐ˜๋ณต
560
  ai_only_conns = ['๋˜ํ•œ','๋”ฐ๋ผ์„œ','๊ทธ๋Ÿฌ๋ฏ€๋กœ','์ด์— ๋”ฐ๋ผ','๋”๋ถˆ์–ด','์•„์šธ๋Ÿฌ','๋ฟ๋งŒ ์•„๋‹ˆ๋ผ',
561
  '์ด๋ฅผ ํ†ตํ•ด','์ด์—','๊ฒฐ๊ณผ์ ์œผ๋กœ','๊ถ๊ทน์ ์œผ๋กœ','๋‚˜์•„๊ฐ€','์ด๋Ÿฌํ•œ']
562
  cr = sum(1 for s in sentences if any(s.strip().startswith(c) for c in ai_only_conns))
563
  crr = cr/len(sentences) if sentences else 0
564
+ ai_conn_score = 85 if crr>0.35 else 65 if crr>0.2 else 40 if crr>0.08 else 15
565
+
566
+ # 4. โ˜… ๊ตฌ๋ฌธ ํ…œํ”Œ๋ฆฟ ๋ฐ˜๋ณต (์ฃผ์–ด+์กฐ์‚ฌ+...+์ข…๊ฒฐ ํŒจํ„ด)
567
+ templates = []
568
+ for s in sentences:
569
+ ws = split_words(s)
570
+ if len(ws) >= 4:
571
+ # ์ฒซ ์–ด์ ˆ + ๋งˆ์ง€๋ง‰ ์–ด์ ˆ ํŒจํ„ด
572
+ templates.append((ws[0], ws[-1]))
573
+ template_rep = 0
574
+ if templates:
575
+ tc = Counter(templates)
576
+ template_rep = sum(1 for c in tc.values() if c > 1) / len(tc) if tc else 0
577
+ template_score = 80 if template_rep > 0.3 else 55 if template_rep > 0.1 else 25
578
+
579
+ # 5. โ˜… ์ข…๊ฒฐ์–ด๋ฏธ ๋‹ค์–‘์„ฑ (AI๋Š” ๊ฐ™์€ ์ข…๊ฒฐ์–ด๋ฏธ ๋ฐ˜๋ณต)
580
+ endings = []
581
+ for s in sentences:
582
+ sr = s.rstrip('.!?ใ€‚')
583
+ for e in AI_ENDINGS + ['์žˆ๋‹ค','ํ–ˆ๋‹ค','๋œ๋‹ค','ํ•œ๋‹ค','์ด๋‹ค','๋Š”๋‹ค']:
584
+ if sr.endswith(e): endings.append(e); break
585
+ ending_div = 50
586
+ if endings:
587
+ unique_e = len(set(endings))/len(endings)
588
+ ending_div = 80 if unique_e < 0.3 else 60 if unique_e < 0.5 else 35 if unique_e < 0.7 else 15
589
+
590
+ final = int(ngram_score*0.15 + opener2_score*0.20 + ai_conn_score*0.25 +
591
+ template_score*0.15 + ending_div*0.25)
592
+ return {"score":final}
593
 
594
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
595
+ # ์ถ•โ‘ฃ ๊ตฌ์กฐ โ€” v5.0: ์ถ”์ƒ์„ฑ/๊ตฌ์ฒด์„ฑ + ๋ฌธ์žฅ๋‹ค์–‘์„ฑ + ๊ตฌ๋‘์ 
596
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
597
+ # AI ์ถ”์ƒ ์ˆ˜์‹์–ด (๊ตฌ์ฒด ๋ช…์‚ฌ ์—†์ด ์“ฐ์ด๋Š” AI์  ํ˜•์šฉ์‚ฌ)
598
+ AI_VAGUE = re.compile(r'๋‹ค์–‘ํ•œ|์ค‘์š”ํ•œ|๊ธ์ •์ ์ธ|๋ถ€์ •์ ์ธ|ํš๊ธฐ์ ์ธ|ํ˜์‹ ์ ์ธ|ํšจ์œจ์ ์ธ|์ฒด๊ณ„์ ์ธ|์ข…ํ•ฉ์ ์ธ|์ „๋ฐ˜์ ์ธ|์ง€์†์ ์ธ|์ ๊ทน์ ์ธ|์ƒ๋‹นํ•œ|์ฃผ์š”ํ•œ')
599
+ # ๊ตฌ์ฒด์„ฑ ์ง€ํ‘œ (์ธ๊ฐ„์  โ€” ๊ณ ์œ ๋ช…์‚ฌ, ์ˆซ์ž+๋‹จ์œ„, ์ธ์šฉ/์ถœ์ฒ˜)
600
+ CONCRETE_PROPER = re.compile(r'์‚ผ์„ฑ|LG|ํ˜„๋Œ€|SK|์นด์นด์˜ค|๋„ค์ด๋ฒ„|๋„ทํ”Œ๋ฆญ์Šค|๊ตฌ๊ธ€|์• ํ”Œ|ํ…Œ์Šฌ๋ผ|์•„๋งˆ์กด|๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ|[๊ฐ€-ํžฃ]{2,}๋Œ€ํ•™|[๊ฐ€-ํžฃ]{2,}๋ณ‘์›|[๊ฐ€-ํžฃ]{1,3}์‹œ[๋Š”์„์ด๊ฐ€]|[๊ฐ€-ํžฃ]{1,3}๊ตฌ[๋Š”์„์ด๊ฐ€]|[๊ฐ€-ํžฃ]{2,}๋™[์—์„œ]')
601
+ CONCRETE_NUMBER = re.compile(r'\d{2,}[๋งŒ์–ต์กฐ์›๋‹ฌ๋Ÿฌ%๊ฐœ๋…„์›”์ผ์œ„๋“ฑํ˜ธ]|\d+\.\d+%|\d{4}๋…„|\d{1,2}์›”')
602
+ CONCRETE_QUOTE = re.compile(r'์— ๋”ฐ๋ฅด๋ฉด|๋ฐœํ‘œํ–ˆ|๋ฐํ˜”๋‹ค|๋ณด๋„ํ–ˆ|์ „ํ–ˆ๋‹ค|๋ผ๊ณ  ๋ง|์ธํ„ฐ๋ทฐ|์„ค๋ฌธ|์กฐ์‚ฌ|ํ†ต๊ณ„์ฒญ|๋ณด๊ณ ์„œ')
603
+
604
  def analyze_structure(text, sentences):
605
+ if not sentences: return {"score":35}
606
+
607
+ # 1. ๋งˆํฌ๋‹ค์šด/๋ฆฌ์ŠคํŠธ
608
+ lt = (len(re.findall(r'^\d+[.)]\s',text,re.M)) + len(re.findall(r'^[-โ€ข*]\s',text,re.M)) +
609
+ len(re.findall(r'^#+\s',text,re.M)) + len(re.findall(r'\*\*[^*]+\*\*',text)))
610
+ list_score = 90 if lt>=5 else 70 if lt>=3 else 45 if lt>=1 else 10
611
+
612
+ # 2. โ˜… ์ถ”์ƒ์„ฑ vs ๊ตฌ์ฒด์„ฑ (ํ•ต์‹ฌ ํŒ๋ณ„ โ€” 40% ๊ฐ€์ค‘์น˜)
613
+ vague_cnt = len(AI_VAGUE.findall(text))
614
+ proper_cnt = len(CONCRETE_PROPER.findall(text))
615
+ number_cnt = len(CONCRETE_NUMBER.findall(text))
616
+ quote_cnt = len(CONCRETE_QUOTE.findall(text))
617
+ concrete_total = proper_cnt + number_cnt + quote_cnt
618
+
619
+ if vague_cnt >= 3 and concrete_total == 0: abstract_score = 90
620
+ elif vague_cnt >= 2 and concrete_total <= 1: abstract_score = 70
621
+ elif vague_cnt >= 1 and concrete_total == 0: abstract_score = 55
622
+ elif concrete_total >= 3: abstract_score = 10
623
+ elif concrete_total >= 2: abstract_score = 20
624
+ elif concrete_total >= 1: abstract_score = 30
625
+ else: abstract_score = 45
626
+
627
+ # 3. ๋ฌธ์žฅ ์œ ํ˜• ๋‹ค์–‘์„ฑ (AI=์„œ์ˆ ๋ฌธ๋งŒ, ์ธ๊ฐ„=์˜๋ฌธ/๊ฐํƒ„ ํ˜ผ์šฉ)
628
+ has_question = any(s.strip().endswith('?') for s in sentences)
629
+ has_exclaim = any(s.strip().endswith('!') for s in sentences)
630
+ has_ellipsis = any('...' in s or 'โ€ฆ' in s for s in sentences)
631
+ variety = sum([has_question, has_exclaim, has_ellipsis])
632
+ type_score = 15 if variety >= 2 else 40 if variety >= 1 else 65
633
+
634
+ # 4. ๊ตฌ๋‘์  ๋‹จ์กฐ๋กœ์›€
635
+ puncts = re.findall(r'[!?,;:โ€ฆโ€”\-~]', text)
636
+ unique_punct = len(set(puncts))
637
+ punct_score = 65 if unique_punct <= 1 else 45 if unique_punct <= 3 else 20
638
+
639
+ # 5. ๋ฌธ๋‹จ ๊ตฌ์กฐ (๋‹ค๋ฌธ๋‹จ์ธ ๊ฒฝ์šฐ)
640
  paras = [p.strip() for p in text.split('\n\n') if p.strip()]
641
+ para_score = 35
642
+ if len(paras) >= 2:
643
+ pl = [len(split_sentences(p)) for p in paras]
644
+ avg_p = sum(pl)/len(pl)
645
+ if avg_p > 0:
646
+ pcv = math.sqrt(sum((l-avg_p)**2 for l in pl)/len(pl))/avg_p
647
+ para_score = 75 if pcv < 0.2 else 55 if pcv < 0.35 else 30
648
+ if len(paras) >= 3 and pl[0] < avg_p and pl[-1] < avg_p:
649
+ para_score = min(85, para_score + 10)
650
+
651
+ final = int(list_score*0.10 + abstract_score*0.40 + type_score*0.20 + punct_score*0.10 + para_score*0.20)
652
+ return {"score":final}
653
 
654
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
655
+ # ์ถ•โ‘ค ์ง€๋ฌธ โ€” v5.0: Perplexity + ๋น„๊ฒฉ์‹AI + ์˜์–ด ํŒจํ„ด
656
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
657
  def analyze_model_fingerprint(text, sentences):
658
  ms = {}
659
+ sl = text.lower()
660
  for mn, fp in FP.items():
661
  sc = sum(min(15,text.count(m)*5) for m in fp["m"] if text.count(m)>0)
662
  lm = fp["lp"].findall(text)
 
664
  em = sum(1 for s in sentences if any(s.rstrip('.!?').endswith(e) for e in fp.get("e",[])))
665
  if sentences: sc += int((em/len(sentences))*20)
666
  ms[mn] = min(100,sc)
667
+
668
+ # โ˜… ๋น„๊ฒฉ์‹ AI ์ผ๋ฐ˜ ์ง€๋ฌธ (ํŠน์ • ๋ชจ๋ธ ๋ถˆ๋ฌธ)
669
+ general_ai = 0
670
+ # ๋น„๊ฒฉ์‹ AI ์ƒํˆฌ
671
+ general_ai += sum(5 for f in AI_CASUAL_FILLER if f in text)
672
+ # ๋น„๊ฒฉ์‹ AI ์ข…๊ฒฐ
673
+ casual_end_cnt = sum(1 for s in sentences if any(s.rstrip('.!?').endswith(e) for e in AI_CASUAL_ENDINGS))
674
+ general_ai += casual_end_cnt * 5
675
+ # ์–‘๋ณด ํŒจํ„ด
676
+ general_ai += len(AI_CONCESSION.findall(text)) * 8
677
+ ms["๋น„๊ฒฉ์‹AI"] = min(100, general_ai)
678
+
679
+ # โ˜… ์˜์–ด AI ์ง€๋ฌธ
680
+ en_score = sum(5 for em in EN_AI_MARKERS if em in sl)
681
+ ms["์˜์–ดAI"] = min(100, en_score)
682
+
683
  mx = max(ms.values()) if ms else 0
684
+ # ๋ณต์ˆ˜ ๋ชจ๋ธ์—์„œ ์ ์ˆ˜๊ฐ€ ๋‚˜์˜ค๋ฉด ๋” AI์ 
685
+ multi = sum(1 for v in ms.values() if v >= 10)
686
+ multi_bonus = 10 if multi >= 3 else 5 if multi >= 2 else 0
687
+
688
+ base = 85 if mx>=50 else 65 if mx>=35 else 45 if mx>=20 else 25 if mx>=10 else 10
689
+ return {"score":min(95, base + multi_bonus),"model_scores":{k:v for k,v in ms.items() if k not in ("๋น„๊ฒฉ์‹AI","์˜์–ดAI") or v > 0}}
690
 
691
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
692
  # ํ’ˆ์งˆ
 
835
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
836
  # ์ข…ํ•ฉ ํŒ์ • (์ผ๊ด€๋œ ๊ธฐ์ค€)
837
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
838
+ def compute_verdict(scores, llm_score=-1, sent_avg=-1):
839
+ w={"ํ†ต๊ณ„":.08,"๋ฌธ์ฒด":.30,"๋ฐ˜๋ณต์„ฑ":.12,"๊ตฌ์กฐ":.15,"์ง€๋ฌธ":.35}
840
  ws=sum(scores[k]*w[k] for k in w)
841
+
842
+ # โ˜… ๊ต์ฐจ ์‹ ํ˜ธ ๋ถ€์ŠคํŠธ โ€” ๋ฌธ์ฒด/์ง€๋ฌธ ์ค‘์‹ฌ
843
+ style = scores["๋ฌธ์ฒด"]; fp = scores["์ง€๋ฌธ"]; rep = scores["๋ฐ˜๋ณต์„ฑ"]; struct = scores["๊ตฌ์กฐ"]
844
+ if style >= 35 and fp >= 35: ws += 8 # ๋ฌธ์ฒด+์ง€๋ฌธ ๋™์‹œ โ†’ ๊ฐ•ํ•œ AI ์‹ ํ˜ธ
845
+ elif style >= 30 and fp >= 25: ws += 4
846
+ if style >= 30 and rep >= 25 and fp >= 20: ws += 4 # 3์ถ• ์•ฝ์‹ ํ˜ธ
847
+ if fp >= 45: ws += 3 # ๊ฐ•ํ•œ ์ง€๋ฌธ ๋‹จ๋… ๋ถ€์ŠคํŠธ
848
+ if struct >= 50 and style >= 30: ws += 3 # ์ถ”์ƒ์ +๊ฒฉ์‹ ๋ฌธ์ฒด
849
+
850
+ # โ˜… ๋ฌธ์žฅ ์ˆ˜์ค€ ๋ถ€์ŠคํŠธ (๋Œ์–ด๋‚ด๋ฆฌ์ง€ ์•Š์Œ)
851
+ if sent_avg >= 0 and sent_avg > ws:
852
+ ws = ws * 0.80 + sent_avg * 0.20
853
+
854
  hi=sum(1 for v in scores.values() if v>=50)
855
+ if hi>=4: ws+=8
856
+ elif hi>=3: ws+=5
857
+ elif hi>=2: ws+=2
858
+
859
+ # โ˜… ์ธ๊ฐ„ ๊ฒฉ์‹๋ฌธ ํ• ์ธ โ€” ์ง€๋ฌธ์ด ๋‚ฎ๊ณ  ๊ตฌ์กฐ๊ฐ€ ๊ตฌ์ฒด์ (๋‚ฎ์€)์ธ ๊ฒฝ์šฐ๋งŒ
860
+ if style < 40 and fp <= 20 and rep < 22 and struct < 35:
861
+ ws -= 5 # ๊ฒฉ์‹์ด์ง€๋งŒ AI ์ง€๋ฌธ ์—†๊ณ  ๊ตฌ์ฒด์  = ์ธ๊ฐ„
862
+
863
+ lo=sum(1 for v in scores.values() if v<20)
864
+ if lo>=3: ws-=8
865
+ elif lo>=2: ws-=3
866
  if llm_score>=0: ws=ws*0.70+llm_score*0.30
867
  fs=max(0,min(100,int(ws)))
868
  if fs>=75: return fs,"AI ์ž‘์„ฑ ํ™•์‹ ","ai_high"
 
876
  sc={"ํ†ต๊ณ„":analyze_statistics(text,sents,words)["score"],"๋ฌธ์ฒด":analyze_korean_style(text,sents,morphs)["score"],
877
  "๋ฐ˜๋ณต์„ฑ":analyze_repetition(text,sents,words)["score"],"๊ตฌ์กฐ":analyze_structure(text,sents)["score"],
878
  "์ง€๋ฌธ":analyze_model_fingerprint(text,sents)["score"]}
879
+ # ๋ฌธ์žฅ ์ˆ˜์ค€ ํ‰๊ท  ๊ณ„์‚ฐ
880
+ sent_scores = [score_sentence(s)[0] for s in sents]
881
+ sent_avg = sum(sent_scores)/len(sent_scores) if sent_scores else -1
882
+ fs,v,lv=compute_verdict(sc, sent_avg=sent_avg); return fs,v,lv,sc
883
 
884
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
886
  # โ˜… ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Brave Search ๋ณ‘๋ ฌ + KCI/RISS/ARXIV + Gemini)
 
887
  def brave_search(query, count=5):
888
  """Brave Search API โ€” ๋‹จ์ผ ์ฟผ๋ฆฌ"""
889
  if not BRAVE_KEY: return []
 
1441
  progress(0.62); qr=analyze_quality(text,sents,words,morphs)
1442
  progress(0.75); lr=llm_cross_check(text)
1443
  sc={"ํ†ต๊ณ„":s1["score"],"๋ฌธ์ฒด":s2["score"],"๋ฐ˜๋ณต์„ฑ":s3["score"],"๊ตฌ์กฐ":s4["score"],"์ง€๋ฌธ":s5["score"]}
1444
+ # ๋ฌธ์žฅ๋ณ„ ์ ์ˆ˜ (ํƒญ2์™€ ๋™์ผ ๊ธฐ์ค€)
1445
+ sent_scores = [score_sentence(s)[0] for s in sents]
1446
+ sent_avg = sum(sent_scores)/len(sent_scores) if sent_scores else -1
1447
+ fs,verdict,level=compute_verdict(sc,lr["score"],sent_avg=sent_avg)
1448
  progress(0.95)
1449
  cm={"ai_high":("#FF4444","#FFE0E0","๋†’์Œ"),"ai_medium":("#FF8800","#FFF0DD","์ค‘๊ฐ„~๋†’์Œ"),"ai_low":("#DDAA00","#FFFBE0","์ค‘๊ฐ„"),"uncertain":("#888","#F0F0F0","๋‚ฎ์Œ"),"human":("#22AA44","#E0FFE8","๋งค์šฐ ๋‚ฎ์Œ")}
1450
  fg,bg,conf=cm.get(level,("#888","#F0F0F0","?"))
1451
  ms=s5.get("model_scores",{}); tm=max(ms,key=ms.get) if ms else "N/A"; tms=ms.get(tm,0)
1452
  mt=f"{tm} ({tms}์ )" if tms>=15 else "ํŠน์ • ๋ถˆ๊ฐ€"
1453
 
 
 
1454
  ai_sents = sum(1 for s in sent_scores if s >= 40)
1455
  human_sents = sum(1 for s in sent_scores if s < 20)
1456
 
 
1548
  sents=split_sentences(text)
1549
  hl=[]
1550
  for s in sents:
1551
+ sc, reasons = score_sentence(s)
1552
+ # 5๋‹จ๊ณ„ ์ƒ‰์ƒ
1553
+ if sc >= 60: bg="rgba(220,38,38,0.35)"; level="AIํ™•์‹ "
1554
+ elif sc >= 40: bg="rgba(249,115,22,0.30)"; level="AI์˜์‹ฌ"
1555
+ elif sc >= 25: bg="rgba(234,179,8,0.25)"; level="์ฃผ์˜"
1556
+ elif sc >= 10: bg="rgba(132,204,22,0.15)"; level="์ธ๊ฐ„์ถ”์ •"
1557
+ else: bg="rgba(34,197,94,0.20)"; level="์ธ๊ฐ„"
1558
+ # ๊ทผ๊ฑฐ ์ƒ์„ธ
1559
+ detail_parts = []
1560
+ for r in reasons:
1561
+ if '๊ฒฉ์‹' in r or '๋น„๊ฒฉ์‹AI' in r: detail_parts.append(f"๐Ÿ”ค {r}")
1562
+ elif '์ ‘์†์‚ฌ' in r: detail_parts.append(f"๐Ÿ”— {r}")
1563
+ elif '์ƒํˆฌ' in r: detail_parts.append(f"๐Ÿ“‹ {r}")
1564
+ elif '์ง€๋ฌธ' in r: detail_parts.append(f"๐Ÿ” {r}")
1565
+ elif '์ธ๊ฐ„' in r or '๊ตฌ์–ด' in r or '๋งž์ถค๋ฒ•' in r: detail_parts.append(f"โœ… {r}")
1566
+ else: detail_parts.append(r)
1567
+ tt = ' | '.join(detail_parts) if detail_parts else 'ํŠน์ด ํŒจํ„ด ์—†์Œ'
1568
+ hl.append(f'<span style="background:{bg};padding:2px 4px;border-radius:4px;display:inline;line-height:2.2;border-bottom:2px solid {"#DC2626" if sc>=60 else "#F97316" if sc>=40 else "#EAB308" if sc>=25 else "#84CC16" if sc>=10 else "#22C55E"};" title="[{level}] {tt} ({sc}์ )">{s}</span>')
1569
 
1570
  total_scores = [score_sentence(s)[0] for s in sents]
1571
  avg_sc = sum(total_scores)/len(total_scores) if total_scores else 0
1572
+ ai_high = sum(1 for s in total_scores if s >= 60)
1573
+ ai_mid = sum(1 for s in total_scores if 40 <= s < 60)
1574
  human_cnt = sum(1 for s in total_scores if s < 25)
1575
 
1576
  return f"""<div style='font-family:Pretendard,sans-serif;'>
1577
  <div style='margin-bottom:10px;padding:10px;background:#F8F8FF;border-radius:8px;'>
1578
+ <div style='display:flex;gap:8px;align-items:center;font-size:11px;margin-bottom:6px;flex-wrap:wrap;'>
1579
+ <span style='background:rgba(220,38,38,0.35);padding:2px 8px;border-radius:3px;'>๐Ÿ”ด AIํ™•์‹  {ai_high}</span>
1580
+ <span style='background:rgba(249,115,22,0.30);padding:2px 8px;border-radius:3px;'>๐ŸŸ  AI์˜์‹ฌ {ai_mid}</span>
1581
+ <span style='background:rgba(234,179,8,0.25);padding:2px 8px;border-radius:3px;'>๐ŸŸก ์ฃผ์˜</span>
1582
+ <span style='background:rgba(132,204,22,0.15);padding:2px 8px;border-radius:3px;'>๐ŸŸข ์ธ๊ฐ„ {human_cnt}</span>
1583
+ <span style='color:#888;'>ํ‰๊ท  {avg_sc:.0f}์  | ๋งˆ์šฐ์Šค ์˜ค๋ฒ„โ†’์ƒ์„ธ ๊ทผ๊ฑฐ</span>
1584
  </div>
1585
+ <div style='font-size:10px;color:#888;'>๐Ÿ’ก ๊ฒฉ์‹์–ด๋ฏธ(22) + AI์ ‘์†์‚ฌ(18) + ์ƒํˆฌํ‘œํ˜„(10~25) + ์–‘๋ณดํŒจํ„ด(10) + ๋ชจ๋ธ์ง€๋ฌธ(8) + ๋ณตํ•ฉ๋ณด๋„ˆ์Šค(8) โˆ’ ์ธ๊ฐ„๋งˆ์ปค(~25)</div>
1586
  </div>
1587
+ <div style='line-height:2.4;font-size:14px;'>{' '.join(hl)}</div>
1588
  </div>"""
1589
 
1590
  # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
1637
  progress(0.30, "LLM ๊ต์ฐจ๊ฒ€์ฆ...")
1638
  llm_result = llm_cross_check(full_text[:3000])
1639
  if llm_result["score"] >= 0:
1640
+ _sent_scores = [score_sentence(s)[0] for s in sents_all]
1641
+ _sent_avg = sum(_sent_scores)/len(_sent_scores) if _sent_scores else -1
1642
+ total_score, total_verdict, total_level = compute_verdict(total_axes, llm_result["score"], sent_avg=_sent_avg)
1643
 
1644
  # ์„น์…˜๋ณ„ ๋ถ„์„
1645
  progress(0.45, f"{len(sections)}๊ฐœ ์„น์…˜ ๋ถ„์„...")
 
1886
 
1887
 
1888
  with gr.Blocks(title="AI ๊ธ€ ํŒ๋ณ„๊ธฐ v4.0") as demo:
1889
+ gr.Markdown("# ๐Ÿ”Ž AI ๊ธ€ ํŒ๋ณ„๊ธฐ v5.0\n**5์ถ• AI ํƒ์ง€ ยท ํ’ˆ์งˆ ์ธก์ • ยท LLM ๊ต์ฐจ๊ฒ€์ฆ ยท ํ‘œ์ ˆ ๊ฒ€์‚ฌ ยท ํŒŒ์ผ ์—…๋กœ๋“œ**")
1890
  with gr.Tab("๐Ÿ” ๋ถ„์„"):
1891
  gr.Markdown("ํ…์ŠคํŠธ๊ฐ€ AI์— ์˜ํ•ด ์ž‘์„ฑ๋˜์—ˆ๋Š”์ง€ 5๊ฐœ ์ถ•์œผ๋กœ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. 0~100์  (๋†’์„์ˆ˜๋ก AI ๊ฐ€๋Šฅ์„ฑ ๋†’์Œ)")
1892
  inp=gr.Textbox(label="๋ถ„์„ํ•  ํ…์ŠคํŠธ",placeholder="์ตœ์†Œ 50์ž ์ด์ƒ...",lines=10)
 
1900
  gr.Markdown("๋ฌธ์žฅ๋ณ„๋กœ AI ํ™•๋ฅ ์„ ์ƒ‰์ƒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. **ํƒญ1๊ณผ ๋™์ผํ•œ ๊ธฐ์ค€**์œผ๋กœ ํŒ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์šฐ์Šค ์˜ค๋ฒ„ ์‹œ ๊ทผ๊ฑฐ ํ™•์ธ.")
1901
  ih=gr.Textbox(label="ํ…์ŠคํŠธ",lines=8); bh=gr.Button("๐ŸŽจ ํ•˜์ด๋ผ์ดํŠธ ๋ถ„์„",variant="primary"); hr=gr.HTML()
1902
  bh.click(run_highlight,[ih],[hr],api_name="run_highlight")
 
 
 
 
 
 
 
 
 
1903
  with gr.Tab("๐Ÿ” ํ‘œ์ ˆ ๊ฒ€์‚ฌ"):
1904
  gr.Markdown("**Brave Search ๋ณ‘๋ ฌ(์ตœ๋Œ€20) + KCI ยท RISS ยท arXiv + Gemini Google Search** ๊ธฐ๋ฐ˜ ํ‘œ์ ˆ ๊ฒ€์‚ฌ. CopyKiller ์Šคํƒ€์ผ ๋ณด๊ณ ์„œ.")
1905
  inp_plag=gr.Textbox(label="๊ฒ€์‚ฌํ•  ํ…์ŠคํŠธ",placeholder="ํ‘œ์ ˆ ๊ฒ€์‚ฌํ•  ํ…์ŠคํŠธ (์ตœ์†Œ 50์ž)...",lines=10)
 
1911
  btn_ps.click(lambda:SAMPLE_AI,outputs=[inp_plag])
1912
  with gr.Tab("๐Ÿ“– ์„ค๋ช…"):
1913
  gr.Markdown("""
1914
+ ### ์•„ํ‚คํ…์ฒ˜ v5.0
1915
  - **ํƒ์ง€ 5์ถ•:** ํ†ต๊ณ„(25%)ยท๋ฌธ์ฒด(30%)ยท๋ฐ˜๋ณต(15%)ยท๊ตฌ์กฐ(15%)ยท์ง€๋ฌธ(15%)
1916
  - **ํ’ˆ์งˆ 6ํ•ญ๋ชฉ:** ๊ฐ€๋…์„ฑยท์–ดํœ˜ยท๋…ผ๋ฆฌยท์ •ํ™•์„ฑยทํ‘œํ˜„ยท์ •๋ณด๋ฐ€๋„
1917
  - **LLM ๊ต์ฐจ๊ฒ€์ฆ:** GPT-OSS-120BยทQwen3-32BยทKimi-K2 (GROQ)
 
1920
  - `score_sentence()` ํ†ตํ•ฉ ํ•จ์ˆ˜๋กœ ๋™์ผ ๊ธฐ์ค€ ํŒ์ •
1921
  - ๊ฒฉ์‹์–ด๋ฏธ(25์ ) + AI์ ‘์†์‚ฌ(20์ ) + ์ƒํˆฌํ‘œํ˜„(15~25์ ) + ๋ชจ๋ธ์ง€๋ฌธ(10์ ) โˆ’ ์ธ๊ฐ„๋งˆ์ปค(30์ )
1922
 
 
 
 
 
 
 
1923
  ### ํ‘œ์ ˆ ๊ฒ€์‚ฌ
1924
  - **Brave Search**: ๋ณ‘๋ ฌ 20๊ฐœ ๋™์‹œ ์›น๊ฒ€์ƒ‰
1925
  - **ํ•™์ˆ  DB**: KCI(ํ•œ๊ตญํ•™์ˆ ์ง€์ธ์šฉ์ƒ‰์ธ), RISS(ํ•™์ˆ ์—ฐ๊ตฌ์ •๋ณด), arXiv
1926
  - **Gemini**: Google Search Grounding
1927
  - **๋ณด๊ณ ์„œ**: CopyKiller ์Šคํƒ€์ผ โ€” ์œ ์‚ฌ๋„%, ์ถœ์ฒ˜ํ‘œ, ๋ฌธ์žฅ๋ณ„ ํ•˜์ด๋ผ์ดํŠธ
1928
 
 
 
 
 
 
 
1929
  ### ํ™˜๊ฒฝ๋ณ€์ˆ˜
1930
+ - `GROQ_API_KEY` โ€” LLM ๊ต์ฐจ๊ฒ€์ฆ
1931
  - `GEMINI_API_KEY` โ€” ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Google Search Grounding)
1932
  - `BRAVE_API_KEY` โ€” ํ‘œ์ ˆ ๊ฒ€์‚ฌ (Brave Search ๋ณ‘๋ ฌ)
1933
  """)