Corrections: how the method became stricter

Corrections: how the method became stricter

CompanyGraph is not presented as perfect. It is a system that has learned to distrust itself in specific, inspectable ways.

Every claim CompanyGraph shows a reader travels a path — data, quality, claim, proof, evidence, trust, placement, surface — with feedback steering the earlier stages. This page is the steering stage made public: real defects, found by the system's own checks or by deliberate audit, each traced to the stage where the mistake entered, with what changed because of it.

Newspapers publish corrections because a record of fixed mistakes is stronger evidence of care than a claim of accuracy. Same idea here. These cases are dated, specific, and written the same way each time:

  • What went wrong — the claim a reader could have met, stated plainly.
  • Where it entered — the stage of the path that let it through, which is not always where it was noticed.
  • What changed — the actual fix.
  • The guard now — what catches the class of mistake, not just the instance.
  • What remains limited — because "fixed forever" is exactly the kind of claim this product refuses.

This list is representative, not complete. It grows as corrections happen. The current trust state of every observation — what these checks have and have not confirmed today — is the companion page, How far to trust each observation.

A ratio with no ceiling

What went wrong. A structural reading — "priced high relative to the business" — fired whenever price-to-sales exceeded a threshold, with no upper bound and no validation of the provider-computed ratio underneath. Broken inputs sailed through: companies with collapsed revenue figures showed price-to-sales values in the tens of thousands, and the system dressed those artifacts as a structural observation. Hundreds of companies were affected.

Where it entered. The quality stage. A corrupt input crossed into the claim layer with no guard — while sibling readings already had exactly the needed small-denominator guards. The failure class was known; this instance was simply never given the same protection.

What changed. A plausibility band now bounds the reading (June 2026), mirroring the sibling guards, and implausible provider ratios are gated at the source before anything is computed from them.

The guard now. Ratio plausibility checks at the data boundary, and a rule learned the hard way: a guard that protects one metric but not its siblings is a hole, not a policy.

What remains limited. Provider-computed ratios are still imperfect upstream. A plausibility band bounds what we will say; it does not verify the ratio.

Money compared in the wrong currency

What went wrong. Cross-company money comparisons — market capitalization, revenue, scale bands — were made in each company's native reporting currency. A company reporting in yen can show a number twenty times larger than one reporting in dollars while being a fraction of its size. One comparison surface shipped with this bug live; an audit then found several more places sharing the same root.

Where it entered. The quality stage again — units and currency are part of a number's meaning, and a comparison that ignores them is arithmetic on words. The bug hides on US-only data and surfaces the moment a foreign company enters, which is why it survived review.

What changed. Every cross-company money comparison now uses USD-normalized values (June 2026); native-currency values are for single-company display only, with their currency symbol. The doctrine is written down, and the known violations were fixed together as a class.

The guard now. Dedicated USD-normalized fields for comparison, and a standing test habit: never validate a comparison surface on US-only companies.

What remains limited. Native values are still shown in single-company contexts by design, so every new comparison surface must choose the normalized field. That choice is held by doctrine and review, not by a compiler — which means it can be gotten wrong again, and this page will say so if it is.

A name that said more than the formula

What went wrong. An observation named as a margin trend actually measured operating income rising — the formula never touched margins at all. Five interpretations consumed it under the false meaning. Every consumer downstream — matching, evidence, trust records, captions — reads the name, never the math, so a misnamed measurement is a false claim wearing a valid proof.

Where it entered. The claim stage, at naming time, years before it was caught. The doctrine — a name states what the formula measures, never more — existed; the older catalog predated its enforcement.

What changed. Reader-facing titles were corrected first; then ten misleading machine keys were renamed in place across every record that carried them (July 2026), so the stored keys stopped lying too. The rule that shipped with the fix: a key may be ugly, but it must not lie.

The guard now. Behavioral witness tests block a new observation from entering the catalog without declaring what it measures, and naming is treated as part of the formula's contract — reviewed like code, because it functions like code.

