The slow way, made survivable Chapter 14 of 18 Svenska

The map · Part Two · The machine · Chapter 14 of 18

How the machine reads

The claimThe machine may measure and remember but not decide, and it refuses to print a value whose origin has a gap.

About 12 minutes · the builder's route.

Everything in Part One could be done with a notebook. For a while it was. Here is what happens to a method that lives in a notebook.

You forget. Six names on a watchlist, each with a buy price and a stop, and on a Tuesday evening in March you do not remember which report you last valued NAME B on. You drift. The cushion was 25 percent, but this one is nearly there and you understand it well, so 22 would be fine, just this once. You re-argue. A price falls 8 percent in a day, and a question you settled in writing three weeks ago is suddenly open again, because now there is a stake in the answer. None of this is stupidity. It is what a person does with a rule when the person is tired, or excited, or has money on the line. Part One is a set of habits designed to be hard to bend, and habits are exactly what a person under pressure bends first.

So the method was given a machine: a small program on a rented server that runs every night, fetches the day's prices, compares them with the lines the owner has already written down, and reports. It remembers what the owner decided and when. It does not get excited.

The governing principle

The tool measures. The owner decides. That is the whole design, and it is enforced, not merely intended. Since 2026-10-27 (ruling E141) the owner has handed the judgment calls, the rulings, values and verdicts, to a Claude Code session, which decides, records and tells him. Orders and pushes stay the owner's. The scheduled machine still decides nothing.

The line the tool never crossesTwo columns separated by a thick vertical wall. Left, what the tool may do: fetch, compute, compare, write a report, point, say data missing. Right, in purple, what only the owner may do: enter a name, assign a tier, write a growth view, set a value or stop, move a name, decide. the tool mayonly the owner mayfetch prices and filingscompute a fixed set of figurescompare a price to a linewrite a report and a pagepoint at what needs a looksay DATA MISSINGenter a nameassign a tierwrite a growth viewset a value, a buy line, a stopmove a name between statusesdecidethe wall is code: a write across it fails the run
The governing rule as a wall. Everything on the left is measurement. Everything on the right is a decision, and the code that writes measurements is built so it cannot write a decision.

What the machine may do is a short list of verbs. It may fetch prices and filings. It may compute a fixed set of figures. It may compare a price to a line. It may write a report and a page. It may point at a name that needs the owner's attention. It may say that a figure is missing. What it may never do is longer, and every item is written into the code as a refusal: it never writes a verdict, never enters a company into the process, never assigns a tier, never sets a value or a buy price or a stop, never moves a name from one status to another. In the tool's own summary, it computes market data only, and "everything requiring judgment is a MANUAL field you fill in by hand; the tool never computes, guesses, defaults or back-fills any of it."11README. The scheduled screen 'may SNAPSHOT, RANK and REPORT; it never enters a name' (ruling E93). A refresh 'FETCHES, EXTRACTS and REPORTS; it never decides' (ruling E92). The price watch 'points once per crossing and writes nothing' (ruling E97). The page generator: 'this project's design is that the page measures and I decide.'

The two walls

The first wall is around the owner's file. It holds every status, every tier, every value, every sell line: the decisions. No part that runs on a schedule may write to it. The refresh command asserts it did not. The scheduled screen's unit cannot write to the folder. A hand-typed buy price in the file fails the run outright, because a buy price must come from a value and a tier or it has no origin.22Rulings E92 and E93; README on the provenance conflict; the read-only configuration directory in deploy/vss-screen.service.

The second wall is around the arithmetic. Nothing that computes may fetch, and nothing that fetches may compute. A figure crosses from one side to the other only through the record, with its origin attached. This is what makes a number reproducible, and what makes a wrong number traceable to the fetch that brought it in rather than lost inside a calculation.

The commands, for the curious

