Spaces:
Runtime error
Runtime error
| """DATA FRAUD detection β statistical fingerprints of | |
| fabricated numbers. Text plagiarism tools ignore this entirely. | |
| BENFORD'S LAW : in genuine natural datasets the leading digit follows | |
| P(d) = log10(1 + 1/d) (1 appears ~30%, 9 ~4.6%). | |
| Fabricated/manually-invented numbers are too uniform -> | |
| chi-square + MAD divergence from Benford flags them. | |
| GRIM TEST : a reported mean of integer-valued items over N items must | |
| be one of only N possible fractions. A mean that is | |
| arithmetically impossible for its sample size is a red flag | |
| for fabricated or mistyped summary statistics. | |
| Pure numpy, no dependencies. Returns evidence, never a hard verdict β these | |
| are leads for a human reviewer (consistent with the recall-first philosophy). | |
| """ | |
| import re | |
| import numpy as np | |
| BENFORD = np.array([np.log10(1 + 1 / d) for d in range(1, 10)]) | |
| # numbers like 12, 3.4, 1,234.5, 67% β capture the numeric token | |
| _NUM = re.compile(r"(?<![\w.])(\d{1,3}(?:,\d{3})+|\d+)(?:\.(\d+))?") | |
| # "mean/average ... of N" patterns for GRIM | |
| _MEAN_CTX = re.compile( | |
| r"(?:mean|average|M)\s*(?:=|of|was|is|:)?\s*(\d+\.\d+)", re.I) | |
| _N_CTX = re.compile(r"\b[nN]\s*=\s*(\d{1,5})\b") | |
| def _leading_digit(num_str): | |
| for ch in num_str.replace(",", "").lstrip("0."): | |
| if ch.isdigit() and ch != "0": | |
| return int(ch) | |
| return None | |
| def benford_analysis(text): | |
| digits = [] | |
| for m in _NUM.finditer(text): | |
| whole = m.group(1) | |
| # skip years and tiny integers that aren't "data" (1900-2099, 0-9) | |
| clean = whole.replace(",", "") | |
| if len(clean) == 1: | |
| continue | |
| if re.fullmatch(r"(19|20)\d{2}", clean): | |
| continue | |
| d = _leading_digit(whole) | |
| if d: | |
| digits.append(d) | |
| if len(digits) < 30: # too few numbers to judge | |
| return None | |
| digits = np.array(digits) | |
| counts = np.array([(digits == d).sum() for d in range(1, 10)], dtype=float) | |
| observed = counts / counts.sum() | |
| expected = BENFORD | |
| exp_counts = expected * counts.sum() | |
| chi2 = float(((counts - exp_counts) ** 2 / exp_counts).sum()) | |
| mad = float(np.mean(np.abs(observed - expected))) # mean abs deviation | |
| # Nigrini's MAD conformity thresholds for first-digit Benford | |
| conformity = ("close" if mad < 0.006 else | |
| "acceptable" if mad < 0.012 else | |
| "marginal" if mad < 0.015 else "nonconformant") | |
| return { | |
| "n_numbers": int(len(digits)), | |
| "observed": [round(float(x), 4) for x in observed], | |
| "expected": [round(float(x), 4) for x in expected], | |
| "chi_square": round(chi2, 2), | |
| "chi2_critical_p05": 15.51, # df=8 | |
| "mad": round(mad, 5), | |
| "conformity": conformity, | |
| "suspicious": bool(chi2 > 15.51 and mad >= 0.012), | |
| } | |
| def grim_test(text): | |
| """Check reported means against GRIM-possible values for their N.""" | |
| means = [(m.group(1), m.start()) for m in _MEAN_CTX.finditer(text)] | |
| ns = [(int(m.group(1)), m.start()) for m in _N_CTX.finditer(text)] | |
| if not means or not ns: | |
| return None | |
| flagged = [] | |
| for mean_str, mpos in means: | |
| decimals = len(mean_str.split(".")[1]) | |
| if decimals == 0 or decimals > 3: | |
| continue | |
| mean_val = float(mean_str) | |
| # nearest reported N (same sentence-ish window) | |
| n = min(ns, key=lambda x: abs(x[1] - mpos))[0] | |
| if not 1 <= n <= 10000: | |
| continue | |
| # GRIM: mean*N should be (close to) an integer | |
| prod = mean_val * n | |
| frac = abs(prod - round(prod)) | |
| tol = 0.5 / n + 1e-9 # rounding tolerance for given N | |
| if frac > tol and min(frac, 1 - frac) > tol: | |
| flagged.append({"reported_mean": mean_val, "n": n, | |
| "implied_total": round(prod, 3), | |
| "off_by": round(min(frac, 1 - frac), 4)}) | |
| if not flagged: | |
| return None | |
| return {"inconsistent_means": flagged[:10], "count": len(flagged)} | |
| def detect_fraud(text): | |
| """Returns {benford, grim, flags} or None when there's too little data.""" | |
| benford = benford_analysis(text) | |
| grim = grim_test(text) | |
| if benford is None and grim is None: | |
| return None | |
| flags = [] | |
| if benford and benford["suspicious"]: | |
| flags.append(f"leading-digit distribution diverges from Benford's law " | |
| f"(MAD {benford['mad']}, chi2 {benford['chi_square']}) β " | |
| f"numbers may be fabricated or hand-invented") | |
| if grim: | |
| flags.append(f"{grim['count']} reported mean(s) arithmetically " | |
| f"impossible for the stated sample size (GRIM test)") | |
| return {"benford": benford, "grim": grim, "flags": flags, | |
| "suspicious": bool(flags)} | |