What remains limited. Renaming corrects the label, not the history of anyone who read the old one. And name review is a human judgment; the guard catches structure, not every shade of implication.

The square root that failed quietly

What went wrong. A hand-rolled square-root routine inside one observation's formula silently failed to converge on large inputs, inflating the result roughly 4.75× and producing confident false firings.

Where it entered. The claim stage — an implementation detail no review had caught, because the output looked plausible.

What changed. This one the system caught itself, exactly as designed: the independent proof stage recomputes observations through a different code path, and its recompute disagreed with the engine 215 times. The contradiction count named the bug; the routine was replaced with the standard library implementation and re-run (30 June 2026), and the contradictions went to zero. The observation's trust level then moved up — calibration goes both directions when the check earns it.

The guard now. The proof stage itself: a claim-maker is never allowed to certify its own claim. The recompute path shares no code with the engine, precisely so one bug cannot hide in both.

What remains limited. Even now, this observation does not appear in reports — its traditional name is a valuation verdict that the recompute does not support. Being computed correctly and being worth stating are different questions, answered by different parts of the system.

One truth living in two places

What went wrong. During a long migration, one set of financial figures lived in two places at once — the owning service and a "temporary" synchronized copy. The owning side fixed a currency-unit bug; the copy kept the corrupt values. At audit (July 2026), a large share of companies differed materially between the two — the same company could exist at two different market capitalizations depending on which copy a surface read. One downstream consumer even declined obviously absurd figures on its own — which is luck, not a guard: a plausible wrong number would have sailed through.

Where it entered. The ownership boundary. Duplicated truth has no contract: no compile error, no test, nothing that makes a fix travel from the original to the copy.

What changed. Sanity gates now bound the values, and cross-company benchmarks are mirrored from the owning source rather than recomputed from the copy.

The guard now. A named rule — no data lives in two places; any temporary duplication must carry a named cleanup phase — and deterministic plausibility bands where the duplicate still exists.

What remains limited. Honestly: the duplicate itself still exists. Collapsing it is a named, open piece of work, and until it lands this page lists it as the system's known weakest point. That is what this page is for.

True numbers, presented as current

What went wrong. Roughly 40% of covered companies had financial statements around eighteen months old — and company pages rendered their figures with no "as of" line and no staleness warning. Every number was genuinely reported; presenting them undated silently asserted they were current. An old true number shown as current is a false claim.

Where it entered. The surface stage. The dates existed in the data all along; they simply were not part of the rendered claim. Freshness tooling existed but was internal-only.

What changed. Company pages now carry a deterministic as-of line — the newest fiscal period on file — and a visible flag when the newest annual statement is eighteen months old or more (July 2026).

The guard now. "As of when" is treated as part of every claim, not metadata. Reports carried their as-of dates already; now the free pages hold the same rule.

What remains limited. The underlying statements are annual, and coverage depends on an upstream data provider. A staleness flag makes age visible; it cannot make data young.

Our words in the company's mouth

What went wrong. Generated reports attributed CompanyGraph's own model-written structural readings to the company itself — phrasing like "the company describes…" in front of text the company never said. On well-known companies the error was invisible, because the model's account tends to match reality. On a lesser-known company, the model invented a specific capability — and the system presented that invention as the company's own account of itself. A fabricated claim, wearing a real company's voice, on an indexable page.

Where it entered. The surface stage — the report writer's attribution default. The deeper cause: a claim's origin was not carried with the claim, so nothing downstream could check attribution against source. The rule against this existed in doctrine; no mechanism enforced it.

What changed. The attribution boundary is now explicit (July 2026): generated structural readings are attributed to CompanyGraph — "CompanyGraph reads this as…" — and "the company's own statements" is reserved for the company's reported figures. Previously generated reports were regenerated under the rule.

The guard now. Origin is part of the claim. Model-generated interpretation is labelled as interpretation wherever it appears, and the writer's instructions carry this case as the standing counterexample.

