Type to search across all articles

Pet Projects & Experiments

35 projects across 5 years

Things I built outside of client work — mostly to answer a question, occasionally because the tool I wanted didn't exist. Some grew into articles on this blog, some into a paper, most stayed exactly as small as they needed to be. The day job is on the CV.

Nothing here with that tag.

2026

17 projects

diag-vectorizer

Raster diagrams back into editable draw.io

The rebuilt booking diagram open in draw.io: boxes, the central circle, arrows and labels all editable shapes

Takes a PNG or JPG of a diagram and reconstructs an editable vector version of it — both .drawio and Mermaid. Computer vision finds the geometry (shapes, coordinates, nesting, lines); a multimodal LLM supplies the semantics (text, arrows, colors).

More about it

Two interchangeable detectors sit behind one interface: a classic OpenCV pipeline (adaptive threshold or Canny → morphology → findContours, then shape classification by circularity, fill ratio, aspect and corner angles) and Segment Anything. Detected regions are arranged into a containment tree, and only then does the LLM get involved — for OCR, connections and styles, with its answers cached.

It recognizes rectangles, circles, ellipses, diamonds, triangles, cylinders (database icons) and arrows, along with labels, styles and background colors. Still a work in progress: neither of the diagrams in the post below was touched up before publishing.

Python · OpenCV · Gemini

osbench

A benchmark stand for order-sourcing algorithms

An open, reproducible benchmark for comparing order-sourcing (warehouse-selection) algorithms in an e-commerce network: a discrete-event simulator with common random numbers and a strict comparison protocol.

More about it

Sourcing is one of those areas where everyone has an opinion and nobody has numbers. osbench generates a world and an order stream from a config, replays it against eleven sourcing and five replenishment policies under identical random draws, and reports paired confidence intervals rather than single runs.

It also models the things that make sourcing hard in practice: four delivery-promise methods with customer acceptance, three kinds of shortage (substitution, re-sourcing, backorder), deferred sourcing, and ATP in both basic and cumulative flavors with tolerance. There is a live web dashboard with a map and per-warehouse drill-down, a visual config studio, weight tuning, parameter sweeps and offline HTML reports. Everything model-relevant is config-driven.

Python · discrete-event simulation

Piano Ear Trainer

Intervals, chords and progressions, by ear

A browser ear trainer that plays something and waits for you to play it back on a MIDI keyboard. One self-contained HTML file — no install, no account.

More about it

Five courses, from single notes through intervals and triads to inversions, seventh chords, melodic interval chains and chord progressions. The key, the mode (major, natural and harmonic minor, or no key at all), the range in semitones and the harmonic level are all adjustable, and there is an adaptive mode that keeps track of what you keep getting wrong and comes back to it.

The task can be played through a connected MIDI instrument or through the browser's own synthesis; answers come back over Web MIDI. Progress is kept in the browser, per key and mode. The interface itself is in Russian.

One HTML file · Web MIDI · Web Audio

Tube Color Trainer

Learning oil paints by name and by sight

Tube Color Trainer: a blue swatch with four paint names to choose from, and a strip of oil paint colors

A quiz for oil painters: given a swatch, name the tube — across Winsor & Newton, Gamblin and Richeson Shiva. You can limit the palette to the tubes you actually own and compare similar hues side by side.

One HTML file

llm-hotkey

Translate, fix and rephrase the selection anywhere in macOS

Select text in any Mac app, press a hotkey, and the selection is replaced by the LLM's result: translation (⌘⌥T), grammar fixes (⌘⌥G), a cleaner rewrite (⌘⌥R), or read aloud (⌘⌥S).

More about it

The second take on openai-hotkey, and mostly a study in doing the keyboard plumbing properly. Hotkeys go through Carbon's RegisterEventHotKey rather than a global event tap, so macOS can't silently time it out and disable it. It waits for the modifiers to be released before sending ⌘C from a private event source, waits for the pasteboard to actually change instead of sleeping a fixed interval, and restores your previous clipboard contents afterwards.

