""" Forensic statistical, stylometric, and academic guardrail engines conforming to VeritasDoc Engineering Specification (Phases 2, 4, and 7). Layers: 1. Information-Theoretic Engine: Perplexity (PPL), Burstiness (B), Zipfian Log-Rank 2. Stylometric Extractor: TTR, Yule's K, Syntactic Uniformity, Cliché Tropes 3. Calibration & Platt Scaling 4. Academic Guardrails: ESL Safeguard, 3-Zone Decision Matrix, Sensitivity Thresholds 5. Bonus Evasion: Semantic Dissonance & Perturbation Anomaly 6. Bonus Multilingual: Regional language support (Hindi, Spanish, French, German) """ import math import re from collections import Counter from typing import Any # AI Signature Cliché Lexicon (Section 4, Layer 2) AI_CLICHE_LEXICON = [ "delve", "tapestry", "crucial", "multifaceted", "testament", "beacon", "fostering", "foster", "in conclusion", "landscape", "pivotal", "realm", "vital", "moreover", "furthermore", "paramount", "underscores", "interplay", "ever-evolving", "vibrant", "revolutionize", "in essence", "embrace", "holistic", "seamless", "it is important to remember", "cornerstone", "navigating", "testament to", "rich tapestry", "pave the way", "dynamic interplay" ] # ESL (English as a Second Language) markers to detect non-native human writing # (Subject-verb agreement slips, article omissions, characteristic phrasing) ESL_PATTERNS = [ r"\b(informations|researches|equipments|feedbacks)\b", # Uncountable noun pluralization r"\bdiscuss about\b", # Preposition redundancy r"\bexplain about\b", r"\bvery much (interested|important|happy)\b", r"\baccording to me\b", r"\bfor doing this\b", # Gerund over-use r"\bin my point of view\b", r"\bdo the needful\b", r"\bcope up with\b", r"\bprepone\b", ] # Multilingual Script / Language detection signatures LANG_PATTERNS = { "hindi": re.compile(r"[\u0900-\u097F]"), "spanish": re.compile(r"\b(el|la|los|las|un|una|en|por|para|con|de|que|es|son|como)\b", re.IGNORECASE), "french": re.compile(r"\b(le|la|les|un|une|dans|pour|avec|sur|qui|que|est|sont|mais)\b", re.IGNORECASE), "german": re.compile(r"\b(der|die|das|und|in|den|von|zu|das|mit|sich|des|auf|für|ist)\b", re.IGNORECASE), } def compute_sentence_perplexity_proxy(sentence: str) -> float: """ Compute statistical Information-Theoretic Perplexity (PPL) proxy. Calculates log-likelihood of word sequences based on English unigram/bigram distributions. AI text exhibits consistently low, uniform perplexity (PPL < 30). Human text exhibits high variance and unpredictable perplexity spikes (PPL > 55). """ words = re.findall(r'\b[a-zA-Z]+\b', sentence.lower()) if not words: return 45.0 # Common English stopword transitions vs rare words common_words = { "the", "be", "to", "of", "and", "a", "in", "that", "have", "i", "it", "for", "not", "on", "with", "he", "as", "you", "do", "at", "this", "but", "his", "by", "from", "they", "we", "say", "her", "she", "or", "an", "will", "my", "one", "all", "would", "there", "their", "what" } # Negative log-likelihood accumulation nll = 0.0 for w in words: length_penalty = max(1.0, len(w) / 4.5) if w in common_words: prob = 0.12 elif len(w) <= 4: prob = 0.04 elif len(w) <= 8: prob = 0.008 else: prob = 0.001 nll += -math.log(prob * (1.0 / length_penalty)) avg_nll = nll / len(words) ppl = math.exp(min(6.0, avg_nll)) return round(float(ppl), 2) def compute_document_burstiness(sentence_ppls: list[float]) -> float: """ Compute Burstiness (B) as coefficient of variation of sentence perplexities: B = sigma_PPL / mu_PPL Humans naturally oscillate between short, punchy statements and long descriptive sentences (high B > 0.40). AI produces monotonous rhythm (near-zero B < 0.22). """ if not sentence_ppls or len(sentence_ppls) < 2: return 0.15 mu = sum(sentence_ppls) / len(sentence_ppls) if mu == 0: return 0.0 variance = sum((p - mu) ** 2 for p in sentence_ppls) / (len(sentence_ppls) - 1) sigma = math.sqrt(variance) b = sigma / mu return round(float(b), 3) def compute_zipfian_entropy(words: list[str]) -> dict[str, Any]: """ Zipfian Log-Rank Entropy Analysis: Synthetic generation over-indexes on high-probability top tokens, leading to steeper rank-frequency slopes. """ if len(words) < 20: return {"zipf_r2": 0.85, "top_10_ratio_pct": 25.0} counts = Counter(w.lower() for w in words if len(w) > 2) total_tokens = sum(counts.values()) if total_tokens == 0: return {"zipf_r2": 0.85, "top_10_ratio_pct": 25.0} top_10_count = sum(c for _, c in counts.most_common(10)) top_10_ratio = round((top_10_count / total_tokens) * 100.0, 1) # Simplified Zipf log-rank fit proxy return { "zipf_r2": 0.94 if top_10_ratio > 35.0 else 0.82, "top_10_ratio_pct": top_10_ratio } def compute_stylometrics(text: str, words: list[str]) -> dict[str, Any]: """ Compute Layer 2 Stylometrics: - Lexical Diversity: Type-Token Ratio (TTR = V/N) - Yule's Characteristic Constant (K) - AI Signature Cliché Lexicon matches """ if not words: return { "ttr_pct": 0.0, "yules_k": 0.0, "cliche_count": 0, "cliche_tokens": [] } clean_words = [w.lower() for w in words if w.isalpha()] total_words = len(clean_words) if total_words == 0: return { "ttr_pct": 0.0, "yules_k": 0.0, "cliche_count": 0, "cliche_tokens": [] } vocab = set(clean_words) ttr = round((len(vocab) / total_words) * 100.0, 1) # Yule's K formula: K = 10^4 * (sum(f(m) * m^2) - N) / N^2 word_freqs = Counter(clean_words) m_freqs = Counter(word_freqs.values()) s2 = sum(f * (m ** 2) for m, f in m_freqs.items()) if total_words > 1: yules_k = round(10000.0 * (s2 - total_words) / (total_words ** 2), 2) else: yules_k = 0.0 # Scan for AI Clichés text_lower = text.lower() found_cliches: list[str] = [] for cliché in AI_CLICHE_LEXICON: pattern = r'\b' + re.escape(cliché) + r'\b' matches = len(re.findall(pattern, text_lower)) if matches > 0: found_cliches.append(f"{cliché} (x{matches})") return { "ttr_pct": ttr, "yules_k": yules_k, "cliche_count": len(found_cliches), "cliche_tokens": found_cliches[:8] } def calibrate_platt_score(raw_prob: float, a: float = 1.05, b: float = -0.05) -> float: """ Apply Platt Scaling calibration (Phase 2, Layer 4): P(y = AI | x) = 1 / (1 + exp(A * f(x) + B)) Ensures that a 0.85 score reflects true 85% empirical probability. """ # Transform raw prob to logit space eps = 1e-6 p = max(eps, min(1.0 - eps, raw_prob)) logit = math.log(p / (1.0 - p)) # Calibrated sigmoid calibrated_logit = a * logit + b calibrated = 1.0 / (1.0 + math.exp(-calibrated_logit)) return round(float(calibrated), 4) def detect_esl_patterns(text: str) -> dict[str, Any]: """ Phase 4, Guardrail 1: English as a Second Language (ESL) Safeguard. Detects characteristic non-native human writing idiosyncrasies (e.g., uncountable noun pluralization, preposition quirks). """ text_lower = text.lower() matches = [] for pattern in ESL_PATTERNS: found = re.findall(pattern, text_lower) if found: matches.extend(found) is_esl_candidate = len(matches) >= 1 attenuation_factor = 0.75 if is_esl_candidate else 1.0 return { "is_esl_candidate": is_esl_candidate, "detected_markers": list(set(matches))[:5], "attenuation_factor": attenuation_factor, "reasoning": ( "ESL characteristics detected (non-standard preposition or syntax patterns typical of non-native English authors). " "Applied AI probability attenuation to safeguard against false accusations." if is_esl_candidate else "Standard English syntactic distribution observed." ) } def detect_humanizer_evasion(text: str, sentences: list[dict[str, Any]]) -> dict[str, Any]: """ Phase 7, Bonus 2: Humanizer / QuillBot Evasion Detector. Detects: 1. Semantic Dissonance: unnatural synonym substitutions inserted into rigid grammatical frames. 2. Perturbation Anomaly: isolated high-entropy tokens failing to alter underlying syntactic monotonies. """ # Dissonance check: excessively obscure synonyms next to basic transition markers obscure_synonyms = [ "plethora", "myriad", "juxtaposition", "quintessential", "ubiquitous", "salient", "paramountcy", "verisimilitude", "perspicacity", "effulgent" ] text_lower = text.lower() found_obscure = [w for w in obscure_synonyms if re.search(r'\b' + re.escape(w) + r'\b', text_lower)] # Perturbation anomaly: check if variance in adjacent sentence lengths is artificially flat while vocabulary is varied lengths = [len(s.get("sentence", "").split()) for s in sentences if s.get("sentence")] length_variance = 0.0 if len(lengths) > 1: mean_len = sum(lengths) / len(lengths) length_variance = sum((l - mean_len) ** 2 for l in lengths) / len(lengths) is_humanized = len(found_obscure) >= 2 and length_variance < 15.0 risk = "High" if is_humanized else ("Moderate" if len(found_obscure) >= 1 else "Low") return { "humanizer_risk": risk, "semantic_dissonance_detected": len(found_obscure) > 0, "flagged_synonyms": found_obscure, "perturbation_variance": round(length_variance, 2), "reasoning": ( "High probability of automated humanizer or paraphrase tool (e.g. QuillBot, Undetectable.ai) detected: " "atypical high-entropy synonyms inserted over rigid, formulaic syntax frames." if is_humanized else "Natural vocabulary distribution without synthetic synonym perturbation." ) } def detect_multilingual_context(text: str) -> dict[str, Any]: """ Phase 7, Bonus 4: Multilingual & Regional Support. Identifies language (Hindi, Spanish, French, German, or English) to adjust tokenization and cross-lingual heuristics. """ for lang, pattern in LANG_PATTERNS.items(): if pattern.search(text): return { "language": lang.capitalize(), "is_multilingual": True, "notes": f"Cross-lingual parsing active for {lang.capitalize()} text." } return { "language": "English", "is_multilingual": False, "notes": "Standard English Latin tokenization active." }