Case Study: NLP at the Apple Developer Institute

CPNLES is an offline iPad app I built for my fiancée, to help her study for Indonesia's CPNS exam, now with an AI that checks its own math in code, not the model.

Verifying math in code, not the model, took valid questions from 60% to 95%.
The CPNLES iPad app showing a CPNS practice question beside a handwritten note
About the Project

CPNLES is the third and most personal thing I built at the Apple Developer Institute for AIML, the NLP Natural Language Processing, the branch of AI concerned with understanding and generating human languagecounterpart to Pezen's audio challenge and MTS's visual one. It started before the challenge existed. My fiancée has been taking classes to prepare for CPNS, Indonesia's civil servant, or Aparatur Sipil Negara, entrance examthe national civil service exam, and she does most of her studying on an iPad, keeping a handwritten note next to almost every practice question. I noticed she kept losing track of which note belonged to which question, so I built her a small offline quiz app with no AI at all, just a clean way to take a question and keep her note attached to it. Once I had collected enough real questions for her, I ran into a slower problem. Sourcing new questions and writing their explanations by hand takes real time, and the timeline happened to line up with the Institute's challenge, so building an AI to generate both was the obvious next step. I named it CPNLES, a mashup of CPNS and les, the Indonesian word for a tutoring class.

NLPApplied MLiOSFull-Stack
The original offline quiz app, no AI, just notes kept with their questions
Her iPad, Her Notes

Before any AI existed, CPNLES was already solving her actual problem. It is a SwiftUI Apple's declarative framework for building user interfaces across its platformsiPad app, offline-first, so she can practice anywhere without signal. She works through TWK ( Tes Wawasan Kebangsaan National Insight Test, civic and national-values knowledge, scored 5 points correct and 0 wrong), TIU ( Tes Intelegensia Umum General Intelligence Test, covering verbal, numerical, and logical reasoning, scored the same way), and TKP ( Tes Karakteristik Pribadi, Personal Characteristics Test, a behavioral assessment where every option scores between 1 and 5 depending on how well it fitsweighted rather than right-or-wrong), and can write on any question with Apple Pencil through PencilKit, Apple's framework for pressure-sensitive drawing and handwriting inputthe same notes she used to keep loose, now attached to the exact question they belong to. Those notes and her practice sessions live only on her device. They are the point of the app for her, not administrative data, so nothing about them is ever uploaded.

A TWK, TIU, or TKP question open, with a PencilKit note beside it
The Challenge

Two different problems needed two different kinds of AI, and neither could be left to improvise. For question generation, hand-authoring more TWK and TIU questions and their explanations was the bottleneck, but a language model let loose on multiple-choice authorship will happily invent a plausible-sounding but wrong answer, especially anywhere arithmetic is involved. For the math quick tips, A short method or shortcut for solving a specific type of question faster than working it through in fullI already had the harder part done by hand, real questions with real tips worked out for them, but writing a fresh tip for every new question with a similar pattern was still manual work I wanted to hand off. Both problems shared the same trap. Trusting a language model with anything that has one objectively correct answer is asking for trouble.

A math question the model generated with a hallucinated answer
The Deterministic Layer

The fix, and the part of this I am proudest of, is refusing to let the model do arithmetic at all. Early on, generating a single valid math question meant the model inventing plausible-looking numbers and then confidently getting the actual answer wrong, and I could not trust a question I could not verify. So I split the job. The language model still writes the question's wording and its five options, but every arithmetic claim gets checked afterward using Python's fractions.Fraction, A Python type that does exact rational arithmetic instead of floating-point approximation, so a checked answer is never off by a rounding errornever a floating-point approximation that could quietly be wrong. If the check fails, the mismatch goes back to the model for a self-correction pass, then to Python's own inference as a last resort. That loop, generate, verify, correct, took the rate of valid questions from around 60 percent to about 95 percent. It is the same lesson I keep relearning on every AI project I build. Let the model handle language, and let code handle anything that has to be exactly right.

The generated answer, before verificationThe same question, after exact verification
Quick Tips, Not From Scratch

The quick-tip generator works the same way in spirit, but the raw material is different. Instead of trusting the model to invent a tip from nothing, I pre-computed embeddings Numeric vector representations of text, positioned so similar meanings sit close togetherfor the roughly 400 questions I already had tips for, using a small multilingual sentence-embedding model, and stored them as a single NumPy array of about 600 kilobytes. There is no vector database behind it. A cosine similarity search across the whole array finishes in under a millisecond, plenty fast enough that a dedicated database would have been pure overhead. When a new question needs a tip, the system finds the handful of existing questions most like it, hands those to the language model as few-shot Giving a model a small number of worked examples in the prompt itself, instead of fine-tuning it, so it can imitate the patternexamples, and lets it write a tip in that same style. The explanation generator for TWK and TIU questions works the same way, retrieve a few similar questions, hand them to the model as examples, and let it explain the new one in a consistent voice.

Embedding search surfacing similar questions to write a new tip from
Under the Hood

Everything sits on a small monorepo with four layers that each do one job. A Rust A systems programming language focused on performance and reliabilityserver built on Axum A web framework for Rustowns the REST API and MongoDB, and it is also the thing that keeps the AI half alive, launching and supervising both Ollama The local model runtime the system uses to run its language modeland a Python ML service over a Unix domain socket A same-machine communication channel, faster and simpler than an HTTP port for two processes that always run togetherrather than a network port. A 15-second watchdog loop restarts either process if it dies, so the entire stack, database, model runtime, and inference service, comes up from a single cargo run. A Next.js admin dashboard is where I manage the question bank and approve what the AI generates before it goes live, and the SwiftUI iPad app is the actual practice client, downloading published quizzes with delta sync so it only pulls what changed. It is not a large system, but getting four pieces in three languages to start up, stay up, and talk to each other reliably was its own kind of engineering, separate from anything the AI itself does.

The four-layer architecture: Rust server, Python ML service, admin dashboard, and iPad app
Outcome

CPNLES is still a working prototype, not a finished product, but it already does its actual job. My fiancée uses it for real practice, on her own iPad, today. What is stopping me from shipping it publicly is the architecture, not the idea. Right now the AI pipeline lives on a server I run myself, and I do not want to be the one hosting inference for anyone who downloads the app. I am waiting on iOS 27, expected to bring meaningfully better on-device machine learning, to rebuild the whole AI pipeline to run on the iPad itself, the same on-device instinct behind Pezen's waiter. Once that lands, there is nothing left stopping a real App Store release. Until then, it keeps doing exactly what it was built for. One real person, actually studying, actually helped.