Skip to main content

FKTD — Fraud KYC KTP Detector: OCR + strict cross-validation for Indonesian ID cards (KTP).

Project description

FKTD — Fraud KYC KTP Detector

PyPI version Python versions License: MIT

Multi-engine OCR plus strict cross-validation for Indonesian ID cards (KTP).

Most validators only check whether a field is empty. FKTD also decodes the 16-digit NIK, which encodes the cardholder's birth date, sex, province, and regency, and checks those against the printed fields. A card where the NIK says South Jakarta but the printed city says West Jakarta will fail, even though every field is filled in.

This is structural validation, not identity verification. It does not talk to Dukcapil. See Disclaimer.

Contents

Pipeline

image -> OCR (EasyOCR / PaddleOCR) -> field extraction -> fraud rules -> FraudResult

process() runs OCR, parses the text into 19 fields, detects the photo and signature, runs the rule engine, and returns a FraudResult with is_passed, a risk_score (0–100), and the per-rule breakdown.

Requirements

Minimum Notes
Python 3.9 CPython, 64-bit. Tested through 3.12.
OS macOS / Linux / Windows Apple Silicon works without extra setup.
RAM ~4 GB EasyOCR loads PyTorch and model weights into memory.
Disk ~2–3 GB PyTorch and OpenCV wheels are large; EasyOCR weights add ~100 MB.
Network once EasyOCR downloads weights on first run. The region API is optional.

Core dependencies (pulled in automatically): easyocr>=1.7 (brings PyTorch), opencv-python>=4.10, Pillow>=10.4, numpy>=1.26, pydantic>=2.8, python-dateutil>=2.9.

The first FKTD() call downloads EasyOCR weights and is slow. After that it runs offline unless you enable the live region API.

Install

pip install fktd

EasyOCR is included and needs no system binaries. PaddleOCR is optional:

pip install "fktd[paddle]"   # paddlepaddle is unreliable on Apple Silicon

From source:

git clone https://github.com/rezayw/fktd.git
cd fktd
pip install -e ".[dev]"      # pytest + ruff
Engine Status Notes
EasyOCR included Default. Indonesian out of the box, fine on macOS.
PaddleOCR extra [paddle] Higher accuracy/speed; best on Linux.

Usage

from fktd import FKTD

detector = FKTD(engine="easyocr")        # or engine="paddle"
result = detector.process("ktp.jpg")

result.is_passed          # bool
result.risk_score         # 0 (safe) .. 100 (suspicious)
result.summary()          # failed checks as text
result.data.model_dump()  # all extracted fields

summary() looks like:

[FAIL] risk_score=55
  - (critical) nik.province_match: Provinsi di NIK (DKI JAKARTA) != field (JAWA BARAT)
  - (critical) vision.foto: Foto tidak terdeteksi

API

FKTD(...)

FKTD(
    engine="easyocr",          # "easyocr" | "paddle" | an OCREngine instance
    lang=None,                 # OCR language override
    risk_threshold=25,         # pass requires risk_score < this
    min_confidence=60.0,       # OCR confidence floor for critical fields
    detect_vision=True,        # run photo + signature detection
    wilayah_provider="auto",   # "auto" | "api" | "local"
    **engine_kwargs,           # forwarded to the engine
)
Method Returns Notes
process(image, today=None) FraudResult Full pipeline.
extract_fields(image) KTPData OCR + extraction only, no rules.

image accepts a path, bytes, a PIL image, or a NumPy array. today overrides the reference date used for age and birth-date checks (useful in tests).

FraudResult

Field Type Notes
is_passed bool Overall result.
risk_score int 0–100.
checks list[FraudCheck] Every rule, passed and failed.
failed_checks list[FraudCheck] Failed rules only (property).
data KTPData Extracted fields.
summary() str Status plus failed checks.

FraudCheck carries code, passed, severity (info / warning / critical), message, and weight (added to risk_score on failure).

Extracted fields

19 text fields: Province, City/Regency, NIK, Name, Place of Birth, Date of Birth, Sex, Blood Type, Address, RT/RW, Village (Kel/Desa), District (Kecamatan), Religion, Marital Status, Occupation, Citizenship, Valid Until, Place Issued, Date Issued.

Two boolean flags from vision: has_foto, has_tanda_tangan.

Fraud rules

