Rahzaan

It tells a student
when it does not know.

rah (path) + zaan (one who knows)

An academic career-guidance system for Karachi secondary students. It reasons about which degrees a student can genuinely enter, how competitive they would be, and where the honest answer is that the data does not exist.

Merit standing, sample outputCatalogue as of July 2026
DegreeEligibleClosing meritStanding
Programme AYes76.4%Strong fit
Programme BYes81.2%Reach
Programme CNo68.0%Subject not held
Programme DYesnot publishedconfirm with the university

Row four is the reason this project exists. Many universities never publish a closing merit. The system leaves that cell empty and says so, rather than filling it with a plausible number a student would act on.

Highlights

The four things worth knowing first

20-25s to 3.7s
First-response latency, cut by re-architecting a sequential pipeline into parallel fan-out and fan-in stages.
26 steps that cannot deadlock
Every step runs on every turn with no conditional edges, so the graph cannot stall on an unmet dependency.
Honesty enforced in code
Unsupported claims are removed after the model writes, rather than forbidden in the prompt, which was tried and failed.
Deployed and validated live
60 conversation turns against production with every step traced, and 592 automated tests with none failing.
The problem

Two requirements that pull against each other

A student in Karachi choosing a degree is making one of the least reversible decisions of their life, usually with no access to professional counselling. The hard part is not recommendation. It is reasoning truthfully about eligibility across multiple examination boards, five academic streams, equivalence rules for international qualifications, professional gating from three separate regulatory councils, and merit mechanics that differ per university and are often not published at all.

Fair to every stream, or limited by the data

The admission data is richest for exactly the students who already have the most options. Building only where the data is good means building only for them.

Helpful, or honest

Showing a student more possibilities is helpful right up to the moment one of them is a degree a regulator will never let them enter.

Every significant decision in this system resolves one of those two tensions.

The approach

Decisions in code. Language only to explain them.

Eligibility, competitiveness, and ranking are computed in plain deterministic code with no model in the loop. The language model receives what was already decided and explains it to the student. A wrong recommendation is therefore a bug with a line number, not a prompt that behaved differently on a Tuesday.

It also bounds the blast radius of the least trustworthy inputs. The most-validated signals drive the ranking. The softer, less-validated ones shape only how the result is worded, and cannot override it. The worst case for the weakest input is awkward phrasing, not a wrong degree.

Student marks, subjects, DETERMINISTIC ENGINE Rules applied to what the student holds Independent steps, parallel Joined into one ranked set Unknown stays unknown. Nothing is invented. EXPLANATION LAYER Receives the decision. Explains it to the student. Never decides anything. explanation returns to the student, streamed as it is produced The model sits downstream of every decision. There is no path by which it can change a ranking.
Where the model is used

The conversational layer explains what the engine decided, answers follow-up questions about cost and prospects, and adapts to how each student writes, including Roman Urdu and mixed script. It never decides eligibility, competitiveness, or rank.

Design decisions

Five corrections, and what each one replaced

None of these were designed on a whiteboard. Each replaced something already running and already wrong.

01

Gate cross-stream eligibility on the subject actually held

Eligibility follows the foundational subject a student studied, read against the governing regulations, rather than being inferred from how capable they appear.

It replacedAn approach that inferred eligibility from assessed capability, and could present a professionally gated medical degree as reachable to a student no regulator would admit.
02

Treat an unpublished cutoff as a state, not a missing number

Degrees with no published closing merit carry an explicit state saying so, and point the student at the admissions office. Cutoffs are derived from official merit lists or left empty.

It replacedA silent default into a middle competitiveness band, which meant the system was manufacturing an assessment and presenting it as a judgement.
03

Assess students on the curriculum they actually studied

Assessment is driven by the intersection of what a student studied and what their reachable degrees actually test.

It replacedA science-keyed battery that measured commerce and humanities students on material they had never been taught, then proxied their merit from it.
04

Never apply a rule retroactively

Pass thresholds change over time. A student who passed under an older rule is never re-judged against a newer one.

It replacedNothing visible. It would have quietly harmed a small number of real students, and nobody would have noticed.
05

Show reach options deliberately

The ranked set always includes a guaranteed minimum of aspirational targets that sit above the student's current standing but are genuinely eligible.

It replacedA list of safe options only, which is its own kind of dishonesty.
Systems

A static graph, not a chain of calls

The reasoning engine is a directed graph of independent steps rather than a sequence of prompts. Steps that do not depend on each other execute concurrently, so the work that can overlap does.