Swift · OpenAI · Gemini

ushi

Local speech-to-text with the screen attached

ushi web UI: a recorded talk with the beautified transcript showing speakers, sections and slides inline, and the captured frames on the right

Narrate what you're doing on screen, record a call, or play a talk — ushi transcribes it with local models, keeps screenshots next to the words they belong to, and turns the result into a readable document. Recognition never leaves the machine.

More about it

Three recognition engines, all running locally on CPU with int8: faster-whisper large-v3-turbo for any language, GigaAM v3 for Russian (about 20× faster than Whisper), and Parakeet TDT v3 for mixed Russian/English. Silero VAD means only speech is recognized, and timestamps come from the audio itself, so they stay correct even when recognition falls behind. Speaker diarization is optional and can be recomputed later without re-recording.

Audio comes from the microphone or from a single app — Teams, Zoom, Meet in a browser — alone or together with the mic as [Them] / [Me] lines, via Core Audio process taps on macOS and WASAPI process loopback on Windows. Screenshots are captured on a hotkey or automatically, classified, and placed inline. The "beautify" pass, which turns the raw transcript into a document with speakers, sections and slides, is the only step that can use a remote LLM — and you choose which.

Packaged as a desktop app for macOS and Windows with Python, ffmpeg and the helpers inside; models download on first use and then it works offline.

Python · faster-whisper · GigaAM · Parakeet · Electron

artprojector

Camera, projector and a canvas on an easel

The overlay tool: a portrait on the easel seen by the camera, with the reference drawn over it as green contours

A USB camera looks at a canvas on an easel; from a printed calibration target the tool works out the perspective of the canvas, rectifies the view, and overlays a reference image aligned to the canvas plane — as contours or semi-transparent.

More about it

The camera rarely sees the whole canvas, so the tool computes a homography between the image and the canvas plane in millimeters, which is enough to both rectify what the camera sees and to project a reference onto it. Two printed targets are supported: six 63 mm squares on a single A4 sheet in the corner of the canvas, or a one-inch ArUco grid covering the canvas, where any two or three cells in frame are enough and the markers identify themselves — so the camera can come as close as you like.

The second half is for an easel with a real projector: one tool measures where the projector's pixels land on the canvas, using the calibrated camera as the instrument, and another throws a reference onto the canvas pre-distorted so that it lands square whatever angle the projector sits at.

Python · OpenCV · ArUco

ambi

Ambigram search and 3D ambigram models

The MERCEDES / STOWAWAY / ODOMETER sculpture seen from the front, where it reads MERCEDES

Finds word triples that can be read as an ambigram and renders them — in the browser and as printable STL models of the three-way kind that spells a different word from each direction.

Python · OpenSCAD/STL

BloomSpan

Memory-efficient maximal frequent substring mining

The first page of the BloomSpan paper: title, authors and abstract

An algorithm for mining maximal contiguous frequent phrases from very large text corpora, combining a counting Bloom filter for probabilistic frequency estimation with prioritized seeds and depth-first expansion directly on the corpus — no projected databases.

More about it

Written up as a paper with Joeran Beel (University of Siegen). Seed generation provably produces no false negatives, and output equivalence with exact baselines was confirmed on every Gutenberg-8k subset where any baseline finishes at all. On the Gutenberg-BookCorpus-Cleaned corpus — up to 3.07 billion tokens — it runs 11–15× faster than FHK with up to 2× lower peak memory, and it was the only algorithm in the evaluation to complete at 50,000 documents within a 40 GB heap.

The repository carries the paper, a Java benchmark framework with all the competing algorithms, and a C++ prototype.

Java · C++ · research paper

Smartfolio

A knowledge workspace of pages inside pages

Smartfolio's landing page over a screenshot of nested documents, each explaining a fragment of the one before it