The program is run from the command line as python -m vss followed by one of about two dozen sub-commands. The ones a reader of this site has met: run (the nightly run), screen (the weekly screen), watch (disclosures), refresh (re-read a company's figures on a report date), earnings, xbrl, nordic, manual, appendix (the ways figures come in), briefing (assemble a reading), overview (the page), checkin (the heartbeat's own timer), sales (closed positions measured against the index), shadow (the shadow book), queue (every valued name against its lines, nightly), reportread (fetch and read a new report twice), outlook (re-value after a report and compare). The master list is built by tools/master_list.py. Beside the program sit small tools for screening by hand: tools/warren.py runs the quality tests and a fair value on any US filer straight from its SEC filings, tools/rough_fv.py gives a rough value for a company outside the US, and tools/fetch_mfn.py fetches a Nordic company's annual and interim reports as PDF from the MFN news feed, which carries Cision's releases too. Every one of them prints a report to standard output and logs to standard error, and none of them writes a decision.

The seven kinds of piece, one by one

Fetching: bringing figures in from outside

One part fetches daily prices and keeps a copy on disk, so that when the price service is down the run has yesterday's figures marked as stale rather than nothing at all. Another reads the structured data American companies must file with their regulator. Two more read the Nordic exchanges' own disclosure feeds, which is where a Swedish or Norwegian company's reports actually appear. One reads a company's spreadsheet appendix through a map, committed with the code, that says which cell is which figure. One fetches a company's own report page and nothing else: it does not search, guess or follow links. A new report is fetched and read twice by a model (vss/reportread.py), and the company is then re-valued on its last four quarters and compared with its own previous reading (vss/outlook.py). And one hands a fetched document to a language model with a narrow instruction: pull out the stated figures and quote the sentence each came from; never derive, estimate or reconcile a figure, never decide whether something is material, never write prose.33Module descriptions in vss/fetch.py, vss/xbrl.py, vss/nordic.py, vss/oslo.py, vss/appendix.py, vss/source.py and vss/extract.py; README on the extractor: it 'never derives, estimates or reconciles a figure, never decides materiality, and never writes prose.'

The forbidden thing, for every fetcher: it may not judge what it fetched. It brings a figure in with its origin attached, and stops.

The record: keeping every figure

One part writes one row per company per run into a small database, so the history can be rebuilt later. One keeps the run record for each valuation, the eight things that must be declared before a value may be printed (this chapter). One keeps a snapshot of every screening run, the raw prices and the list of names, so the run can be replayed exactly. One holds hand-entered figures for companies no feed serves, each marked as checked or not. One tests whether a price series is a real measurement or a feed artefact.44vss/store.py, vss/runrecord.py, vss/snapshot.py, vss/manual.py, vss/series_sanity.py.

The forbidden thing: the record never fills a gap. A figure that was not fetched is absent, and stays absent.

Computing: the arithmetic, kept pure

The parts that compute figures, apply the rules and solve the valuation do no input and no output. They do not read files, do not touch the network, and do not know what time it is; the date is always handed to them. This is a wall, not a convention: it means a computed figure can be reproduced later from the same inputs and will come out the same, because nothing in the computation could have depended on the world outside it.55The rules module: 'This module performs ZERO I/O. No file access, no network, no logging, and deliberately no datetime.now().' The metrics module is the same, with the as-of date always passed in.

Reporting: the report and the page

One part writes the nightly report in a fixed order: when the run happened and how fresh the data was; then blockers, the things that stopped a judgement; then actions, the things that need the owner; then the full table of every name. Another generates the one-page overview that reaches the phone (chapter 15). A third assembles a research briefing for a name being read, which is a collection of the figures and their sources, not a view.66vss/report.py, vss/overview.py, vss/briefing.py.

The forbidden thing: no report says buy, sell, or good. It says how far, how fresh, and what is missing.

The screen

The parts from chapter 4: the fixed list of names, the two filters, the fixed-seed random ordering, and a wrapper that runs them on a Saturday morning. Plus one small part, and it is the only piece in the screener allowed to write into the owner's file: the one that enters a proposed name. It runs only when the owner starts it by hand.77vss/screen.py, vss/filters.py, vss/universe.py, vss/ranking.py, vss/screenwatch.py; vss/pipeline.py is 'the only module in the screener that writes to the file holding the owner's live positions', and the scheduled unit is started without the flag that would invoke it.

