FKTD — Fraud KYC KTP Detector: OCR + strict cross-validation for Indonesian ID cards (KTP).
Project description
FKTD — Fraud KYC KTP Detector
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
- Requirements
- Install
- Usage
- API
- Extracted fields
- Fraud rules
- Pass criteria
- Region data
- Disclaimer
- License
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:
- all required fields present,
- no failed
CRITICALchecks, 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
66d695eca33db79af3dd01faf7983606765cc6815693658dce4e870b208c853a
|
|
| MD5 |
50e5a2510ee641c2ded0800f388d427c
|
|
| BLAKE2b-256 |
4899d590d3c1e06c4ecd1d65b9ca0ea33173f62b813f87dd51809bfbc61eafff
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2141dac624214d6fba8d1166211b7fa0ed4ec66aa2d0d02810ef5f86ae71921a
|
|
| MD5 |
9000043fb8119959555ab5e0b3830b14
|
|
| BLAKE2b-256 |
a15e2f4d3031ff17de7017610883de7d20f96637781e9ae18e998e170b76b03f
|