26
reasoning steps: 24 inside the graph, 2 running after the response has streamed
52
fields in the typed state object threaded through every step
22
API endpoints over 6 database tables
29
routes in the app, across a single Android and web codebase
7
psychometric instruments, 265 items presented per session
1,409
items in the assessment banks the session draws from
14,595
job titles mapped to canonical fields by the labour-market pipeline
592
automated tests, zero failing
SEQUENTIAL PIPELINE 20 to 25 seconds before a student saw anything at all PARALLEL FAN-OUT AND FAN-IN 3.7s first response, with the rest streaming in behind it Independent work was separated so it could run concurrently, and several steps were split into finer sub-steps so less sat on the critical path. Bars are to scale.

It cannot deadlock, by construction

Every step runs on every turn and there are no conditional edges. An activation pass makes irrelevant steps no-op instead of removing them from the graph, so every join is always satisfied. A stalled turn caused by an unmet dependency is not a bug that was fixed. It is a state the graph cannot enter.

One typed state object, threaded end to end

Every step reads and writes a single typed structure rather than passing loose dictionaries. No step can invent a field another step expects, and a shape change surfaces at the boundary instead of three steps downstream as a silent default.

Streaming that survives the infrastructure

Responses stream incrementally so the student sees reasoning as it arrives. Under live conditions on the deployed service, a turn held 106.3 seconds past a 30-second proxy timeout without dropping the connection, verified against the production deployment rather than a local run.

Adding a university requires no code

Eligibility logic is written against a schema, not against institutions. A new university inherits every gate, filter, and merit rule the moment its data conforms. The tradeoff is recorded honestly: the logic is schema-dependent, so a missing field silently widens eligibility rather than failing loudly, which is a known hazard rather than a solved one.

Changes must leave existing output byte-identical

Any change that is not meant to alter what students see is verified to produce identical output for existing profiles. Improvements are additive by default, and a restructure has to be argued for rather than assumed.

Parallel fan-out and fan-in

The pipeline was originally sequential and took 20 to 25 seconds before a student saw anything. Independent work was separated to run concurrently and several steps were split into finer sub-steps, so less of the work sat on the critical path. First response now arrives in 3.7 seconds, with the rest streaming in behind it.

Bilingual by design, not by translation

All seven instruments and the full 265-item session exist in both English and Roman Urdu, the register students actually write in. The conversation follows whichever the student uses, including mixed script mid-sentence. This was built in from the start rather than added as a translation layer, because a psychometric item that reads awkwardly measures something other than what it was validated to measure.

The project report is compiled, not written

The full report is generated by a program: seventeen chapter modules, a diagram generator, a shared formatting layer, and an audit pass, compiled into a single document. Figures and the text referencing them cannot drift apart, and the whole thing is reproducible from source rather than being a file three people take turns editing.

A five-stage labour-market pipeline

Collection, daily refresh, classification, aggregation, and orchestration, each a separate stage with atomic writes and resumable progress. The source would not expose one attribute directly, so the collector recovers it through multiple filtered passes cross-referenced by record id. 14,595 titles were mapped to canonical fields and aggregated across 51 of them, with the records it skipped and the fields that received nothing both recorded rather than quietly dropped.

The model is a swappable component

Prompts are written so that anything working on the weakest supported model works unchanged on a stronger one. Changing which model runs a given step is a configuration edit, not a rewrite, which is what made it possible to develop against one provider and deploy on another.

Engineering

A check that cannot fail is worse than no check

A guard that never fires is indistinguishable from a guard that does not exist, and worse, because you now trust something that is not working. This principle shaped more of the system than any feature did.

Guards are tested against the case built to defeat them

One gate strips claims the system has no measurement to support. To prove the gate was doing the work rather than a neighbouring check, it was fed a plausible, carefully hedged sentence that passed every other quality criterion. Stripped when unsupported, preserved when supported. A controlled experiment, not an assumption.

Documentation is generated from source and refuses to guess

The internal architecture reference is built by reading the running system. Where the source records nothing it prints a refusal rather than a plausible sentence, and it displays its own age, because an earlier version once looked confidently current while sitting 151 commits behind.

Confirm the model can make a distinction before designing on it

A planned feature depended on output varying with how firmly an instruction was phrased. A controlled probe held everything else constant and tested a light and a firm version of the same instruction. The firm one produced less of what was wanted, inverting the intended gradient, so the feature was dropped before it was built. A gradient the model cannot produce is theatre.