A personal knowledge workspace built around pages that contain other pages. Instead of filing documents into folders you inset one page into another, and a child page can explain the fragment it hangs off with the whole parent page as its context.

More about it

You write in a rich editor — prose, math, images, embedded PDFs with annotations, clipped web pages. An inbox takes things in from outside, search finds whatever got orphaned, and a handful of LLM plugins do the jobs that are tedious by hand: structuring a raw transcript into linked sections, collecting a whole subtree into one document, translating a page while leaving its formulas and layout intact.

It started as a SaaS storing documents only in Turso. It now also writes to local SQLite, a folder on disk, a GitHub repository or a Google Drive folder — and synchronizes between any two of them three-way: what A has, what B has, and what the two last agreed on, so "these differ" becomes "this one changed" without consulting a clock on someone else's machine. Pairings are bidirectional by default, can be scoped to a page and everything it reaches (assets included, found by reading ids out of the content), and offer a dry run that decides everything and changes nothing. Conflicts keep both sides as a conflicted copy rather than merging or dropping; deletions leave tombstones, so a page that merely vanished can't wipe the other side's copy.

Desktop builds for macOS, Windows and Linux, and it works from a mobile browser. Invite-only, both the service and the source.

Web app · SQLite/Turso · Electron

The Reader's Glossary

A 600-page reading companion for Lolita

The printed glossary: the cover beside an open spread of chapter entries with their definitions

5,200 words from Nabokov's Lolita, defined in the context they appear in and ordered chapter by chapter rather than alphabetically — so it can be read next to the novel instead of looked up. In English, Russian, French and German.

More about it

The idea is to keep the reader in flow: you glance at the next four or five definitions, they sit in short-term memory, and then you read the actual sentence and recognize the word instead of stopping. Entries are graded into three difficulty levels, and the whole thing runs to 600–700 pages depending on the language — about twice the length of the novel.

The definitions aren't generic dictionary entries; they take into account how Nabokov himself rendered the passage when he translated the book into Russian. Getting that required a custom sentence aligner, since the off-the-shelf ones weren't accurate enough: recursive anchoring, where the longest sentences are embedded with multilingual-e5 and matched by cosine similarity to become anchors, and the segments between anchors are then aligned the same way. It handles sentences that were split, merged or dropped in translation. Both language versions of each sentence go to the LLM, so the author's own choices ground the definition.

Python · multilingual-e5 · LLM pipeline

Who's Who in Search & RecSys

84 researchers and 40+ labs, with links

A hand-curated directory of the people and labs doing information-retrieval and recommender-systems research, each with links and a short note on what they actually work on. Built because I kept re-deriving the same list from paper author lists.

Static page

Chess move predictor

A network proposes, minimax checks

The engine playing itself in CuteChess: board, move list with per-move times, and the engine debug log

A small UCI engine put together in an evening: a network trained on 20,000 decisive Lichess games proposes the five most likely moves from a position, and a minimax search looks only at those, a few plies deep.

More about it

The games are picked out of 800,000 by taking only decisive results and sorting by rating gap, on the theory that a wide gap means a more decisive win — a crude proxy, but better than none. That leaves 20,000 games and 534,453 positions. The weaker side's moves are dropped during training rather than beforehand: if it is White to move and White won, the position is learned; if the side to move went on to lose, it is skipped. The network takes a board and emits 4096 values, one per from-square/to-square pair.

Selecting the games takes about five minutes and training about ten, so the obvious next step is simply more games. It is not strong, but it gets through the opening without embarrassing itself — which was the whole point of the exercise.

Python · PyTorch · CuteChess

Chat archive to Q&A pairs

A year and a half of an NLP chat, made searchable

Search results as Q&A pairs on the left, the original chat block with the answering messages highlighted on the right

Turns 65,000 messages from a Telegram group into question-and-answer pairs with semantic search over them. Clicking a result opens the original conversation with the answering messages highlighted.

