Pair an Excel register with the drawing set, and see both at once. Your spreadsheet, your drawings, your machine — no service, no subscription.
PlanLink reads information straight out of an .xlsx, derives each
site's status from its dates, checks that answer against the workbook's own
formulas, and puts the matching drawing on screen beside it. It is pure
standard-library Python plus one HTML file: no installation requirement and
no additional procurement budget needed — nothing to sign into, and no network
request in the whole system.
v1.0 — released as code. This is a working system, generalised out of an experimental project. It ships with a complete synthetic dataset, so
git cloneand a double-click onindex.htmlis the entire demo. It is meant to be adapted with an AI coding assistant (Claude Code, Cursor, Codex) to whatever your own register looks like —CLAUDE.md/AGENTS.mdis the map written for exactly that.
- Reads and writes
.xlsxwith nothing installed. An Excel file is a zip full of XML, andzipfileandxml.etreehave shipped with Python for twenty years.tools/xlsx.pyreads shared strings, cell references, styles and date serials, and writes workbooks complete with live formulas and their cached values.openpyxlis used when it happens to be present; the fallback is the product, not the consolation prize. - Discovers your columns instead of declaring them. The data layer does not know what the columns are. They are found on every import, matched by header text and never by position, and stored entity-attribute-value. Add a column to the register and it appears. Rename one and a single command carries its history across. Delete one and nothing it ever held is lost.
- Derives status from the dates — and then argues with the spreadsheet. Two parallel ladders (drawing production and approvals), because the two workstreams overlap and a single "furthest milestone" hides whichever one is the bottleneck. The same rung tables generate the Python status and the workbook's Excel formula, and every refresh reports where the two part company. Ignoring a column that disagrees with you doesn't remove the disagreement, it just makes it invisible.
- Pairs each site with its drawings, and checks them. A drawing's type comes from the folder it was filed in, not its filename — a filename is a label somebody typed, a folder is a decision somebody made. A small PDF text extractor then reads each title block and holds it against the register. Disagreements and absent drawings are listed apart from one another, because resolving them falls to different people. Anything the selection skipped is listed too, with why: passed over as superseded and passed over unrecognised look the same from outside, and only one is acceptable.
- A per-cell change log. Who changed what, when, and what it was before — inferred by diffing snapshots, with formatting churn canonicalised away so the log records edits rather than re-saves.
- A viewer that needs no server.
index.htmlis one file with its CSS and JavaScript inline. The data arrives as three script assignments, which is the one decision that lets the whole thing work from a double-clicked file: afetch()of a JSON file overfile://is blocked, and a script tag is not.
git clone <this repo> && cd planlink
python3 refresh.py # read the register, pair the drawings, run the checks
open index.html # or: python3 start.pyThere is no virtualenv step and no pip install, because there is nothing to
install. Python 3.8 or newer is the only requirement, and even that is optional
for reading — the repository ships with the demo already built, so opening
index.html straight after cloning works.
On Windows, double-click START HERE (Windows).bat. If no Python is found it
opens index.html directly and says so.
To point it at your own register, edit config.json — every key is documented in
the file, and tools/paths.py is the only thing that reads it.
- The pipeline runs one way. The workbook is the single source of truth and nothing here ever writes to it. Every other file in the system is derived and can be deleted and rebuilt identically.
- Refuse rather than guess. An ambiguous phase, a duplicated row, an import that changes a quarter of the cells — each stops with a message naming the file and the row. An import that fails quietly and lets the next step run on stale data reports success while being wrong.
- Derive, and then reconcile. Two implementations of one rule will drift. The only question is whether you find out by measuring or by being told.
- Absent, not empty. A column that disappears keeps every value and every change it ever carried. A drawing the selection passed over is named, with the reason — because "a newer revision beat it" and "nothing recognised it" are the same silence from outside, and only one of them is acceptable.
- The PDF reader is deliberately small. It handles Flate and ASCIIHex
streams,
Tj/TJ, and text positioned by matrix — enough to read a title block out of a drawing produced by a drawing tool. It does not do CMap-mapped subset fonts, encryption, or scanned images. When it cannot read something it says which of those it was, because they need different fixes. - This is a viewer and a checker, not an editor. v1.0 has a read-only JSON API and no write path at all. That is the safe half of the fuller version it grew out of; the editing half is not here.
- The status ladders are one programme's. The rung labels describe one way
of getting a drawing reviewed and issued. The shape — two overlapping
workstreams, first match wins walking backwards, a branch that is not a step —
generalises; the labels are a table in
tools/derive.pyand are meant to be replaced. - It reports, it does not decide. PlanLink tells you what your spreadsheet and your drawings say, including when they disagree. Deciding which one is right is not something a tool can do for you.
- Everything in the demo is invented. No place is named at all — the work is
just a street being rebuilt — and the one organisation the drawings mention, ABC
Design Studio, is a placeholder chosen to read as one. Every address, owner,
parcel reference and date is made up, and parcel references use the form
PR-####-###, which is not the shape of any real identifier scheme.
The fuller version this was generalised from also carries an inbox that files
dropped drawings and saved .msg emails automatically — writing a reader for
the OLE compound file format, because a .msg is a small filesystem in a file.
It drives Outlook from a script to open a pre-filled draft, never to send one:
Windows exposes running applications through COM (Component Object Model),
its long-standing way of letting one program operate another, and PowerShell can
take hold of one in a line — New-Object -ComObject Outlook.Application. Also
an editor UI with a per-user note log, and a bundle-check tool whose every
assertion was born from a silent failure. None of them are here, because none of
them could be separated from the data they were built around.
- An allow-listed write endpoint, so a correction can be queued from the viewer
- CMap support in the PDF reader, for drawings from more sources
- A second worked example with a different ladder, to show the shape generalising
Pull requests are not being accepted at this stage (v1.0 is a code-drop
release) — see CONTRIBUTING.md. Bug reports and questions are welcome as
Issues. Want changes now? Fork it.
Test this against a copy of your own data before you rely on it, and before any commercial use.
PlanLink is provided as is, without warranty or condition of any kind. grndtech accepts no liability for lost or corrupted data, incorrect figures, missed obligations, or any other loss arising from use of this software — see sections 7 and 8 of LICENSE, which govern. Verifying the output before you act on it is your responsibility.
PlanLink reports what your spreadsheet and your drawings say. It is not a substitute for a qualified review of the register or the drawing set.
Apache License 2.0 — see LICENSE. The license covers the code, not the name:
fork freely, but ship your fork under your own name (see NOTICE).
Built by grndtech — the story behind this system is at grndtech.com/planlink.