Separate by decomposition, not by instruction

A single call cannot be told to use some context for one purpose but not another. So the call that decides never receives the material at all, and a second call applies it to the already-decided output. That turns a property you have to test for into one the structure guarantees, because the deciding call's input is identical to a known-good baseline.

Every non-trivial algorithm has a written specification

Not a comment. Purpose, fully typed inputs and outputs, formal step-numbered pseudocode rather than a paste of the code, complexity, all three classes of invariant, enumerated edge cases, and the alternatives rejected with the reason. Written only after the component is complete, because a document written mid-implementation records the design you started with and then misleads with authority.

Passing tests are treated as claims, not proof

An audit of the boundary between app and service found five real defects that 209 passing tests had missed, because every fixture seeded the canonical values the code expected and never the payload the live app actually sends.

WHAT DID NOT WORK Telling the model, inside the prompt, "never state what you did not measure" failed three times, live WHAT WORKS Model writes freely with no such rule Code checks each claim against what was measured Measured: kept Not measured: removed What the student actually reads Enforcement moved out of the prompt and into code that runs after the model has written.

Corrections are additive. Stale text is never deleted or quietly reworded, so the record contains cases where a correction was itself wrong and had to be corrected back. Evidence behind the assessment design is graded on a seven-point scale by how close each source sits to Karachi students of this age, and every step away from that population is stated rather than absorbed.

Engineering log

What broke, what it would have done to a student, and what fixed it

Severity in this project is measured in wrong guidance to a student, not in code quality. This is a selected record, newest first. Dates are real; nothing here is reconstructed.

Open

Known, recorded, not yet fixed
open

The system does not know whether its advice is good

There is no holistic review of counselling quality and no outcome data. Building it needs labelled outcomes that do not exist yet.

open

Two thirds of completed work is believed rather than verified

Finished items are tagged either live-exercised or reasoned-from-code. Of 55 completed items, 18 have been exercised against a live run. The remaining 37 are believed. The tagging exists specifically so that suspicion stays pointed at the right set.

open

The conversation can borrow a student's biography

When a student volunteers a rich personal history, the reply can retell it as though it were the system's own experience. Four instances, all inside the one case that volunteered detail, none in the other four. The existing guard misses it because it checks claims about the student, and this is a false claim about the speaker.

open

A save can fail while the screen says it succeeded

Result cards are sent to the screen before the record is written. If the write fails the student sees their results, the error is swallowed, and the dashboard is empty on next launch.

open

Labour-market signal is collected but not connected

The job-market pipeline runs and produces a per-field demand history. Nothing in the ranking reads it yet.

Resolved

Newest first
2026-07-25

A fix introduced a defect, and the next gate caught it

A change made three days earlier produced wrong guidance for one group of students.

Fixed in the same pass that found it. The neighbouring finding was pre-existing rather than newly introduced, and was deliberately recorded and left rather than bundled in.
2026-07-24

A third of commerce students would have seen the same question twice

The way one question bank was assembled meant roughly 32% of commerce students would have been asked a duplicate.

Symptom fixed, root cause recorded as open. Deduplication makes the student-facing behaviour safe. The log states plainly that the underlying bank is still wrong and needs a decision, so the fix cannot be mistaken for a resolution.
2026-07-22

Stored admission data disagreed with the prospectus

Re-verifying one university against its published documents found a legal minimum stored as 50% where the source says 60%, an incomplete list of mandatory subjects, and fees that had been entered as estimates.

Corrected against the primary source. What could not be verified was recorded as a finding, including three documents with no extractable text.
2026-07-17

A previous fix was proven harmful and reverted

A change made the day before, intended to keep resumed answers in sync, was traced through its consumers and shown to cause silent scoring corruption on submit.

Reverted with evidence. The useful half was kept and an honest non-blocking warning shipped instead of the broken guarantee.
2026-07-16

A cited study did not exist

A scripted answer leaned on a large 2024 study that could not be found. It traced back to a single non-peer-reviewed analysis from 2023.

Corrected without deleting the error. The wrong script remains in the file, struck through and marked unusable, with a peer-reviewed replacement beneath it. Three further citation defects were found in the same pass, including a source cited for adolescents whose sample was adults.
2026-06-30

An investigation reversed its own earlier finding

An earlier pass concluded the reasoning over-asserted when data was thin. A later pass found the opposite: the thinnest case is the most honest, because the component declines outright, and over-assertion is triggered by conflicting data instead.

Filed alongside the original, not over it. Both findings remain in the record with the correction dated.
2026-06-28