What remains limited. Generated interpretation is still generated. The fix makes its source honest; it does not make the reading infallible — which is why every reading also carries its support level.

The wrong company entirely

What went wrong. Fourteen listings whose ticker contains an ampersand — Mahindra & Mahindra and GE T&D India among them — carried another company's data. The data vendor treats an unescaped ampersand as the end of the symbol, so a request for M&M was answered with data for M: Macy's. The answer arrived with a success code, so nothing failed visibly. Through spring 2026 these pages showed the wrong company's financial statements, price history, dividends, business description, classification — and the generated description text built from all of it. One page described an Indian grid-equipment maker as the operator of the Channel Tunnel; another gave a major Indian automaker a department-store retailer's identity, down to the page title.

Where it entered. The data stage, at the very first request: the symbol was sent without URL-encoding, and the vendor's silent wrong-company answer was stored as if it were the requested company. Every later stage — quality checks, claims, proofs — worked correctly on wrong input, which is why none of them caught it: the data was internally consistent, just about someone else.

What changed. Request encoding was fixed in May 2026, which stopped new contamination but left the stored rows standing. A July audit of a currency inconsistency exposed them — the numbers only looked odd where two currencies met, and eight of the nine “anomalies” turned out to be entire wrong companies. Every stored wrong-company row was then removed from both databases: statements, price bars, dividends, earnings, statistics, descriptions, and the generated text built on them. What could be restored honestly was restored — the real company name, the exchange's real country; what could not was left visibly empty rather than filled with a guess.

The guard now. Every symbol is URL-encoded before it reaches the vendor, and the check that found the stored residue — the vendor's own echoed symbol must match the symbol that was asked for — is recorded as the standing detector for this class, independent of currency or magnitude. It works because the wrong answer honestly names itself.

What remains limited. The purged pages show less until the data connection resumes and refetches them — absence, deliberately, instead of wrong data. The last stored trace — a classification label whose storage required a value — was cleared the same night, once a schema change allowed an honest blank. And the affected observation results are being re-evaluated on the cleaned data rather than assumed fixed.

A yardstick that measured something else

What went wrong. One reading asks how much of a company’s operating cash survives its capital spending, and grades the answer against the range its industry peers occupy. The reading and the industry range were built from different quantities. The consequence was not a wrong number but a silent one: because of how the two scales related, the reading could not fire at all in three quarters of industry groups, no matter what a company’s cash flow actually did. Meanwhile the companion trust page listed it among the observations an independent recomputation had confirmed.

Where it entered. The yardstick, not the reading. Both sides were individually correct and both were checked — the recomputation genuinely reproduced the score, because it grades a value against the range it was handed. A recomputation can confirm that a comparison was carried out faithfully; it cannot ask whether the thing compared against measures the same quantity. That question had never been asked, because the two halves live in different parts of the system and agreed on a shared name.

What changed. The industry range is now built from the same quantity the reading computes: operating cash flow less capital spending, divided by operating cash flow. Its previous numerator was a provider-supplied free-cash-flow figure the system had already stopped trusting elsewhere, for the documented reason that it frequently disagrees with the components of the very statement it appears on.

Then the fix turned out to be half a fix. With the range rebuilt and the two sides computing the same quantity, the reading was still unreachable in more than half of all industry groups. The reason was not what either side measured but who it measured. The reading refuses to grade a company whose operating cash flow is negative — the ratio means nothing there — but the industry range was still being drawn across those companies, a third of the pool. For this particular ratio that is not a small distortion: it cannot exceed 1 while operating cash flow is positive, and has no upper limit at all once it is negative, so the excluded companies alone set a top of the range that no company being graded could ever reach. Eight further ranges turned out to share the same flaw. All were corrected — including the four where it happened to make no visible difference, because a rule that is only applied where someone checked it mattered is not a rule.