More about it

The whole thing rests on one assumption: people reply to messages that are close behind them, and a message several people reply to was probably worth asking. So the tool cuts a window starting at a message with at least three replies and ending at the last message in its reply chain, then summarizes that window into a Q&A pair. Windows overlap freely — someone asking a new question while others are still answering the previous one produces two of them, nested.

Python · embeddings

Spline halftone printing

An image as one continuous thread

A slice of cake drawn entirely in fine Bezier lines over CMYK plates, dark areas denser than light ones

An image is split into squares, each square becomes a count of Bézier lines proportional to how dark it is, and because neighboring squares share their boundary points the result is not a grid of cells but a single continuous thread. Output is SVG, so it can be printed the size of a wall.

More about it

Dividing an image into 10×10 blocks costs you a factor of ten in resolution, so each channel is drawn in several passes with different cell sizes on shifted grids — large cells first, then finer ones offset by ten pixels, then finer still offset diagonally. Color goes through CMYK separation as in printing: each channel gets its own grid and its own lines, and the plates are then laid over each other with transparency.

The whole run is driven by a JSON config, with separate parameters per channel and per pass within a channel. It only really works large — at phone size the lines merge into gray.

Python · SVG

Friend-or-foe matrix

Every country pair, as one model sees it

The full relationship matrix with country labels: green blocks of alliance, yellow neutrality, red and purple conflict

A full country-by-country relationship matrix, each pair sorted by an LLM into one of five buckets from "at daggers drawn" to "best friends" — then processed to see what structure falls out of the result.

Python · Gemini

This blog

200+ articles out of WordPress and into Markdown

hybrismart.com is a static site generated from Markdown by a build script in this repository — no WordPress, no database. All 200-plus articles were migrated and reworked: a readable typeface, clickable illustrations, in-article navigation and full-text search.

More about it

The part I am most pleased with is the contextual recommendation system. Where a paragraph mentions Drools, a link to the related article appears right next to it, with a note on why it is relevant there specifically — as a margin note on desktop and an inline callout on mobile. Articles come in English, Russian and Ukrainian from the same source, and the build also produces this page, the per-vacancy CVs and the search index.

Node · Markdown · static HTML

2025

10 projects

volleyball-editor

Drawing volleyball drills in 2D and 3D

A drill in the 3D court view: posed players, numbered positions and colour-coded ball trajectories over the net

An editor for volleyball court diagrams: place players, the ball and movement arrows on a court, switch to a 3D view with posed figures (serve, pass, attack, block), and export the result as PNG or SVG.

React · Vite · three.js

Coveo Search Debugger & Exporter

Turning rankingInfo into a spreadsheet

Replays the page's last Coveo search with debug ranking enabled, parses the opaque rankingInfo blob into discrete metrics — document weights, boosts, QRE contributions — and exports the whole result set to CSV.

More about it

Relevance work on Coveo keeps running into the same wall: the ranking information is right there in the response, as a semi-structured text blob nobody wants to parse by hand. The extension picks up valid authentication tokens and headers from ordinary user activity, replays the request with debug=true and debugRankingInformation=true, and hands you a table you can open in Excel or pandas.

Chrome extension · JavaScript

Coveo Search Request Interceptor

Changing search parameters without touching the app

Intercepts requests to the Coveo Search V2 API in the browser and overrides parameters — query, result count, advanced query expressions — from a small JSON config, so search behavior can be tested without changing the application's code.

More about it

It hooks both window.fetch and XMLHttpRequest (still widely used by Sitecore/Coveo integrations) and rewrites the POST body immediately before the request goes out, targeting only the /coveo/rest/search/v2 endpoint so nothing else on the page is disturbed. Overrides apply to the next request, with no page reload.

Chrome extension · JavaScript

Read and Learn

The dictionary is in the next room