Each rule emits a FraudCheck. A failure adds its weight to risk_score, capped at 100.

Completeness — 16 required fields. Empty is CRITICAL, weight 15 each. Blood type and the issued-at/on fields are not required.

NIK cross-check (values encoded in the NIK):

Check Severity Weight
NIK is exactly 16 digits CRITICAL 30
Birth date in NIK == Date of Birth field CRITICAL 30
Sex in NIK (day > 40 means female) == Sex field CRITICAL 25

Location (NIK codes vs. official region data):

Check Severity Weight
Province code (2 digits) known and matches Province CRITICAL 25
Regency code (4 digits) known and matches City/Regency CRITICAL 25

Enums (exact match):

Field Allowed Severity Weight
Sex LAKI-LAKI, PEREMPUAN CRITICAL 20
Religion ISLAM, KRISTEN, KRISTEN PROTESTAN, KATOLIK, HINDU, BUDHA, KONGHUCU CRITICAL 20
Citizenship WNI, WNA CRITICAL 20
Marital Status BELUM KAWIN, KAWIN, CERAI HIDUP, CERAI MATI WARNING 10
Blood Type A, B, AB, O, - WARNING 5

Text format:

  • Field values must be uppercase (WARNING, 10).
  • Name must not contain digits (CRITICAL, 20).
  • RT/RW matches NNN/NNN. Birth date must parse, not be in the future (CRITICAL, 25), and imply an age <= 120.
  • "Valid Until" must be SEUMUR HIDUP (CRITICAL, 25). e-KTPs since 2016 are valid for life; an explicit expiry date means an old or invalid card.

OCR confidence — confidence below min_confidence on NIK, Name, or Date of Birth adds WARNING (8).

Vision — missing photo is CRITICAL (20); missing signature is WARNING (10).

Pass criteria

is_passed is true only when all of:

  1. all required fields present,
  2. no failed CRITICAL checks,
  3. risk_score < risk_threshold (default 25).

Region data

wilayah_provider controls where region codes are resolved:

Mode Behaviour
auto (default) Local snapshot first, live API for codes the snapshot lacks.
api Always hit the live region API (emsifa).
local Snapshot only, no network.
FKTD(wilayah_provider="local")   # no outbound requests

Disclaimer

e-KTP only. FKTD targets the electronic KTP (KTP-el), the standard since 2011, valid for life. Rules like "Valid Until must be SEUMUR HIDUP" do not apply to pre-2011 cards that carry an expiry date; those will fail because they are no longer valid.

Structural, not official. NIK and region checks validate the structure and internal consistency of the data on the card. FKTD does not access Dukcapil, so it cannot confirm a NIK is registered or belongs to a real person. PASS does not prove the card is genuine; FAIL does not prove forgery (it can be a bad scan, an old card, or an OCR miss). risk_score is an indicator, not a decision.

For production KYC, combine FKTD with official verification (Dukcapil or a licensed NIK provider) and manual review where needed.

Privacy. A KTP holds personal data. Make sure your use complies with Indonesia's UU PDP (Law No. 27/2022): lawful basis or consent, limited retention, secured storage. FKTD never uploads card images; the only outbound traffic is the optional region API (province/regency codes), which wilayah_provider="local" disables.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fktd-0.1.1.tar.gz (32.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fktd-0.1.1-py3-none-any.whl (26.7 kB view details)

Uploaded Python 3

File details

Details for the file fktd-0.1.1.tar.gz.

File metadata

  • Download URL: fktd-0.1.1.tar.gz
  • Upload date:
  • Size: 32.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for fktd-0.1.1.tar.gz
Algorithm Hash digest
SHA256 66d695eca33db79af3dd01faf7983606765cc6815693658dce4e870b208c853a
MD5 50e5a2510ee641c2ded0800f388d427c
BLAKE2b-256 4899d590d3c1e06c4ecd1d65b9ca0ea33173f62b813f87dd51809bfbc61eafff

See more details on using hashes here.

File details

Details for the file fktd-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: fktd-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 26.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for fktd-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2141dac624214d6fba8d1166211b7fa0ed4ec66aa2d0d02810ef5f86ae71921a
MD5 9000043fb8119959555ab5e0b3830b14
BLAKE2b-256 a15e2f4d3031ff17de7017610883de7d20f96637781e9ae18e998e170b76b03f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page