The guard now. A test compares, for every reading scored this way, the quantity the reading computes against the quantity its industry range divides, and fails the build when they differ. It was written before the fix and required to report the mismatches already measured — which it did, along with one false alarm of its own that had to be corrected first. A guard that cries wolf gets ignored, and an ignored guard finds nothing. It now checks the second question too, and was again required to name all nine affected ranges before any of them was touched.

What remains limited. A second range is still known not to match its reading, and it has deliberately been left alone: two sources for the same quantity disagree far more than their different time periods can explain, and the measurement does not say which one is right. Repointing it would be choosing a winner by assumption, so it stays recorded and unfixed until a third, independent source settles it. The new guard also compares which figures a range reads, not the arithmetic between them — so it would not notice a subtraction written where an addition was meant. And the deeper limit is the one this correction demonstrated on itself: the first guard was written against the mistake that had been found, and caught only mistakes of that shape. A second kind was sitting underneath it and took a second look to see. There is no reason to believe there is not a third. And the corrected range only reaches a reader after the next scoring pass; until then the affected pages show the old result.

A number we refused to print, used to make a claim

What went wrong. On twenty-seven companies a page withheld its Gross Margin figure — our own quality check had judged the data provider's value too low to publish — and then, further down the same page, showed a reading built by dividing by that very number. The reading was “return on equity is high relative to gross margin”, and because the suspect value sat in the denominator, the error is what produced the reading: measured against the gross profit and revenue printed on the same page, the ratio the page acted on ran between four and a hundred and seventy-five times too high. Nine of the fourteen we could check both ways would not have qualified at all.

Where it entered. At the seam between two correct decisions. The quality check that convicts the provider's margin runs where pages are assembled; the scoring engine deliberately reads the raw data instead, because an engine graded on pre-filtered numbers would only ever prove it agreed with the filter. That independence is worth keeping. What nobody had noticed is that it also means a value withheld from a reader can still power a claim shown to that reader.

What changed. The reading now computes the gross margin itself, from the gross profit and the revenue reported for the same period, instead of taking the provider's own margin figure. The independent check that re-derives the score was repointed in the same change — and that half matters more than the first, because it had been reading the same field as the claim it was checking. It reported “520 of 520 reproduced”, a perfect score, which was true and meant only that both sides had looked up the same number. Two calculations that share an input cannot check each other.

The guard now. The provider's margin field was deliberately not banned outright: measured across the whole list, it differs from the recomputed margin on about a quarter of companies, and that is mostly a difference in how the figure is defined rather than an error — banning it would have moved another reading for a reason that does not apply to it. Instead the recomputed margin is offered alongside, and a test now pins which of the two this reading divides by, so pointing it back is a build failure rather than a quiet edit.

What remains limited. Recomputing does not make the margin true; it makes it agree with the two figures printed beside it. Both come from the same data provider, and where it fills gross profit with revenue outright — which we found and blocked separately the same day — the recomputed margin inherits that, on four of the companies in scope. More honestly still: this was found by reading a page and asking why a number was missing, not by any check we own. We do not have an instrument that finds the general case, where a claim and its proof quietly share a source.

Two currencies in one row

What went wrong. A company's headline figures are published together in the currency its shares trade in. When a company keeps its accounts in one currency and trades in another, those figures have to be converted first — and one of them, a common measure of operating earnings, was arriving unconverted while everything beside it had been converted. The number published was the company's own reported figure, correct in the currency of its accounts, presented as though it were a figure in the currency of its shares. On the worst examples that made a company's earnings look roughly a hundred and fifty times larger than they were. The same figure is also the reference a valuation multiple on the same page is checked against, so it did not only mislead on its own line: it set the standard by which the number beside it was judged.

Where it entered. Not in a calculation of ours; the figure is published as supplied. It entered at an assumption — that a single row of figures is denominated in a single currency. Every check the system had was asking whether a value looked implausible on its own, or contradicted its neighbours inside that same row, and a figure in the wrong currency looks entirely ordinary by both tests. It contradicts nothing, because what it contradicts is a fact the row never states.