Reading Le Petit Prince in French, with picked words collected on the right and a study session waiting to start

A reader built around a deliberately inconvenient idea: you mark unfamiliar words while reading, then study their translations with the text hidden, then return to the text with the translations gone. The detour is the point — it is what makes the words stick.

More about it

Text is paginated on sentence and paragraph boundaries, so no fragment is cut mid-thought. Tokenization is dictionary-driven and greedy: if the dictionary has "web application", it wins over the two separate words. Reading position is kept per file via an MD5 of its contents, so a session resumes exactly where it stopped.

Python · web UI

ML Approximation Playground

Watching a network learn a function — and fail

An interactive playground for fitting synthetic datasets — parabolas, sines, absolute values, step functions — with neural networks, polynomial regression and decision trees, built to make the Universal Approximation Theorem, overfitting and extrapolation visible rather than quotable.

More about it

The plot deliberately extends beyond the training range, so you can watch each model's behavior fall apart outside the data it saw. The part I built it for is the neural decomposition view: the individual basis functions each hidden neuron contributes, and how they sum into the final prediction.

Python · Streamlit · PyTorch

Recommender System Laboratory

25+ recommender algorithms you can turn the knobs on

The lab running BPR: hyperparameter controls on the left, convergence plots and latent-factor snapshots on the right

The interactive companion to my book Recommender Algorithms: train, configure and compare more than 25 recommendation algorithms in real time, with the visualizations that explain what each one is actually doing.

More about it

It is deliberately built for small datasets. It will run on larger ones, but the visualizations get less informative and some algorithms need more data than an interactive session can reasonably hold — this was never meant to be a full-scale benchmark, it is a teaching instrument.

Python · Streamlit

recommenders-samples

ALS plus XGBoost on MovieLens

A hybrid recommender sample: Spark ALS produces user and item latent factors, XGBoost then refines the rating predictions on top of them, on MovieLens 100K.

Python · Spark · XGBoost

TestMySearch

Making search-relevance work measurable

The search agent mid-run: its reasoning log on the left, the storefront it is working through on the right

A platform for testing on-site search systematically: feed it query sets and expected results, point it at Solr, Elasticsearch, Coveo or Algolia, and it runs batch comparisons across configurations overnight and reports which one is better and by how much.

More about it

The problem it exists for: you have a production search and a hypothesis about improving it, and no way to tell whether the change quietly broke something else. The platform computes NDCG@k, MAP, precision, recall and a couple of dozen other metrics, with statistical significance rather than single runs, and only at the very end uses an LLM — to explain the numbers, not to produce them.

It also works as a virtual assessor, grading how well each result matches its query, which for long documents means chunking, embeddings and per-chunk evaluation. Around it grew several smaller pieces: context-aware autocomplete from a locally trained model ("Tyson Foods stock" suggests price, "Tyson Foods chicken stock" suggests recipe), and an agent that is given a site and a goal in plain language, writes its own queries, judges the results against the original intent, and can carry the session through to checkout — which makes both ground-truth data and realistic session data to train recommenders on.

Python · Solr/Elasticsearch/Coveo/Algolia · LLMs

Boolean query simplifier

Ten sites, ten unreadable search filters

The parsed filter expression before and after simplification: a sprawling tree on the left, a compact one on the right

Parses the enormous machine-generated filter expressions that a CMS appends to every search query into an AST, converts them to Boolean algebra, simplifies them with SymPy, and then diffs the results across sites to find what they actually have in common.

More about it

Ten sites shared one search index and sent it ten different versions of the same inscrutable expression — template ids and page ids to include or exclude, ten open parentheses and a few closing ones placed seemingly at random, with deeply nested conditions where "exclude this template" applies only when some other field equals one. Reformatting it did not help; reading it by hand helped less.