Watching

Two parts. One watches the disclosure feeds for the names on the waiting lists and reports only three kinds of document: a periodic report, a change to the company's own forecast, a profit warning. Everything else the exchange publishes is rejected and the rejection printed. The other watches the prices of the first twenty names of the screener's list against an indicative level and points, once, when a price crosses it. The level is a distance from a rough value, not a buy line, and the ruling says so.88vss/watch.py; vss/pricewatch.py, ruling E97: 'a distance, not a buy line'.

The heartbeat

One part whose only job is to make a run that did not happen look different from a run that had nothing to say. Chapter 15.

The runner

One part ties the nightly run together: load the owner's file, fetch, compute, assess, report. All the reading and writing of the run lives here and in the fetch, store and report parts, so that everything else can stay pure.99vss/runner.py: 'All I/O lives here and in fetch/store/report. rules.py and metrics.py stay pure.'

Four places a figure can live

  • The run database. One row per company per nightly run: the price, the computed figures, the verdict codes, the blockers. Two things read it back: a valuation's before-and-after table reads the confidence level in force, and the check for a crossing caused by a moved line compares tonight's line with the one the previous run recorded. Otherwise it accumulates, so the history can be reconstructed later.1010vss/store.py.
  • The fundamentals store. Every period of accounts ever fetched for a company, kept for good. A quarter never displaces a year. A store that pruned old periods would make an old valuation impossible to rebuild.1111Ruling E49: the store retains every period it has ever fetched. Ruling E17: a quarter never displaces a year.
  • The screener snapshots. One small database per screening run, holding raw daily prices, never computed figures, plus the list of names, the rejections and a fetch status per company. The last four price snapshots are kept; every ranking is kept forever, because every future comparison stands on it.1212vss/snapshot.py; retention described in the project brief §6.5.
  • The owner's file. Statuses, tiers, values, sell lines, notes, and hand-entered figures. Tracked under version control, on the owner's decision, so that the history of decisions is in the history of the file.1313README on config/watchlist.yaml, tracked since 2026-08-25.

The run record: eight declarations or nothing

A valuation is not a number. It is a number plus everything that was decided to get it. So before a value may be printed, the run record for that valuation must declare eight things: which share count was used and as of when; how share-based pay was treated; where the company books its interest, and therefore what "free cash flow" means for it; every item in the bridge from the value of the whole business to the value of its shares; the arithmetic conventions; the discount rate with its anchor and the growth rate with the view it came from; the three as-of dates and the price date; and the origin of every input.1414vss/runrecord.py: 'A RUN WITHOUT A COMPLETE RECORD DOES NOT PRINT A FAIR VALUE. Not \'prints one with a warning\'.'

Why refusing beats continuing

The reasonable-sounding way, which is wrong

Seven of the eight declarations are in place. The share count is present but the date it was stated is not. Print the value anyway, with a note: "share count date unknown". The owner will see the note.

What the code does

The valuation raises an error and prints no value for that company. The report says which declaration is missing. The other companies in the same run are unaffected: one name's missing declaration is that name's DATA MISSING, never a reason to stop the run for the rest.

The note would be read the first time and not the tenth. A number on a page is a number; the caveat beside it is decoration, and decoration gets ignored. The only caveat that cannot be ignored is the absence of the number. So the record refuses, and the refusal is a line in the report that says what to go and find.

Two levels of refusal exist, and it is worth being exact. A bad or missing figure for one company degrades that company's row and nothing else; the nightly run is built so that one name never breaks it. A corrupt owner's file, a malformed entry or a hand-typed buy price, stops the whole run before it starts, loudly, because a run on a corrupt file would write corrupt rows into the record for every name.1515The runner catches per-name exceptions and continues ('one name never breaks it'); the configuration loader fails the whole run on a malformed entry or a provenance conflict.

Checked, unchecked, absent, not there