A check we already had was pointed the wrong way. The system does compare these summary figures against the company's own annual accounts, and records them when the two disagree by more than a wide margin. That check could not have found this. An unconverted figure matches the accounts exactly, and matching is what the check treats as health. On the very same companies it instead flagged the neighbouring revenue figure — the one that had been converted correctly — because being converted is precisely what makes it differ from the accounts. The check was not merely blind here; it was reading the sound number as suspect and the broken one as sound. Agreement with a source is not evidence of correctness when the two can be denominated differently.

What changed. The figure is now withheld where the company's own annual accounts show it was left behind. The accounts establish what conversion was applied to the row, because the revenue beside it carries that conversion plainly and this figure carries none of it. It is refused rather than corrected: the evidence shows the number is in the wrong currency, but not what the right number would be, and the same source has elsewhere supplied one company's accounts under two different currency labels on different days. Rescaling would be a second claim built on top of a broken one.

The guard now. The refusal is written once and applied at both points where these figures leave the system — the page that displays them, and the converted projection behind the screener and the peer ranges — with a test that fails the build if either one quietly stops asking. A second test pins the boundary of the rule itself, because the first version was written as a plus-or-minus tolerance where the quantity being judged is a ratio. The two forms read as the same rule and are not: the tolerance reaches further on one side, and it had already refused four companies that the measurement behind the rule never examined. That was caught by counting the rows the running code actually touched, not by rereading it.

What remains limited. The detection depends on the two currencies being far enough apart to be visible: a conversion is only recognised once it moves a figure by more than threefold, so a company keeping its accounts in one major currency and trading in another of similar value is invisible to this check, and there is no way to count what is being missed. It examines only companies whose earnings measure is positive on both sides; the loss-making equivalent is left alone rather than handled by a rule nobody measured. It needs the company's annual accounts to be present, and stands down rather than guessing where they are not. And a figure already used to build a peer range stays in that range until the range is next rebuilt.

A reading and its yardstick, measured from different sources

What went wrong. One reading asks what share of a company's revenue arrives as operating cash, and grades the answer against the range its industry peers occupy. The reading took both figures from the provider's rolling twelve-month summary. The industry range was built from the companies' filed annual accounts. Same ratio, same name, two different sources — and on the quantity that matters here the two sources do not agree: they differ on whether operating cash flow is positive or negative for one company in five. This was the most frequently shown of all the peer-graded readings, so the mismatch reached more pages than any other of its kind.

Where it entered. In neither figure. Both were computed correctly from what they read; the reading and the range were simply written in different places, at different times, by people asking different questions, and they agreed on a NAME. Nothing compared the two expressions, because the check that grades this reading recomputes the score against the range it is handed — it can confirm the comparison was carried out faithfully, never that the thing compared against measures the same quantity from the same place.

Why it took a second attempt to fix. The first time this was examined, the honest conclusion was that we could not act: the two sources disagreed far too much for the difference in period to explain, which proved one of them wrong without saying which. Choosing the more convenient one would have been picking a winner by assumption, so the mismatch was recorded, left in place, and the reading kept running. That is the uncomfortable part worth stating plainly: for several days we knew a published reading rested on a disagreement we had not resolved, and we left it visible rather than quietly picking a side.

What changed. A third, independent check settled it. A cash flow statement contains the individual lines that make up its own operating cash flow, so those lines can be added up and compared with the total the same statement reports — a test that needs no second source and cannot be wrong in the same direction as the thing it checks. The filed figure passes that test on 96.9% of companies, and on exactly the companies where the two sources disagree it passes on 98.8%: more coherent where the argument was, not less. The rolling summary figure has no comparable check of its own, and it is not a copy of any other figure we hold — every candidate was tested. So the reading now takes both of its figures from the filed accounts, the same place its industry range comes from, and it was renamed so that its name says which period it describes.

