Ten projects, in three groups. Each group makes a claim you can check against every project underneath it.
Models that leave the notebook
Trained, then put somewhere a person can open.
Wadjet
An Egyptian heritage platform where the image models run inside your browser
- reported hieroglyph classification accuracy
- 98.2%
- Gardiner sign classes
- 171
- reported landmark classification accuracy
- 93.8%
- Egyptian sites in the landmark classifier
- 52
Point a phone at a hieroglyphic inscription and Wadjet reads the signs back to you. The reading is the obvious part. The interesting part is that the image models run in the browser, not on the server.
I trained three classifiers, exported them to ONNX, and ran them client-side through ONNX Runtime Web. So the photo never leaves the device, which is a real property for somebody standing in a museum rather than a line of marketing. Inference costs the server nothing, so the app survives on a free tier instead of falling over. And it keeps working on a bad connection, because the model is already cached.
The hieroglyph classifier reports 98.2% across 171 Gardiner sign classes. The landmark classifier reports 93.8% across 52 Egyptian sites. Both figures come from the repo's own README. The test set composition is not published there, so read them as reported model accuracy, not as a benchmark result.
Before this, there was Horus AI
The first version got to ~80%, up from 50%. What moved it was not a bigger model.
The Sphinx was being classified under the pyramid complex, because the two classes were not meaningfully distinct in the data. Merging confusing classes, balancing the rest, and checking labels by hand did the work. The taxonomy was wrong before the model was.
Wadjet is what I built once I knew that.
Open the live version
What it does not do
- These are the accuracies the repo reports. The test set composition is not published, so they are reported model accuracy and not a benchmark result.
- The live version runs on a free tier that sleeps, so a first load can be slow.
Built with Python, TensorFlow and Keras, CNNs, Transfer learning, ONNX Runtime Web, FastAPI, SQLAlchemy, JWT auth, Docker, Hugging Face Spaces, Gemini API
Figures: the project's own README.
Incident Forecasting Pipeline
Predicting where incidents happen next
An operations team needed to know how many incidents each of its locations would see in the coming year, so it could move from reacting towards preventing. I built the pipeline that produces those forecasts.
The best thing in it is what I deleted. Several features taken from the same year as the target were almost perfectly correlated with it. Left in, the model would have scored beautifully and forecast nothing. Catching your own leakage is worth more than any accuracy figure, because the alternative is a number that flatters everyone until it is used.
The rest is method. Many candidate features, narrowed to the ones that held up, ranked by importance averaged across two ensembles rather than trusted from one. A log-transformed target, because incident counts are skewed. A strictly temporal split, with no random cross-validation. That scores worse, and it is the only honest choice for time-ordered data. Feature engineering mattered more than model complexity.
What it does not do
- This was internship work, so it is described by its method only: no organisation, no data, no figures.
Built with Python, pandas, NumPy, scikit-learn, Gradient boosting, Feature engineering, Temporal validation, Leakage detection
Cerebral Stroke Prediction
Logistic regression with the gradient descent written by hand
There is a custom logistic regression class in this project instead of an import, with the gradient descent written out as a loop. A scikit-learn KNN model sits next to it as a baseline, so the hand-written one has something to be measured against.
Class imbalance is handled by random over- and under-sampling. Both models are scored on accuracy, precision, recall, and F1, rather than on accuracy alone, which on an imbalanced medical dataset would be close to meaningless. The final classifier is wrapped in a small desktop window so somebody who does not write code can enter the features and read a risk score.
The score matters less than the fact that I typed the mathematics out once, by hand, before I ever imported it.
What it does not do
- It is a demonstration of the mathematics on a public dataset. It is not a medical tool and nothing here should be read as one.
Built with Python, NumPy, scikit-learn
Systems that stop
Every one of these knows a condition under which it refuses to continue.
Grounded RAG PDF Q&A
A retrieval system built to say "I don't know" and proven to do it
- golden eval, twelve grounded questions and five adversarial ones
- 17/17
- relevance threshold below which it refuses without calling the model
- 0.55
- offline deterministic tests, needing no API key
- 15
Upload PDFs, ask questions, get answers that cite the document and the page they came from. Ask something the documents do not answer and it returns one exact sentence:
I couldn't find this in the documents.
That refusal is the product. Anyone can wire an embedding model to a language model. Making the thing shut up when it should is the engineering.
Three gates do it. A best-chunk relevance score below 0.55 returns the refusal without calling the model at all, which is deterministic, free, and unit-tested with a stub that fails the test if the model is ever invoked. A grounded prompt tells the model that a source mentioning a topic is not a source answering the question. And a normaliser rewrites any refusal-shaped reply to the exact contract string, because models drift.
Citations cannot be faked either. Retrieved chunks arrive numbered with their document and page, and the markers are mapped back afterwards. Out-of-range markers get discarded, so the model cannot invent a source it was never shown.
The golden set passes 17/17, and 15 more tests run offline with no API key. The case worth quoting asks about stock options. The documents do discuss 401(k) vesting, so related content comes back. It still refuses.
What it does not do
- Text PDFs only. There is no OCR, so scanned documents are out of scope.
- Roughly ten requests a minute on the free tier.
- The hosted vector store is shared and ephemeral.
- Auth, queues, reranking, streaming, and conversational memory were deliberately not built. It is a portfolio demo engineered properly, not a production system.
Built with Python, Retrieval-augmented generation, Chroma, Gemini API, Streamlit, pytest, GitHub Actions
Figures: the project's own README.
workflow-doc-agent
An agent that reads a folder of production work and writes its documentation
- tests, none of which touch the network
- 9
- months of synthetic revenue data in the bundled sample
- 24
Teams keep production work that nobody has written down: a script that builds a report, and the workbook it fills. This agent documents it. It summarises what the code is for, asks a few questions about how often the work runs and who owns it, and then writes the documentation.
It reads the folder without executing anything: AST for the Python, sampled sheets and headers for the workbook, so no whole file is ever dumped into a prompt. A cheap model writes the summary and asks the clarifying questions. A stronger model writes the final runbook. Each stage is validated against a schema before the next one starts.
Two decisions matter more than the rest. Both model providers sit behind one interface with identical schemas and an identical loop, so switching is one environment variable. And a cost tracker stops the run at a hard budget ceiling instead of spending past it. Anyone who has handed an autonomous loop an API key knows why the second one is there.
It ships with 9 tests, a capability eval, a regression eval, and a sample holding 24 months of synthetic data.
What it does not do
- It reads a folder holding a Python file and a workbook. It is not a general-purpose codebase reader.
Built with Python, Gemini API, Claude API, Pydantic, Typer CLIs, pytest
Figures: the project's own README.
Multi-agent reporting pipeline
A pipeline of agents that checks its own work and knows when to stop
The pipeline takes a tabular dataset and a written brief, and produces a finished, standards-compliant Excel deliverable along with its own handoff documentation. It runs on demand, or unattended overnight, with a filesystem watcher draining a queue one job at a time.
Most multi-agent projects are three prompts in a loop. This one knows how to stop.
A quality stage sends the work to specialist checks, and a critical failure blocks shipping. On failure the run loops back to the build stage and tries again, a limited number of times, then finalises as failed rather than pretending. An architect stage picks between a cached path for recurring report shapes and a custom build for anything else. The standards checklist the quality agents apply is also a standalone validator that can audit any completed run on its own, so verification does not depend on the agent being honest about its own work.
Alongside it came a Python automation that drives Excel through COM, tested against an external process that leaks and hangs. Getting there needs a session-scoped safety net that kills orphaned processes, so one failed test cannot poison the next.
What it does not do
- Concurrency is deliberately unsupported. The watcher serialises the queue, because the stages mutate a shared workspace.
- This was client work, so it is described by its engineering only: no client, no sector, no figures.
Built with Python, Excel
Second opinions
Each one measures the same thing twice, because one measurement is not evidence.
Paired time-series synchrony analysis
Two ways of measuring the same thing, so neither one has to be trusted alone
- paired computations, sixty-one channels across one hundred and thirty-one segments
- 7,991
- samples per channel set
- 714,474
- sampling rate
- 7.81 Hz
- cells in the first delivery, before it was split
- 119
A delivered research contract. Two simultaneous multi-channel recordings of the same event, and one question: do the two signals move together.
I answered it twice. Cross-correlation and dynamic time warping share no assumptions. One looks for a shifted linear match, the other for the cheapest alignment path. Running both and then testing whether they converge is the difference between a result and a claim. Most analysis stops after the first method.
The scale was 7,991 paired computations over 714,474 samples per channel set, sampled at 7.81 Hz, bandpassed from 0.01 to 0.1 Hz with a fourth-order zero-phase Butterworth filter, then read through a 30-second rolling window.
The delivery is the part I would show first. The first version was one notebook of 119 cells. It ran, and it was the wrong shape for the person receiving it. So it became four notebooks that each open cold, run alone, and hand the next one a saved array rather than a live kernel. Every code cell is preceded by an explanation of the reasoning rather than the syntax. The client was a researcher, not an engineer, and a deliverable they cannot open is not a deliverable.
It shipped with an explicit list of what the analysis could not resolve. They had not asked for that section.
The research is unpublished, so no finding appears here.
What it does not do
- The research is unpublished and the results belong to the client, so no finding appears here: not its direction, not its strength, and no characterisation of it.
- The deliverable itself named what the analysis could not resolve.
Built with Python, NumPy, pandas
Figures: the delivered notebooks and their saved outputs.
Sentiment Alignment, Amazon reviews against BERT
Star ratings and review text measure different things, and the gap is the finding
Most sentiment projects stop at classifying the review. This one runs a pre-trained multilingual BERT sentiment model across Amazon reviews, then compares what the model reads against the star rating the person actually gave, and sorts the result into four alignment categories.
The gap is the finding. A three-star review often carries positive text: good product, not great. A five-star review is often flatly neutral: it works. Stars and text measure different things, and quantifying where they part company is more interesting than either number on its own.
This is inference on a pre-trained model, not fine-tuning.
What it does not do
- This is inference on a pre-trained model, not training and not fine-tuning.
Built with Python, pandas, Hugging Face Transformers
Student Success Analytics
Some of the students at risk were ones whose grades looked fine
Student demographics, academic records, engagement with the learning platform, and risk labels, read with SQL and Excel against five intervention questions.
Low attendance combined with low forum activity predicted risk better than GPA alone. The segment worth naming is the one a GPA threshold never flags: high grades, zero engagement. On paper those students look fine. That is the whole finding, and it is the kind that only shows up when you measure the second thing as well as the obvious one.
It is analysis rather than machine learning, and it should not be described as anything else.
What it does not do
- It is analysis in SQL and Excel, not machine learning.
Built with SQL, Excel
T2D Adipose Tissue Research
Which genes matter is a question about the network, not the gene
Differentially expressed genes in type 2 diabetes adipose tissue, drawn from public GEO datasets, analysed with GEO2R alongside custom R and Python, then run through STRING for protein-protein interactions and read in Cytoscape.
Expression level answers which genes changed. Network topology answers which of those changes matter, and the two answers are not the same list. The hub genes here come out of how many edges meet at a node, not out of how far the expression moved.
It is the only work here in R, and the only work here in biology. There is no public artifact for it, and this page does not imply one.
What it does not do
- There is no public artifact for this one. It is described work, and nothing here implies a repository exists.
Built with R, Python