A figure's verification state travels with it. It enters unchecked. A person reads it back against the page named beside it and marks it checked, and the mark says which kind of check: against the regulator's structured data, across two separate documents, or against the same page it came from. Where a figure was searched for in a full report and is not there, the record says so, which is different from a zero and different from absent.1616Verification kinds: ruling E40 and vss/manual.py. Not presented: ruling E85. A zero needs a basis: ruling E25.

Two independent readings agreeing is what makes a figure the owner's. Where two readings disagree, the figure is re-extracted before anyone escalates it, because the first question about a disagreement is whether one reading was simply wrong.1717Ruling E104.

The rules are a document first

The method exists as a document before it exists as code. That is not a figure of speech. There is a file, the framework, which has been two pages since 2026-11-08, and there is a second, much longer file of rulings that amend it, each with a letter or a number, a date, and the statement of who ruled it: the owner or, since 2026-10-27, the Claude Code session on his behalf (E141). The code implements the rulings. It does not make them.

Three files, three jobs

  • The framework. The method itself: the gates, the hard kills, the valuation, the entry and exit discipline, the position sizing. It holds no dated holdings and no market data, so it never goes stale. It has been two pages since 2026-11-08. The rulings it names are archived in reference/archive/FRAMEWORK-EDITS-2026-11-08.md, and the rules that were struck are in reference/archive/STRUCK-2026-11-08.md.
  • The rulings. Every ambiguity, deviation and amendment, in lettered series. A for structural fixes. B for real ambiguities in the gates, each raised with a date and either decided or left open, and an open one is a question the owner owes an answer to. C for money-relevant ambiguities. D for consistency fixes. E for rulings on the tooling and, later, the substantive rules, E1 to E169 at the time of writing. F, the newest series, for the overview page. The file is appended to and never rewritten.1818reference/FRAMEWORK.md and reference/FRAMEWORK-EDITS.md. The rulings file opens: 'Each item is a place where a human reading it knows what you mean and a machine does not. I have not applied any of these — every one changes a rule, and they are your rules.'
  • The backlog. Things wrong in the code. Its first lines draw the line: "A rule question goes to the rulings file and gets a letter; a defect goes here and gets fixed. Nothing in this file is a decision the owner owes an answer to."1919reference/BACKLOG.md, opening.

The separation between the second and third files is the important one. A threshold that is wrong because the rule was wrong is a ruling. A threshold that is wrong because the code misread the rule is a bug. Mixing them lets a bug fix quietly change a rule, or a rule change hide inside a bug fix. Every threshold in the code cites the ruling that set it, and the screener's thresholds live in a configuration file where each one names its framework section, not in the code at all.2020config/screener_filter2.yaml, each limb citing its section or ruling; module docstrings throughout vss/ citing E-numbers as the authority for a threshold or a field.

Why the rule is written first

What happens without a written rule

NAME A is at 91, the buy line is 85, and the owner has just re-read the reports and feels more confident than the tier allows. The sensible thing is to move it to tier 1, which puts the line at 89. Then wait for 89.

The rule, E100

A change to a cushion is a dated ruling, and it takes effect at each company's next scheduled reassessment, never immediately. A buy line that crosses because the cushion moved arms nothing.

The whole point of writing a rule before the situation arises is that at that moment you have no stake in the answer. When the situation arrives, you do, and the rule you wrote earlier is the only version of you that was thinking clearly. The rulings say "written before the code" on their face, and the version-control history is treated as evidence of the order.2121Ruling E100 on cushion changes. The project brief §1.2: 'A ruling is written BEFORE the code that implements it, and many say so on their face.' A session that changes what the code does without a ruling behind it is, in the project's own words, doing the wrong thing.2222The project brief, §0.

Every ruling states its price

The rulings are written in an unusual register: declarative, absolute where a rule is absolute, and explicit about what a decision costs. A deletion names what was lost by deleting. A new requirement names what it will make slower. This site's decision boxes copy that shape from the rulings.

Rules that were withdrawn before the tests