The guard now. A test compares, for every peer-graded reading, the quantity the reading computes against the quantity its industry range divides, and fails the build when they differ. It was written before either repair and was required to name the mismatches already measured. Both are now repaired rather than parked, so the list of known exceptions is empty — and the same test fails if an empty list ever hides a real disagreement, so emptiness cannot be faked by deletion.

What remains limited. The independent check validates the filed figure; it does not prove the rolling summary wrong. A twelve-month window over a volatile quantity may legitimately differ from the last closed year, including in sign. What justified acting is not that one figure was convicted but that only one of them can be checked at all, and a yardstick should be built from the side that can be checked. The change also costs coverage: the reading now appears for about 1,600 fewer companies, and around 275 lose it because their two statements do not yet report the same year — a company can be silent here simply because one filing has arrived and the other has not. And every reading still carrying the old name disappears from its page until that company is next scored, which happens on a rolling basis over hours, not instantly.

A window named in a unit we never read

What went wrong. CompanyGraph averages weekly price bars — that is the only price data it holds. Several readings nonetheless described their windows in days, the unit the conventional versions of these indicators use. One of them was wrong by more than a label: a reading that compares a 50-week average against a 200-week one — roughly one year against four — told the reader its periods "correspond to the conventional 'Golden Cross' periods". Those are 50 and 200 days, about ten and forty weeks. The numbers matched; the quantities did not, and a reader was handed a four-year average described as the familiar one-year one. Five further sentences named a "200-day moving average" for readings that average forty weekly closes — there the span really is about the same, but it still described a calculation we do not perform, on a chart where a reader could not check it.

Where it entered. The claim stage, in prose sitting beside a correct number. Every one of these readings computed exactly what it was configured to compute; no score was affected. That is what makes the class expensive: recomputing a claim proves the arithmetic, and there is no arithmetic in a sentence. The description is the one thing on the path that nothing downstream can check.

What changed. The crossing reading now states its real windows, says plainly that the daily form is not computed here, and drops the folklore reading — that a "golden cross" predicts gains — for what a crossing was measured to coincide with. Across 11,074 companies and 20,747 crossings, the crossing week carries about 12% more trading volume and a 6% wider price range than weeks where the two averages sit just as close and do not cross; it peaks in that week and is back to normal within about two months. Weeks that come close without crossing show no rise at all. That is the whole of the effect, and it says nothing about what follows. The five "200-day" sentences now say forty weeks.

The guard now. A build test fails when any sentence a reader can meet states a period in days without naming the week equivalent beside it. Its limit is worth stating, because it is the sentence that started this: the test would not have caught it. That claim asserted a match with an outside convention without ever writing the word "days", and knowing what that convention is takes knowledge no test of ours holds. The guard catches the general shape — a day figure put in front of a reader — not the specific mistake that revealed it.

What remains limited. A wrong description of a right number stays the hardest class here to catch mechanically, and this guard covers one shape of it. A reading can still be described in the correct unit and the wrong terms. The internal identifier for the forty-week readings also still ends in "200d" — honest about the convention it approximates, misleading about what is computed — and is left as it is because stored records are joined to it.

The recurring shape

Every case above, one anatomy: a claim outran its evidence at a boundary that carried no guard. The ratio had no ceiling, the currency was not part of the number, the name outran the formula, the arithmetic certified itself, the copy had no contract, the date was not part of the claim, the origin was not attached, the yardstick measured a different quantity, a proof shared its input with the claim, the window was described in a unit the engine never reads, the symbol was not encoded — so a success code delivered someone else's company.

And one meta-lesson sits on top, found by auditing the audits themselves: several of these rules were doctrine without mechanism — true in the documentation, unenforced in the pipeline. A rule that lives only in prose fails exactly when nobody is looking. The corrections above each ended the same way: the rule was moved from prose into a gate, a test, a band, a boundary.

None of this makes the current system correct. It makes it corrected — so far, in these specific ways, with the checks now standing. The live state of what those checks currently confirm is the trust ledger; the path every claim travels, including the steering stage that produced this page, is From data to checked claims.