I build data and AI-automation pipelines with a verification layer built in: confidence scores, checks against source, and a human-review queue for anything uncertain. Anyone can get a model to emit JSON; what a team pays for is output it can trust and sign off on.
Recent work: a WNBA player-prop model end to end (scraping, projections, live board) and a Supabase migration for a US sports-analytics site, now on a third consecutive contract. I also audit inherited pipelines. On that project I found a scoring column silently returning 0 for every row because one line selected a DataFrame column by position and landed on the wrong one after an upstream change; nothing raised an error and the dashboard displayed 0 as a valid score.
Korean is my first language, so Korean documents, forms and OCR output are things I audit natively rather than guess at.
I work fully async, in writing. Public code: github.com/jigonyoo
Skills
Experience Level
Language
Work Experience
Education
Qualifications
Industry Experience
My public work hub: 60 runnable demos across ETL, web scraping, document AI, RAG, analytics, machine learning, developer tooling and robot data.
Every demo ships the output its own code produced, so the numbers on each page are numbers you can reproduce rather than claims you have to take on trust. All 60 are public on GitHub, and 57 of them carry test suites.
How I work. Verification is part of the build rather than an afterthought: confidence scores, checks against source, and a human-review queue for anything uncertain. Anyone can get a model to emit JSON; what a team pays for is output it can trust and sign off on.
What ships with a project. Tests, and a plain-English handover document, so the client never has to touch the code to understand what it does or why it stopped.
How I work with clients. Fully async, in writing - no live calls. Fixed price by scope where possible, so the cost is known before the work starts.
Browse everything: github.com/jigonyoo
Production work for a US sports-analytics client, now on a third consecutive contract.
What it is. Python ETL and scraping pipelines feeding a live WNBA player-prop model, plus a full PostgreSQL/Supabase migration with row-level security and a scheduled job that refreshes the board every morning. The front end is a dark, sortable prop board with per-player sparklines, book line versus projection colouring and a click-through player detail panel, deployed as a standalone page that updates on every push.
The bug worth telling you about. I found a scoring column silently returning 0 for every single row. One line selected a DataFrame column by position, and after an upstream schema change it landed on the wrong one. Nothing errored. The dashboard had looked fine the whole time.
That is the reason every pipeline I build ships with a verification layer rather than just tests that pass. A pipeline that fails loudly is a good day; a pipeline that is quietly wrong costs a client months.
- Spacing around particles and bound nouns, where a wrong space changes the meaning of a field rather than just its formatting
- NFC/NFD normalisation of Hangul jamo, where two visually identical strings compare as unequal and quietly break joins, deduplication and lookups
- Mixed Korean-English strings that split incorrectly under naive tokenisers
- Korean/English bilingual QA, where the two versions of a record have to agree
Korean documents, forms and OCR output audited natively rather than guessed at - the failure modes an English-first pipeline cannot see.
Why this is a distinct skill. Korean is my first language, so I catch things a general-purpose extraction pipeline silently gets wrong:
How it fits the rest of my work. The same verification layer applies: field-level confidence scores, checks against the source document, and a human-review queue for anything uncertain. An OCR pipeline that cannot tell you which fields it is unsure about is not finished.
Document AI code with the trust layer: github.com/jigonyoo/invoice-to-structured
Raw form submissions become normalized, scored, CRM-ready leads - while uncertain or unreachable cases stop in a human-review queue instead of being auto-routed.
The problem. A high lead score is useless if nobody can reach the lead. Automated intake often stops after classification, so an urgent inquiry with a broken email is silently dropped or misfiled.
Committed run: 10 synthetic leads - 6 auto-routed, 4 held for review.
One warm lead scoring 53/100 with clearly urgent intent was held back because its email was malformed and no phone number was given. Overall confidence 0.568, below the auto-route trust threshold. The promising lead is surfaced for contact repair rather than discarded.
Contact validity, ambiguity and review routing are made explicit, so the queue is a feature rather than an exception path.
An offline, dated static scan for the model IDs and API surfaces scattered across source, configuration, environment defaults and documentation - with days remaining and blast radius attached.
The problem. The vendor publishes a date. The codebase forgets where the dependency lives. A production app can wake up broken on shutdown day because model references hide in settings, test fixtures, old modules and runtime routers.
This builds the inventory. It does not perform the migration, and it does not pretend to.
Evidence stays in its lane: 6 confirmed references, 2 likely, 2 unverified hypotheses, 1 needing runtime data. Static analysis cannot resolve dynamically constructed model names, so the report says so rather than guessing.
Every registry entry carries a vendor source URL and a snapshot date, so each deadline is auditable rather than asserted. Standard library only, no network calls at scan time.
A churn classifier evaluated the way a client should ask for it: split before scaling, a baseline to beat, cross-validation, and explicit limits.
The problem. One lucky accuracy score is not evidence. Churn demos often leak test information into training, ignore the majority baseline, and omit uncertainty entirely.
Committed result: held-out ROC-AUC 0.7982 against a 0.5000 majority baseline; 5-fold cross-validated ROC-AUC 0.819 +/- 0.0298.
1,500 fictional customers are split first into 1,125 training and 375 held-out test rows, and the scaler is fit on training rows only - so the test set never informs the transform.
Against the majority baseline: accuracy 0.7067 vs 0.5893, precision 0.6571 vs 0.0, recall 0.5974 vs 0.0, F1 0.6259 vs 0.0. The model card states what the model does not know as clearly as what it does.
A deliberately messy fictional sales CSV becomes a repeatable KPI report, with every cleaning action and anomaly surfaced for review.
The problem. A polished chart can still be confidently wrong. Duplicates, invalid quantities, missing values and fat-fingered revenue silently distort dashboards. The useful deliverable is not the number - it is the number together with the reasons a human should question it.
Committed output: $120,691.08 total revenue across 397 clean orders, $304.01 average order value, 113 unique customers - plus the list of rows that were dropped or flagged and why.
Raw input included exact duplicates, missing regions, non-positive quantities, mixed date formats and missing revenue. None of it was quietly averaged away.
A reproducible fictional retail pipeline that ranks cross-sell rules by lift and turns Recency, Frequency and Monetary behaviour into explainable customer segments.
The problem. Popular items can make weak pairings look strong. Confidence alone rewards common products; lift asks whether two items co-occur more often than chance would predict. RFM separates valuable but quiet customers from generic top spenders.
Input: 3,000 transactions, 300 customers, 16 items - with patterns deliberately planted (milk + sugar to coffee, butter + eggs to bread) that the pipeline has to rediscover on its own rather than being told about.
All 300 customers are assigned to exactly one segment - Loyal, Champions, Lost, Needs attention, At-risk, or New/Promising - so the output is actionable rather than decorative. Rules are ranked by excess co-occurrence over chance, not by raw support.
A structured audit of ROS2 bag exports that measures topic health, detects planted anomalies independently, and separates evidence from candidate causes.
The problem. A bag can record cleanly and still contain operationally bad data. Rate drops, dropout gaps, clock skew, sensor-range violations, command/odometry disagreement and missing transforms all need one evidence-graded report before anyone starts diagnosing the robot.
Result on the committed run: 7 topics audited, 6 of 6 deliberately planted anomalies independently detected, 10 total findings. The scanner never reads the list of planted faults - it finds them or it does not.
Findings are graded, not asserted: 5 confirmed evidence, 1 likely cause, 1 unverified hypothesis, 3 needing additional data. A monitoring tool that states a cause it cannot prove is worse than one that says it does not know yet.
Code and full report: github.com/jigonyoo/ros2-bag-data-audit
A product-independent migration verification demo. It compares source and target CSV/JSON exports in a static browser page or standard-library Python CLI. It reports row and key differences, value mismatches, null ratios, type/format changes, attachment/reference integrity, fixed-length truncation, and consistent timezone shifts.
All fixtures are synthetic. They do not represent any client, CRM vendor, or production migration.
The measured run.
Both source and broken-target exports contain 300 rows. The broken target contains one explicit synthetic replacement key to preserve fixed row count while planting three missing source keys and two duplicate target rows.
| Control | Planted | Caught | Missed | False alarms |
|---|---|---|---|---|
| Clean migration | 0 | 0 | 0 | 0 |
| Broken migration | 25 | 25 | 0 | 0 |
The 25 findings are: three source-only keys, one target-only replacement key, two duplicate target rows, twelve absent attachment/reference values, five 50-character truncations, one mixed date-format signal, one consistent +9-hour timezone signal.
Honest limits.
The checker produces explicit evidence for a person to review. It does not decide whether a migration should be accepted. There is no server endpoint, upload, storage, cookie, analytics script, or external dependency. Files remain in browser memory. No pip install needed.
Data contracts define expectations for CSV and JSON formats. When data drifts, contracts catch it before bad data corrupts downstream systems. This static browser application checks CSV or JSON data against an editable contract and compares basic drift metrics. Files are parsed in browser memory and are not uploaded, stored, or transmitted.
There are no runtime dependencies, external CDNs, cookies, analytics scripts, localStorage, or server endpoints. The browser uses File.text() for user-selected files. The only fetch() calls load the two same-origin bundled fixtures. Type inference is deliberately conservative and remains editable.
The measured run.
| Fixture | Planted | Caught | Missed | False alarms |
|---|---|---|---|---|
| clean.csv | 0 | 0 | 0 | 0 |
| broken.csv | 24 | 24 | 0 | 0 |
The broken control contains four instances of each fault: Fahrenheit-like values in a Celsius field, missing required IDs, out-of-enum status values, duplicate IDs, mixed date formats, and comma-formatted numeric strings.
Honest limits.
Maximum input size is 5 MB. Drift uses the bundled synthetic clean fixture as a visible demonstration baseline; a production baseline would need to be supplied and reviewed by its owner. Results are evidence for a person to interpret, not an automated fitness decision.
Diagnosing failures in code and pipelines requires reproducing the fault, understanding its cause, and showing the fix. This portfolio contains two synthetic before/after cases where faults were intentionally introduced in local code and fixtures. No client system, production data, or commercial website was used.
Case one, silent_pipeline, shows a pipeline completing without an exception but mapping reordered columns by position and misreading changed encoding. Case two, slow_page, contains a local static page with a large hero asset, a blocking script, four font stylesheets, and an injected layout shift; an optimized variant removes the measured bottlenecks.
Each case includes scripts to generate fixtures, run tests, and reproduce the faults and repairs. The PDFs are two-page summaries generated from the same measured evidence. Numbers in the summaries are measurements of these files, not of anyone’s production site. The fixes hold for the fault that was planted here and for the evidence shown alongside it, not as general remedies.
Hire a Data Engineer
We have the best data engineer experts on Twine. Hire a data engineer in Seoul today.