The system's own record of what ran was false

A live probe found a class of gates that executed on every turn regardless of their condition, then wrote and persisted false ran-or-skipped entries. Reading the code would not have found it. Only running it and comparing the record against reality did.

Gates corrected and the telemetry made truthful. The same pass found a measured signal that never reached the explanation layer, so under conflicting data the narrative could assert an identity the measurement contradicted.
2026-06-16

209 passing tests missed five real defects

An audit of the seam between app and service found a naming mismatch that both deflated merit and triggered a wrong regulatory exclusion, and a scale mismatch that had left counselling branches dead for every live student.

Fixed, and the reason recorded. Every fixture seeded the canonical values the code expected and never the payload the live app actually sends. Tests written against the author's own assumptions could not fail, so they proved nothing about the seam.
2026-05-15

The job-market collector was silently discarding 42% of results

It assumed 25 results per page. The source returns nine or ten, so it skipped every position past the first ten of each page. No error, no warning, and a perfectly plausible dataset.

Corrected the page stride, which raised a full pull from 631 records to 1097, and applied the same fix to every pass in every script.
2026-04-28

An automated pass had invented merit figures

A prior data session had written merit history values that did not match any published source.

Replaced with authoritative figures and the correction recorded against the session that introduced it.
2026-04-24

Every degree was scoring identically

A pre-demo gate check found a knowledge file empty, which collapsed the match component of every degree's score to the same constant.

Recorded as a blocking condition rather than a pass. The verdict was amber with a named prerequisite, not green.
The app

What a student actually sees

Home
Entry point, and where a returning student picks up.
Build your profile
Stream, board, and personal constraints such as budget and travel.
Grades
Marks entered per subject, with per-subject ceilings applied on entry.
Interest inventory
Sixty items establishing the interest profile that anchors matching.
Capability assessment
One of 265 items per session across seven instruments, English or Roman Urdu.
Preferences
Constraints that filter rather than rank: cost, location, institution type.
Ranked roadmap
The ranked set. Capture one showing a degree with no published merit if you can.
Counsellor conversation
The explanation layer, answering follow-ups in the student's own language.
My profile
The stored profile a student returns to, and can revise.
Evidence

Stated at the strength it can be defended

60
live conversation turns against the deployed system, every reasoning step traced and every response stream captured as an artifact
24 / 24
reasoning steps visible in traces of the deployed run, confirming every step of the graph executed in production rather than only in test
592
automated tests passing, zero failing. Every correctness fix ships with a regression test that pins it
8 / 20
universities fully curated of a target twenty, spanning 252 degree programmes, public and private, general and specialist
5
representative personas modelling the Karachi student distribution, evaluated across 50 turns in realistic sessions. Not recruited humans. A study with real students is in progress and will supersede them
27
peer-reviewed sources behind the assessment design, including cross-cultural validation work and Pakistan-specific research on family influence in career choice

The evaluation is five rather than more for a reason worth stating: at a measured 10.6 model calls per turn against a daily quota, roughly 42 turns per day were possible. Depth was chosen over breadth deliberately.

Limitations

What the evidence does not cover

The interest model has been tested on Pakistani students twice, and both studies found it does not hold

Two independent structural studies, roughly a thousand students between them, found the circular ordering of the underlying interest model does not fit this population. The instrument's psychometrics were sound and interest-to-aspiration correspondence held for several careers, but the geometry did not. The weighting was deliberately not changed, because the limitation is an absence of local structural validation rather than a weak signal, and down-weighting the best-validated input in favour of less-validated ones would make the system less defensible. It is disclosed instead, and the robustness comes from architecture: no recommendation rests on that geometry alone.

The instruments are construct-grounded, not instrument-validated

Original items built on established frameworks. First-party reliability on a Karachi sample is not established and will be computed as data accrues. Support is not uniform across them, and the weakest is named as such rather than averaged into the rest.

Prestige and regional demand are encoded from market knowledge, not surveyed

The system models how families in Karachi actually weigh degree status, because a recommendation that ignores it will be rejected regardless of fit. Those values were assigned from general market knowledge and have not been validated by a survey.

The repository

Why there is no code here

The source, the curated admission data, and the derived eligibility rules are private. The curation is the work: months of reconciling regulations, prospectuses, and published merit lists into something a system can reason over correctly.

Everything on this page is reasoning rather than mechanism. I am glad to walk through the architecture in a conversation.

Or write to ma.sharif.waqas@gmail.com.