I was frustrated that first-time buyers had no good central place to compare metros. Sites show average prices and incomes, and some take your salary, but none compared metros before and after the loans and vouchers set aside for first-time buyers. So I built one for young buyers deciding where to plan and save. You write your situation as a sentence, and it tries every loan and program combination you qualify for in each place, using 151 sourced program records in a Virginia and Texas pilot. The need is real: since 2022 the typical US home has taken more income than the typical household earns.
About
I'm Landon. Data scientist, woodworker, electronics hobbyist, doodler, and general tinkerer of things.
I enjoy designing and implementing solutions that make life easier for both work and play. Beyond my digital creations I also enjoy studying markets, statistics, architecture, and tinkering with electronics.
Work
LLMs write Mermaid well, and its dagre layout gets most of the way. The last 10% makes a diagram presentation-ready, and Mermaid can't move one box. My first try converted Mermaid to draw.io, where the source went stale. Wermaid leaves Mermaid in charge. It reads the finished layout back from Mermaid's SVG, applies the nudges, pins and bends you drag in the editor, and re-routes only the edges they touch. The changes are saved as Mermaid comments in the source, so GitHub and every other renderer still draw the stock diagram. It embeds in chat apps where an LLM writes the diagram and a person finishes it.
A lot of paperwork is still a PDF form, and the same data gets typed into it again and again. formcast is a Python library that does the mapping once. A detector, CommonForms FFDNet, finds the blanks on each page. An AI agent reads the page's text, geometry and image, and links each blank to a field in a JSON Schema. The result is a template that fills new records with no model call. The demo runs the detector in your browser. Its sample forms come pre-mapped: Auto map pairs the agent's mapping with your boxes by position, and Fill previews the filled PDF to download.
doc-rooter is the next step after the Document Classifier: the same question, how little a model needs to see, made into a standalone package. It reads a PDF into its section tree and shows Jev one rung at a time, from the headings to the full text, counting every token. Jev names the type and tags every heading with what it means: a "statement of financial position" is a balance sheet. From the headings alone it named the type right 98.2% of the time on 113 public documents, and 95% on 40 held-out ones.
Every filing, statement and report has to be sorted by type before anything else can happen to it. Keyword rules read words, not documents, and an LLM is slow and costly at that volume. I built this to use Jev, a decision model, for fast, accurate classification. Decision models bring frontier-level judgment but answer with probabilities instead of generated text, so inference scales cheaply enough to open up a new class of work, something neither a bi-encoder nor a decoder-only LLM does both cheaply and accurately. On 43 test documents Jev matched GPT-5.5 and Claude Opus 5.5 at 100%, in 136 ms, for about 1/100 of the cost.
Fund documents explain the distribution waterfall in a page of legal prose, and I kept wanting to just see it: where every dollar goes as a fund returns capital, clears the hurdle, lets the manager catch up, then splits the profit. So I built a model you can push on. Change the hurdle, the carry or one deal's exit and watch the split move. The question that kept me going was the classic one: does deal-by-deal (American) carry really overpay the manager? Running 3,000 simulated funds through both structures answers it in milliseconds, because the random draws are made once and every slider reshapes them.
Last First Words is a speed reading app built on Rapid Serial Visual Presentation (RSVP), which flashes one word at a time in a fixed spot. I built it because the apps I tried were clunky or paywalled, and I wanted a simple answer to an old problem: finding time to focus and learn.
It keeps what those apps do well and improves it: a reading and goals tracker, an iPod-style speed dial and themes for comfortable reading. Next is dynamic speed: slower through dense passages, faster through easy ones. It's free on iOS and iPadOS, reads EPUBs, PDFs and web articles, and collects only your name and email.
Narrative AI is meant to be a general-purpose analyst: point it at your data, ask in plain English, and get the analysis and the chart back without writing code. We tested it on CSV files, but it isn't tied to any one source. My first version had the model write Python for a Jupyter kernel, with a five-attempt loop to repair its own crashes. It worked, slowly, and broke in creative ways. The rebuild gives one Claude agent two typed tools instead, one that profiles the data and one that saves a chart spec, so the model makes the choices and the server touches the data.
I needed long-form text read aloud in a cloned voice, on my own GPU, so I went looking for open models and found TTS-Story, an open-source audiobook studio built around Kokoro and Chatterbox. I kept its engine wrappers and turned the rest into a headless service: a FastAPI API, a Redis priority queue and a GPU worker that renders one chunk at a time, so listening can start before the job finishes, and an out-of-memory error retries a chunk instead of the whole job.
Assembly-line restaurants have a bottleneck you can watch from the counter: paying takes under a minute, but making the order takes two or three, so the cashier mostly waits while the line backs up. OrderVision was my answer: a camera over the make line that recognizes each ingredient as it goes on and builds the order by itself, so the customer just taps to pay at the end. I trained a YOLOv8 model on 19,000 annotations across 16 ingredients, and an item only joins the order once it has been seen in five frames at 70% confidence or more.
OrderVision only works if the camera can tell one ingredient from another, so this is the model underneath it. I labeled 150 to 250 images per ingredient with bounding boxes instead of segmentation masks, which are far quicker to annotate and far cheaper to train, then fine-tuned a pretrained YOLOv8m in Colab and served it through Roboflow's hosted API. My first run scored 89.4 mAP. Rather than reach for a bigger model, I went back to the data: dropping the hardest annotations and retuning the augmentation took it to 97.7. It's a lesson I keep relearning: the fix is usually in the data.
An early one, written while I was learning R: how do you group data when nothing is labeled? I used the Iris measurements because the right answer is known, which makes it a fair test. NbClust voted across its indices and picked three clusters, matching the three species. K-means with 50 random starts then grouped the flowers by petal length and width, and a contingency table showed how closely the clusters matched the real species. The catch worth remembering is that cluster numbers are arbitrary labels. Hierarchical clustering, cut at three groups, gave a second opinion.
This one started with a portfolio. Every bank I ran through my DCF model came out as nonsense, and the model wasn't broken: for a bank, debt is raw material, capex barely exists and working capital moves with the balance sheet. So I built the model banks actually call for. The excess return model values equity as book value plus the present value of returns above the cost of equity. I wrote it in R on the Financial Modeling Prep API, with a regression beta, winsorized ROE and capped terminal growth. Untuned, it valued JPMorgan at $189.81 against a $170.10 price.
I set out to test whether a rising tide really lifts all boats in the stock market, and the question turned into a sharper one: when a stock looks expensive next to its peers, is it, or are the peers just cheap? Instead of comparing ratios directly, I tracked the gap between SUN's price-to-book and its peers' average over 20 quarters in Python, and built a 99.9% interval around the usual gap. One peer, PARR, came out after a pandemic spike skewed the average. SUN's current gap of 2.72 sat above the interval's top of 2.47: it looked overvalued after a 37% run while most peers fell.
Cigna traded at a steep discount to its peers even though its sales grew in line with theirs, and I wanted to know whether the market was wrong or seeing something. After the Express Scripts merger its P/E averaged 12.52x, against peers near 19–21x. I tested the gap against Humana, UnitedHealth and Elevance. Measured against the pre-merger years it was significant (p = 0.0008); against the post-merger years it wasn't (p = 0.5625). The market had re-rated the company rather than mispriced it. A DCF at an 8.48% WACC still put fair value at $358.58 to $377.37 a share.
A look back at Nike in the early 2000s, built around one question: how much of a valuation is really the discount rate? Analysts at the time used 12%. Re-estimating the cost of equity as the average of CAPM and a dividend discount model gave 7.87%, and a WACC of 7.54%. I discounted the same ten years of free cash flow, with 3% terminal growth, at both rates. At 12% the shares were worth $37.27; at 7.54%, $84.71, against a $42.09 price. Four and a half points of discount rate moved the value by more than $12 billion.
Adobe interested me because it has lived under both software business models. It sold one-time licenses until 2013, then moved to subscriptions, which are now 93% of revenue. Net income fell from $832 million in 2012 to under $300 million during the switch, then reached $4.76 billion in 2022. I adapted the five forces and the value chain to a pure software company, swapping supplier power for employee bargaining power, and found R&D paying off with diminishing returns: 2022 revenue growth was a ten-year low of 12%. I closed with three recommendations, starting with a cheaper Creative Cloud tier.
Off the clock
Away from the screen I build furniture, restore antiques, tinker with electronics, run trails, and read about markets and architecture.
Contact
Connect the dots, create an envelope