Simplification discards duplicates, collapses nesting and drops conditions logically absorbed by others, which flattens the trees into something readable. To prove nothing was lost, a generator reassembles the simplified logic into a working request and checks that the document count matches the original. From there a comparison matrix shows the common core — conditions guaranteed to hold for every document that passes — against the per-site tails.

Python · SymPy

Category-Aligned Retrieval

Predict the category, then search inside it

A short paper on predicting the most likely product categories for a query and then filtering retrieval by them, using trainable category prototypes — category representations learned directly from user queries rather than from the taxonomy.

Python · PyTorch · research paper

2024

5 projects

semantic_chunking

Where to cut a document for RAG

Experiments in splitting documents by meaning rather than by character count — building a tree over the text and choosing cut points where the topic actually changes.

Python

proxy-for-apis-demo

Changing API traffic without touching either side

A browser extension and a small Python proxy for intercepting and rewriting API requests and responses — useful when you need to prototype a change against a system where you own neither the frontend nor the backend.

Python · browser extension

img2img-turbo for Apple Silicon

A port to the mps device

An adaptation of the img2img-turbo model code to run on Apple Silicon's mps device. The model and the original implementation are not mine — this is the porting work needed to make it run on a Mac.

Python · PyTorch

Saturday Evening Post archive search

A century of a magazine, made findable

Every page of The Saturday Evening Post from 1900 onwards, OCR'd and made searchable by both keyword and meaning — Tesseract for the text, Solr for lexical retrieval, embeddings and FAISS for semantic, and an ML reranker over the two.

More about it

It began as a nostalgic afternoon in the archives and turned into a decent excuse to build a hybrid retrieval stack end to end on material that fights back: century-old scans, period typography, and illustrations that matter as much as the text.

Python · Tesseract · Solr · FAISS

Facebook cross-posting pipeline

6,000 posts out of a social network

Migrated 6,000 Facebook posts to two blogs of my own — beinginamerica.com in Russian and raufaliev.com in English — and then kept them in sync, because social media is a bad place to leave anything you might want to find again.

More about it

A new post triggers the whole chain: translate it, process the attached images and describe them, derive a title and tags from the text plus those descriptions, write the record to Turso, build embeddings, index them in Qdrant, upload the images through the WordPress API and publish both language versions. The 2024 article below documents the original migration; the pipeline was rebuilt in 2026 around the cloud databases.

Python · WordPress API · Turso · Qdrant

2023

1 project

fsdumpviewer

Diffing two machines from an ls -lR

Two filesystem snapshots side by side, with differing files marked in colour and export controls below

Views and compares filesystem snapshots taken with ls -lR, dir /S or find . on different machines, then generates a script that synchronizes them or packs just the files you selected into an archive.

More about it

Two use cases it was built for. First, pulling specific files off a remote system you only have SSH to: take a snapshot, pick the files in the UI, get a .sh script that produces a tar.gz with exactly those. Second, two people debugging the same system and suspecting their environments differ — each takes a snapshot, the tool shows what diverges, and generates the script to close the gap.

Python · Qt

2021

2 projects

Turntable 3D scanner

An easel, a phone and photogrammetry

A chess piece as bare mesh, as textured model, and the turntable rig that produced both

A rotating platform, an iPhone and an automated pipeline: FFmpeg pulls frames from the video, COLMAP recovers the camera positions and a sparse cloud, OpenMVS densifies and meshes it. One command from video to model.

Python · COLMAP · OpenMVS · FFmpeg

Physical chessboard reader

Playing a real board against an engine

A camera watches a real chessboard, a ResNet-18 classifier reads the pieces off the squares and works out what moved, and the move is fed to a chess engine — so the physical board becomes the interface to the virtual game.

More about it

The hardware was an easel, a walking stick and a quantity of tape, which is roughly the right level of engineering for a first run. OpenCV and PIL handle the board detection and square extraction; the network does piece recognition per square, and diffing two consecutive readings gives the move.

Python · PyTorch · OpenCV