A rules file that only ever grows is suspicious. This one records its own reversals, and they are the best evidence that the process works, because each one shows a rule being corrected by a dated decision rather than quietly bent.

  • A gate with two limbs that measured nothing. Two tests of stability in reported figures were found to be measuring currency and season, not the business, and to have changed no verdict across five companies. Deleted, with the sentence "not widened, not softened, removed".2323Ruling E30, 2026-08-25.
  • A debt cap loosened for the wrong reason. The first screener cap was set loose, on the principle that a coarse filter must not lose a name the careful reading would keep. Four days later a test run showed 72 names passing the loose cap and failing the method's own stricter test. Tightened, with the count in the ruling; the cap was later removed altogether (E168).2424Ruling E2 replaced by E44, 2026-08-26.
  • A buy-price formula superseded, with the old figures kept. When the cushion was moved from the low-case value to the base-case value, three buy prices already struck under the old formula were not silently recomputed. They were kept, marked as superseded wherever they print, and the old multipliers stay in the code with a comment saying why. The cushion is now information only (E168).2525Rulings E28, E32 and E90. The code keeps both multiplier tables: 'MBP_TIER_MULTIPLIER above is KEPT for the superseded paths' history.'
  • A citation that did not exist. The commit that built the phone tunnel cited a framework section as prohibiting a server. The section does not exist. The ruling that followed says: "The citation was asserted without reading the file and is withdrawn here." The commit message is immutable; the ruling is the correction of record.2626Ruling F1, 2026-09-13.
  • A requirement withdrawn the day it was ruled. Ruling F1 required the phone page to mark itself stale after 26 hours. Ruling F2, the same day, withdrew that requirement as unbuildable: a static file cannot compare its own age to the time it is read without a script, and the same ruling forbids scripts. The two limbs contradicted each other; one fell. What remained, the page stating its own age twice, was implemented that day.2727Ruling F2, 2026-09-13.

None of these is an embarrassment. Each is a rule meeting reality and being corrected on the record, with the cost named, by the person entitled to change it. A method that cannot show a withdrawal has either never been tested or is hiding something.

Decided
The framework is amended only by dated, numbered rulings made by the owner or, since 2026-10-27, by the session on his behalf (E141). A decision rule changes only on a backtest registered in advance (E164). Code cites the ruling it implements. A rule question and a defect go to different files.
Why
A rule written before the situation arises is written by someone with no stake in the answer. A rule that lives only in the code can be changed by a bug fix.
Cost
Change is slow. A threshold the owner knows is wrong stays wrong until a ruling is written, and some open questions have stayed open for weeks. The rulings file is many thousands of lines.
Try it
ls reference/run-records/ | grep CENTA

One company's run records. Every valuation ever made of it can be replayed. From the repository's root, after the install in the manual.

In three lines
  1. The tool measures; the owner decides. No part that runs on a schedule may write a decision.
  2. A value is printed only from a complete run record: every input and every choice, or nothing.
  3. The rules are a dated document first, and the code implements them.

Sources for this chapter

  1. Parasuraman and Riley (1997), Humans and Automation: Use, Misuse, Disuse, Abuse
    https://journals.sagepub.com/doi/10.1518/001872097778543886
  2. What each automated step may and may not do — read from this repository: README.md; reference/FRAMEWORK-EDITS.md E92, E93, E97; vss/refresh.py; vss/screenwatch.py; vss/overview.py
  3. Module layout and each module's stated scope — read from this repository: vss/*.py module docstrings; vss/__main__.py; README.md
  4. The record, the run record and the store — read from this repository: vss/store.py; vss/runrecord.py; vss/snapshot.py; vss/manual.py; vss/runner.py; reference/FRAMEWORK-EDITS.md E17, E25, E40, E49, E85, E104
  5. The framework, the rulings and the backlog — read from this repository: reference/FRAMEWORK.md; reference/archive/FRAMEWORK-EDITS-2026-11-08.md (A1–A3, B1–B48, C1–C4, D1–D2, E1–E169, F1–F2); reference/BACKLOG.md; config/screener_filter2.yaml

The slow way, made survivable. Built 2026-11-09 from the repository as it stood that day. NAME A and NAME B are fictional. One real company, CENTA, is followed through the book, with figures from its own filings and the owner's own trades. Not financial advice: one private investor's method, shared as it is.