Dados B3 › Transparency
Transparency
Transparency — how you check the numbers yourself
Data version 2026-11-09-ancora-do-fre · page generated 2026-11-12 08:01 UTC. If these do not match /saude, you are reading a cached copy.
What is checked, before every publication — 1298 automated tests, collected live
1167 are DATA invariants (does the number tie out?) and 131 are product tests — access gate, navigation, language, AI connector. Calling all 1298 'invariants' would be inflating it: the split is checkable in the list itself. Not a hand-picked highlight reel: every test below was found by introspecting the actual test files just now, the same way pytest --collect-only would. Grouped by what it protects against, not by which file it lives in.
Level 1 — Integrity: does the data tie out? (1028)
I-X05— Every quote is positive and within a possible range.I-X06— No daily jump above 15%.I-X07— The US$ return must equal the R$ return minus the FX move — the identity the calculation itself asserts.I-X10— The `n` of each lag is EXACTLY the overlap between the flow and the shifted return — not one pair more.I-X11— No `n` may exceed the length of the shorter series.I-X12— The permutation preserves each group's size AND shuffles blocks that are contiguous IN TIME.I-X13— Attacks I-X12 with three defective implementations and requires it to fail two and pass one.I-X14— A Central Bank (SGS) window that fails is tried again before it becomes a gap, and each failure logs its REASON. In the 26/09/2026 build the 2008–2017 window came back 'unavailable' and the exchange-rate series had a nine-year hole; the same query answered minutes later. Without a retry a hiccup of the API is a hole until the next Sunday; without the reason, 'unavailable' could not tell a hiccup from a changed source.I-B34— `situacao` may only be one of the four declared values — an open domain would become free text, and free text in a field that exists to separate 'confirmed' from 'unknown' reintroduces the ambiguity it was built to remove.I-B35— A company marked `com_proventos` really has dividends, and one marked `sem_proventos` has none — the stamp is recomputable from the table it describes, so it cannot lie in the dangerous direction: claiming confirmed absence where the fetch failed.I-P16— Every stored unit composition respects the maximum error and minimum sample the inference itself declares — the difference between 'the function applies the rule' and 'what is in the database went through it'.I-P17— COUNTER-PROOF from B3's RAW file: recomputes `a×ON + b×PN` against the unit price without going through the function that inferred it. A wrong pair still minimises some error; what it cannot do is explain the price within 2% across thousands of sessions.I-P18— A company whose primary ticker is a unit WITH a known composition can no longer carry the not-adjusted flag, and its price-to-book must land in the same plausible band as everyone else — the outcome test that proves the basket was applied, not merely discovered.I-B57— The ÚLTIMO ordering is the ORIGINAL filing and PENÚLTIMO is the restatement, and the proof is structural rather than remembered. The oldest year in the base exists only as PENÚLTIMO, because it reaches us through the following year's file, the first one we download; and the most recent year exists only as ÚLTIMO, because next year's file has not been published. If that asymmetry ever disappears, the meaning of the two columns has changed and everything depending on it must be revisited.I-B58— On the restatements page, the first column serves the ORIGINAL filing and the second serves the restatement. This was published inverted: the largest-adjustments table showed one holding company's 2012 total assets rising from 39 to 364 billion when the movement was the opposite. Counts and the absolute difference never depended on the ordering; direction did, and a wrong direction on a page whose whole argument is rigour is worse than no page.I-E06— The ticker returned by the bulk dump is the same one `ticker WHERE principal=1` would give — never `empresa.ticker`, which is hand-curated enrichment covering only 20 companies and left 94% of the paid indicators dump with a blank ticker.I-E07— The `datas_suspeitas` dump dataset is EXACTLY what `eventos.datas_suspeitas()` computes internally — same source, two formats. If they diverged, the dump would be lying about what the pipeline itself treats as suspicious.I-E08— The counter-proof of the whole finding: BBDC3, ITSA3, FLRY3 and ALUP11 carry no suspicious date at all — a consumer that excludes a whole company instead of a single date would drop them for no reason. The problem was never too little confirmation; it was the wrong granularity in whoever consumed the data.I-B01— Total assets == total liabilities (account 2 already includes equity).I-B02— Gross profit == revenue + cost (cost arrives negative).I-B02b— Pre-tax income + tax == income from continuing operations (3.09).I-B03— NOPAT = EBIT × (1 − tax rate) with the rate in [0, 45%], and ROIC is only published when average capital is positive.I-B04— The database universe is EXACTLY what the published rule produces right now — recomputed by a route independent of what the ingest wrote.I-B04b— The 20 hand-resolved companies keep their ticker in the database — enrichment must not be lost in a bulk re-ingestion.I-B27— ROA = net income / average assets, and total assets are always positive (I-B05 already guarantees 0 <= assets < 1e14).I-B06— A hole in the MIDDLE of a company's series is a loading defect — data that should be there went missing.I-B09— Gross margin recomputed straight from the RAW filing (bypassing the standardised fact) matches the published indicator.I-B26— The Piotroski F-Score is a sum of 9 binaries: it can only be an integer from 0 to 9. Anything else means a criterion leaked a non-boolean.I-B28— Current ratio recomputed straight from the RAW filing (account 1.01 of the balance sheet over 2.01, bypassing the fact) matches the published indicator.I-B30— Every company with a fact has a macro sector, and the sector is one of the 10. A new registry sector that matches no rule leaves the company sector-less and fails this test — instead of landing silently in the wrong bucket.I-B21— A dividend is a cash inflow: value > 0, always.I-B22— Type within a closed domain and ex-date in ISO YYYY-MM-DD — what the endpoints and the dividend-yield calculation assume.I-E01— `mult_qtd` is recomputable from B3's RAW field by the published rule, and `mult_grupo` is the product of the events sharing the same day and ISIN.I-E02— Every event marked `conferido=1` genuinely passes the check against the COTAHIST price — the stamp cannot be more generous than the rule it claims to apply.I-E03— A verified event carries no rejection flag, and a rejected event is not marked verified.I-F01— PRODUCT INVARIANT: the book value per share reported in the filing equals equity ÷ shares from that SAME filing.I-F03— Counter-proof: P/B recomputed here (price ÷ book value per share), outside the fiis module.I-F04— The ISIN cross-match (COTAHIST ↔ filing) resolved a ticker for a reasonable mass of funds, and most of them have a P/B.I-X01— `net = inflow − outflow` in EVERY month.I-X04— The reference period is always 'YYYY-MM'.I-L01— When a fact's origin is composite ("DFC_MI:6.02.01+6.02.02"), the sum of the referenced lines equals the fact's value.I-L02— Every account cited in a fact's origin genuinely exists in the raw filing it points to.I-L05— End-to-end counter-proof: for a sample of known companies the lineage delivers indicator → input → account line → document with publication date → CVM file, and the line's value matches the fact.I-B19— The stored trailing-twelve-month profit matches the TTM recomputed HERE, straight from the annual and quarterly fact tables, bypassing the pricing code.I-B20— Every year from 2010 to the current one has prices in the database.I-B29— FCF yield = (CFO + capex) / market cap, recomputed straight from the fact (CFO 6.01 + capex 6.02), bypassing the pricing code.I-P01— No trading code is tied to more than one company.I-P03— In the window where the old and the new code coexist, the company has ONE price per day — the primary one's.I-B13— The same invariant as I-B01 (assets = liabilities), at quarterly granularity.I-B10— Numbers checked by hand, one by one, against the computed output.I-P21— A multiple whose price came two or more years after the fiscal year always carries a flag. The regulator's receipt date belongs to the LAST version of a filing, so when a company re-files, the date moves forward and our first-session-after-publication rule lands years past the year being measured. Anyone filtering on 'no flag' must not receive one year's profit matched against a price three years later.I-P22— Put the defect back and the stale-pair mark has to appear. Without this attack the rule could never fire and the invariant above would pass on an empty set, which is exactly how a checker is born decorative. It also checks that an existing flag is preserved rather than overwritten, and that the ordinary case — a filing published the following year — is left unmarked.I-P06— low ≤ open, close ≤ high, in EVERY row.I-P08— On a date where the company traded under TWO codes, all five prices come from the winning code — not one from each.I-P09— Reference prices, checked in ABSOLUTE value.I-P10— Financial volume ≈ quantity × average price — the anchor that CROSSES field slices.I-P11— Volume, quantity and trade count checked in ABSOLUTE value.I-P13— No primary ticker has far less traded value than a still-active sibling of a DIFFERENT share class — the property the liquidity correction exists to guarantee, asked of the database rather than trusted from the code that wrote it.I-B55— No company the regulator lists as non-active sits inside our universe. The page stating what this product does NOT fix publishes that intersection as zero, so if anyone ever loosens the ACTIVE-only filter on the company register, the published claim silently becomes a lie while nothing else breaks. This invariant is what turns a written promise into a checked one.I-B56— The measured size of our survivorship bias cannot quietly become zero. If ingestion of the company register fails, the page would publish 'no companies left the market since 2010', and a limitation that disappears is worse than a limitation admitted: the reader would conclude the base has no survivorship problem at all. The floor is deliberately far below the real count so only an ingestion failure trips it.I-B102— A company the regulator marks as non-active leaves the database: its company row and every per-company table go, the survivorship snapshot (years of annual filings) and the regulator's stated reason stay on the outside list, and the count of removed rows is recorded. A company outside the rule WITHOUT the regulator marking it non-active is NOT deleted; an empty universe deletes no one.I-B103— The shrink guard forgives the drop in counts that is EXACTLY a cancelled company leaving, and nothing else: the exit must be recorded, the regulator must mark it non-active, its rows in the previous database must equal the recorded ones and none may remain in the new one; any other drop is rejected as before.I-B104— The build-machine guard prints the cancelled company, the regulator's reason and the rows removed per table, discounts only exits made AFTER the build now online, and still aborts on any other shrink.I-B36— The published trail points at an account that EXISTS in that company's filing. Born from the public challenge, the first time it ran: provenance declared '3.11 (consolidated profit)' for every company, and 3.11 does NOT exist in Itau's statements — banks use a shifted chart and their profit sits in 3.09. Worse, the code varies between banks (Bradesco 3.11, Itau 3.09, Banco da Amazonia 3.13), so ingestion resolves those items by description and records the real account. The data was always right; the TRAIL was wrong — and the trail is what this product sells.I-B59— The most recent trading session in the base has to be recent. The battery had 266 checks on the CONTENT of the numbers and none on their AGE — nothing asked when the data was from. It cost 25 days in production: prices stopped on 10 August and the site kept serving them into September while the weekly rebuild reported ok every Sunday. Stale prices are not WRONG prices, which is what makes them slip through content checks; each row is still right for its own day, and it is the set that lies when it is presented as the series up to today. So this test reads no value at all, only the clock. The threshold is deliberately generous, because a failed build freezes everything, including what was fine.I-B60— The health endpoint publishes how far the series reaches, not just how many prices it holds. A count measures volume, not freshness, and it was the count alone that let the series freeze quietly: 869,232 prices looks healthy even when it has not moved in a month. This keeps the date published rather than internal, and checks that the limit shown is the limit actually enforced.I-B61— A balance-sheet account that drops about a thousandfold and comes back is flagged, and so is the return-on-equity built on it. The older detector finds the year where DOZENS of accounts are off scale, and everything downstream leans on one premise: if all of it is off by the same factor, the factor cancels in a ratio, so ROE and margins stay valid and get published. When a single account slips out alone that premise breaks — the profit is right and the denominator is not. One retailer filed equity of R$ 467 thousand between R$ 517 million and R$ 284 million, two accounts out of twenty-eight, and its ROE went out at -201.2% with no caveat when the truth was about -106%; because ROE uses average equity, the following year was contaminated too. A wrong number served as clean is the worst defect here, because the reader has no way to suspect it. An earlier version of this test only read the finished database and passed even with the flagging code deleted, so it now drives the rule itself over a synthetic series.I-B62— Equity on its way through zero is not mistaken for a scale error. A telecom holding shows equity of R$ 1.8 million between +R$ 116 million and -R$ 219 million: not a unit slip, a trajectory crossing zero on the way negative. A detector that accused that would be crying wolf, and a detector people learn to ignore costs more than none. Honest note: the sign guard was credited with protecting that case, and attacking it showed the case is actually excluded by the hundredfold minimum drop — across the whole base the sign guard currently prevents zero cases. It stays because the rule is right and data changes, but it is exercised against a synthetic trajectory so that removing it still fails this test.I-B63— The quarterly series publishes only what the quarterly filing publishes: Q1, Q2 and Q3. The regulator's interim filing never carries a standalone fourth quarter — it comes out of full year minus the nine months, an identity that closes by construction and that a direct competitor computes. We do not, because a figure we derived would enter the same list as the figures the company reported, carrying the error of two filings and erasing the line between what was filed and what we calculated, which is the line this whole database exists to hold. The rest of the test is the old rule: a ratio is only valid between accounts of the same vintage and the same consolidation perimeter, and a financial institution gets no gross or operating margin because that chart of accounts has none.I-B63b— Quarterly return on equity is twelve months of profit over the AVERAGE controllers' equity of the same twelve months, and it says so when there is no pair to average. A trailing profit against an end-of-period snapshot compares a year of flow with one photograph; the annual series already solved this with an average and declares the first year of the series. The window that matters is the SAME quarter of the previous year — not the immediately preceding quarter, which would average three months against twelve months of profit.I-B64— The decision to rebuild the database is read from disk, not held in the process's memory: a week already rebuilt successfully is not rebuilt again, and outside the early-morning window only an unfinished build from that same week is resumed. Every Sunday deploy used to rebuild the whole database, because the memory of “this week is done” was a local variable that resets with the process. Four deploys on one Sunday started four rebuilds, each killing the last, and on a single-CPU instance the visitor paid for it: a median of 1.9 seconds, a 90th percentile of 7.6 and peaks of 13 — including on the robots file, which is a constant and touches nothing. Nothing flagged it, because the panel read “running”, exactly what a healthy rebuild reads. The window itself was already promised in the loop's own docstring, which said Sunday small hours while the code fired at any hour of Sunday.I-B64b— Reading the build state never breaks the loop that it watches. A database from before this change simply has no record of the last successful build, and the behaviour then has to fall back to rebuilding — the safe side — rather than raising an error that would kill the maintenance thread. The same rule already applied to writing the state: the alarm must not be what burns the house down.I-B65— An indicator that a whole class of company never has is declared as not applicable, rather than rendered as a dash that reads as “we could not get it”. A bank's page showed sixteen years of blank return-on-invested-capital: the exclusion list named gross margin, operating margin, net-debt/EBITDA and current ratio, and forgot that one. The JSON route already said the right sentence; the page did not, and the page is what almost everyone reads. The test measures instead of trusting the list — an indicator with zero rows across all twenty-one financial companies must be declared, so the list cannot age in silence when a new indicator arrives.I-B66— The nightly price update looks for exactly the business days missing after the base's last session, up to today — and today only after the hour at which the exchange publishes its daily file; outside the night window it does not try. Prices used to enter only in the Sunday rebuild, from the exchange's ANNUAL file, which itself arrives days late: the base built on 9 September had 3 September as its last session, so the price was anywhere from zero to six days old and nothing said which. The exchange also publishes a daily file around 9 pm Brasília time; that is what now enters every night, and the price served by the API, the connector and the dump becomes yesterday's or today's close. The rule is pure on purpose, so it can be attacked without network: asking for today before 9 pm is a guaranteed 404, so is Saturday, and skipping the Monday after a Friday holiday is a hole forever.I-B66b— The nightly run ingests the daily file into a COPY of the base, checks it, and only then swaps the live base. A malformed file (minimum above close) is refused and the live base does not change by a byte; a night without a file (holiday, or not yet published) does not touch it. Never replacing good data has been the weekly build's rule from the start, and a routine that runs every night has more chances to go wrong than one that runs every Sunday. The ingestion functions commit midway, so writing into the live base would not be atomic; the copy restores atomicity, and the check is the daily gate: minimum ≤ close ≤ maximum on every new row (a shifted slice of the fixed-width layout produces a plausible number, not an error), last session advanced, nothing shrank, version stamp intact.I-B66c— The cache key of the screener, the today page and the ticker page changes when the base FILE changes, not only when the pipeline version changes. It used to be the version alone, and that does not change on Sunday: the weekly rebuild swaps the file and keeps the version, so the screener and the today page kept serving the previous week's list until the next deploy. With prices entering every night that would become a stale number every day, served from cache and looking fresh. The company page already keyed on (version, mtime) and did not suffer; the rule now lives in one place.I-B66d— Swapping the production base requires the write-ahead log of the target to be consolidated and empty; with pending frames beside it, the swap is refused. A plain file rename left the OLD log next to the NEW base. The base runs in WAL mode: if the log still holds unconsolidated frames (one open reader when the last writer closed is enough), SQLite applies them onto the new base at the next open — an old page on top of a new table. That is silent corruption no content invariant would see, and the routine that records build state in the live base is exactly such a writer. The swap now checkpoints and truncates the log first and refuses if it could not, for the nightly run and for Sunday alike.I-B66e— A nightly run with no new session to ingest (weekend, holiday, file not yet published) is recorded, and /saude no longer reports it as `nunca_rodou`: with no successful run yet but at least one run, the state is `sem_novidade` and `ultima_rodada` carries the time and result (ok, sem_novidade, recusado or falhou). A run without news never overwrites the last `ok`. The block also explains the state, names `cobertura_de_preco.ultimo_pregao` advancing as the real proof of freshness, and gives the next execution window in UTC. Born on 2026-10-19 from a client who monitors /saude and saw `nunca_rodou` while the routine was running.I-B66f— `nunca_rodou` appears in /saude only when there is no record of any run at all: `ultima_rodada` is then empty (no invented time or result), the explanation says there is no record, and the execution window matches the one the loop uses. A label must not claim a run that never happened.I-B67— The company profile (description, listing segment, headquarters, share registrar, auditor, control) comes from each company's MOST RECENT registration form; the listing segment is the one of the PRINCIPAL ticker; the address is the head office; the registrar is the one currently acting. An older version never overwrites a newer one, and the versioned derivative round-trips without losing a field. The first screen used to say the tax id and the regulator's code and nothing else. Comparing with a competitor made the gap obvious: what the company does, where it is, since when, in which segment it is listed, who keeps the share register, who audits, who controls. All of it was already in the files downloaded every week and simply not read. The description is the text the company ITSELF files, and the page says so; the rule choosing the version matters, because the form arrives with every version of the year and a reordering of rows must not swap the profile for an old one.I-B68— Besides the price, the nightly update asks the exchange for stock dividends and fund distributions, and the regulator for dividend filings (the future ex-date), on the same copy and before the yield is recomputed, and records how many came in. A source that is down does not bring the run down nor erase what already exists. The calendar was being born without a future: stock dividends only entered in the Sunday rebuild, so the newest announcement in the base was 18 days old and there was not one future ex-date for a stock, while funds had 138 scheduled payments. The build's rule applies here: an external source is best effort — if it fails, what was there stays, with the reason recorded.I-B69— Boot applies COLUMN migrations, not just CREATE TABLE IF NOT EXISTS — which never adds a column to a table that already exists. The migrations used to live only in the ingestion path, which runs on the weekly rebuild, so a new column could take a week to exist in production while the page depending on it kept saying 'not measured yet' with the data already in the repository. And reloading a versioned file cannot depend only on 'is the table empty?': that table was already full with the old columns, so the condition said no and the new portrait would never enter.I-B70— With the ETF table already filled, boot ADDS the rows the versioned file has and the database lacks, and leaves existing rows untouched. The old rule skipped the file entirely whenever the table had any row, so the history the backfill brought — the August 2025 anchor that enables the 12-month return — sat in the repository without ever reaching production, and the page kept showing zero funds. The house rule still holds: boot never overwrites what the build or the nightly run wrote. Completing is INSERT OR IGNORE, never REPLACE.I-B71— `dados_b3.leitura` is the stable internal reading interface: it only reads, takes the connection from the caller, imports no page or style module — and what it returns MATCHES what the company page shows, value by value and flag by flag. Sector medians are the same calculation; the company's own range uses only clean values; the universe distribution's 'fraction above X' equals a direct count. Without it, any new layer would be welded to the 1,133-line function that builds the page.I-B72— Rebuilding the database from scratch PRESERVES the snapshot of the companies that left the exchange (years of DFP filed, tickers, trading sessions), both in the table and in the re-exported versioned file. The build creates the table from the CVM registry (five columns) and rewrites the versioned file with what the table has; the snapshot comes from a manual survey that writes only to the file, and almost a third of the companies with a snapshot are not even in the registry of inactive companies. The first full rebuild after the survey rewrote the file with no snapshot, the build gate loaded that file and I-H11/I-B69 failed on every attempt — production stayed pinned to the previous data version for two days. The registry rules the five columns it knows; the survey rules the seven of the snapshot; a row only the survey knows enters whole.I-B73— The Selic target (Central Bank series 432) enters as continuous validity periods: a missing day breaks the period instead of stretching the previous rate over the gap; the fiscal-year average only comes out with the whole year covered; a source window that fails writes nothing; the versioned derived file is never rewritten with fewer days than it already covers; the boot loads it into an empty database; and the database covers every closed year with an indicator. A wrong average that looks right would decide the ROIC band of hundreds of companies.I-B74— The company page reads the indicator series, the multiples, the quarterly series, the restatements and the dividends through the data layer (first slice of the migration) — and what it receives EQUALS a direct database read, value and flag, for 20 companies of different kinds (bank, insurer, non-December fiscal year, withheld multiple). During the migration the HTML of those 20 pages, in Portuguese and English, came out byte-for-byte identical before and after, apart from the time stamp.I-B75— A registered restatement is the one of the aggregation the published figure uses that year (consolidated, or individual when the company does not consolidate), with the aggregation IN THE KEY — never again the individual statement overwriting the consolidated one. The real case, reproduced: Bradesco 2024, whose consolidated total assets matched across the two filings while the individual comparative came empty, and the old key recorded 'total assets from R$ 1.69 trillion to zero'. Measured on 26/09/2026: 9,473 → 8,660 rows, and 25 of the 356 traded companies move from a yellow to a green flag.I-F09— When ingestion receives a REIT monthly report with a higher version than the stored one, every published field that changed is recorded with before, after, both versions and both delivery dates — before the upsert overwrites it. Same version records nothing; a lower version neither overwrites nor records; a higher version with no published change records that nothing changed.I-F10— In a from-scratch build the “before” lives in the PREVIOUS database: every report whose version went up between the two is recorded field by field; with no previous database, or one without the report table, it returns zero and does not raise.I-F11— The record is a versioned derived file: the build loads it BEFORE ingesting, compares with the previous database and only then re-exports, never with fewer lines than the file has; the round trip keeps every field. The page reads on a read-only connection and accepts a database without the table.I-H09— Raw downloads never land inside `_build/`, the directory wiped at the start of every rebuild. This cost hours: the rebuild runs a subprocess with DADOS_DIR pointing at _build, and without ZIPS_DIR declared the naive default put ~3GB of raw files exactly there — every restart erased them and re-downloaded from scratch, so no attempt ever finished. The environment variable fixes it, but depending on one is fragile: deleting it was what created the loop. This test makes the DEFAULT safe, so configuration is a tweak and not a requirement.I-H10— The rebuild declares TMPDIR and SQLITE_TMPDIR outside /tmp. SQLite writes temporary files when creating indexes and sorting large tables, looking for a directory in this order: SQLITE_TMPDIR, TMPDIR, /tmp. Neither of the first two existed, so it fell through to /tmp — capped at 2GB on Render. The build died at the very end every time, right after ~550k REIT prices, during index creation: instance killed, restart, full ingestion again, never finishing. The first suspicion — the raw downloads — was wrong: moving those to disk changed nothing, because they were never the consumer at that stage.I-H11— A database missing the derived tables gains them at BOOT, not through a full rebuild. Covenants and the excluded-companies list depend on nothing the rebuild recomputes: they come from files versioned in the repository. Taking the official route of bumping the data version would cost around twenty-six minutes of reconstruction in a build that has already died of memory exhaustion and, on one occasion, took down an external audit in progress. The test loads into an empty database and checks it loaded, then runs again and checks it did NOT reload, because overwriting what a build placed would be the boot overruling the build's authority.I-H12— A failure loading a derived file becomes a log warning, never an exception at boot. One page missing is bad; the whole site being down is worse, and this product has already been unavailable precisely to the people trying to audit it. The attack forces ingestion to blow up and requires that the service still comes up, by checking that the caller wraps the load in a try/except rather than trusting it.I-P19— A B3 code is 4 positions plus a class digit, not 4 LETTERS plus a digit. The exchange itself trades as B3SA3, with a numeral inside the ticker root. The regex demanded [A-Z]{4} and the catalogue filtered on isalpha(), so B3 was discarded in both places: never ingested from COTAHIST, never present in the catalogue. It sat in the base under BVMF3, retired in 2017, with zero recent volume — anyone typing the real code found nothing, while B3SA3 shows 658 sessions and R$ 353bn traded.I-P20— A truncated B3 catalogue does NOT get to change which ticker represents a company. The endpoint is paginated and fails mid-way often — one measurement delivered 9 of 30 pages — and it used to do so in silence, returning half a catalogue as if it were whole. Deciding the representative ticker from half a source is worse than not deciding: the right code may simply not have arrived.I-P24— When the regulator’s file carries the whole consolidated block as zeros (TIM S.A., 2024 and 2025 annual filings) while the individual statements have figures, the standardised data uses the individual block, annotates the origin of every item and flags the indicator. Before, the zero was read as a value and TIM became a ‘company with no operations’. A single non-zero figure in the consolidated block keeps it; zeros on both sides have nowhere to fall.I-P25— For every company whose primary instrument is a unit with a known basket, the published market cap equals (price ÷ basket) × shares, recomputed here from the tables, in the annual and in the quarterly multiples, with or without the thousands-scale correction. The defect it guards against appeared on the first build with promoted units: the thousands-scale correction recomputed price × shares and dropped the divisor, so Santander came out at R$ 250bn (about R$ 125bn real) while flagged as adjusted, and the quarterly multiples had never divided any unit at all.I-P26— No PUBLISHED multiple has a share count 3x larger or smaller than the last published year before it without a corporate event to explain it. The plausibility band against equity let CSN Mineração through with a R$ 271 billion market cap in 2025 (33 billion in 2024, price falling): P/E 164 and P/B 40 published as clean. A share count does not change 10x without an event; the database holds the events; the cross-check was missing. The year after an IPO, counts already adjusted for events or scale, and any recorded event in the year, the previous or the next are accepted explanations.I-P46— On the real database, the quarterly-multiples set of the bulk export is Pro-only (no key 401, free key 403), has the 14 requested columns in order and one row per quarterly multiple. In every row with shares and EPS, EPS × shares equals TTM profit; where TTM P/E exists it equals price per share (price over the unit basket read from the flag) over EPS; no TTM profit or no market cap means no EPS. For WEGE3, PETR4 and BPAC11 (a 3-share unit) the CSV row is IDENTICAL to the JSON row of /multiplos for the last three quarters, the quarterly series carries the same two numbers, and annual EPS closes with the fiscal-year profit and the annual P/E. Attack: dividing profit by a share count that is not the market cap’s, EPS where there is no TTM, or CSV and JSON computed by different formulas.I-P48— A fully zero consolidated block is treated differently when the company has no subsidiaries (individual statements show no investment in subsidiaries and no equity-method result): the new mark consolidado_vazio_sem_controladas is a NOTE and net margin, ROIC and net debt/EBITDA are published; with subsidiaries (by investment or equity-method result) the old caveat stays and the figures are withheld.I-P49— When the controlling-shareholder profit line is missing or zero and there is no minority interest (individual statements, or minority equity and non-controlling profit both zero or absent), total profit is the controllers' profit and the lucro_total_sem_minoritarios note no longer appears; with relevant minority interest or a filled line nothing changes.I-Q01— An ETF’s premium or discount is close ÷ NAV per share − 1 only on a day where both exist. A day with a price and no NAV, or the reverse, produces no row: nothing is interpolated, no ‘last known NAV’. Beyond ±10% the figure carries a flag and never disappears.I-Q02— The daily report filed on day D carries the month up to D. Reprocessing a month writes with the NEWEST document, and an older document never overwrites what a newer one wrote — the administrator’s latest version wins, as a restatement does for financial statements.I-Q03— Result accounts in the COFI chart accumulate over the fund’s fiscal year. The month’s fee expense is the balance of the month minus the previous month’s; a falling balance means a new fiscal year and the balance itself is the expense; without the previous month the figure is cumulative and flagged. The effective fee is expense ÷ average daily net assets × 12, flagged outside 0–5% a year.I-Q04— An ETF exists for the site only when the CVM registers it as an index-fund class, B3 lists it with an acronym and returns its CNPJ, and the ticker traded in the exchange file. A fund listed by B3 without the CVM class is out; one registered without a B3 acronym is out; one matched but without a price has no page. There is no hand-kept ticker list.I-Q05— The ETF page and the /etfs hub open as HTML with no key and no script, with the site frame, anchors only for sections that exist, the premium stated as same-day and the fee stated as EFFECTIVE, the index stated as inferred from the name; JSON through the same URL only with a key or an explicit Accept; an unknown ticker is 404; the count on the hub equals the table.I-Q06— Two sources, one figure: the NAV per share on the last day of the month (daily report) must equal net assets ÷ shares outstanding from the same month’s balance sheet within 1%, fund by fund, in the real base. If they disagree, either the parser read the wrong column or the administrator filed different things in the two documents — in both cases the figure cannot ship clean.I-Q17— When an ETF ranking has nobody in it, the page SAYS why, with the date the series starts — it does not vanish from the screen. NAV comes from FNET's daily filing, one document per fund-month, and the source throttles bursts, so the 12-month return and the effective fee start empty and fill in over time. A block that disappears makes the reader conclude the wrong thing — that this house does not compute it — when the truth is that the data is not there yet. The explanation names the missing data, not a missing method, and no substitute is invented: the headline fee stays out.I-Q22— Each backfill round skips FNET documents whose id is already in the database. Rounds have a time budget and each one re-lists the month; without this check a round re-downloaded what the previous one had stored — the second August 2026 round fetched 89 balance sheets and only 8 were new, so the month never finished while the log looked like progress.I-B37— The revenue-growth denominator is the RESTATED comparative, not the figure as originally filed. Found by reconstructing the published sample against the raw CVM archives, after an external reviewer could audit only a handful of cases because their tooling refuses zip files. The published formula said revenue(year) / revenue(year-1) - 1 without saying WHICH version of the prior year. For one retailer the two readings differ six-fold. The comparative is the right choice for the same reason that governs the rest of the product: whoever opens a year's statement does so the following year, and the comparative shown there is already restated. Mixing the old denominator with the new numerator compares two different accounting vintages. Cases with no comparative fall back to the original figure and are stamped with a flag, so a reader can always tell which base produced the number.I-B38— A financial year whose declared currency scale is contradicted by the following filing does not publish absolute values. Found by reconstructing the audit sample against the raw CVM archives: one company was showing revenue growth of over a hundred thousand percent, because its own filing declared units while reporting thousands. The trap was diagnosing this per financial year: a filing at the wrong scale corrupts BOTH columns it publishes, its own year and the prior-year comparative, so the neighbouring good year looks broken too. The rule therefore requires both columns of the same filing to agree before calling it. And we diagnose without ever rewriting a value: a corrected number would stop matching the accounts it cites, and a number that does not tie to its own source is worse than an absent one because it looks auditable and is not. Ratios still publish, since they divide two accounts from the same vintage and the scale cancels.I-B39— No fallback flag may cover an entire class of companies. Born from a mistake made the same day the rule was written: revenue growth started using the restated comparative, with a stamped fallback for filings that have none, but the lookup matched the item name used by ordinary companies while financial institutions store it under a different name. Every single bank series fell into the fallback and none used the new rule, while the commit claimed banks were covered. The defect is dangerous because it looks tidy: each number carries a flag, and a flag reads as an explanation. Only the proportion gives it away. An exception that applies to everyone in a class is not an exception, it is the rule failing for that class.I-B40— Closing equity equals opening equity plus transactions with owners plus comprehensive income plus internal movements. Born from an audit that could not finish: a reviewer built this bridge for one company and stopped halfway, because dividends and other comprehensive income were exposed nowhere in the API, so they could reach a suspicion but not a conclusion. Their finding turned out to be a false alarm, but the hole that prevented them from confirming it was real, and this bridge is precisely the check that catches an equity error. Run across the whole base, 5,347 of 5,361 financial years close. The fourteen that do not are defects in the source, one company filing every closing balance as zero, and those are suppressed rather than published: an internally inconsistent set is worse than an absent one, because whoever checks it concludes the error is ours.I-B41— An account the regulator's file reports twice with different values is not published. The archive repeats the same account, same financial year, same column, with contradictory amounts and nothing to tell them apart: same statement group, same dates, same description. One company files profit as both one real and zero on the same line. Until now this resolved by accident on both sides: our ingestion kept the last row read, and the reconstruction script we wrote to AUDIT ourselves kept the first. The two disagreed about the same company, and that is the only reason the conflict surfaced. Arbitrary resolution does not announce itself as arbitrary; it only shows when two of your own parts choose differently, and most systems do not have two parts reading the same source by independent paths.I-B42— The scale warning travels on the FACT, not only on the indicator. Found by an external audit: the scale-divergence rule suppressed absolute indicators and multiples, but the underlying facts still shipped clean, so a utility published equity of 1.6 million where the following year's comparative says 1.6 billion. Anyone reading the raw facts or the bulk dump got a number that could be a thousandfold wrong with nothing marking it. Marking rather than suppressing is deliberate here: ratios come from these same facts and remain correct, because the scale factor cancels between two accounts of the same vintage, so suppressing would kill good data to remove bad. The general rule it closes: a warning confined to one layer is not a warning, because whoever consumes the layer below never sees it.I-B43— Every published indicator recomputes from the facts that produced it. Found by adversarial mutation testing: defects were injected into the data to measure how many the suite caught, and doubling a net margin without touching any fact went completely undetected. The product's central promise, that the published formula is the applied formula, had no invariant at all — it was checked by hand whenever someone thought to look. The drift needs no bad faith: recompute the facts and forget the indicators, which happened in this very project.I-B44— A flag the rules require must actually be present. Two mutation survivors: erasing a flag from one indicator and a shell-company warning from another went unnoticed. The existing checks verified a flag was CORRECT when present, never that it was PRESENT when due. Flags carry nearly every judgement in this base, so a vanished one returns the number to the world looking clean, which is the worst way to be wrong because nobody reading it suspects.I-B45— Impossible values are never published: non-positive prices or market caps, or a market cap beyond any plausible order of magnitude. A mutation flipped a price sign and nothing complained. A negative price is not a wrong number but an impossible one, and an impossible value passing means nobody watches that layer. The worst incident on record here was a market capitalisation of 1.67 trillion reais, impossible before it was wrong.I-B46— Return on equity and on assets also recompute from the facts, not just the simple ratios. Round one of the mutation audit found that no indicator was recomputed at all and produced the first recomputation invariant; the detection rate then hit 100%, which almost always means the attack set is too easy rather than the system being safe. Round three swapped one company's return on equity for another's: perfect shape, plausible value, normal range, only the owner wrong, and it passed. These two had been left out because they average two financial years, which is more work to reconstruct. More work is not a reason, it is exactly where defects hide, because whoever writes the test also picks the easy path.I-B47— The version stamped in the database is the version of the code that built it. A mutation replaced the stamp with an invented label and nothing complained. That stamp governs the publication gate and every claim that a user knows which state they audited. A wrong stamp is worse than a missing one, because whoever cites the version ends up citing one that never existed.I-B48— A recorded restatement must actually show a divergence. The existing check asked the opposite question, whether every real divergence was recorded; nobody asked whether every record corresponded to a divergence. The asymmetry is easy to miss and the effect is bad in both directions, since the restatement count is a headline number on the home page and an empty row inflates a transparency argument with nothing.I-B49— There is no alternative route into the batch of published facts. This invariant fixes a pattern, not a case: in one week the same thing happened five times, where a rule was applied on one path through the code and forgotten on another, and every one of them was caught by an invariant rather than by review. The cause was structural, since more than one path led into the batch and whoever added an item by a new path had to remember to reapply each rule by hand. A rule applied on three of four paths is not worth 75%, it is worth zero, because the defect picks precisely the forgotten path. The fix is not remembering better, it is making forgetting impossible, and the test is structural because a behavioural one would only fail after the next incident.I-B50— The quarterly price-to-earnings ratio equals market cap divided by trailing twelve-month profit. A mutation shifted it by 40% and nothing complained: the annual indicators were recomputed by an earlier invariant, the quarterly series had no equivalent. Same pattern that has now appeared six times in this project, a rule applied to one layer and missing from the neighbouring one.I-B51— Interest on own capital does not vanish from shareholder payouts. A mutation deleted every such entry and no invariant complained, which would have halved the dividend yield of every company that pays it, silently. This is already a declared case in the public challenge, since that instrument is booked as a financial expense rather than a distribution, so anyone summing only the dividend line understates real remuneration. There was a challenge case and no invariant: the challenge proves we can defend that case, the invariant stops it breaking unnoticed.I-B52— The sum of the quarters cannot contradict the financial year. The mutation that prompted this was the least interesting part: while calibrating the threshold, the worst cases came in at 740 times and turned out to be financial years already flagged for a currency-scale problem. The comparison was detecting the same defect through an independent path, and that became a second scale detector which reaches what the first cannot — the last filing of a series has no following comparative, so it was undiagnosable by construction. The flagged population went from 27 to 45.I-B53— An income statement filed without its cost line does not publish a clean gross margin. The investigation started in the wrong place: mutation testing flagged company-years where the quarters summed to two or three times the full year, and it was recorded as an inflated quarterly series of unknown cause. The opposite was true. The quarterly filings were right and the annual one was malformed, reporting zero cost with gross profit equal to revenue, and the annual revenue figure matched the estimated annual GROSS PROFIT rather than revenue. A hundred and seven company-years publish a gross margin of exactly 100%, which does not exist in an operating company, and the number ships clean, so anyone sorting the market by gross margin gets them at the top.I-B54— When a company's quarterly and annual filings disagree, the data says so. Of the twenty-one contradictions found, only eight had the missing cost line; the rest have real costs and still show the quarters summing to twice the year, most plausibly because the consolidation perimeter changed mid-year, which is legitimate accounting and nobody's defect. The mark is therefore descriptive rather than accusatory: it states that the company's two publications disagree without asserting which is right. The opposite temptation nearly won, declaring the quarterly wrong because the annual is the number we publish.GC24— Without a key the events route and tool return a sample (the 3 most recent events with the full total and the free-key path); an invalid key is still refusedI-N100— When a re-filing was captured, the page shows month, field, before, after, difference and versions; when not, it says plainly that the CVM publishes only the version in force and that there is no “before” figure — never a silent empty table.I-N101— In /comparar/A/B each indicator comes from the comparison data in the house format (percent ×100 with one decimal, multiple with two and “x”, F-Score “/9”), FCF yield in %, the multiple year is one when it matches and “A × B” when not; the card carries name and sector; an account that does not exist for banks says whose it is; the JSON-LD lists both in positions 1 and 2.I-N102— /vigia/{T}: for a company or a fund, the ticker’s events — the SAME as the watcher’s, filtered by ticker — with a type filter, the follow form (companies only) and links to the ticker page, the general watcher and the JSON; a ticker with no event is noindex,follow; unknown is 404; not in the sitemap. The company “follow” block and the fund card link here.I-N103— On the real base, the company that publishes only the individual statements and has a restatement with one side zeroed in the CVM file: the restatements page says “individual — company does not consolidate” on each row and “no value in the file” where the side came zeroed, never R$ 0; the JSON at the same URL and /empresas/{T}/reapresentacoes carry the aggregation and the empty side, row for row as the table; the company page does not print 0.0 either. A company that consolidates stays con. PT and EN; no banned term.I-N104— On the real base: the curated sector comparisons (the three most liquid of each sector) never exceed the configured cap nor 60; each opens in PT and EN with the title “X × Y × Z: margin, ROIC, debt, P/E (year)”, the table with the three side by side and the verdict link of each; they are in the sitemap with lastmod (and so is the index), WITHOUT noindex, and the index lists them all; an unknown slug is 404; /comparar/A/B stays noindex; the page of a company in the trio links to “compare with the sector”. No banned term outside the disclaimers.I-N105— For 30 companies, 10 REITs, 5 ETFs and every fact page, the English HTML has the SAME data numbers as the Portuguese one (extracted by regex and normalised: 1.234,5 = 1,234.5; dates in ISO), the same anchors, the same internal links and the same number of rows in each table; the difference is text only. Source text in Portuguese (the subject of a filing with the CVM) is marked lang=pt and left out. A declared exception, with its reason, sits in the test.I-N106— On every parameter-free HTML page in the sitemap and a sample of the parameterised ones (companies, REITs, ETFs, guides, methodologies), in PT and EN: viewport meta; every img with alt; every form field labelled (label, label for or aria-label); every table inside a scroll container or covered by the style’s mobile rule (a loose table scrolls on its own); no fixed width wider than the screen. And the contrast of the style’s colour tokens — text, muted, accent, warning, positive, negative over background, surface and soft accent, in the light and dark themes — is at least 4.5:1. The report comes out per page.I-N107— Every public HTML page in the sitemap (company, REIT, ETF and comparison pages by sample) declares exactly three alternate hreflang links: pt-BR is the URL itself, en is the URL with ?lang=en, x-default is pt-BR; the ?lang=en version exists (html lang en) and declares the SAME three. At least 100 routes are checked. Exception since 2026-10-19 (I-N170): the page of a single asset (stock, REIT, ETF, BDR and their subpages) declares no hreflang in either language, because its English version is noindex,follow.I-N108— One JSON-LD block, valid JSON, in the head. The home page and the hubs carry Dataset (name, description, the page URL, licence, distribution at /api and a temporalCoverage taken from the data) and Organization with sameAs from the configuration; company, REIT, ETF and curated comparison pages carry a BreadcrumbList 1..n ending at the page URL in the requested language. No forbidden term in the texts.I-N109— Each sitemap url with an English version carries the three xhtml:link pairs (pt-BR, en, x-default) equal to the page head; a route without an English version (/docs) carries none; the xhtml namespace is declared and the XML is valid. Single-asset pages (stock, REIT, ETF, BDR and subpages) carry none either, since 2026-10-19: their English version is noindex,follow (I-N170).I-N110— Every response carries HSTS, nosniff, Referrer-Policy, Permissions-Policy and a Content Security Policy, and none sets a cookie. Loading the public pages (the sitemap sample, the company page with the TradingView widget in Portuguese and English, /docs and /redoc), the policy allows EXACTLY what they use: each external script, stylesheet, image, frame and font fits the right directive, and each origin the policy allows is used by some page, in the HTML or by what one of its resources requests after loading (the widget frame and logos, the Google fonts). The only inline script is the JSON-LD, which is data; on /docs, the Swagger bootstrap script is allowed by its hash.I-N111— Crawlers on the configured deny list (Baiduspider, PetalBot, Bytespider, SEO bots…) get a fast 403 on every route before any query, by User-Agent only, never by IP; browsers, Googlebot, Bingbot, DuckDuckBot and on-demand AI agents pass. robots.txt disallows the ?ano=/?janela=/?ordem= variants for every agent (the canonical page has no parameters) and closes the door to the same list. On 27/09/2026 Baidu ranges crawled every variant of every company page, dozens of requests per second, and the single-CPU production stopped answering for everyone.I-N114— /cobertura, requested as a page (Accept text/html), shows per company the fiscal years (from the first to the last DFP filed) and the core fields that are present, present with a caveat or missing; the total per fiscal year; and every gap with its reason, the source's or ours, never “we don't have it” without saying why. Counted from the database: the fiscal years add up to the DFPs filed, every gap or caveat code in the database has a text in cobertura_config.json, and an operating cash flow missing from the indirect-method statement but present in the direct-method one is marked as OUR gap. PT and EN show the same figures; the page is in the sitemap; the route's JSON is unchanged for whoever asks for JSON.I-N115— The three analytics writes (page visit, API-key use, tasting) run on a worker thread of the pool, never on the application's event loop. With one worker, a slow database write on the loop stalled every in-flight response, and the loop was exactly what stopped answering in the 2026-09-27 incident.I-N116— When the build gate fails, the error shown on /saude carries the first assertion line of EACH failed test, not six lines of the first one. On 2026-10-01 two tests failed in production and only the first was explained, so the second one needed another full build to be understood.I-N117— The search title of REIT and ETF pages carries the ticker, the name as people search it (without the registry jargon) and, for REITs, the 12-month yield and P/BV when the number has no caveat; and it fits what Google shows.I-N118— Each company has three answers (/acoes/X/dividendos, /lucro, /divida; banks and insurers without debt): the sentence starts with the ticker and the name, carries number and year, never calls a loss a negative profit, never turns unknown dividends into zero, counts the current year only within the last 12 months; the page has a check-it-yourself section, the FAQPage, the .md version, the link on the company page and the sitemap entry; the module writes no SQL; no advice terms.I-N119— The IndexNow key file answers 200 as plain text with the key and nothing else (and only that exact route: any other .txt name at the root stays 404), outside the sitemap. The notice builds the protocol JSON (host, key, keyLocation, urlList), splits it into batches of at most 10,000, drops duplicates, http and URLs of other domains (including the look-alike domain), and never raises: a network that blows up, a search engine that refuses or a malformed list become a field of the summary. The daily-update hook does not send outside production (empty environment, or RENDER=true under pytest), and the list of pages that changed holds the stock with a new price, the one with a new dividend (with its dividends page) and none of those that did not change. On 2026-10-17 ChatGPT, which searches through the Bing index, was not citing the site, and Bing only found changed pages through the sitemap, days later.I-N120— Each answer page (/acoes/X/dividendos, /lucro, /divida) carries one valid block of schema.org structured data: a FAQPage whose answer is the SAME sentence as the first visible paragraph (same numbers), the date of the most recent data used (never a future one), the source it is based on (B3 or CVM) and the canonical address in the requested language; a Dataset only when there is a table. A ready-to-copy citation line (ticker, data date and address) appears on the page and in the .md version. A ticker without data gets no number, no date and no Dataset in the structured data, and a closing-tag sequence in the text does not break the block. Why: in a blind test on 2026-10-17, Gemini, ChatGPT and Claude answered how much PETR4 paid and cited other sites.I-N121— The privacy policy and the terms of use exist in Portuguese and English (/privacidade, /termos, the ?lang=en versions and the short addresses /privacy and /terms): each answers 200, names the operating company with its CNPJ and the contact address, carries the update date, is in the sitemap, and the footer of every page links both. The policy states what the code records: no cookie, no IP in the visit count, the key stored as a hash, only the tool name for connector calls; and the daily limits it quotes are the ones the API enforces.I-N121b— The 12-month retention the privacy policy promises really happens, checked on synthetic databases: in the analytics a 366-day-old row goes and a 364-day-old one stays, in all four tables and in batches; an alert request cancelled 366 days ago is deleted with its e-mail and its sends, while the 364-day one and the active one stay; a campaign line older than 365 days loses the e-mail and keeps its hash, and whoever got it still does not get it again; in the queue of e-mails the provider refused, a 366-day-old line goes and a 364-day-old one stays. The site loop runs the clean-up outside the test battery, and the policy quotes the same period as the code, the 15-day answer time and the courts.I-N122— The site has an icon, and every HTML page declares it exactly once. The favicon (16, 32 and 48 px), the SVG, the Apple touch icon and the 192 and 512 pixel icons answer 200 with the right content type and a one-week cache; they are the brand mark of the page header (a dark green rounded square with a white rising line, with literal colors), stay out of the sitemap and out of the analytics, and the touch icon has no transparency. The icon links enter the head of every page in one place, only when the page does not already declare its own, and the content length stays right. The MCP connector declares the large icon. Why: on 2026-10-18 the favicon address returned 404 and no page had an icon; the Claude connector directory shows the favicon of the domain of the MCP server, and a generic letter reads as a site without an owner.I-N123— The screener filters by P/E (`pl`, annual, and `pl_ttm`, trailing 12 months) and, with `as_of`, uses the P/E of the latest filing already received on that date, priced with a trading day that already existed. Every returned value comes from a filing received and a price traded on or before the date asked, and matches an independent recomputation from the multiples tables; a P/E from negative earnings, above 200 or missing never passes a maximum; without `as_of` the latest filing is used. Each row states the filing date and the price date of the value used. A strict mode accepts only P/E values with no caveat at all; the default also accepts values with an adjustment already made or a minor caveat, which the row lists, and never one with a blocking or unrecognised caveat. The response accounts for every company left out, by reason, so that the passed and excluded counts add up to the whole universe.I-N124— The stock screener page (/buscar), with a P/E filter, says in short plain sentences how many companies were left out and why, grouped (for example: 38 with no profit in the period, 12 with no price on the date), and which of the ones listed carry a minor caveat. It uses the very same calculation as the screener API, so the page and the API cannot disagree, and the form offers the P/E for the last 12 months, the date and the rigor (standard or strict). So a reader who does not follow balance sheets does not mistake a short list for a gap in the site. Checked on synthetic data: the numbers in the notice match, Portuguese and English carry the same figures, the table shows the P/E of the chosen date and not today's, and an invalid date is flagged instead of silently ignored.I-N150— Each B3 index in the hub has its own page at /indices/{code} (Ibovespa, IFIX, SMLL and the others): last close with its date, change on the day, in the month, in the year and in 12 months over the base the page states (no base in the series, no number), a server-side SVG history, the performance with the same numbers as /indices/{code}/desempenho, links to the JSON, a how-to-cite line and one JSON-LD block (Dataset, FAQPage with the same sentence as the page, breadcrumb). /indices answers JSON as before and, for a browser, lists every index with a link to its page; all pages are in the sitemap, public for 5 minutes and have an English version with the same numbers. An index we do not keep gets a 404, friendly in the browser; lower case redirects to upper case. Attack: change on the wrong base, a 12-month number borrowed without a series covering the date, an orphan page, a page that disagrees with the JSON, a recommendation word, a second JSON-LD block.I-N151— The ChatGPT apps directory verification token is served as plain text at /.well-known/openai-apps-challenge, with no key and nothing else in the body, so the OpenAI portal can confirm the connector domain is ours. Attack: a wrong token, HTML around it, or the route behind a key.I-N160— The llms.txt guide sends a reader WITHOUT a key to open pages first (/acoes/TICKER, /quanto-rendeu, /fiis, /etfs, /indices/IBOV, /buscar, /demo/chatgpt, the ChatGPT connector), and keeps the key-only JSON addresses (/empresas/TICKER/..., /screener) in their own section marked as needing a free key; every address outside that section answers 200 with no key. The per-asset restatement pages are not in the sitemap (decision of 19/10/2026, to concentrate crawling); they stay online and linked from the asset page, and the /reapresentacoes hub stays in the map.I-N170— The English version (?lang=en) of a single-asset page (/acoes/X, /fiis/X, /etfs/X, /bdrs/X and subpages such as restatements and dividends) opens normally but carries exactly one robots meta with noindex,follow, and neither language of those pages declares hreflang; the sitemap carries no language pair for them. The home, the hubs, /indices and /indices/X, the guides, the methodology, the connector page, the demo and the questions stay indexable in English with the three hreflang pairs. The asset index (/ativos) and each stock sector, REIT segment and first-letter page answer 200 in both languages with a public 5-minute cache, one h1, labelled navigation, a JSON-LD block with a breadcrumb and an item list, and a title count equal to the number of links; together they link EVERY stock and EVERY REIT in the sitemap twice, are in the sitemap and in the footer, and the ETF and BDR hubs carry the A to Z navigation. Why (2026-10-19): Search Console showed 479 pages indexed, 2,019 discovered but not indexed and 102 crawled but not indexed, almost all in English; the crawler prioritises by link, and each asset had a single link from a long hub. Attack: an hreflang pointing from Portuguese to a noindex alternative, noindex on English hubs, a sector or letter missing an asset, an index with no footer link, a hand-written count.I-N180— The first sentence of a stock page answers "how much did it pay?" with a number and a date, generated from the database: the year figure equals the sum of that year's rows in the page's own dividends table, classes are never added together (ON and PN apart), it holds for the most-opened tickers, the English page carries the same numbers, and the /dividendos page opens with the same sentence.I-N181— The first sentence of a REIT (FII) page gives the 12-month income paid and the 12-month yield with their dates, from the same row as the DY 12m card; a fund with fewer than 12 months of payments gets a partial sum with no annualised yield, and an extreme yield is left out with the caveat stated.I-N182— When there is no data the direct-answer sentence stays silent (never a false R$ 0,00); only the part that exists is written, announced future record dates do not count as the last 12 months, and neither the generator nor the answers page contains SQL.I-N183— The direct-answer generator works for every ticker, not a fixed list: it walks stocks and REITs of the database, every page opens, and a sentence that exists never contains NaN, None or R$ 0,00.I-N184— On a stock page the direct-answer sentence and the DY 12m card of the valuation block show the same percentage and the same R$ per share (the sentence uses the card's own figure); when a record date is already announced the sum includes it and the sentence says how much and until when.I-N190— In the three dated list pages (/listas/maiores-pagadoras-de-dividendos-YYYY, /listas/acoes-pl-abaixo-de-10 and /listas/fiis-maior-dy-12-meses), in Portuguese and English, the FIRST sentence carries the ticker and the figure of the FIRST table row (DY or P/E, formatted the same) and a date; the table is in order (DY high to low, P/E low to high); the REIT list starts with exactly the funds of the /fiis hub ranking, and each P/E row uses the close of the last session and a year no later than it. Attack: an unsorted list, a sentence quoting the runner-up, a REIT ranking different from the hub, a P/E outside /buscar.I-N191— In the dividend list each row is ONE share class (the class of the main ticker: ON, PN, PNA, PNB or unit); the 12-month payout is the sum of that class only, with a record date inside the window, checked straight against the dividend table; the DY is that sum over the last session close; a ticker never repeats; only stocks averaging at least the home page liquidity floor over 63 sessions enter; and the test bites, since some company pays in more than one class and adding ON+PN would give another number. Attack: adding the classes, a 13-month window, an illiquid stock.I-N192— Each list shows the date of its data (the last session in the database, or the price date of the REIT yield) in the first sentence and in a data line, explains the criterion in one line, has a How to cite block with the canonical URL, ends with a not-advice note, and declares ONE JSON-LD block with an ItemList (item count equals table rows, first item equals first row) and a Dataset whose dateModified is the page date; the P/E page says the price is NOT today's price; no script runs besides the JSON-LD. Attack: a hand-written date, an ItemList longer than the table, a Dataset without a date, an executable script.I-N193— The three lists are in the sitemap (with lastmod), in the llms.txt open-pages section (no key needed) and linked from /hoje in Portuguese and English; each has a plain-text .md version with the same first sentence; the dividend list address of a past year answers 301 to the database year, a future year is 404 and an invented slug is 404. Attack: a list missing from the sitemap, an llms.txt with the old year, /hoje without the link, an .md with a different number.I-N194— The three lists answer 200 in Portuguese and English with the right html lang, one h1, the language toggle, the three hreflang links (English stays indexable because it is a list page, not an asset page), no accessibility problems, and every ticker in the table links to its own page (/acoes/X or /fiis/X), which also answers 200. Attack: English as 404, a table without links, a duplicated h1, English with noindex.I-N200— One single map turns a ticker into its exact share class (3 ON, 4 PN, 5 PNA, 6 PNB, 7 PNC, 8 PND, 11 unit), the same vocabulary as the dividend table; the company page, the direct answer, the dividends JSON/connector, the home ranking and the rest all use it, so ON and PN never add up and neither do PNA and PNB.I-N201— A preferred share ending in 5 to 8 (PNA, PNB, PNC) that paid in the last 12 months shows the DY 12m card and the direct-answer sentence; the figure is the sum of that exact class in the dividend table, the sentence and the card are the same calculation, and the dividends JSON gives the same yield.I-N202— The home ranking of highest 12-month yields reads shares ending in 5 to 8 by their exact class: a main PNA/PNB/PNC share enters only because of dividends of its own class, never those of ON or PN.I-N20— Any page using the fact-page frame is registered in the panel's explicit list. The panel returns the thirty most-visited pages and its last row had twenty-eight visits, so a fact page born with five simply vanishes from it — and 'did not appear' reads as 'nobody visited' to whoever is looking. The fix was listing each one explicitly, which only works while the list stays complete; whoever adds the twelfth page and forgets to register it finds out here rather than a month later, staring at a zero that was never a zero.I-N210— The embeddable card /cartao/{ticker}/{type}.svg is a valid SVG under 15 KB with the source and the page URL written inside it, and carries no price, DY, P/E or index value; dividends are of the ticker's own share class (PETR4 in 2025 = R$ 3.27).I-N211— The card is served with an ETag and Last-Modified tied to the data version and a long cache, answers 304 to If-None-Match, and an invalid ticker, type or fund gives a clean 404 with no empty card.I-N212— The /cartao/{ticker} page shows each card with its HTML, Markdown and BBCode in read-only textareas, no JavaScript, noindex,follow and out of the sitemap; the stock page links to 'Embed this chart'.I-N213— Each /cartao/*.svg request is counted with only the Referer HOST (no path, query or port) in its own table with no personal data, and /admin/stats lists the outside domains that embedded the cards.I-N21— Every path in the panel's fact-page list actually answers. The counterpart of the invariant above: without it the list can accumulate the path of a page that was removed, and the panel would publish an eternally zeroed row — a zero that does not mean 'nobody visited', it means 'this no longer exists'.I-N220— /empresas opens the sales page of the Business plan in Portuguese and English for whoever asks for HTML, with the sentence about the CNPJ contract, a first sentence whose company count comes from the database, and a plain POST form with no JavaScript; whoever asks for `*/*` (the Python package, curl) still gets the list of companies as JSON. The page is in the sitemap, in the footer and in llms.txt, and llms.txt keeps the performance answer in its first 2,500 characters. Why: on 2026-10-21 there were 0 Business contracts and no page selling the plan or taking a trial request.I-N221— The prices on /empresas are the approved ones (R$ 490 a month or R$ 4,900 a year; R$ 1,290 a month or R$ 12,900 a year; 25% off the first year for analysis platforms; custom by proposal; payment in advance) in both languages, and each code example (Python, Excel/Power Query, curl) calls a route the app really has, including the export set the Excel example reads.I-N222— The POST on /empresas stores the 14-day trial request in its own table in the persistent file (not in the weekly rebuilt database), answers "we received it; we answer within 1 business day" without repeating the e-mail, and stores nothing for: a filled hidden field, a crawler user-agent, a personal e-mail provider, a CNPJ whose check digits fail, a missing consent, a use outside the list. It creates no key. An hourly ceiling answers 429.I-N223— /admin/stats carries the block `pedidos_teste_empresa` (count and the last 10 requests) and never the full e-mail or the name: only `m***@domain`. Without the token the route does not exist.I-N224— The privacy policy (Portuguese and English) describes the /empresas form, what it keeps and the number of days the code applies before deleting the request, and the columns of the request table are exactly the ones the text describes: add a column (IP, user-agent) and the test fails until the policy is revised.I-N225— The page /privacidade/apagar (Portuguese and English) says what Dados B3 keeps, why, for how long, how to delete, what is deleted and what stays (charges and invoices at Stripe, a hash of the e-mail), shows the form and names the data protection officer (encarregado) with the contact; the periods in days are the ones the code applies; the privacy policy links to the page and names the same officer.I-N226— docs/LGPD_INVENTARIO.md names, between backticks, every table that exists in the personal-data files on disk (follow-ups, e-mail proof, analytics, receipts), keeps the caveat that legal bases are to be confirmed by a lawyer, names the officer and lists the daily snapshot as a source with no personal data.I-N22— A company page answers 200 WITHOUT a key and is in the sitemap. That is the whole point of the page: the site was invisible three ways at once — the sitemap held 568 URLs of which 503 were audit samples and none was a company, a search for a well-known company's ROIC returned seven competitors and not us, and an AI asked for a bank's ROE hit a 401 and answered with another site. If anyone puts a gate here all three holes come back silently, because the page still exists and nobody can reach it.I-N23— The index on the stocks hub and the sitemap cover the same companies. Without it the index can list a code that does not open — a broken link on a page built to be indexed — or the sitemap can promise a URL that does not exist, and a crawler that hits a 404 arriving from the sitemap distrusts the whole site.I-N240— A request whose Host is the old address dados-b3.onrender.com gets a 301 to the same path and query on https://dadosb3.com (GET and HEAD only), so crawlers stop loading the whole site twice. The redirect is the outermost layer, so it is not counted as a visit.I-N241— On the old host, /mcp, /mcp/ and anything under it, /saude, /webhook/stripe, /.well-known/, /robots.txt and the IndexNow key file are never redirected: the official MCP registry entry, the health check and the Stripe webhook keep working.I-N242— Requests on the normal host dadosb3.com or on a local test host are never redirected, a POST to the old host outside the exceptions is passed through untouched, and the switch REDIRECT_HOST_ANTIGO=0 turns the redirect off.I-N243— The 301 of the old host leaves no trace in the visit counter: it sits outside the visit-recording layer, which is checked by the order of the middleware.I-N24— A withheld figure shows its reason on the company page instead of a blank. On a shop-window page the temptation to hide is greater, not smaller: a clean dash looks better than a dash with a caveat. The house rule holds here too, and this test pins it against real flagged indicators.I-N250— No rendered page (/termos, /metodologia, /info, /conector, in Portuguese and English) calls the COTAHIST 'free distribution' or cites a numbered item of the B3 policy; /termos says B3 data needs a B3 licence for commercial use and that keeping or redistributing the database requires the Business Plan.I-N25— A fund page answers 200 in HTML WITHOUT a key, on the SAME URL that serves the JSON. The format comes from the Accept header: a browser reads, an agent consumes. Before this the hub linked each of its funds to a gated JSON, so a person who clicked a name got a 401 — explained, but shut — and to a search engine or an AI the fund did not exist. If anyone flips the order, gating the HTML or serving JSON to browsers, those links go back to being a closed door and the funds go invisible again.I-N260— The uvicorn access log never holds the value of a token: a request with t=, key=, chave=, token= or X-Admin-Token= in the URL, or a token in a /confirmar/ or /sair/ path, is logged with *** in place of the value, and the client address is logged as -; method, path and status stay.I-N261— A message marked secret (the API key re-send and the portal link) is not written in clear to the pending e-mail queue when the provider refuses or fails: the queue keeps only recipient, subject and a flag. Ordinary messages are still kept whole, so no customer e-mail is lost.I-N262— The pending e-mail queue is cleaned of lines older than 14 days (not 365); a 13-day-old line and an unreadable line stay.I-N263— The weekly-notice table (week, e-mail, state) loses rows older than 365 days, since it only prevents a double send in the same week; younger rows stay and a missing table does not raise.I-N26— The index on the funds hub and the sitemap cover the same funds. A page no other page links to is the last one a crawler visits, and a sitemap promising a URL that does not exist makes a crawler distrust the whole site, so the three lists — index, hub, sitemap — have to be one and the same.I-N27— A price-to-book with a caveat shows the caveat on the fund page instead of a clean number. A P/BV flagged out-of-range, shown bare, reads as a discount that is not there — and on a shop-window page the pull to tidy it away is strongest. The test picks a fund whose latest P/BV carries a flag and demands that flag in the HTML.I-N28— Without a key, a fund page returns the PAGE under any Accept header. The fourth time in this family, and this one was self-inflicted hours after the page was written: the route first negotiated on an explicit text/html, so Googlebot got the page while */* — the default of curl, GPTBot and ClaudeBot — got a 401. Search Console refused the indexing request for that URL and the reason was exactly this. The rule is the KEY, not the Accept: whoever sends a key wants the feed, and whoever does not has no access to the JSON anyway, so a 401 only hides public content.I-N290— The panel counts every 5xx response by UTC hour and visitor family (googlebot, bingbot, other bots, browser) over the last 72 hours, with the 5 most affected paths and no IP, user-agent or query string; the first 5xx of each hour is written to the error log with path and time.I-N291— The visit middleware records every 5xx, on any path and from any visitor, including an exception with no handler (counted as 500); /admin/stats shows the erros_5xx_por_hora block and a 200 response is not counted.I-N292— After the database swap the server builds the sitemap once on the swap thread so the first visitor does not pay the cold build; a failure to build never raises and the function does nothing under the test battery.I-N29— The page and the JSON of an audit sample carry the same cases. Serving one sample in two formats risks them drifting apart, and then a seed would have two versions — which is exactly what TR24 prevents between the two URLs, while nothing prevented it between the two formats of one URL. An earlier draft of this invariant demanded the page be served to */* by analogy with the fund pages; the battery refused it, correctly, because there */* got a 401 with no content while here it gets the whole artefact, and that JSON is the citable proof the published protocol tells auditors to request.I-N300— Each dividend in the REST route and the dividendos tool carries parcela and data_pagamento (added fields, none removed); rows with the same class, type, record date and amount differ in the installment, so they are separate payments, not duplicates.I-N301— The 12-month dividend yield carries ano_corrente_ate_hoje and ano_anterior_fechado (BRL per share by record date, ticker class), checked against the sum of the list; the stock page repeats both next to the 12-month total and the table shows the installment number on events with more than one row.I-N302— The verdict of a company (JSON and page) says the date of the P/E price in the top sentence, carries valor_preco_atual and data_preco_atual in the pl and pvp items while the value stays point-in-time, and the page shows the same sentence and the price date on the P/E card.I-N30— No URL in the sitemap answers with a noindex tag. The two are opposite instructions, and for months 500 audit-sample URLs carried both: they were listed in the sitemap on the sound reasoning that a tool which only opens indexed URLs stalls otherwise, while the page itself said noindex — so it could never be indexed, the tool stalled anyway, and the entries took up 36% of a sitemap on a site where Google already reported forty pages detected-but-not-indexed. Each half was defensible alone and nothing looked at them together. The fix was to drop the samples from the sitemap rather than drop the noindex: the seed hub is indexable, sits in the sitemap and links all five hundred, so the tool reaches the hub and follows a link, while five hundred generated near-duplicates stay out of the index where they would compete with the real pages.I-N31— The screener table is rendered by the server, with no JavaScript. Building the table in the browser is the natural choice and would hand a crawler or an AI an empty page — and the 820 public pages opened in September are worth something precisely because the content is in the HTML. So sorting is a link and filtering is a GET form: the crawler walks exactly what a person sees, and every slice is an address.I-N32— Filters stack rather than replace, and each one can be removed on its own. A screener whose second filter silently replaces the first looks like it works — it returns rows and raises no error — and does not do the job; only counting catches it. Each active filter also carries a link that drops just that one and keeps the rest.I-N33— A row with no clean figure never leads the ranking. The classic sortable-table bug: a missing value becomes zero and the company we know nothing about shows up as the cheapest on the market. Flagged values are the same story — every negative P/E in the base carries a flag, so without this rule the top of 'cheapest' would be a queue of loss-makers. Those rows stay in the table with their figure visible and marked; they simply do not lead.I-N34— Every company page links to the other companies in its CVM sector, and every fund page to the other funds in its segment — all of them, none of itself. The 820 pages opened in September were born as islands, reachable only from the hub or the sitemap, and a page no other page cites is the last one a crawler visits; Search Console showed forty of them detected but not indexed. The grouping is the regulator's own classification, not ours.I-N35— Company and fund pages carry structured data (schema.org JSON-LD) that parses, names the right type — Corporation or InvestmentFund — and matches the page's ticker and CNPJ, with a breadcrumb trail. It is the format search engines and AI systems use to extract facts without reading prose. It is labelled JSON, not JavaScript: the screener stays script-free and this test does not touch it.I-N360— /agenda-de-balancos and /data-com-da-semana, in Portuguese and English: the FIRST sentence carries a number and a date (in the language's format); the page shows the criterion, a 'How to cite' block with the canonical URL, one JSON-LD (ItemList and Dataset) and no script other than JSON-LD (no JavaScript); the earnings-calendar sentence says any future date is ESTIMATED and not an official calendar. Attack: a sentence without a number, a behaviour script, no citation block, a future date stated without the label.I-N361— The same URL returns JSON only with an explicit Accept: application/json (Vary: Accept); */* and no Accept get the page, so no crawler loses it. The tickers in the JSON are those of the page tables, in the same order. Attack: JSON on a separate route, */* receiving JSON, JSON ordered differently from the page.I-N362— Each table of /agenda-de-balancos is in DESCENDING market-value order (the figure of the company page's valuation block), companies without a market value last; the JSON declares the ordering; nothing in the page, the JSON or the code reads an index weight (the official weight is B3 licensed data). Attack: sorting by Ibovespa weight or alphabetically, a market value that differs from the company page.I-N363— In /agenda-de-balancos (both languages) and the JSON, EVERY row dated after today is 'Estimated', carries last year's filing date it comes from (364 days earlier, same weekday) and is never 'Delivered'; every 'Delivered' row is dated no later than today and the last day the base saw; estimates exist only for companies that have not filed this year. Attack: a future date on a Delivered row, an estimate without last year's date, estimating a company that already filed.I-N364— /agenda-de-balancos and /data-com-da-semana have the SAME tickers in the same order and the same figures (market values, amounts per share) in Portuguese and English; only the text and the date format change; the English first sentence has no Portuguese word and vice versa; each page links to the other language. Attack: an English table with fewer rows, a number with the other language's separator, an untranslated first sentence.I-N365— Every row of /data-com-da-semana (ex-date, ticker, amount per share or unit) and every payment date in it is in the /hoje agenda with the same values; the page JSON has only whitelisted fields (no new B3 field such as price on the ex-date, factor, flag, ISIN or approval date); the window is 7 days from today; the page cites its source as /hoje does. Attack: a B3 column /hoje does not show, a 30-day window, an invented payment, a hidden source.I-N366— /agenda-de-balancos and /data-com-da-semana are in the sitemap (with lastmod), in the dated-lists section of llms.txt (with the note on JSON by Accept), linked from /hoje (both languages) and in the dashboard's fact-page list. Attack: a new page nobody finds, missing from llms.txt, missing from the dashboard.I-N367— On a hand-built small database: whoever filed in the week is 'delivered' (real date of the first version, even with a later re-filing); whoever filed last year's document in this week and has not filed this year's is 'estimated' (date +364 days); whoever already filed this year BEFORE the week is not estimated; last week never estimates; the period is the last quarter-end before the week (DFP in December, ITR otherwise); an empty week returns zeros and a sentence with a number and a date. Attack: a re-filing moving the delivery date, estimating a company that filed, estimating last week, the wrong period in January.I-N368— The new home (Portuguese and English) has no JavaScript besides the JSON-LD, and every market block carries the date of its data (each pulse card; gains and falls, the three tabs and the agenda in the block header); the e-mail follow field reuses the existing POST /acompanhar flow.I-N369— What the home shows is what the base holds: last value and daily change of Ibovespa, IFIX and the dollar, the Selic target with its in-force date (no daily change), and the 5 gains and 5 falls among liquid stocks of the last session without a corporate event, checked by independent SQL.I-N36— The about page names the person who builds the site, in both languages, with the credentials that speak to this product. It used to explain the operating company and say nothing about who makes it — and a site with no visible owner is the first thing a search engine or an AI discounts. A reference has a name.I-N370— The staleness lock: a block more than 3 sessions behind the base's last session, or missing, shows 'data being updated' instead of numbers (tested on a hand-built base: Ibovespa 6 behind hidden, IFIX 1 behind and dollar exactly 3 behind shown), and the /saude home block lists the stale block.I-N371— Every internal link on the home (Portuguese and English) answers 200 (or the redirect it promises; only /dump, the keyed door, answers 401), the e-mail form posts to the existing route, and the six tools in the grid open.I-N372— Portuguese/English parity of the home: same ids, same links (only ?lang=en changes), same number of cards, rows and dates; the English page declares lang=en, shows the dollar price and leaks none of the Portuguese interface phrases.I-N373— Every text/background pair the new home draws has a WCAG contrast of at least 4.5 to 1 in light and dark mode (computed in the test from the hex colours); every CSS variable used exists; dark mode, visible focus, prefers-reduced-motion and scroll-snap are present.I-N374— The home HTML (Portuguese and English) stays under the measured ceiling of 130 KB raw and 30 KB gzipped (phase 1 measured about 98 KB and 22.8 KB on 24/10/2026; phase 2 added the week on the exchange block and the regulator timeline, about 112 KB and 25.4 KB); phase 3 added the session heat map (at most 60 tiles), the 'How much did R$ 1,000 earn' block and the block tag on the market links, about 126.7 KB and 28.8 KB, and the ceiling became 130 KB and 30 KB; going over takes a decision, not an accident.I-N375— The /saude response has a home block with the last session, the 3-session limit and, for every home block (coverage, gains and falls, stocks, REITs, ETFs, agenda, regulator, index and the four pulse cards), the date, the delay in sessions and the ok/stale state; the stale list matches.I-N376— B3 licence: the home (Portuguese and English) visibly says 'historical information, not a recommendation' and 'independent, not affiliated with B3' in the market blocks and the footer, and the market blocks carry no download or CSV link and no recommendation wording.I-N377— The new 'week on the exchange' block matches the base by independent SQL: last session of the latest CLOSED week against the previous week's last session; the thermometer (IBOV, IDIV, IFIX, SMLL) is the index change between those dates with bars on one scale; the scoreboard counts liquid stocks (63-session average volume of R$ 5 million or more, main ticker, no corporate event in the week) that rose, fell or stayed flat; the 5 gains and 5 falls come from the same universe.I-N378— Every 'Did you know?' sentence on the home comes from a RULE over the base (index that turned direction, longest run of sessions in one direction of a liquid stock, count of liquid stocks with P/E between 0 and 10, highest 12-month REIT yield with 5,000+ holders, largest weekly volume), carries a number, a date and a source, matches independent SQL, and never names a stock or REIT with a 12-month yield above 30%; 2 to 4 sentences.I-N379— The staleness lock of the new blocks on a hand-built base: the week is the last closed one (a Friday holiday does not break it); illiquid stocks and stocks with a split that week are left out; indices stuck in another week turn the thermometer into 'data being updated' while the scoreboard stays; more than 5 sessions behind, or no previous week, replaces every number of the week block with the notice; the regulator timeline more than 10 sessions behind shows the notice; /saude lists each.I-N37— The what-changed page opens under any Accept header, is in the sitemap and in the panel's explicit list, and declares both its window and how far each dataset reaches. It exists because after 820 open pages and a screener the site was an excellent dictionary with no reason to come back tomorrow; this is the recurring reason, built entirely from our own data. Each source lags differently, so the page shows the lag instead of hiding it.I-N380— The regulator timeline on the home only reads tables the base already has (the CVM IPE notices index and DFP/ITR filings, version above 1 = restatement): a 7-day window up to the latest filing, every item exists in the table with that date and type, the count per type matches independent SQL, and the English page never shows the company's free-text subject in Portuguese.I-N381— Every link in the week block and the regulator timeline (Portuguese and English) answers 200: the index pages, the index performance page, the P/E list, the REIT yield ranking and every named stock; English links keep ?lang=en.I-N382— Portuguese/English parity of the week block and the regulator timeline: same number of indices, squares, gains and falls, 'did you know' sentences, days and timeline items, and none of the new Portuguese interface phrases leak into the English page; the 'week' anchor exists in both.I-N383— The text/background pairs the week block and the regulator timeline draw are in the contrast list (4.5 to 1 in light and dark mode); the new CSS uses colour only through tokens; colour is never the only signal (every change has an arrow and a sign, the scoreboard has a numbered legend).I-N384— Weight of the new home blocks: the whole week block under 12 KB and the regulator timeline under 5 KB, in Portuguese and English, with no new SVG (bars and squares are CSS) and at most 4 days by 4 items in the timeline.I-N385— The /saude home block knows the new blocks (week, week indices, regulator timeline) with date, delay in sessions and state, and the per-block limits (5, 5, 10); the state is the same one that decides what the home shows.I-N386— The home's session heat map matches the base by independent SQL: liquid stocks (63-session average volume of R$ 5 million or more, main ticker) of the last session, at most 60 (largest average volume), grouped by the base's sector, without stocks with a corporate event between the two sessions; each tile links to the stock page, writes the close-to-close change, has the colour band of that same rounded number and the close in its title; each sector shows its count and median.I-N387— The staleness lock of the phase 3 blocks on a hand-built base: the heat map keeps only liquid stocks with no corporate event that day (an illiquid stock and one split on the last session are left out; one split earlier shows 0.0% in neutral colour); 3 sessions behind still shows, 4 shows 'data being updated'; with no PETR4 the 'How much did R$ 1,000 earn' example is replaced by the notice (never a zero) while the form stays; an example ending 4 sessions back is hidden; /saude lists both.I-N388— The home's 'How much did R$ 1,000 earn' example (PETR4, 12 months) is the 1-year row of the /quanto-rendeu/PETR4 page (same price-only and dividends-reinvested amounts, same dates; price-only checked by SQL when no corporate event falls in the window); the form is a script-free GET to /quanto-rendeu, which answers 302 to the canonical URL (main ticker resolved, no second redirect) with X-Robots-Tag noindex, stays out of the sitemap and is disallowed in robots.txt; an invalid ticker goes back to the home block.I-N389— The home tools grid (Portuguese and English) has eight cards, each description starting with what the person does (a verb), with no sales adjective or recommendation, at least three using PETR4 as the example, every link answering 200, and what each card promises is on the page it opens (1/3/5/10 years, PETR4 in the oil and gas comparison, P/E and ROE in the screener, value, weight and payouts in the portfolio, the six verdict figures, P/E and top payers in the rankings, PU, business days and basis point value in the DI calculator, initial amount, monthly contribution, todays Selic and the year-by-year table in the compound interest calculator).I-N38— Every figure on the what-changed page equals the database count for the same window, and an empty section says so in words. A page that summarises the last thirty days is the easiest place for a hand-written number to age silently; here the headline of each section is compared with the query behind it, and a zero is rendered as an explicit sentence rather than an absent block.I-N390— The links of the home's market and tools blocks carry ?h=<block> with their own block name (closed list) and no other link does; the middleware removes the tag before the route, so for one URL of every destination family the tagged page is byte-identical to the untagged one (canonical without the tag, no redirect, the portfolio does not reject it); robots.txt disallows ?h= like the other parameters; removing the tag leaves the rest of the query byte for byte.I-N391— Every click by a person on a home block is counted in the home_cliques block of /admin/stats, per block, over 7 and 30 days: by the ?h= tag or, without one, by the home Referer ('others'); the 'How much did R$ 1,000 earn' form counts once; robots (counted apart), images or icons requested by the home, other referers, error responses and rows older than 30 days do not count; the table holds only day, block, destination and robot flag; the privacy policy (Portuguese and English) states it.I-N392— Portuguese/English parity of the heat map, the 'How much did R$ 1,000 earn' block and the tools: same tiles in the same order and colour, same sectors (translated in English), same links (only the language changes), same number of cards; English numbers use a decimal point; no Portuguese interface phrase leaks into the English page; the #mapa and #rendeu anchors exist in both.I-N393— The seven heat map bands and the 'How much did R$ 1,000 earn' band are in the contrast list and pass 4.5 to 1 in light and dark mode; the new CSS uses colour only through tokens; colour is never the only signal (every tile writes the signed number, the key gives each colour's range in text); on a phone the map does not widen the page (grid with min(100%,...), 68 px tiles that wrap, no fixed width above 340 px).I-N394— The /saude home block has the heat map and the 'How much did R$ 1,000 earn' example with date, delay in sessions and state (daily 3-session rule), the same state that decides what the home shows.I-N395— Weight of the phase 3 home blocks: the heat map under 10 KB (at most 60 tiles, style in the CSS), the 'How much did R$ 1,000 earn' block under 2.5 KB, no SVG and no inline style, in Portuguese and English.I-N396— The heat map groups stocks by the CVM register's ACTIVITY (the same one the sector comparison uses), not by the ten macro-sectors: a holding of an end-sector ('Emp. Adm. Part. - Electric Power') goes into the end activity, two spellings of the same activity are one group, an activity with a single stock (or 'No Main Sector') goes into 'Other', which is always the last group; in English the names are translated. On the real base no group other than 'Other' has fewer than 2 stocks and none passes 12; without the CVM register the grouping falls back to the macro-sector.I-N397— The 'How much did R$ 1,000 earn' example rotates by a fixed rule: the 6 stocks with the highest average volume (63 sessions to the last session, main ticker, ties by ticker), and the position is the day of the year of the last session modulo 6, redone here by independent SQL. The rule depends only on the date (6 consecutive sessions cover the 6 stocks), Portuguese and English show the same stock (and the same on a second read, the version cache), a stock without the 1-year line passes its turn, and the text is neutral: the rule and the 6 tickers are written, no 'best' and no 'recommended'.I-N398— The home 'Foreign flow' block is the fluxo_estrangeiro table (independent SQL): last 24 months, the last month (net, in and out) and the 12-month sum with sign, equal to the /fluxo-estrangeiro JSON and the /fluxo-estrangeiro/grafico card (same calculation, same drawing function). Net-inflow bars sit ABOVE the zero line and outflow bars BELOW, height proportional to the value, class following the sign; the note 'not the foreign investors' balance on the B3 floor' and the BCB source are visible in both languages.I-N399— The home sector race is the indice_setorial table (independent SQL): the same 261-week window as /setores, the 5 sectors with the highest cumulative return, the 3 with the lowest and the market index (9 lines, at most 10), each return written with sign and equal to the '5 years' column of /setores; at most 70 points per line (a sample every 4 weeks, first and last points of the window kept); the drawing is the /setores function; the note says the Dados B3 total-return index (dividends included) is not an official B3 index, with date and links to /setores and the methodology.I-N39— The what-changed page serves JSON only to a client that asks for application/json explicitly, and keeps serving the page to everything else. The format may follow the Accept header; access never may. That distinction cost this codebase four repetitions: on the fund pages the KEY decides, because there is a commercial gate there and answering 401 to */* hid public content from GPTBot, ClaudeBot and curl. Here there is no gate at all, so the header picks packaging and nothing else, and a missing or */* Accept still gets HTML. The totals in the JSON are compared against the database behind the page, so the two cannot drift apart.I-N400— The lock on the two charts on a hand-built base. Foreign flow (monthly BCB series, published late): up to 3 months between the competence and the month of the last session shows it, 4 months, an empty table or fewer than 12 months shows 'Data being updated' with no number. Sector race: last index point up to 3 sessions behind shows it, 4 hides it, no table hides it; on the hand-built base it shows the 5 highest and 3 lowest returns plus the market ('8 of 10'). /saude lists 'corrida' (sessions) and 'fluxo' (months, limites_em_meses) and raises 'atencao'.I-N401— Portuguese/English parity of the two charts: same bars, lines and list items in the same order (sector names translated), same links (only the language changes), same ids; English numbers use a thousands comma and decimal point; no Portuguese interface phrase leaks into English; the anchor bar has #corrida and #fluxo in both languages in the order pulse, gains and falls, heat map, week, sector race, foreign flow, the rest; every link answers 200 and the ?h= version returns the same page.I-N402— Contrast and text alternative of the two charts: the eight line colours are in the contrast list (on the card and on the background) and pass 4.5 to 1 in light and dark mode, as do the flow bars (--pos and --coral); the new CSS uses colour only through tokens and no fixed width above 340 px; the SVGs carry no inline colour; colour is never the only signal: each chart has role='img' with an aria-label spelling the key numbers, the race has a list with signed returns and dashed classes for lines 4 to 8, the flow has the last month and the 12-month sum written with sign.I-N403— Weight of the phase 4 blocks: the sector race under 10 KB (at most 9 paths of 70 points with integer coordinates, colour by CSS class), the foreign flow under 5 KB (24 bars by class), in Portuguese and English; the whole home under 150 KB and 34 KB compressed (the owner's phase 4 target); no JavaScript in the two charts.I-N404— The /saude home block has 'corrida' (date, delay in sessions, state) and 'fluxo' (competence, delay in MONTHS, state) with the same state that decides the home, plus limites_em_meses; 'corrida' and 'fluxo' are in the closed list of block clicks and a click tagged ?h=corrida or ?h=fluxo counts in home_cliques without changing the destination page.I-N405— Only liquid securities keep their "How much did R$ 1,000 earn" page in the sitemap (stocks: 63-session average of R$ 5 million a day; funds: 5,000 holders; ETFs: 60-session average of R$ 1 million a day, the site's existing rules); the pages that leave the sitemap still answer 200 with a canonical to themselves and no noindex.I-N406— The sitemap liquidity rule uses the site's own constants (no new numbers); the trimming function removes only the illiquid "How much did R$ 1,000 earn" routes, keeps the order of the rest, and leaves the list untouched if the calculation fails.I-N407— Every restatement page for stocks and funds carries a noindex,follow meta robots (with or without restatements), still answers 200, keeps its internal links both ways and is not in the sitemap; the JSON on the same URL is unchanged and the asset pages stay indexable.I-N408— JSON and CSV data responses carry X-Robots-Tag: noindex (health, performance, verdict, restatement JSON); HTML pages, robots.txt, sitemap.xml, llms.txt, llms-full.txt, Markdown files, the IndexNow key, /.well-known and the MCP endpoints do not.I-N409— The data-response middleware only adds the header, by content type (JSON, +json, NDJSON, CSV) and never under /mcp or /.well-known; body and status stay identical with and without it, and a header set by the route is not duplicated.I-N40— The methodology index answers in JSON to whoever asks, carrying the title of each page. The MCP connector publishes a methodology() tool that called this index and parsed the result as JSON — and the result was always HTML, so any AI calling that tool got a parsing error instead of an answer. It survived because reading one page always worked; only the index was dead, and nothing on our side exercised that path. The lesson is not that JSON was missing: a published contract with no invariant exercising it is a promise, and the tool sat in the list, fully described, with no one on our side ever calling it.I-N410— The llms.txt dated-lists section lists the three lists with their canonical URLs (the dividend one with the base year), says ready answer, names the `listas` tool of /mcp/ (really in the catalogue; the ChatGPT showcase tool stays out until the app is approved), the Tools line includes `listas`, and the section comes before any part about keys.I-N414— The home's daily ETF flow table matches an independent SQL computation that does not import the module: the day is the latest one reported by more than half of the liquid ETFs (the same liquidity ruler as the ETF rankings: 60-session average of BRL 1 million a day or more), each ETF's net flow is subscriptions minus redemptions of the daily report, the day total and the 5-day sum agree, the five largest inflows and outflows come out in the same order with signed written numbers, bars share one scale, and each ETF links to its own page.I-N415— On a hand-built base the ETF flow rules hold: more than half of the liquid ETFs must report for a day to count (2 of 5 and 2 of 4 do not), an ETF with no inflow or outflow that day is left out and counted, never turned into zero, a line whose net flow exceeds the fund's own net assets is discarded and named, the illiquid ETF never enters, the day total and the 5-day sum are exact, and the staleness lock shows the block at 25 sessions behind the last session and hides it with no number at 26 (and the health endpoint agrees).I-N416— The ETF flow block has the same structure in Portuguese and English (same ETFs in the same order, same bars and links, decimal point in English, no Portuguese leaking), every ETF link answers 200, the block sits after the foreign-flow block with its own anchor, has no JavaScript, says honestly that net inflow is not exchange trading, names FNET as the source and that the daily report comes in monthly files, carries the historical-information notice and uses no recommendation wording.I-N417— On a small hand-built base of 14 stocks (260 sessions) the breadth thermometer matches a computation redone with SQLite window functions and an independently written RSI: percent of stocks above the 200-session average today and 5 sessions ago, the 20-session series, 52-week highs and lows, average RSI and the counts above 70 and below 30; a stock with a 3-for-1 split enters at its adjusted price, a stock with an unexplained jump, one with short history and an illiquid one stay out, and the page states the universe size.I-N418— On the real base the breadth figures are checked stock by stock against independent SQL (window AVG over 200 closes, MAX/MIN over 252) for every stock without a corporate event in the window, the RSI of five of the most traded stocks is redone separately, the aggregate counts equal the sum of the per-stock detail, and the percentage written on the home is that count over the universe.I-N419— The breadth staleness lock on a hand-built base where the liquid stocks stop while one illiquid paper keeps trading: with the last measured session up to 3 sessions behind the last session of the base the block shows (with the data date), at 4 it becomes 'Data being updated' with no number, the health endpoint lists it with the same state, and a universe under 10 stocks also becomes a notice.I-N41— A company page carries the quarterly series, every figure equal to the database, with no fourth quarter, in both languages. The annual series only exists once the fiscal year closes, so a company that filed its second quarter in July still showed its last full year, and all 455 pages stayed identical for twelve months. The quarterly data had always been in the database; it had never reached the person reading. A page that changes every quarter is one a search engine and an AI have reason to revisit; one that changes once a year is not. The fourth quarter stays out here for the same reason it stays out of the API: the interim filing does not publish it on its own, and deriving it would put a figure we computed in the same table as the figures the company reported.I-N420— The breadth thermometer has the same structure and numbers in Portuguese and English, each indicator carries its one-line explanation, it shows the universe and the date, states the price is adjusted for corporate events, only describes the number (no buy, sell, overbought or oversold wording in either language), the 20-session chart is an svg with a text alternative, there is no JavaScript, and the anchor sits right after the week block.I-N421— The heat map switches between three criteria with CSS only (three radios, the first checked, no JavaScript): every cell is emitted once and carries three numbers of the same session (daily change, the session's financial volume and the market value from the company page's own computation, a dash when unavailable); the sequential colour bands follow the fixed limits written in the legends; title and aria-label carry all three numbers; cells are the same and in the same order in Portuguese and English, at most 60.I-N422— On a hand-built base without balance sheets the heat map shows the session volume (BRL 50 million, first band), writes a dash for the unavailable market value with no band, says 'unavailable' in title and aria-label instead of inventing a number, adds the explanation to the market-value legend, and keeps leaving out the stock with a corporate event on the day.I-N423— Contrast and colour for phase 5: the five bands of the sequential heat-map scale pass 4.5:1 in both modes and are in the contrast pairs, the scale is a single blue hue running light to dark in light mode and dark to light in dark mode, the criterion selector only uses existing pairs, the new CSS uses colour tokens only with no fixed width above 340 px, and the selector has a keyboard focus outline.I-N424— The health endpoint's home block lists the breadth thermometer (3-session ruler) and the ETF flow block (25-session ruler) with date, delay and state, the same state that decides the page; both blocks are in the closed list of click blocks and a marked click counts in the admin stats without changing the destination page.I-N425— Phase 5 weight: the breadth block under 7 KB and the ETF flow block under 6 KB (at most 5 inflows and 5 outflows, one bar per CSS class, a single inline custom property), no JavaScript or image, at most 60 map cells, and the whole home under the I-N374 ceiling of 165 KB and 38 KB gzipped, in both languages.I-N426— COTAHIST parse for ETF and FII keeps the whole trading day (open, high, low, close, value, trades, units): on a fixture each fund has exactly the rows the file has (count redone outside the module), an incoherent open/high/low becomes NULL while the row stays with its close, nothing is corrected or invented, options, zero closes, unknown codes and unknown ISINs stay out, and re-ingesting does not duplicate.I-N427— The nightly daily update works on a copy of the live database that still lacks the new ETF/FII columns: the copy gains them (NULL), the new day lands with open, high, low, trades and units, and the old rows stay exactly as they were.I-N428— A database from before the new columns (and without the ETF distribution and event tables) keeps serving the existing routes (ETF and FII performance, ETF daily) with status 200, and the new price route answers 200 with the new columns NULL, not adjusted, no error.I-N429— ETF distributions and unit events from the B3 record: only fund-unit ISINs enter (stock ISINs and total-redemption rows do not), amortization is kept apart from income, a declared split adjusts only when the price confirms it (declared 4:1, price fell about 4x: confirmed; price did not move: not confirmed and flagged), and an empty answer stores nothing.I-N42— The what-we-got-wrong page is generated from the invariants' own docstrings, not from a hand-kept list, and every dated defect in the code appears on it. Each invariant here was born from a real defect and keeps the story, with its date, in its docstring: sixty-five of them, written over months, none of which had ever left the code. What this test holds is not the page but the impossibility of curating it — a hand-written list would allow choosing what to show, and the temptation would be to omit the ugliest defect exactly when it is the most instructive. The page is a function of the docstrings: if a dated invariant exists in the code, it must be on the page.I-N430— Adjusted ETF series and total return match a calculation redone outside: adjusted close equals close divided by 4 up to the record date and equals the close afterwards, open/high/low/volume are not adjusted, the 1-month return with distributions reinvested equals the independent formula, cropping by date does not change any adjusted value, and the CSV has the same rows plus the adjusted column.I-N431— A price jump that no confirmed event explains is flagged and never adjusted or masked: ETF with a 4x drop and no event, ETF whose declared event the price does not confirm, and FII all report the jump with dates and ratio, the series is not adjusted, and the return window crossing the jump has no number.I-N432— Access to the ETF and FII daily series follows the existing price routes: BOVA11 and MXRF11 open and whole; others return the last 30 sessions as a sample without a key (CSV becomes JSON) and the whole series with a key; unknown ticker 404, bad date or format 400, other database version 503 with Retry-After.I-N433— On the real database, the number of sessions, first and last day and every close of BOVA11 and MXRF11 served by the route equal an independent SELECT, dates are ordered without repeats, and wherever open/high/low exist they are coherent (low above zero and not above open or close, which are not above the high).I-N434— The Pro dump now includes etf_precos, fii_precos, etf_proventos and etf_eventos: the price CSVs have the header and every row equal to an independent SELECT, and events and distributions come out with the price check.I-N435— The ETF methodology page (Portuguese and English) explains the daily series, the adjusted series and distributions: only price-confirmed events adjust, unexplained jumps are flagged and not adjusted, the threshold in the text is the code's, B3 reports about 12 months of distributions, BOVA11 does not distribute and nothing is estimated, fixed-income ETFs do not answer at the source.I-N436— A 4xx answer on /mcp is recorded with the request method and the MCP protocol version, so the dashboard says why an AI got a 400; no other header and no body is recorded; successful /mcp answers and 4xx elsewhere keep the plain path.I-N43— An English page is either in English, or it says why part of it is not. Sweeping all seventy-five pages for words that exist only in Portuguese turned up eight, and seven were false positives of the most instructive kind: a company's legal name and the regulator's own account labels. Proper nouns and primary-source labels are not translated, because translating them would invent data the source does not have. The eighth was real: the audit-history page translated its frame and left seventy findings in Portuguese with no explanation. Translating those would be worse than leaving them — a rewritten audit record stops being a record, and the difference between what the auditor said and what we say they said is the whole point of keeping the table. So the page declares it, which is what this codebase does with every limitation.I-N445— By the same liquidity rule, the company answer pages (dividends, profit, debt) of illiquid stocks leave the sitemap while the liquid ones and every main company page stay; /bancos enters the sitemap only when the banks page is published.I-N446— The main stock page and the FII page show a ready-to-copy "How to cite" line right below the direct answer, with the site name, the ticker and the page's own canonical URL (with ?lang=en in English).I-N447— The "left the exchange" page opens with who stopped trading on the B3 in the last closed year (count and companies with their ticker, by year of last trade), the current year to date, a by-year table with the CVM status (cancelled with reason, or registration still active) and a ready "How to cite" line; with no data it stays silent.I-N448— Who left the exchange is counted by the last trading session: a company cancelled at the CVM counts in the year of its last trade, and a company still registered that traded regularly and stopped for over 60 days also counts; an illiquid stock that is just idle, a cancelled company with no trade and a company still trading stay out.I-N44— Search finds an asset by ticker, by name with or without accents, and by tax ID; an exact ticker goes straight to the asset; an empty term returns nothing. The site had 824 asset pages and no search box at all: whoever arrived wanting one company had to open a list of 356 and hunt with their eyes, or guess the URL. That cost breaks no test and shows in no log — it shows on the first visit, which is the only one most people make. Accents cannot hide a result, because nobody searching knows how the regulator spelled the name, and requiring them to know turns search into a quiz. An empty term returns empty rather than the whole base, because 824 rows answering a question nobody asked look like a result and are not.I-N45— A comparison of two companies uses the most recent year in which BOTH have the figure and says which year that is, declares what does not exist for that kind of company, and never names a winner. Comparing one company's 2025 against another's 2023 renders cleanly and compares two different economies. Naming the better one would be a recommendation, which this site does not give, and a recommendation disguised as arithmetic, which is the worst kind. What this page has that others do not is trust side by side: each figure's caveat, and how many times each company restated its filings — a return on equity of 20% from a company that restated 113 rows is not the same figure as one that never restated.I-N46— The company page carries charts as SVG generated on the server — no script — one per indicator that applies; a year without a value is a gap in the line, a point with a caveat is drawn hollow, and the card’s last value is the database’s. The choice was deliberate: an AI reads the page as text and would receive a hole where an interactive chart sits; a phone opens it in half a second with no program to run; and a browser error is invisible from our side. What a chart must never do is invent continuity — joining two years across a year with no value draws a number that does not exist, the same defect as interpolation, only visual. And the caveat has to travel to the drawing: a point flagged in the table and clean in the chart is the table saying one thing and the figure another.I-N471— The home page shows a ready-to-copy "How to cite" line below the coverage cards, with the same counts as the cards, the dated close and the home URL (with ?lang=en in English); no recommendation, English only in English.I-N472— The ETF page opens with a database-generated sentence (close, 12-month NAV return, net assets, each with its own date) and a "How to cite" line with the ticker, date and canonical URL; missing data stays silent; no recommendation, English only in English.I-N473— The /hoje page opens with a sentence giving the window totals (filings, re-filings, dividends, FII distributions) with both dates and the last session, matching the section titles, plus a dated "How to cite" line; no recommendation, English only in English.I-N47— The company, fund, ETF, BDR and index pages show two prices with two owners, and say so. Ours is the closing price from the exchange's official daily file, entered every night, stored, served by the API, the connector and the dump, and reproducible by an audit. The other is a 15-minute delayed quote drawn by the TradingView widget: it arrives through their script, we never receive, store or serve it, and the page states that it never enters our API. This is the one exception to the no-script rule, decided by the owner after pricing the alternative (a delayed-data licence from the exchange plus a feed, display-only by contract). The widget is free only with attribution, and removing the credit gets a site banned, so the credit is our own text next to it and this invariant requires it. The widget is also on the ETF, BDR and index pages (never on the home page), only where TradingView has the symbol: a BDR with no trade in the last 10 days and the IDIV index, whose total-return and price-return versions could be confused, show no widget. The page stays readable as text, with the closing price and its date, and no other script is allowed in.I-N48— The home page opens with the working shown for a real company: its return on invested capital recomputed from the raw filing lines, each line naming the regulator's account it came from, and the result is the SAME figure the base publishes. If the sum does not close, the section is not shown. The whole page carries a single script, the structured-data block; the live counts match; search works without JavaScript. The old home spoke to programmers, with a script playground and a headline about scraping, and 95% of visitors left without a click while two thousand people a month landed directly on company pages from AI assistants without ever seeing it. The new one is generated from the database, and instead of SAYING that every figure traces to the regulator's account it SHOWS it. A sum that failed to close there would be the opposite of what the site sells, so it is checked against the published indicator before it reaches the page.I-N490— Following only plain <a href> links without query parameters from the home page, every sitemap URL is reached within 3 clicks and no page on the way returns an error.I-N491— Every /quanto-rendeu/{T} and /acoes/{T}/dividendos in the sitemap is linked from at least 2 sitemap pages, every B3 index page from at least 3, and the median /quanto-rendeu receives 4 or more links.I-N492— The stock page lists the 6 largest companies of the same sector by market value with links to their page, dividends and returns, all 200; the breadcrumb goes through the sector page; the block is English with ?lang=en links in English, shows the dated lists the stock is on, and weighs under 2 KB.I-N493— The dividends and returns pages link the same-sector (stock) or same-segment by shareholders (REIT) peers, the REIT page links its segment peers, the IFIX and the dated list with its rank; every link returns 200 and the .md text has no block.I-N494— The /acoes hub links /indices, /small-caps, /carteira and /saiu-da-bolsa, /macro links /indices and /fiis links the REIT list and the IFIX; the sitemap no longer lists /quanto-rendeu of ETFs with less than one year of prices, which stay online and link back to the ETF.I-N49— The company page opens with what the company does and where it is (the registration-form profile, with its source declared), carries an anchor bar in which EVERY entry points to a section that exists, and a server-side SVG price chart whose last point is the last close in the base. The owner compared the site with a competitor and was right about the company page, which is where people land. What they do with 81 scripts and 747 KB can be done with HTML and SVG: the profile in cards, the sticky section bar and the price series. The chart is the close in the BASE, monthly, not split-adjusted and saying so, not a third-party quote: the same figure the API serves.I-N50— The fund page has what the company page has: a profile in cards with its source declared, an anchor bar in which every entry points to a section that exists, three server-side SVG charts (price, point-in-time P/BV, distribution per share) whose last point matches the base, and a trimmed P/BV series (24 reports) with the full one a link away. After the profile, the anchors and the chart entered the company page, the fund page looked like an earlier version of the product: no anchors, no chart, and a P/BV table with one report per row since 2010 on the main screen. First item of the post-review roadmap. What changed is the presentation; the figures are the same, and the test checks the last P/BV and the last distribution on the chart against the base.I-N51— The company page opens a “Size and valuation” block: market value (today's close times the latest public share count), enterprise value, earnings and book value per share, 12-month dividend yield, payout and share count, each card with its year, and the point-in-time multiples now carry the year in their label. The market value follows the SAME rules as the multiple (units, counts in thousands, plausibility band) and the yield is the one the dividends route serves. “P/E 33.1” did not say when, and what every competitor shows at the top was missing although all of it was already in the base. The risk was recomputing market value without the three rules the multiple learned the hard way: the largest bank came out at R$ 458 million (a count in thousands), a unit would come out N times inflated, and a garbage count from the registration form would become trillions. The test pins the three with real and synthetic cases.I-N52— Next to the close, the company and fund pages show the 1-month, 12-month and year-to-date return and the 52-week range, computed from the closing series in the base. A window that crosses a stock split or reverse split gets NO figure: a dash with the reason. A series shorter than 52 weeks says “since X” instead of “52 weeks”. The series is not split-adjusted (the exchange's file is not, and it is what the API serves): a 12-month return across a 10-for-1 split would read as −90% and look like a tragedy that never happened, so every window is checked before it becomes a number.I-N530— The empty /procurar page (and a search with no result, such as "dados b3") opens, before the search box, with one sentence saying what Dados B3 is: the counts of companies, FIIs, ETFs and BDRs equal to the home coverage cards, the dated close (B3, COTAHIST), CVM filings since 2010, the data version shown in the footer, and that the site is not affiliated with B3 or the CVM; a "How to cite" line with the /procurar URL follows. The empty page stays indexable with canonical /procurar, Portuguese and English carry the same figures, and there is no adjective, recommendation or B3 licence claim. Why: /procurar was the page Microsoft Copilot cited most (88 of 285 citations, Bing Webmaster, 2026-10-20 to 2026-11-01) and "dados b3" the top grounding query.I-N531— A name search with a single result ahead in the search order opens with that asset's facts from the base: for a stock (petrobras -> PETR4) the last close and its date, the 12-month dividends per share and yield from the page's valuation block, the 12-month P/E with its quarter and a link to the full page; for a FII or ETF the sentence of its own page; and a "How to cite" line pointing at the asset page. A search with no single result ahead (itau) picks nothing and shows only the list, an exact ticker still redirects with 302, Portuguese and English carry the same figures, and a part without data is left out.I-N532— The first paragraph of the home page starts with the counts of companies, FIIs, ETFs and BDRs equal to the coverage cards and the dated close, followed by the usual text, in Portuguese and English with the same figures and no recommendation.I-N533— The first sentence of /etfs says how many funds there are, how many trade above R$ 1 million a day and the date of the prices (the same date as the page's "prices to" line), in Portuguese and English with the same figures.I-N53— Next to each indicator and multiple, the company page shows the median of the OTHER companies in the same regulator sector, each in its latest year, with how many they are. Only figures without a caveat enter; the company itself stays out; below three peers there is no median. The page used to answer the number and not the question “is 32% return on equity a lot?”, while the fund page already said what the peers' median was. Median rather than mean because one company in judicial recovery with a return of minus two hundred percent drags the mean and not the median; clean figures only because a caveat (scale, capital without an average) must not become a reference for the others.I-N54— Below the ratios, the company page shows the history in REAIS — revenue, controlling net income, EBITDA, net debt, cash and investments, equity — year by year with the unit in the label. The values are the base's own (standardised lines and the methodology's derived figures), cash exists only in a year where the cash account was filed, and banks and insurers get “does not apply” where the account does not exist instead of a dash. A ratio alone hides size: forty billion in revenue and forty million have the same margin. Cash adds cash and financial investments because that is what the return-on-capital formula deducts; investments without a cash line do not become “cash and investments”, since a partial sum would read as a whole one.I-N55— The dividend calendar (on the today page, the home and the asset pages) shows only what has a date ahead — fund payments and ex-dates, stock ex-dates — never an impossible date (the exchange writes 9999-12-31 for “to be defined”), and SAYS how far the base can see (the date of the newest announcement it holds). When an asset has nothing scheduled the page says so with that date instead of staying silent. Fund distributions arrive with a payment date and the base held 138 future payments; stock dividends arrive with an ex-date only, and since the exchange source is read on Sunday the newest announcement was 18 days old with no future ex-date. Promising “the next 30 days” on Sunday's data would sell delay as freshness, so the page carries the cut-off date with it.I-N56— The future ex-date of a stock comes from the dividend report the company files with the regulator (the IPE category Relatorio Proventos): the form is read (approval date, last day with rights, ISIN, amount, payment date), enters without a type because the form does not separate dividends from interest on equity and the page says so, does not repeat what the exchange already published (same company, share class, ex-date and amount), keeps only the share class of the main ticker, and every item carries the link to the document. Free-format filings stay recorded as unparsed, with the link. HTML served in place of the PDF is not stored; a new version of a document replaces the old one; what was already read is not downloaded again. The exchange only publishes a stock dividend after its ex-date: a sweep of the whole universe (356 issuers) on 2026-09-11 found zero future ex-dates, while the regulator filing arrives a median of two days before, with a payment date the exchange does not carry.I-N57— The last pages that still ran JavaScript now come ready from the server: the sector race is an SVG drawn in Python with window and scale as links, the foreign-flow chart is an SVG drawn in Python, the transparency page filters by family through a link the server answers, and the methodology title is written into the page instead of rewritten in the browser. Apart from the JSON-LD block and the quote widget the owner allowed, no public page carries a script — an AI agent and a phone read the same page a browser does.I-N582— The company page /acoes/{T} is built in its own queue of two at a time, without holding a slot of the shared thread pool while it waits: a burst of simultaneous requests never builds more than two pages at once, every request still gets its page, and the page, the old-code redirect and the 404 behave as before.I-N583— IndexNow notifies only the sitemap URLs not yet announced, at most once per 20 h, only in production, with a 7-day baseline on first run; a refused batch is retried; never touches the network in tests.I-N58— The company page lists every cash dividend of the main share class as a table: type, record date, ex-date, payment date and amount per share. The ex-date is the first trading session after the record date taken from our own price history, never a guessed business day. The payment date comes from the exchange company file, and when that file has none, from the filing to the regulator, marked as such. The year filter is a link the server answers, the default shows the latest 24 rows, and a summary sentence states the latest dividend, the trailing yield and the 12-month count and sum. The exchange file also fills payment dates on existing rows and adds dividends the list endpoint had not published; a date of 9999 is not a date.I-N596— After the database swap the warm-up waits about 60 s, releases the database file cache and skips the sitemap and fund-portfolio warm-up when process memory is above about 1200 MB; a measuring failure does not prevent it.I-N59— The price chart on the company and fund pages takes its window from a link (one month, three, six, year to date, one year, five years, everything) and draws the closing price from the base at the granularity that fits: daily up to a year, weekly for five years, monthly for the whole history. The label states the return of the window, first close to last, annualized only when the window is clearly longer than a year. A split inside the window is not averaged away: the series starts after it and the label says so. No script is involved; every window is a page of its own.I-N60— The table in reais gains two columns from the quarterly filing: the latest quarter on its own and the trailing twelve months, built by the same identity as the TTM price-to-earnings (previous year minus its quarters to date plus this year to date); a missing piece leaves the cell empty, never interpolated. Balance lines (cash, equity) take the quarter-end figure; lines without a quarterly series (EBITDA, net debt) stay blank. Every multiple and valuation card carries a one-line definition, and the valuation block adds price to operating cash flow and price to free cash flow, outside banks and insurers, as with enterprise value.I-N61— After the coherence pass on the company page (the price chart sits with the quote, valuation shows its two moments in one section, the sparklines sit with the indicators they summarize, nine anchors instead of eleven), the page gains three things seen at a competitor and built only from what the base already held: indicators grouped by theme (valuation, debt and liquidity, efficiency, profitability, growth), each with a one-line definition and a mark when today market value is divided by an account of the fiscal year, with no multiple over a non-positive denominator and nothing that does not apply to banks and insurers; the three statements in one table (income, balance sheet, cash flow) with derived lines named for what they add up; and the corporate actions with their multiplier and whether they matched the observed price move.I-N62— From a second competitor, and again only from what the base already held: a return table by window (one month to ten years) with the price alone and with dividends reinvested at the close of the ex-date, the starting price adjusted by the corporate actions checked against the price and a jump they do not explain leaving the window blank; yield per year over the close of the year's last session and the five-year average; the months with a record date in the last five years, counted rather than projected; the average daily traded value over the last sixty-three sessions on the company card; and a checklist of nine objective facts with the threshold written in each line, banks and insurers exempt from the ones that do not apply, described as facts and not as advice.I-N63— The today block of the company page carries the twelve-month snapshot other sites call current: price to earnings over the trailing twelve months built by identity from the quarterly filings, price to book on the latest quarterly balance, return on equity and net margin over the same twelve months, each naming the quarter, with a note saying which cards use the last four quarterly filings and which use the annual one. A non-positive denominator yields no number. The ROIC card states the whole formula, and the methodology page explains why our ROIC differs from other sites (window, tax rate, invested capital on the average with goodwill), after an external audit found the largest gap there.I-N64— The company card shows the company logo, and the way it is done is the defensible one: the source is the website the company itself declares in its registration form, never an aggregator; every file carries its origin (URL, source, date) in a versioned manifest; the image is a square 64-pixel PNG of at most forty kilobytes served from our own domain, so no third party is called when a page is viewed; the alt and title say where it came from; without a file there is no image and no generic icon in its place; and a company listed in the withdrawal file disappears at the next build.I-N65— The return table gains a real line: the reinvested return divided by the inflation accumulated in the window, from the monthly IPCA the central bank publishes (series 433), compounded month by month with the edge months pro rata by days. A month missing inside the window leaves the cell blank, never interpolated; a missing latest month, which is only published around the tenth of the following month, stops the count there and the label says up to which month it went. The series is read in the build and every night, and a build that cannot reach the source inherits what the previous base had.I-N66— The company page gains shareholders and free float from the reference form the company files with the regulator, in its most recent version: only direct holdings, with the form's own Others line as the market and the capital distribution as the free float, and the page states the filing date and version; the chain of control is not reproduced and no taxpayer id is stored. Tag along comes neither from the form nor from the exchange, so what is published is the minimum guaranteed by the listing segment or by law, labelled as a minimum. Revenue by segment does not exist in structured public data and is therefore absent.I-N67— The price chart and the one-month, twelve-month and year-to-date line adjust the series by the corporate actions the base checked against the observed price drop, instead of cutting the series short or leaving the window blank. The point of each event carries a dashed mark with its factor, the label says how many events it adjusted and stops claiming the prices are unadjusted when they are. An event that was not checked adjusts nothing: a jump nobody explained still cuts the series, with the reason. The last close shown is always the one actually traded.I-N68— An old trading code redirects with a permanent redirect to the current one and the destination page says where the visitor came from, instead of silently answering a different question. A live share class of the same company never redirects. A mistyped code lands on a not-found page that suggests the closest codes, and a code that resembles nothing gets no invented suggestion.I-N69— The calendar shows the future payment date of a stock dividend, which the exchange company file carries and which until now appeared only in the company table, the way fund distributions already did. The filing to the regulator still covers only what the exchange has not published: the same dividend never appears twice under two sources, and a payment of a share class other than the main ticker's never appears at all.I-N70— Every public page wears the same frame and the same palette. Methodology, comparison, guides and the connector page were born with a header and footer of their own, without search or language, and the frame is now swapped at serving time for the site's. No page may declare a fixed colour outside the token block, because a hard-coded light background turns into unreadable text under the reader's dark system theme, and the owner decided against a theme button, so the system theme is the only one there is. A variable used without a definition, or pointing at itself, is a defect too. The categorical palette of the sector chart is the declared exception: it is colour of data, not colour of frame.I-N71— Three loose ends closed. Next to the sector median comes the rank in the sector, computed from the very values the median used, with the direction declared (high return on equity is good, a low price-to-earnings is good) and no rank invented for an indicator with no obvious direction. On the fund page the median of the segment peers leaves the loose paragraph and joins the price-to-book and yield cards, as it already did for companies. And each line of the table in reais gains the shape of its series as a tiny line, breaking where a year is missing and drawing the zero line when the series changes sign.I-N72— The home page opens with the market as the base sees it: the biggest gains and falls of the last session, the highest trailing twelve-month yields and the sectors by twelve-month return of our own total-return index. Three rules keep a data site from turning into a tip sheet: only stocks that actually traded that session, because below that a gain is the noise of a parked stock; any stock with a corporate action that day is left out, so a split never shows up as a fifty per cent fall; and the section states the date, the criterion and that it is descriptive, with no adjective anywhere.I-N73— What the company filed with the securities regulator outside its financial statements — material facts, notices to shareholders, minutes, market announcements — becomes a list on its page, newest filing first, each with the link to the document at the regulator. It is an index, not content: we do not download, summarise or classify the document, because our own summary of a material fact would be the first interpretation this base ever published. The subject line is the company’s own free text and goes as it came. The source is the same public filing index we already download every night for dividends, one extra reading of the same file, and if it does not answer the previous list stays standing.I-N74— Five things that only show up when you read the whole site at once, and each of them chips at the same impression — that one base sits behind the pages. The home announced seventeen years of filings while the Petrobras page, on the same visit, said sixteen; the extra year came from six companies whose financial year falls outside the calendar. Closed financial years are what count now. The English home showed the dividend type exactly as the source writes it, in Portuguese capitals, and charged in reais while the English subscription page charges in dollars — one plan with two prices in one language. The sector line above the company name stayed in Portuguese on the English page, with the translation two lines away. And the quarterly table printed figures with no unit at all. The version stamp now lives in the footer, once per page.I-N75— The /para-quem page is the summary an AI assistant can quote: who the service is for, who it is NOT for, what it costs and how to start, in Portuguese and English, with the FAQ as schema.org/FAQPage JSON-LD. What it states must be true NOW: the connector's tool count comes from the MCP server, the company/FII/ETF counts from the same indexes that feed the sitemap, the price from llms.txt. The 'when not to use' section names at least four limits, each also present on the limits pages — no limit invented to look honest. Every FAQ question in the JSON-LD appears as text on the page.I-N76— The FII and ETF hubs serve JSON at the same URL. The ETF ranking is open in both formats; the FII ranking follows the key rule of the internal MCP tool: with a key or an explicit Accept: application/json it returns the tool's dictionary, and without a valid key it returns a 401 in JSON with 'como_resolver', never the HTML page. The public connector (dados-b3-mcp) reads exactly these two routes, so this test also guards it against silently receiving HTML.I-N82— Earnings per share, book value per share and the share count come out in the API, and they are the SAME figures the company page shows — one computation, not two. A reader asked for it: he builds a Graham fair-value spreadsheet pulling book value and earnings per share 'from the base', but neither was in the JSON, and he could not derive them either, since the share count is absent from /fatos too. The block carries the page's three guards (units, thousands scale, a plausibility band against equity) and declares that it belongs to a different time than the annual series: today's snapshot, not point-in-time — that series stays what backtests use.I-N83— A testimonial meets the same demands a number does, adapted to what it is: somebody else's words. It has an owner and dates (who said it, since when they use the API, and when they gave permission — no anonymous 'a user says' here); it is verbatim, in the order it was written, with cuts marked; it is labelled as opinion rather than a figure of ours; and it sits OUTSIDE the proof strip, where every item links to the page that backs it. A translation declares itself a translation, because the original is in Portuguese.I-N84— The page about companies that left the exchange measures the hole without pretending to fix it, and publishes no inferred ticker. Every figure on it is counted from the database as it is served — none is typed into the prose. A trading code appears only where the company itself declared it in its registration form; matching by name was measured and errs (one in 28 checked), and a wrong ticker that looks right ruins a backtest silently. And the page states that recovering a fraction does not remove survivorship bias, pointing at the limits page instead of claiming a reconstruction.I-N85— The screener's `as_of` filters by what was PUBLIC on that date, not by the financial year. About 100 days pass between a year ending and its statement existing at the regulator, so asking for 'year 2018' returns an answer nobody had in 2018; `as_of` asks the question research needs — what could have been chosen on that day. Every row declares the date its statement became available, none uses a statement filed after the date asked, and the response states what `as_of` does NOT fix: the universe is still today's active companies, with the size of that hole measured on the page about the ones that left.I-N86— The portfolio-in-a-link page computes value, weight, return and dividends to receive, and hides nothing it could not compute. An unknown ticker is listed as left out instead of silently changing everyone else's weight; the database keeps one price series per company (the main share class), so a holder of the other class gets no borrowed price; every dividend shows its record date, since only holders on that date receive it. Nothing is stored — the address is the portfolio — and there is no recommendation.I-N87— `screener?...&recibo=1` freezes the answer at a permanent address; the same question on the same data version yields the SAME receipt; the published sha256 matches the stored result following the recipe in `como_conferir`; and the receipt stays the same after the database changes, while its page says the data moved on. A screener list is only valid until the next update — a receipt that changes with the data, whose hash does not match, or that duplicates on every request is a cache, not a receipt.I-N88— Every company data response (API and connector) carries a ready-to-copy `citacao` field: source page, data version and the date of the company's last filing received by the regulator. An AI that reads a number without it writes 'according to public data'; with it, it writes the source. There is also a stable address per indicator (/empresas/{ticker}/indicadores/{name}) with the series, the company's own range, the sector median, the universe distribution and the methodology link.I-N89— /empresas/{ticker}/veredito answers without a key: six numbers in three questions (does it make money? is it indebted? is it expensive relative to its sector and its own history?), each with a numeric band and comparisons to the sector median, the company's own range and the whole universe, plus the confidence flag about the SNAPSHOT itself (restatement, staleness, flagged values) with its reasons, a top sentence of at most 25 words, and the ready-to-copy citation. No judgement adjective, no recommendation; the same comes through the AI connector.I-N90— The company page opens with the verdict — the same top sentence, flag and six texts as the JSON — anchored, with a 'how we compute it' link on every number and no forbidden term. Page and JSON cannot diverge.I-N91— /small-caps lists ONLY the companies inside the configured liquidity window (floor ≤ average < ceiling), between 30 and 120 of them, with none inside the window left out; sorts by each of the six numbers via links, sending companies without the number to the end; filters by sector; and states on the page that the verdict exists for every company, with the path. Listed in the sitemap, llms.txt and the home page.I-N92— /acoes/{T}/reapresentacoes answers in its H1 'Has {name} ever restated its financial statements? In which years?', with one row per restated account (the same count as the data layer) and the CVM file of the year that revealed each one, in Portuguese and English; an unknown ticker is a 404; the page is in the sitemap and linked from the company page when there is a restatement.I-N93— Every company page carries a FAQPage with the five questions of the AI-citation script, and every NUMBER in every answer appears in the page's visible HTML (for the restatement question, in that company's restatements page). No FAQ text, Portuguese or English, carries a forbidden term; and two companies get different answers — it is data, not fixed text.I-N94— Every multiple card on the company page and the point-in-time note state 'price of DD/MM/YYYY', and the multiples JSON carries the same text in preco_de, matched to data_preco. A multiple without its date gets quoted as if it were today's.I-N95— The company page <title> is generated from the data — 'WEGE3: ROE 32.2%, P/E 33.1 (2025)' — with the ROE of the last year without a caveat and the P/E of the last filing, each in the page's own number format; the meta description carries the year's figures, not the corporate purpose. With no clean figure, the generic sentence.I-N96— Generated comparisons answer noindex,follow and are not in the sitemap; a company's restatement page is in the sitemap only when the company restated — otherwise it is noindex,follow. Company, REIT, ETF, fact and guide pages stay indexable, and the FAQPage stays on company pages only.I-N97— Every company page in the sitemap carries lastmod = the receipt date of its latest filing at the CVM; restatement pages the filing that revealed the latest one; REITs and ETFs their latest report. Text pages carry the date their text last changed (since October 2026), and no date is in the future or the build date.I-N98— /acoes lists every company with a page, by sector, each with ticker, name and the ROE of the last year without a caveat (the same figure as its page; a dash when there is none); the footer of every page links /acoes, /fiis and /etfs.I-N99— /fiis/{T}/reapresentacoes: the H1 is the question, the first line answers it, and the table lists every month with version above 1 — the same as the database — with the delivery date and the CVM file; links to FNET and to the fund page, which links back. Without any re-filing the page is noindex,follow; the sitemap has only funds that re-filed, with lastmod = the latest delivery of a version > 1.LM01— On the real database, every url in the sitemap carries a valid lastmod in year-month-day form and never later than today: a dividends page takes the latest approval, record or payment date of its share class up to today; earnings and debt take the latest annual filing; the how-much-it-returned and BDR pages take the last trading day with a price; guides, methodology and fixed pages take the date their text changed, which equals the seeded date while the text is unchanged. Gemini once quoted an outdated dividend for PETR4 because search engines did not know the page had changed.LM02— On a synthetic database the dividends page takes the most recent date up to today among approval, record and payment of the dividends of its own share class: a dividend approved three days ago with record and payment dates still ahead counts by its approval, and a dividend of another class does not count; earnings and debt follow the annual filing, not a later quarterly one; the return page follows the last trading day.LM03— The text ledger keeps, for each page, the fingerprint of its text and the day it was first served: text matching the seed takes the seed date, new text takes the day it was seen and keeps it on the following days, edited text gets a new date, text reverted to the seeded version does not go back to the old date, no date is later than today, and line endings do not change the fingerprint. Stamping today on every sitemap would teach search engines to ignore the field.DL14— In the connector, events of a company that left the exchange answer without an error, dividends return the error with the left-the-exchange block and no empty list, and an unknown code returns the usual error plus suggestions and the 404 status.I-N443— MCP tools use the same normalization as the routes (PETR4.SA, petr4f, BVMF:VALE3, HGLG11.SA, ^BVSP); an unknown code returns an error with the suggestion and the search link, the dashboard stores only the code with the tool of origin, and without a key the connector uses the AI assistant count instead of the ordinary anonymous limit.I-N444— In tools that require a key the error always carries a fix hint, even for an unknown code; the dashboard classifies the message first, so an unknown code is counted as unknown ticker and its code is stored with the tool of origin, while a missing key for an existing code is still counted as a limit.MCP10— The tool list published in llms.txt is generated from the connector's own catalogue, not kept as a parallel list by hand. The hand-written one was already wrong: it announced ten tools while the connector exposed fifteen, so a third of the product was invisible to any AI reading the file that exists precisely to introduce it. Two lists far apart age in silence; this one is derived, and a new tool that does not reach the front door fails the build.MCP11— The connector's `screener` tool accepts `as_of`, like the API. The parameter existed in the API and its menu, but an AI using the connector only sees what the tool declares: without `as_of` in the signature, the question 'what could I have picked on that day' was still answered with the statement of the financial year, which nobody had on that date. The connector answer carries the same guarantees: no row uses a statement received after the date, and the survivorship caveat is stated.MCP12— The connector has `precos` (daily price series) and `eventos` (splits, reverse splits, bonus issues and spin-offs, with source and net effect of the day): WEGE3 opens without a key, a closed ticker returns an error that says the key goes in the tool argument `chave_api` (not an HTTP header) and that REST uses X-API-Key, a bad date comes back as an error instead of an exception.MCP13— The API key may go in the URL as ?chave=: an AI that can only open URLs (no headers) reaches closed tickers. The parameter is removed before the route, the response is private/no-store, no-referrer and noindex, a wrong key still returns 401 with the hint, the header wins over the URL, and /portal, /dump and /admin never accept a key in the URL.MCP14— The connector `desempenho` tool returns ready-made numbers (return, max drawdown, volatility, moving-average crossover) for open tickers, locks closed ones with an error that teaches the key, opens with a key, and returns an error (not an exception) for invalid parameters, premises included.MCP15— Without a key, the daily price series of a closed ticker returns 200 with the last 30 sessions (in order, the last one equal to the full series' last), `amostra: true` and an `aviso` IN THE BODY saying how to get the full series and where the ready answer is (`/desempenho`); csv also comes as JSON so the notice shows; with a valid key the full series comes without notice; WEGE3 stays complete; unknown tickers stay 404. A reader that only sees 'error 401' leaves empty-handed.MCP16— The `indice` connector tool returns IBOV performance with no key, the daily series as columns (a 30-session sample without a key, the full series with one), the list of indices when `codigo` is empty, and `erro` (not an exception) for an index that does not exist.MCP17— The connector's `bdr` tool lists BDRs without a key (with a type filter), returns AAPL34 without a key and says what it lacks, requires a key for the others (an error, not an exception), returns the daily series as columns (30-session sample without a key, full with one) and answers `erro` for an unknown BDR or invalid type.MCP18— The connector's `dividendos` tool answers for ANY ticker with no key (the same data is public on the company's dividends page; a blind test on 17 Oct 2026 showed the AI giving up at the key prompt and citing another site), the REST route keeps requiring a key, and an unknown ticker returns `erro` (not an exception).MCP19— Every tool of the connector declares a non-empty `title` and the annotations `readOnlyHint` true and `destructiveHint` false (a requirement of the Anthropic connectors directory); all tools only read public data.MCP20— The MCP server for the ChatGPT app directory (/mcp/chatgpt/) exposes only data that is already open: no tool takes a credential argument; no tool description and no answer (every tool called with a valid argument, in commercial mode, with no credential) mentions keys, plans, Pro, subscriptions, upgrades or prices; every successful answer carries source_url on https://dadosb3.com/ and the source; an unknown ticker and an invalid parameter come back as tool errors (isError true); every tool has a title and read-only annotations; and the Claude connector at /mcp/ keeps the same 25 tools, untouched. Why (18 Oct 2026): the ChatGPT directory guidelines forbid promoting plans or upgrades inside the app and collecting user credentials, and the /mcp/ connector has a key argument and answers with a sign-up invitation; the owner chose a second, showcase address instead of changing the one already submitted to Anthropic. Attack: put the key argument back on a tool; let a reused text with a sign-up invitation leak into an answer or description; drop source_url; return an error as a success; mount /mcp/chatgpt after /mcp so the Claude server swallows the path.MCP21— The ChatGPT connector tells the model, in the server instructions and in the disclaimer of every answer, never to turn the past data into a buy, sell or hold recommendation and to leave the decision to the user (in a rehearsal on 18 Oct 2026 ChatGPT recommended a stock using our numbers).MCP22— The ChatGPT connector returns the dividends of ONE share class only (the class of the ticker asked) and a ready total per year per share that matches the sum of the listed payments, so the model cannot add two classes together (in a rehearsal on 18 Oct 2026 it reported twice the real PETR4 total).MCP23— The MCP endpoints do not carry X-Robots-Tag (machine path, outside the central noindex rule for data), while plain data JSON such as the health check does; the MCP response body is unchanged.MCP24— The Claude connector has the `listas` tool, open without a key, with title and read-only hint: for each of the three dated lists it returns name, base date, criterion, rows, source and the canonical page URL, with the same order and formatted values as the /listas page and matching an independent SQL computation (dividends of the share class over the last close; fii_yield); the limit trims, empty returns the catalogue, an unknown name returns an error naming the valid ones.MCP25— The etf and fii tools take serie=True and return the daily series in columns, the same as the route and an independent SELECT (BOVA11 and MXRF11 open); date range crops; unknown ticker returns an error object; without serie the answers are unchanged.MCP26— Both MCP servers (/mcp/ and /mcp/chatgpt/) answer 200 to a client on protocol 2026-07-28 (server/discover, tools/list and the health tool call, with the per-request envelope and routing headers) and keep answering clients on 2025-06-18, 2025-11-25 and with no version header (initialize, tools/list, health tool); the tool catalogue is the same for every version; an unknown version gets a structured 400 error.MCP27— The connector's "how much did R$ 1,000 earn" tool returns exactly the text of the page (with the URL to cite) and the "funds that hold this stock" tool returns the same answer as the /empresas/{T}/fundos route.MCP28— The connector's "left the exchange" tool returns the same years and companies as the page, with the URL to cite, and the banks tool never returns figures while bank publishing is switched off.MCP29— The MCP endpoint answers /mcp and /mcp/ (and the ChatGPT one) directly to POST and GET with no 3xx redirect, and the address announced in llms.txt and .well-known answers directly.MCP30— A 400 on /mcp/ is only for a truly invalid request and always carries a structured JSON-RPC error; protocol 2026-07-28, older clients and clients with no version header get 200.MCP31— The 15 tools added to the ChatGPT MCP server on 2026-11-03 (app 1.0.3) and the 13 earlier ones declare all five annotations (title, read-only, not destructive, idempotent, closed world); the new ones answer without any key, in commercial mode, for a ticker outside the free sample, with the same data as the equivalent tool of the general MCP server; the tools that only open a sample without a key (prices, events, scores, restatements, quarters, screeners, ETF detail, funds holding a stock) are not there; and the per-address limit still applies, coming back as a clean tool error that never mentions keys or plans.MCP32— Every 4xx from both MCP servers (/mcp/ and /mcp/chatgpt/) reaches the panel with a closed-list reason word read from the SDK error body (content_type, json_invalido, versao_nao_suportada...) next to method and protocol version, nothing from the client request is recorded, and the client always gets a JSON-RPC error body, even where the SDK sent plain text; valid requests keep status 200 and the bare path.MCP33— Connector tools accept dates the way assistants write them (2024, 2024-06, 06/2024, 01/06/2024, 2024/06/01, 20240601, ISO datetime) and answer exactly as with YYYY-MM-DD; "ate" with a bare year means Dec 31; impossible dates and free text still return the usual error; data/vencimento only change format; monthly series tools (Anatel, ANP) are excluded.MCP34— Every argument of every tool, on both the Claude connector and the ChatGPT connector, carries a non-empty description in its input schema (36 and 28 tools), and the ChatGPT tools keep their five annotations, so AI clients can choose the right tool and send the right values.MCP35— The /conector page (PT and EN) shows the Claude/generic MCP URL and the dedicated ChatGPT URL with the real tool counts from tools/list, the Claude Code plugin line, the GitHub repo link without ?de=, and the full list of tool names.MCP36— Over real HTTP on both MCP servers, the client label comes from the 2026 envelope clientInfo (Cursor), from the User-Agent on the old protocol without clientInfo (Claude-User, openai-mcp) or becomes "other"; asset class and ticker come from the database (PETR4.SA becomes stock/PETR4, MXRF11 becomes FII) and an unknown code becomes "other"; no free text, key, X-Forwarded-For IP, raw User-Agent or raw clientInfo name reaches any analytics table.MCP37— A successful /mcp/ answer only gains the metadados key (data version, source page, methodology page, notes when the code was normalized) and the data equals the REST route; a fund or ETF code in a stock tool returns an error, never a tool exception, with tente_assim naming the right tool and arguments; on /mcp/chatgpt/ the tool error names the app tool that answers.MCP38— The ChatGPT app 1.0.4 text covers exactly the /mcp/chatgpt/ tools, in six blocks in English, with sibling pairs cited both ways, at most 1,800 characters each, the anti-recommendation sentence and the rule to call the matching tool within its scope and cite source_url in the first 512 characters of the instructions (never "instead of searching the web" or "any number"), no word about keys, plans, prices or subscriptions; and it is exactly what the /mcp/chatgpt/ server serves (instructions and every tool description).MCP39— Every tool of the /mcp/ connector has its description in six blocks, in order (first sentence, Use when with 2 or 3 quoted questions, Do NOT use for naming the sibling tool, source and formula, returns, empty or error) plus access; every sibling pair cites each other both ways; descriptions stay under 1,800 characters and the server instructions under 2,048 with the use-this-not-the-web rule and the catalog keywords; the asset code is always called ticker (declared exception: indice.codigo), chave_api has one description everywhere and closed sets carry an enum.MCP40— On the ChatGPT connector, get_dividends for a delisted code (PETZ3, ENBR3) answers instead of a tool error, linking the delisted-company page and carrying the delisting block and only the per-share distributions declared to CVM (IPE), with yearly per-share totals equal to the sum of the list (which may be empty); no FRE total amount reaches the answer; the general MCP tool still answers; PETR4 is unchanged.MCP41— A fund code (HGLG11, BOVA11) in the stock price tool returns the fund series with atendido_como, and the FII 404 says where a company or ETF code livesI-N330— Without a key, the six open-shop connector tools (accounting facts, annual indicators, multiples, dividends, REIT sheet, REIT ranking) answer for a non-trial ticker with a source line, and the data equals the REST route's. The vault tools (events, scores, restatements, quarterly series, ETF sheet, both screeners) still answer with an error that asks for a key. The per-address limit reaches the connector: past the cap the tool returns HTTP code 429 and a wait in seconds, without raising, and another address passes.I-N331— The description an AI reads in the tool list marks the six open-shop tools as open, no key, any ticker, one at a time, with the CURRENT limit read from the config file (never typed by hand, and never the unreplaced marker) and the source line; the vault tools say they need the key argument; none promises free-forever. The ChatGPT connector still mentions no key, plan or subscription.RD06— A JSON-RPC POST to /mcp without the trailing slash answers 200 in the same request with the same tool list as /mcp/, instead of a 307 for every message; a browser GET on /mcp still gets the connector page and /mcp/chatgpt without the slash still reaches the ChatGPT directory server.I-N112b— The list of tests that the home page, /metodologia, /transparencia and /empresas show, read from the TEXT of the test modules without importing them, is identical item by item and in the same order (id, description, file, line, module) to the one obtained by importing the modules and asking inspect. Reading the text exists because importing compiled 3 MB of tests in every process (about 1.1 s on the first home page).I-N112c— On a synthetic module with the test shapes the suite uses (multi-line and functools.wraps decorators, a comment between decorator and def, a multi-line header with a parenthesis inside a string, raw, escaped, single-quoted, multi-line and missing docstrings, a redefined function, async def, a body on the same line, a method inherited from a base class of the same module, a method without docstring inheriting the base one, multi-line strings at the left margin with def test_ and class Test inside, a nested test_ function), the text reader returns exactly the list that importing returns.MED13— The usage record cannot reconstruct who asked for what. Knowing that a given endpoint family was called forty times guides a decision; knowing WHO asked about WHICH company guides nothing worth the cost of storing it. The test attacks both ends — the raw key and the ticker.MED14— The panel states SINCE WHEN it has been measuring key usage. Without that date, a zero in the first weeks reads as 'nobody used it' when it means 'we were not yet measuring' — the same error nearly made with the fact pages, and which its own invariant exists to prevent from the other side.MED15— Every authenticated call goes through the usage counter, and the counter exists. The strategy became 'make the people already using it dependent on the product, then monetise' — which requires knowing whether anyone comes BACK, and that was exactly the question with no instrument: the counter lived in an in-memory dictionary, wiped on every restart. We knew sixteen keys had been created and nothing about what happened next. Third time the same lesson appears: the channel we bet most on was the only one without an instrument.MED16— Tasting — the denominator of the tasting-to-key conversion — is counted in the middleware only for a DATA route of an open ticker, without a key, that succeeded and was requested by a person; it stores path family and day, never the ticker. The aggregate ratio comes out per week and says it is not an individual funnel: without cookies, who tasted is never linked to who created a key.MED17— Retention comes out by cohort of the week of a key's first use: how many started and how many came back N days or more later. A cohort that has not had that long yet is marked and has no ratio — not a zero — and the result never carries a key hash.MED18— /admin/metricas does not exist without ADMIN_TOKEN (404) and, with it, brings the four Block 5 metrics — tasting conversion, retention, arrivals by channel and watcher events per month (the same counter as the /vigia page) — with no e-mail, IP or key hash.MED19— Two synthetic scenarios on the same kind of day: people reading 10 pages many times is not suspicious; the same volume spread once per page over 1,000 pages is flagged as a suspected crawler. The cut-offs come from analytics_config.json, the table has no IP or cookie column, and the panel says "browser", not "human".MED20— derivado/search_console.jsonl (one line per day, entered by hand by the owner) is read by analytics and appears in /admin/metricas as google_real next to the concentration; pulso prints the real Google clicks next to the google.com referer and the days suspected of crawling.MED21— The pulse script reads the weekly “state of the product” report through /admin/metricas and prints the summary, the week and the file where the server saved it (DADOS_DIR/relatorios/YYYY-WW.md). The panel token goes only in the X-Admin-Token header, never in the URL (the query string lands in the server access log), and never appears in anything the script prints, not even a network error message. The server accepts the header (and still the query, for compatibility); a wrong or missing token gets 404.MED22— OAI-SearchBot and Claude-SearchBot (building the AI search index) are `ia_indexador`, not `ia_a_pedido`; ChatGPT-User, Claude-User and Perplexity-User stay `ia_a_pedido`. The panel counts both separately, per day, and says since when the split exists.MED23— The dashboard reads `dia` with the SAME clock (UTC) used to write the row. On a Sunday at 21:30 in São Paulo (Monday 00:30 UTC), the row just written shows up in that week's count, even though the local machine still says Sunday. Writers stored UTC dates while readers used the local date; on a UTC-3 machine the readers missed every row from 21:00 to midnight and the week split on Sunday night.MED31— The dashboard can see AI errors. Every counted page access now stores the HTTP status and the time the answer took; every MCP tool call is counted AFTER it runs, as ok or as an error with a short class (unknown ticker, no data, limit, invalid input, unexpected failure), never with the ticker or the text the user asked. The stats gain a block with AI accesses by status family, the most frequent failing paths, median and 90th percentile time of pages fetched on demand by AI, and failing MCP tools by class. An old database without the new columns is migrated without losing a row.MED40— The dashboard shows WHICH robot it is. Every access classified as a robot stores only the robot family (Googlebot, GPTBot, Bingbot, Bytespider and so on, from a short explicit list, or other-bot / ? when it does not match), never the user-agent text and never the IP. /admin/stats gets a robots-by-family block for 7 days: the count per family and its 5 most requested routes. No response behaviour changes: nothing is blocked or limited.MED41— POST /admin/search-console stores the week's Google Search Console numbers on the persistent disk; reading merges them with the historical repository line (the disk wins on the same day); /admin/metricas returns them; non-integer or negative values get 422 and nothing is stored; without the token, 404.MED42— Each AI on-demand request stores only the route family (ticker becomes *) and its latency in ms as an hourly histogram; browsers, search crawlers and requests without timing are not stored; no query string, IP, ticker or user-agent reaches the database.MED43— The admin panel block ia_erros.latencia_por_rota gives, for 24h, 7d and 30d, route families with n, median, p90, p99 and max (minimum 20 requests, sorted by p90 descending, at most 15) plus the 10 slowest query-free paths of the last 24 hours; it is absent without the admin token.MED44— AI latency storage is bounded: at most 60 route families per hour (the rest becomes (outros) without losing counts), 10 slow paths per hour, hours older than 48 h collapse into daily rows and data older than 35 days is deleted; the middleware records AI requests only, without changing the response.MED45— The connector usage signals keep only closed-list options: an unknown enum value becomes "other"; free-text arguments (name search, oil field name, CNPJ, operator, raw date, key) never appear; dates become a period range; screener filters keep only the filter name, never its value; and the full recording path leaves none of these texts in any analytics table.MED46— A ticker is recorded only when it exists in the public database (stock, FII, ETF, BDR); unknown codes, phrases or e-mails become "other" and calls without a code are "none". The admin panel shows a ticker only with at least 5 non-burst calls spread over at least 3 distinct hours; below that it is summed into "other" and the code appears nowhere in the block; burst calls do not count toward the minimum.MED47— The fifth identical call (same tool, client and arguments) within 60 seconds, and the following ones, get the burst mark; earlier calls, different arguments, a different client or the same call after the window do not. The API key does not split a burst. The comparison digest lives only in memory: the database stores a 0/1 column and nothing else, and the panel splits burst from non-burst by day and by client.MED48— The admin stats panel adds the connector signals block (by closed-list client chatgpt/claude/cursor/other, asset class, tickers, closed parameters, bursts, first use of each tool) and a 30-day AI robots block with 10 routes and per-section totals per family, while every previous key and format stays the same (the 7-day robots block keeps 5 routes and no sections). The protocol clientInfo wins over the User-Agent and unknown names become "other".MED49— The privacy page (PT and EN) describes the connector signals with the numbers the code uses (burst threshold, 35-day guard for codes not found) and says no free text, key, IP or header is recorded; the aggregated table has exactly the columns the policy covers (a new column fails until the policy is reviewed), is created idempotently and is part of the 365-day cleanup.TR34— Every public route WITH A PATH PARAMETER opens under any Accept header and is counted by the panel. TR16 audits public HTML pages but drops every parameterised route by construction and only looks at routes declaring an HTML response class, so the company pages, the fund pages and the 500 audit samples were all invisible to it. The samples served raw JSON to */* and were measured by nothing at all: the panel read zero for them since forever, and that zero meant not-measured, not nobody-came. This test also refuses to let the list of parameterised routes go stale, and proves the data-route label is honest rather than a hiding place.TR35— The bulk-export index states how many rows come without a traded code. Its note used to promise that every line carried the ticker and the CVM code, so the datasets would join against your own base — false for one row in six, 11,739 of 71,085 in the indicators set, across 96 companies. It is not a broken join: those are companies registered with the regulator that have no traded stock, and not one of them has a single trading session, so there is no code to carry. The data is right; the sentence promised more. The cost falls on whoever consumes it and is invisible — joining on ticker drops those rows with no error at all, and an outside audit that found the gap concluded it was a failing join, which is itself the symptom of an undeclared limit. Same defect as the price-session wording fixed a day earlier: a claim above what the data delivers, always in our favour, and the same fix — publish the size, computed from the base.TR36— `/empresas/{ticker}` answers instead of returning 404. Walking one level up a URL is the most natural move for anyone exploring an API, and it used to say not-found for a company that exists — an agent concludes coverage ended there. The root is a DATA route (JSON for everyone, any Accept, like the rest of /empresas/), it is OPEN because it carries no figure, and it says three things: who the company is, where its human-readable page lives, and which resources exist, each declaring whether it needs a key.TR37— The /saude alarms on a synthetic database, one at a time: a build started 3 times (the limit) without finishing lights up, 2 does not, a finished one does not; “running” for more than 6 h lights up, “failed” 7 h ago does not say “running”, “running” for 1 h does not; the last session 7 days ago lights the daily alarm, 6 does not; 3 daily-update failures in a row light up, 2 do not; a database with no price gives null coverage instead of breaking the route.TR38— _com_idioma adds the language switch exactly ONCE and is idempotent, pointing to the other language of the same route; public pages of each family carry one switch; the watcher JSON shows at most 500 events when no limit is given.TR39— _idioma: an explicit ?lang= wins in any case; otherwise the language that appears FIRST in Accept-Language; neither pt nor en gives PT. _com_idioma without a breadcrumb puts the switch right after “back”, once; with two breadcrumbs it marks only the first. _noindex_follow touches only the first <head>. _data_por_extenso drops a time stamp and returns None without a date.TR40— The visit middleware counts, on GET only: each company page, its restatement page, the hubs, the comparisons and the FUND page when the answer is HTML; not an arbitrary /acoes sub-path, not POST, not the fund JSON on the same URL. Key usage counts only answers below 400 and never on the pages of _PAGINAS_ANALYTICS.TR41— On the page URL with a key: an unknown fund or ETF is 404; a database of another version answers 503 “building” (page and JSON); an old fund code redirects with 301 to the current one, and a current code equal to the requested one does NOT redirect.TR42— /vigia/eventos: `desde` includes the day itself; `limite` stays between 1 and 5,000; `tipos` carries the name of each type.TR43— A database with no build or daily-update keys: failures and starts are 0 and there is no warning; the disk is in GB with two decimals and the used share with one, of the same data folder; price coverage carries the FIRST and the last session.TR44— Tasting: /empresas/WEGE3 is an open data route; POST and error answers do not count. Daily limit: exactly the limit passes, the next one is 429, and the message names the plan (“free” or “Pro”). A public page that already declares Vary: Accept goes out with Accept, Accept-Language.TR45— The dadosb3 client (cliente/) runs against the app through the TestClient transport, with no network: verdict, watcher (company and funds), REIT restatements, screener with as_of and receipt, and reading the receipt. The recorded answers the package tests use offline have the SAME top-level keys the route answers today, and every key quoted in the package README exists.TR46— /api has one section per public and keyed route (company, indicators, multiples, dividends, screener with as_of and receipt, receipt, verdict, watcher, company and REIT restatements, REIT, ETF). In PT and EN, each section shows the curl, the five-line Python and the answer the route gives NOW, cut by the same cut function; the Python runs on the pip client and returns the same answer. The access written in each section is what the route does in commercial mode (open, key except for the open tickers, key always), and the screener example still comes out without opening the screener to anyone without a key. The key rule, the cache, the connector version and the MCP registry come from the app constants, and the cache header is what the page says. No banned term.TR47— The code only imports PUBLIC FastAPI and Starlette modules (no fastapi.middleware.* or starlette.middleware.* beyond the documented ones, no private segment starting with an underscore); requirements pins both within a range with an upper bound; and building the /api examples (the whole app through the public httpx ASGI transport) records no visit, key use or trial use, and brings the answer of every section.TR48— The /api examples are generated in a thread right after start-up (fired by the lifespan outside the test battery): after it, the first visit to /api generates nothing. With no database, the warm-up does not generate and /api generates on demand. An incomplete example is redone only after the interval; a complete one holds for the whole data version.TR49— /metodologia/vigia and /metodologia/veredito exist in PT and EN, are in the index, the sitemap and llms.txt, and are linked from the three /vigia tabs, the per-ticker watcher page and the verdict block. The cut-offs quoted are the configuration ones, GENERATED when the page is served: changing a cut-off in the file changes the text; no marker comes out raw (not even in the .md); an unknown marker fails loudly. No banned term.TR50— The production smoke script (testes/fumaca_producao.py), pointed at the app itself instead of the network, passes every check: /saude with the code data version, and each page (hubs, a company, a REIT, an ETF, the watcher, /api, methodology) with status 200, public cache with Vary: Accept-Language, lang pt-BR and en, and the version stamp. It only makes GET requests without a key, declares itself a robot in the User-Agent and is not collected by pytest. A network that does not answer (30 s, one retry) becomes a line “FALHOU <url> <reason>” and exit code 1, never a traceback. Queue 11 item 37: the sitemap in XML with /acoes/WEGE3, robots.txt with Baiduspider “Disallow: /” and the Sitemap line, a 403 for a request that identifies itself as Baiduspider, and the watcher during the event computation: a 503 WITH Retry-After passes, a 503 without it fails.I-N131— The demo page of the ChatGPT connector is public and needs no key. /demo/chatgpt (Portuguese) and /demo/chatgpt?lang=en (English) answer 200 with the recording, the connector address https://dadosb3.com/mcp/chatgpt/ and a link to the Claude connector page; the recording file is served with a one-day cache; the page is in the sitemap, linked from the connector page, declares the three hreflang pairs, and carries no price or plan. Why: the OpenAI app directory asks for a demo recording URL, and that address has to stay up and say only what the connector does.I-N495— The Claude Code page is public in both languages and carries the install commands. /claude-code (Portuguese) and /claude-code?lang=en (English) answer 200 with the plugin install commands (/plugin install dados-b3 --marketplace Val7h/dados-b3-mcp, and the /plugin marketplace add plus /plugin install dados-b3@dados-b3 pair), the one-line claude mcp add --transport http dados-b3 https://dadosb3.com/mcp, three example questions and a link to /conector; they declare the three hreflang pairs and carry no price, plan, B3 licence claim or recommendation. The route is in the sitemap, in llms.txt and among the pages the panel measures, and /conector links to it in both languages. Why: the public repository Val7h/dados-b3-mcp became a Claude Code plugin marketplace, and this page is the address that gets shared.I-B66g— When a new base is activated on the server, the `diario_*` keys of the `meta` table are copied from the database that was live into the new one before it goes live, and the inheritance is recorded (`diario_herdado_em`). If the old database is missing or has no such keys, activation proceeds without error. Born on 2026-10-19: every base rebuilt on the owner's computer and swapped in wiped the nightly-update history, so /saude went back to `nunca_rodou` after each swap.I-B66h— Inheriting the nightly-update history never overwrites a newer value: if the incoming base already holds a more recent history, it stands and only receives what it lacked; if the outgoing database is more recent, it wins. Inheriting twice is harmless.I-H01— The flow case of 21/08: the external source returned ZERO and the database under construction was left with no rows at all.I-H02— The FX case of 21/08 — the one a `if it came back empty: copy` would let through, because it came back half-full, not empty.I-H03— The rule in one sentence: after inheritance, no table may hold FEWER rows than the previous database.I-H04— First build ever (or a wiped disk): there is nothing to inherit from, and that must not raise.I-H05— The REAL numbers of 21/08/2026 have to be refused.I-H06— The gate must NOT be zero, and this is the test that prevents it.I-H07— The normal case: a bigger new database. It passes.I-H08— First build ever: there is nothing to compare against, so it releases.I-H13— A new rule may legitimately remove rows (the restatement key gained the aggregation level and 9,467 rows became 8,654), but only by DECLARING how much, per data version, and only at the version switch: the same declaration does not license the next build of the same version to shrink again, a declaration for one count does not release another, and the drop cannot exceed what was declared. Without this, the anti-shrink guard refused the first rebuild in production and FII/ETF pages stayed unavailable.I-H14— The daily backup copies the three small databases on the persistent disk (follow-up requests, analytics, receipts) through SQLite’s backup API into DADOS_DIR/backup/YYYY-MM-DD/, whole and openable; it does not copy again on the same day; a missing database is left out; only the 7 most recent days are kept; /saude gives the date of the last one and how many files, with no path or content. Everything stays inside DADOS_DIR.I-H15— The finished database goes up in chunks through /admin/base/*, only with the X-Admin-Token header (the URL query and a missing header answer 404), and is parked with the version it carries, without touching the production database. Built on the owner's computer because rebuilding inside the 2 GB container took the site down for lack of memory.I-H16— A chunk with the wrong offset gets 409 with the current size (so the script can resume after a connection drop); empty or oversized chunks and a second upload while a base is ready are refused; a wrong sha256, invalid gzip, or a file that is not an intact database with a data_version fail WITH a reason and delete the partial file so the upload restarts clean.I-H17— The parked database replaces the production one only when it carries the version the running CODE promises, production is not on it yet, and no watched count shrank beyond the limit. A different version WAITS (the base stays); a shrink REFUSES with the reason on its record (the base stays and production is untouched).I-H18— With RECONSTRUIR_NO_SERVIDOR=0 the server does not rebuild the database (new version or Sunday) but still rebuilds when there is no database at all, the rescue for an empty disk; without the variable the old behaviour holds, and the daily update keeps running in the same loop.I-H19— The local script uploads the compressed database in chunks to /admin/base/*, resumes from where the server stopped (without resending what already arrived), ends with the base READY and never writes the admin token to its output.I-H20— /saude reports the process and container memory as the Linux host sees it (resident, peak resident, container including file cache, and the limit), and omits the block where it does not exist. A memory-limit restart on the host could not be explained without it.I-H21— The NAS archive step copies only what can be rebuilt (public CVM/B3 zips, the compressed finished base and the logs), with house-style names, never subscriber databases or credentials, never deletes anything, does not recopy what is already there, and an unreachable NAS only warns instead of failing the upload.I-H22— Checking a parked base (sha256, decompression, quick_check) asks Linux to drop the file cache of the partial upload and of the decompressed database; where the call does not exist (Windows) it is a no-op. The host container counts file cache against its 2 GB memory limit.I-H23— While a parked base is decompressed, checked or received, a helper thread asks Linux to drop the file cache every second (and after every received chunk), and it stops when the block ends. A real-size rehearsal measured the host container touching its 2,048 MB limit through file cache; dropping only at the end of each step was not enough.I-H24— Decompressing a parked base writes and cleans in the same loop: every fixed number of bytes written it forces the write to disk (fsync) and asks Linux to drop the file cache, so the dirty cache never exceeds that amount. A real-size rehearsal still took the host container to its 2,048 MB limit for ~25 s with a once-per-second helper thread, because dirty pages cannot be dropped before they are written.I-H25— If a network query times out in the local script (in the end-of-upload notice, whose reply is lost though the server already started checking, or while waiting for the check) it retries instead of dying and ends with the base READY; a 409 already-in-progress on the repeated notice counts as arrived; with no answer at all it gives up with a message instead of retrying forever. A real-size rehearsal had the base ready on the server while the script crashed on a read timeout.I-H26— The parked-base state endpoint tells whether the server still rebuilds the database (reading RECONSTRUIR_NO_SERVIDOR: true without the variable or with 1, false with 0), which is how the owner confirms from outside that the variable took effect on the host after the restart.I-H27— The local script's --help finishes and prints its text even when the console only understands cp1252 (the Windows one): characters it cannot print are replaced instead of crashing the script. The help text has a star glyph and --help crashed with UnicodeEncodeError on the owner's console.I-H28— On activation the parked base receives from the production database the foreign-flow, exchange-rate, CPI and Selic rows it lacks (INSERT OR IGNORE: what the new base brought stays), and only then does the shrink guard decide. The first local weekly rebuild could not reach the central bank API (NXDOMAIN on every DNS) and brought zero rows; the server refused the swap and two hours of work stalled. The old server build inherited from the previous database; the new flow did not.I-H29— On activation the ETF daily-report and monthly-balance tables receive from the production database the rows the parked base lacks; on a key conflict the row with the HIGHER document id wins (the schema rule); the amendment history only gains what is missing; a table absent on either side is skipped without failing the swap. The FNET backfill has a per-run time budget and only the live database holds everything fetched so far: the first local rebuild had 24% fewer daily reports and no guard watches that count.I-H30— Swapping the parked base keeps the database that was live as `<database>.anterior` (a hard link: no 2.3 GB copy); the next swap replaces it; `reverter` puts the previous one back with the same WAL hygiene and consumes it; without a previous one it answers 404. The first swap deleted the old database and with it ~3,500 ETF daily reports that only it had.I-H31— GET /admin/base/ar streams the live database as gzip that decompresses to the IDENTICAL file, with original size and data version in headers; only with the owner header (404 without it or with the token in the URL); 503 when the WAL has unconsolidated frames and 409 when a download is already running; the lock is released.I-H32— The local script downloads the live database into the NAS before it is replaced, checking the stream while it downloads (decompress and compare with the original size) and writing bases/ar/<date>_ar_<version> plus the sha256 only if it closes; a cut stream never enters the NAS and is retried; an unreachable NAS or silent server only warns and never fails the upload; the token is never echoed.I-H33— The redundant NAS only acts when DADOSB3_NAS_REDUNDANTE is set: it then receives the house files and mirrors the live-database copies already in the main NAS, without re-copying or copying subscriber databases; with the new disk offline it only warns and never stops the build.I-H34— The local script's production gate always runs pytest with PYTEST_DEBUG_TEMPROOT in its own folder inside the build folder (created if missing, overriding any inherited value), and when pytest exits with an error it tells failed tests (FAILED/ERROR lines or a summary with failures) apart from a pytest crash after the tests (neither). On 12/10/2026 every test passed, pytest died cleaning the shared Windows temp folder, and the message said the gate failed with an empty list.I-H35— Between the parked-base file appearing and the record with its version being written, the state answers "processing", never "ready" with an empty version; the local script and the restart only read "ready" once the record says which version arrived. The check swapped the file first and wrote the record after it, and a reader in that window saw "ready" with no version, which made I-H25 fail intermittently.I-H36— On Linux, importing the API caps the memory allocator at 2 arenas and the hourly loop hands freed memory back to the system; elsewhere nothing is called and the state says so. The pool of synchronous routes is capped at 8 threads, and /saude shows threads, arenas and returns. Added on 12/10/2026 after Render restarted the service for memory while AI agents opened dozens of pages in parallel.I-H37— The production gate of the local build script runs test_navegacao as well (broken link or orphan route stops the weekly build), while the server fallback build keeps it out: the module list in api.py is unchanged, because on the server crawling the whole site next to the new database brought the instance down. If the API module list cannot be read, the gate still refuses to run instead of running navigation alone.I-H38— If the server restarts in the middle of checking a parked database (a deploy), the record still saying "processing" now reads "failed" with the reason, and the same upload can be finalized again until it is ready; while the check really runs in this process, a second finalize is still refused. On 12/10/2026 a merge restarted the server four minutes after a 572 MB database arrived, and it stayed "processing" forever.I-H39— A new upload of a DIFFERENT database starts from zero even when the server still holds the complete partial of an old one under a 'failed' (interrupted) record: it is discarded first, and the new database ends up ready. While the server is really checking an upload, the script stops without discarding anything. On 13/10/2026 the script 'resumed' from the end of the old file and the server rejected the upload.I-H40— Each test process (the single one in serial, or each pytest-xdist worker) gets its OWN DADOS_DIR: small files are copied (the SQLite ones through the backup API), the multi-GB b3.sqlite is NOT copied (BANCO keeps pointing to the original, read only) and what is too big to copy (analytics.sqlite) starts empty. Writing in the copy never touches the original. On 23/10/2026 MED03 failed whenever another session wrote to the shared analytics.sqlite, and the 30-minute gate could not run in parallel.I-H46— The local script skips downloading the live database to the NAS when the NAS already holds that same version (built base or earlier live copy, with its sha256 and non-empty); when in doubt (unknown live version, only another version stored, missing sha256 or empty file) it downloads as before, so there is never a missing backup.I-H47— The /saude page publishes eight new counts (stock dividends, fund distributions, dividend notices filed with CVM, corporate events, filing versions, CVM notices, ETF daily reports and ETF balance sheets) with the SAME query the shrink guard uses, and no older key disappeared or was renamed.I-H48— The shrink guard used by the nightly run, the server swap and the rescue build refuses a database with more than 1% fewer rows in ANY of the eight new tables, naming the count; the same database without a drop passes and a slight drop (up to 1%) is still allowed.I-H49— Both sides of the guard with the new counts: the home-PC script does not send a database with fewer stock dividends, CVM notices, events, filing versions or notices than /saude; smaller fund distributions and ETF tables only go to the log because the server fills them from the live database before checking; the server swaps such a database after filling it and refuses one with fewer dividends, leaving the live database untouched.I-H50— The home-PC script's send-only mode reuses the compressed database only when it is newer than the database file, so a stale archive from a previous build is never uploaded in place of the new one.I-H51— The import decision trail records one line per new or reverted data caveat (flag or reason written by the import) since the previous run, summarizes large groups in one line, records nothing when nothing changed, and never breaks the build.PR05— The free-key button leads to a form, not to the payment provider. The free tier is a zero-value subscription, and the path to it used to be the same checkout as the paid plan — no card requested, but wearing the face of a payment form. The funnel measured the cost: 47 people opened it and 16 finished. Asking someone to cross a billing screen to collect something free is friction with nothing on the other side.PR06— The plans page states who the charge comes from. Forty-six checkouts were opened and none was paid, and at the last step the customer met a company name the site had never mentioned. The public name at the payment provider is now the product's, but the registered entity is still another one and it is the entity that appears on the invoice — so the page says so BEFORE, instead of leaving the discovery for the moment of the card.PR07— A malformed e-mail creates no customer, and neither does a crawler. Without the checkout in front, creating a key became an anonymous POST that creates a customer at the payment provider. The two remaining doors are e-mail validation and the crawler filter — the same one already blocking bots on the plans page, where 27 of 72 visits were automation.PR08— A created key records WHERE the person came from — the tag on the link we sent out and the optional 'how did you find us' answer — and none of it is stored with the e-mail. Without cookies, the entry visit and the key created later were rows with nothing linking them: the dashboard said 'N keys' and could not tell whether any came from the AI connector, from llms.txt or from an AI assistant. Tags come from a closed list; an invented tag is dropped, never stored as free text.PR09— The paid checkouts (card and Pix) ask Stripe for abandoned-cart recovery (a recovery link and the marketing-consent box on Stripe's own page); the free one does not. If Stripe rejects those parameters, the session is created again without them: checkout never fails because of the extra. On 16/10/2026, 48 of 55 paid checkouts opened in 30 days were abandoned.PR10— The welcome e-mail approved by the owner goes only to FREE-plan key holders (no Pro, not the owner, no repeated address), with replies going to the owner; the dry run only counts; a second run skips whoever already got it; a failed send is recorded as failed and retried next time; the route does not exist without the admin token.PR11— The site sends the reminder for a PAID checkout that expired only to people who ticked the consent box on Stripe's page, with the recovery link and replies going to the owner; a R$ 0 (free) checkout, one without e-mail or link, or from someone who completed another checkout in the window is skipped; nobody gets it twice; a Stripe failure never raises.PR12— The screener (stocks and REITs) offers a download of this list as a spreadsheet with the SAME filters; the spreadsheet is Pro only (free key 403, no key 401) and holds exactly the rows on screen, in the same order, with percentages in percent and the caveat in its own column; the key may come in the URL (the form sends it that way).PR13— The plans page (PT and EN) shows what Pro does for daily users: the screener list as a spreadsheet, the database as CSV, direct answers within 1 business day and the 7-day guarantee up front; and still says nothing was taken away from the free tier.PR14— Every campaign and reminder e-mail carries a one-click stop link (and Gmail's List-Unsubscribe): the link is signed (no one can unsubscribe someone else) and does not carry the address; opening it (GET) only asks, the POST records; whoever stopped gets neither the campaign nor the reminder; a stop sent by reply enters through the token-protected panel; if the list cannot be read, the campaign does not go out.PR15— An HTML page requested with utm_source/utm_medium/utm_campaign goes out with the same mark on its internal links (the a-href paths starting with a slash), so the next click (for example /assinar) arrives marked and the panel can count the journey from a campaign to the Pro page — no cookie, nobody identified; personal links, admin and the canonical tag are not marked; only the three utm fields, normalized.PR16— The key page tells the FIRST delivery (chave_entregue plus the plan, the conversion) from an existing subscriber reopening their own key (chave_reaberta plus the plan, not a conversion); the key is the same both times; without payment, nothing is delivered and the route answers 402.PR17— A request with a browser User-Agent but without Accept-Language, or claiming Chrome without sec-ch-ua, is a program, not a person: it is recorded as robo_disfarcado (bot) and stays out of the visitor count; real browsers stay human; Firefox does not need sec-ch-ua; no header is stored.PR18— The GET of the subscribe route shows a confirmation page and does not charge: no payment session at the provider and no funnel event. Robots dressed as browsers, in bursts of five hits in the same second, were opening fake checkouts through a GET that had side effects.PR19— The POST of the subscribe route creates the session, records the funnel event and answers with a 303 redirect to the payment page, never a 307 that would resend the POST.PR20— A declared crawler that POSTs to the subscribe route opens neither a session nor an event, for card and for Pix: the robot filter stays on the route that charges.PR21— Someone who gives up on the card form returns to the plans page, not to the home page, where there is no price and no Pix option.PR22— On the plans page Pix and card are buttons of the same weight (same class), side by side, with Pix first for the Portuguese-speaking public.PR23— On the free-key form, the e-mail of a Pro subscriber (or of someone who already has a free key) does NOT return the key in the HTML or the JSON: the page says that if the e-mail already has a key we are sending it there, and the key goes only to that same e-mail. A new e-mail still sees its new key on screen, with no e-mail sent, so the free funnel does not get worse.PR24— No enumeration: on the free-key form the answer for an existing key is identical for Pro and free (it does not reveal the plan); on the subscriber portal the answer is identical for a customer e-mail and an unknown one. Sending happens after the response, so the response time does not depend on whether the customer exists.PR25— The API key alone does not open the Stripe portal: the page asks for the e-mail; a key in the header or in the old form neither redirects nor creates a Stripe session, it only sends a link to the e-mail registered at Stripe.PR26— The portal link is single-use and expires in 30 minutes: opening it only shows a button (an e-mail scanner that pre-opens links does not burn it); the button opens the right customer portal once; a second use, an expired link or an invented one gets 410; the disk keeps only a hash, never the link or the e-mail.PR27— Fail closed: in production (RENDER=true) without the Stripe keys, the Pro dump, the key routes and the Pro check answer 503 instead of opening everything; only the sample stock stays open. Outside production, without Stripe, everything stays open as before (development).PR28— Without the webhook secret, the Stripe webhook answers 503 and neither reads nor processes the body (before, it was read as raw JSON); with the secret, a wrong signature is 400 and the right one is accepted.PR29— Key resends and portal links are capped at 3 per hour and 10 per day for the same e-mail, counted on disk; past the cap the page is the same and nothing is sent, and different e-mails do not share the cap.PR30— Asking for deletion answers the same (status and text) whether or not data is linked to the e-mail, sends the confirmation link only to that same e-mail and only if something exists to delete, deletes nothing at request time, refuses a malformed e-mail and ignores robots.PR31— The deletion link is single-use: opening it only shows a button (an e-mail scanner that pre-opens links deletes nothing), the POST deletes once, an invented or expired link or a portal link deletes nothing, and the deletion link does not open the Stripe portal.PR32— Once the link is confirmed, the Stripe customer record is emptied and deleted and the key leaves the cache; follow-ups, portfolio, bulletin, weekly-send history, trial request, waiting e-mails and the usage counts of the key disappear for that person only; campaigns swap the e-mail for its hash; receipts lose only the owner; the hash goes to the do-not-send list; the event keeps the hash, never the e-mail, and no disk file keeps the e-mail in readable bytes.PR33— Without an e-mail provider the request and confirmation routes answer 503 with the contact and write nothing (not even the pending-mail file, which would keep the address); in production without Stripe, the same.PR34— The daily snapshot file has no column about a person (e-mail, name, key, IP...) and the deletion leaves it byte for byte as it was.PR35— Repeated deletion requests respect the same cap as the portal: at most 3 e-mails per hour and 10 per day to the same address, and the page stays the same.PR36— The hourly resync skips a deleted Stripe customer who once paid by Pix: it does not try to write a key on it (which would break the whole resync) and does not give the access back.PR37— The owner's key lookup route /admin/chave/{prefix} does not exist without the admin header: 404 with no token, a wrong token, or the token in the query string, and 503 with the right token when Stripe is not configured. Nothing is asked of Stripe before the door.PR38— The lookup prefix must be 8 to 32 lowercase hexadecimal characters; anything shorter, longer, upper-case or with symbols is refused with 422, so nobody can list every customer with a one-character prefix.PR39— A matching prefix returns the key owner's e-mail, plan, whether the subscription is active, creation date and a usage summary (successful calls, active days, first and last day, most used endpoint families, 429 count), without caching and without writing anything.PR40— The API key itself, or any part of it, never appears in the lookup response, and the customer's e-mail never reaches the log, even when Stripe fails.PR41— A prefix matching more than 5 customers returns only the count and asks for a longer prefix, with no e-mail; an unknown prefix returns zero.PR42— The key-hash index lives in memory for a few minutes, so repeated lookups scan Stripe once, and the route never modifies, deletes or creates anything in Stripe.PR43— The owner can replace the key of a customer who asks for it: the old key stops working at once, the new one keeps the same plan and goes only to the e-mail on file, never to the response or the log.PR44— Replacing a key needs the owner token (404 without it); an invalid body gets 422; an e-mail with no key, or with two accounts holding keys, gets 409 and nothing changes.PR45— The thank-you e-mail approved by the owner on 2026-11-01 goes only to FREE keys used on 3 or more distinct days in the last 30; Pro, the owner, occasional keys and anyone already sent are left out; the dry run only counts; replies go to the owner; the route does not exist without the admin token.I-Q09— The FNET daily report (latin-1 HTML) parses into the reference month, the CNPJ and one row per day — NAV per share, inflows, redemptions, net assets, total portfolio, shareholders — checked against the real BOVA11 document.I-Q10— The FNET balance sheet (COFI chart) parses into {account: (description, balance)} for ALL accounts, checked against the real document (118 accounts).I-Q11— From the CVM's Resolution 175 registry only the index-fund classes (FIIM) come out, with a 14-digit CNPJ; other types stay out.I-Q12— The portfolio file (CDA cda_fie) parses per position with the month's first day as reference, decimal-point numbers, filterable by CNPJ.I-Q21— The three quota accounts of an ETF balance sheet are stored exactly as the document declares them — no convention is chosen. The COFI chart does not give account 30915158 the same meaning at every administrator: for most it is already the month-end balance; for some (LVOL11 in August 2025 is one) it is the opening balance of the financial year, and the month-end figure is opening + issued − redeemed. An earlier version applied the second formula to everyone and broke most funds. Storing what is declared keeps the information for either convention, and the cross-check (I-Q06) accepts both.V01— The verdict text is number + numeric band + comparison; no judgement adjective enters, and the 'adjective' mode in the configuration file is OFF — it only turns on by an explicit decision, for the day a licensed analyst signs.V02— The top sentence has at most 25 words and is deterministic: the same input always yields the same sentence, with no language model in the request path.V03— Special cases have their own text and never an invented number: negative profit (P/E does not apply, P/B still does), negative EBITDA, net cash, banks (ROIC and debt/EBITDA do not exist), a young company without history, a value carried with a declared note, a flagged value that is not published, and a multiple flag that belongs to another field.V04— The confidence flag speaks about the SNAPSHOT, with reasons: green when nothing is off; yellow when a central number is withheld by a flag, when a material restatement (≥5% in a key account) happened in the last three years, or when the last annual statement is over 15 months old; red when the snapshot may mislead — the year's own statement was restated by more than 10%, or the regulator already holds a newer statement than the one computed here.V05— The configuration file is coherent with the codebase: every flag it names is produced in precos.py or indicadores.py, the not-applicable map equals the company page's, the forbidden-terms list catches what it must and ignores the standard disclaimer, and the translator never queries the database directly.V06— Run over every traded company: no forbidden term in any text, every top sentence within 25 words, every non-green flag carries a reason, the flag is not uniform (if 90%+ were one colour it would inform nothing), and the same ticker yields the same verdict twice.V07— The 'below the Selic rate' cut for ROIC is switched on and off by the configuration file: off, the fixed band applies; on and with the fiscal year's average Selic, the band says below/at-or-above with the year and the rate; on without that year's Selic, it falls back to the fixed band and never invents the rate. No new label carries a forbidden term.V08— Every cut in the configuration file falls on its declared side: a band's lower limit belongs to it; a zero ROIC with the Selic cut on is 'below', not 'negative'; the company's own history enters with 3 years, not 2; a restatement counts within the last 3 years, from exactly 5%, and only with a known difference; red needs MORE than 10% in the year itself and yellow-by-age MORE than 15 months. Born from the code mutation run of 26/09/2026: each of these comparisons could be flipped and the whole suite still passed.V09— The top sentence is only shortened when it EXCEEDS the word limit, and the cut removes exactly the 'against X for the sector' clause (then the reason for red) — nothing else; zero net debt reads as '0.0x', not net cash; a zero margin with a flag is not narrated as a loss; a FLAGGED number is never narrated as 'not applicable'.V10— The flag text states the filing date and how many central numbers were published, exactly — and the restatement reason quotes the cut as written in the file. The earlier test only checked the start of the sentence, and mutation showed that a date with one extra character or a doubled count went through.V11— The verdict comes out in both languages: the top sentence, the six lines, the questions, the flag (text and reasons) and the note have an English version with the SAME numbers in English format (decimal point), with no Portuguese word; every band, “does not apply” mark, note and Selic band in the configuration has a translation; a bank’s “does not apply” and a caveat do too; no banned term in English.V12— The top sentence of the verdict says the date of the P/E price (the first session after the statements became public) and the P/E at the latest close, plus the sector median, in both languages and within 35 words; the pl and pvp items carry valor_preco_atual and data_preco_atual (same earnings or book value, only the price changes), the text says which is which, and the value stays point-in-time.V13— The P/E at the latest close multiplies the stored multiple by the latest close over the statement-day price and by the share factor of the confirmed events in between (a 2-for-1 split with half the price leaves the P/E equal); an event with no known quantity, or a missing or zero price, leaves the value out and the sentence cites only the point-in-time figure.I-B112— ITR ingestion keeps the year-to-date D&A lines inside 6.01 of the indirect cash flow statement for Q2 and Q3 under demo DFC_MI_ACUM; the rest of the year-to-date cash flow, the year-to-date income statement and periods not starting on 1 January stay out; Q1 is not duplicated; the account is kept in the version archive.I-P47— The as_of parameter of the multiples route redoes TTM profit, TTM EPS and TTM P/E with the version of EACH document in the identity (prior annual filing and the quarterly filings of both years) that was in force on that date: a superseded version whose figure the build archived uses the archived figure; the version in force today uses the database; a superseded version with no stored figure is NEVER replaced by the newer version's number, so profit, EPS and P/E are null and the row says the original version is unavailable. A quarter whose filing arrives after the date is left out and counted (the receipt day itself already counts); each row lists the versions it used; the price is the first session after the receipt of the quarterly filing in force. Without the parameter nothing changes, and an invalid date is a 422. Attack: always using the database, falling back to it when the old figure is missing, keeping the last version's price, or copying the TTM rule instead of sharing it.I-V01— `as_of` on /fatos returns, for each period, the highest filing version whose receipt date is on or before the date asked, with the version and receipt date on every row. The day a version arrived already counts as that version; the day before does not; before the first filing the period does not exist.I-V02— When the version in force on the date asked existed but its values were not kept (the regulator publishes only the latest), the period comes back flagged and WITHOUT numbers — never with the next version's figures, which would be the leak of the future that `as_of` exists to prevent. A database from before the version tables still answers.I-V03— The quarterly `as_of` applies the same rule per year and quarter; annual filings never leak into the quarterly answer.I-V05— Ingestion archives the version about to be overwritten: only the key accounts, only when the incoming version is higher, before the upsert; every version of the filing is kept with its date and document id; re-ingesting or a late older version changes nothing. Annual and quarterly.I-V07— The rule that decides which accounts are kept covers every account the standardisation reads and rejects the ones no item uses: an account forgotten here disappears from the past without a trace.I-V08— A build from scratch loses no versions: what the previous database still holds in a lower version is captured, the history already archived is inherited, a missing or incompatible previous database changes nothing, and the archive count is among the shrink-guarded counts.I-V10— A past version read from the filing XML on the regulator's portal is stored in the same convention as the current version: only the current period, consolidated and individual kept apart, scale applied, both number formats read correctly (with and without the thousands dot), the cash-flow method kept, equity only in the total column, quarterly filings only for the isolated quarter, unused template accounts left out, nothing already stored overwritten, and a file that cannot be read or does not match is counted by reason instead of stopping the load.I-V11— A past version read from the old-format filing file (2020 and earlier) is stored in the same convention as the current version, even though that file names neither the company, the version, the period nor the scale: the document comes from the list and the scale from the total assets the base already holds. Annual filings read the first value column, quarterly ones the second (and the fourth for the first-quarter cash flow and income statement); consolidated and individual stay apart, direct and indirect cash flow stay apart, equity is read only from the current period and the total column, a statement left entirely at zero is not stored as if the company had reported zero, and a file with no total assets to compare, an unclear scale or a layout that contradicts the list is counted by reason instead of being guessed.I-V12— `as_of` on /fatos also picks up the figure RESTATED as a comparative in the next year's annual filing, from the receipt date of that exact filing version (the day before it still returns the original). The row says where the number came from, when it became public and the original value; the old version fields keep describing the company's own filing. Without `as_of` nothing changes.I-V13— A restated comparative replaces the original only for the SAME account: same label, not zeroed in the file, not off by a factor of a thousand, same consolidation basis; a later re-filing of the year itself wins over an earlier comparative; when the version in force has no stored numbers, only the items the already-public comparative covers are returned, never the newest version's figures.I-V14— The /fatos route exposes the origin, public date and source document of each row and the count of restated items on the `as_of` path; without `as_of` the published (original) fact is returned, with no new field.I-V15— Three real restatements from the restatement table (an ordinary company, a bank with its own chart of accounts, another ordinary company): the day before the next annual filing is received the original comes back, the day it is received the restated figure comes back, original and restated never swapped.VG01— Each event type in the product instruction is detected — profit turned into loss (or the reverse), net margin and net debt/EBITDA changing band, restated statements, the confidence flag changing colour — each with the previous and new period, the number before and after, the date the revealing filing became public, the source and the data version. A restatement is dated by the NEXT annual filing, where the year comes back with a different number. A series with no change yields no event.VG02— No number lives in the watcher's code: the bands are the verdict's, the restatement cut is the same as the flag's, and the quarter used for comparison is chosen in the configuration file — changing the file changes the event.VG03— Only a number the verdict would publish becomes an event: a flagged value, a not-applicable value (negative EBITDA, zero revenue), an indicator that does not exist for banks, and an account that is zero on one side of a restatement (a missing line, not a republished number) are left out. A number the site withholds cannot change band in public.VG04— No watcher text — Portuguese, English or the e-mail — carries a forbidden term, even when the company described the account as 'sales revenue': the text uses our own label and the filed description travels only in the JSON. The e-mail is a skeleton: it builds subject and body for the followed tickers only, and nothing in the module sends, schedules or stores subscribers.VG05— The watcher reads only through the data layer and the verdict — no SQL in the module or its page — and imports neither the API nor the company page; the page only builds HTML. Deterministic: the same input yields the same events.VG06— Run over every traded company: every event has a known type, both periods, both numbers, a date, a source and the data version; no text carries a forbidden term; all five types occur; the monthly counter adds up to the list; a restatement is never dated in its own year; the same ticker yields the same events twice.VG07— /vigia and /vigia/eventos open without a key; the monthly counter on the page and in the JSON is counted from the list, never typed; each event on the page links to the company page and shows its source; filtering by ticker and type works; an unknown type is a 400; the page is in the sitemap and llms.txt.VG08— REIT watcher, pure function: P/B, 12-month yield and vacancy changing band and unitholders changing beyond the cut-off come with both numbers, both periods, both bands and the date of the revealing filing. Only the immediately previous period is compared (a gap is no event); a flagged number is no event; the cut-offs come from veredito_config.json; unitholders need the minimum in both reports.VG09— A report with version above 1 is an event dated by the delivery of the version in force; if the build captured the re-filing, the event carries the field that changed most (before and after) and how many others changed; without capture it says only what is known. Version 1 is no event.VG10— /vigia?classe=fiis and /vigia/eventos?classe=fiis show what the REIT watcher returns, filterable by type and fund, with a SEPARATE monthly counter; /vigia without a class is still the companies' page; an unknown class or type is 400; no forbidden term in any fund event; the e-mail is only a skeleton.VG11— The 12-month yield has no series at the source: each build snapshots fii_yield into fii_yield_historico (one row per fund and base date), with the versioned derived file in the symmetric step; leitura_fii reads on a read-only connection and accepts a database without the new tables; the REIT watcher has no SQL.VG12— The text formats (reais in thousands/millions/billions in both languages, date, quarter), the detection edges (zero margin is neither profit nor loss; a restatement exactly at the cut-off counts; a number missing on one side makes no band event), the event direction both ways and in English, the restatement source, the flag periods, the counter by type and without date, and the empty, sorted e-mail.VG13— REIT watcher: each band label in both languages; a value exactly at a cut-off belongs to the upper band; unitholders exactly at the minimum count; a fall is written as “fell” and “−”; unitholders with thousands separator and no decimals; on a re-filing, the field that changed most in RELATIVE terms and how many others; the counter by type and month; the filter by ticker and type; the e-mail empty, with inclusive `desde` and ordering.VG14— The restatement text: one account (“one account changed ...”) or several (“N accounts changed ...; the largest”), with the fiscal year and the following annual filing, in PT and EN; among accounts with the same relative change, the one with the largest absolute change is chosen. In the REIT watcher: the English unitholders text without decimals; a capture with only “no field changed” gives the text without numbers; month distance with two-digit months; an undated event sorts after dated ones without breaking.VG15— ETF watcher, pure function: effective fee and premium/discount changing band and net assets changing beyond the cut-off come with both numbers, both months, both bands and the date of whoever revealed it (balance-sheet delivery; last session of the month; daily-report delivery). Only a month against the IMMEDIATELY previous one; a flagged number, a month with too few sessions and a fund below the minimum trigger no event; cut-offs and minimums come from veredito_config.json (changing the cut-off changes the event).VG16— When FNET brings ANOTHER document for an ETF month already stored, the balance-sheet write records, before the replace, the before and after of each field (or one line saying no published field changed); the same document again is not a re-filing. The watcher turns this into an event dated by the new document delivery, with the field that changed the most. The versioned derived file is never rewritten with fewer lines, and reading tolerates a database without the table.VG17— /vigia?classe=etfs, /vigia/eventos?classe=etfs and /vigia/{ETF} show what the ETF watcher returns, with type and ETF filters, and a SEPARATE monthly counter (sum = dated ETF events); the tabs link the three classes; /vigia and ?classe=fiis stay the same; an unknown type is 400; the ETF page links to /vigia/{ETF}; no banned term.VG18— /vigia/semana/YYYY-WW lists the watcher events for companies, REITs and ETFs known from Monday to Sunday of that ISO week (the weekly e-mail window), grouped by type in watcher order, with the full count even when the list is cut. Only closed weeks: the current and future ones are 404, and /vigia/semana redirects to the latest complete week (the one the Monday e-mail covered), keeping the language. A week without events, or outside the recent ones, is served noindex; the sitemap carries the recent weeks with events, lastmod = the last event. The archive keeps the last 104 complete weeks and never goes before the week of the first known event (404 and no previous link). PT and EN; no forbidden term; the home page and the watcher tabs link to it.VG19— The watcher events are computed ONCE per (class, data version), outside the request: twenty simultaneous requests with a cold cache trigger a single computation and all get the same list; a request waits at most the configured time and otherwise gets 503 with Retry-After while the computation goes on, and the next request is served. The sitemap is cached per version only when complete (with the watcher weeks); while the events are being computed it goes out without the weeks and stays out of that cache (only in the partial one, for the configured validity). The /api warm-up waits for the events before building the examples. The real computation runs in a separate process and returns the same as in-process. No route calls the full computation directly.VG20— The child process of the event computation inherits nothing from the server: empty input, discarded output, errors in a file of its own, close_fds=True and, on Windows, the explicit list of inheritable handles (only those three) and no console. When the child fails, its traceback goes to the server log, and nothing is written to the server output. Why (27/09/2026, I-N113 on Windows): with the child alive, requests were reset without reaching the app, and the suspect is a server handle (the listening socket) inherited by the child.VG21— A 12-month yield snapshot with more than 12 months (the 365-day rule used until 2026-11-03) is read with the caveat janela_13_meses and the watch does not compare it, so the first corrected snapshot after a wrong one raises no false 'yield changed band' event; two 12-month snapshots with the same change still raise it.AC01— The whole follow-by-e-mail flow with the file provider: since 30/10/2026 the request on the company page creates an ACTIVE row at once and queues ONE welcome e-mail with the leave link and the not-me link and no confirm link; the weekly notice is built from the watcher events for that company, with the leave link; leaving cancels, and no notice follows. Asking again sends nothing. Unknown ticker is a 404.AC02— No response — request page, confirmation, leave, invalid e-mail error, the admin metrics panel — contains the address, and no log line writes it. The panel counts requests, confirmations, cancellations and the most followed companies: numbers only.AC03— Cleanup deletes a never-confirmed request older than the configured deadline, and only that one: the confirmed stays, the recent stays. The deadline is a parameter from the configuration, not a constant.AC04— The follow block appears right after the verdict on EVERY company page — the three most viewed (ITUB4, PETR4, BBAS3) and the others, in Portuguese and English — with the form to /acompanhar and the right ticker, and no text of the block, the response pages or the e-mails carries a forbidden term.AC05— A crawler following the form creates no request and sends no e-mail, and above the configured hourly cap the answer is 429 — without storing IP addresses, like the free key.AC06— Without RESEND_API_KEY/CORREIO_REMETENTE the provider is 'file': it writes to the pending queue and returns False. With both, it sends a POST to the Resend API with sender, recipient, subject and text — and if the API refuses, the message falls into the queue instead of vanishing. The address never goes to the log.AC07— The weekly job, with the file provider: each CONFIRMED e-mail gets ONE e-mail with the events of the last 7 days (before the sending Monday) of the tickers it follows, with the stop link for each ticker; whoever has no event gets nothing; the configured daily cap holds the rest in the queue, which goes out on the next day of the same week; running again does not send twice; an unconfirmed request gets nothing. Only counts on the panel; the e-mail never reaches the log.AC08— The due week is the most recent Monday on which 08:00 IN THE CONFIGURED TIME ZONE (America/Sao_Paulo) has passed: Monday 07:59 in São Paulo is still the previous week; 08:00 is the new one; Sunday night is the previous Monday. A week is served only up to the configured number of days after its Monday: a deploy on Sunday does not send last Monday’s alert. Cap, time and window come from the file, not the code.AC09— The weekly internal product report is built from the data, one line per figure in funnel order: real Google clicks, concentration, active and returning keys, confirmed follows, the week’s e-mails, watcher events by class (Monday to today only) and backup. A failed or missing source reads “not measured”, never zero. /admin/metricas returns the report and the SAME text is saved in DADOS_DIR/relatorios/YYYY-WW.md. No adjective or recommendation, no e-mail address.AC10— The Pro Monday portfolio e-mail: unconfirmed requests get nothing; a confirmed Pro request gets ONE e-mail with the value, only dividends paid from today on, the week's events for the portfolio's tickers and the stop link; a key that is no longer Pro gets nothing and uses no quota; the daily cap is shared with the watchdog e-mail; re-running does not repeat; asking again replaces the portfolio; the key is stored only as a hash; no e-mail in the panel or the log.AC11— The request on /carteira: a key that is not Pro gets 403 and the plans page, storing nothing; a Pro key stores ONE unconfirmed request and queues the confirmation, and the reply does not repeat the e-mail; the links confirm and stop; an empty portfolio is 400; the portfolio page shows the request form.AC12— The free newsletter: unconfirmed gets nothing; confirmed gets ONE e-mail with the closed ISO week, the counts, at most 5 lines per type plus how many more, the week link and the leave link; a week with no facts sends nothing; the daily cap is ONE for watcher + portfolio + newsletter; re-running does not repeat; leaving stops it; asking again before confirming resends the SAME link. No e-mail in panel or log.AC13— POST /boletim turns the bulletin on at once and queues the welcome e-mail with leave and not-me links, without echoing the e-mail; bots store nothing; invalid e-mail is 400; the old confirm link still turns on a pending request from before the change; leave works and unknown links are a noindex 404; answer pages offer following that company and /hoje the invitation.AC14— The free weekly e-mail for followed companies now carries the dividends to receive of those tickers (amount, record date, payment) and goes out even without a watcher event when there is a dividend; it goes as HTML (house template) with the plain text alongside; dividends of other tickers do not enter; the subject says what is inside; with nothing, nothing is sent.AC15— One sign-up, two lists: choosing both turns on one row per list at once, with the same token, and sends ONE welcome e-mail with the leave link of each list and the not-me link; leaving the magazine removes only the magazine and leaving the bulletin removes only the bulletin; no choice is a 400 and nothing is stored; a robot stores nothing; the page never repeats the e-mail; only the e-mail and the choice are kept.AC16— On /chave-gratis the box to get the Super Dados B3 weekly magazine comes unticked; creating a key without it puts nobody on any list; ticked (the box is the consent) it turns the magazine on at once and sends the welcome e-mail with leave and not-me links, and the bulletin is not added.AC17— Only an issue whose approval field holds the date of the owner OK is public: listed on /revista, served on /revista/{n} as an indexable page with canonical, no script and a link home, and in the sitemap; a draft (null, any text, invalid date, missing file or path outside the folder) is a 404 and shows nowhere.AC18— Sending an issue: no admin token or a wrong one is a 404; no owner OK or a draft is a 400 and nothing is sent; simulate only counts; each confirmed magazine subscriber gets the issue once, in the house e-mail with the highlights, the issue link and the leave link; the shared daily cap leaves the rest pending; the order is logged with the OK and no e-mail; only the admin route calls the send.AC19— The magazine invitation to bulletin subscribers only goes through the admin route with the owner OK, once per confirmed subscriber not already on the magazine and not on the do-not-send list; the invitation alone subscribes nobody; clicking its link subscribes as confirmed, and a token without a sent invitation does nothing.AC20— The two-list form is on /revista in both languages with no option preselected and the home links to it, the footer links the magazine, /admin/stats counts subscribers per list (bulletin, magazine, both) and confirmed ones with no e-mail, and the deletion on request and the 12-month retention reach the magazine table.AC21— Following a company now turns on at once: the row is stored already active and the answer is new; asking again changes nothing; one address stops at the configured cap of 20 followed companies, and leaving frees a slot. An old request from before the change that was never confirmed is NOT turned on by itself: it stays out of the weekly send and is deleted on the deadline, unless the person asks again for that company. The panel counts the new request as request and confirmation of the same week.AC22— The welcome e-mails (company in Portuguese and English, bulletin, magazine, both) say what arrives and when exactly as the sender does it (Mondays at 8 a.m. Brasilia from the sender config; for a company the material facts filed with the CVM, with the source delay of up to a week, the watcher facts and the dividends to receive; nothing in a week with nothing new), carry the one-click leave link and the not-me sentence with its link in text and HTML, and no confirm link. Every later e-mail (weekly notice, bulletin, magazine issue) keeps the leave link.AC23— From one connection at most 5 new company follows per day: the 6th is a 429 that stores nothing and sends nothing; another connection is still free. A separate cap of the same size covers bulletin plus magazine. The IP never reaches disk: the follow database holds neither the IP nor its hash, and the count restarts the next day.AC24— The not-me link opens a noindex page with one button and by itself changes nothing, so an e-mail link scanner cancels nobody; the button cancels everything the address gets (companies, bulletin, magazine) and blocks new sign-ups for 30 days on the three routes, which answer 429 without sending e-mail or echoing the address; the block keeps only a hash of the e-mail and expires; an unknown token is a noindex 404.AC25— Requests made before the change stay pending until the click: the old confirm link (company follow, magazine) still turns on a pending request with a 200; no old request is turned on by a new request for another list or company; the link of a cancelled or unknown request is a 404.AC26— On the company answer pages (dividends, profit, debt, in Portuguese and English) the follow block sits right below the answer at the top, before the table and the how-to-cite line, not at the end; on the company page it stays right below the verdict. The text is the one set by the owner, with the Receber avisos button (Get notices in English) and the link to the watcher page of the ticker. The home page gained nothing.AC27— Each sign-up sends a welcome e-mail, so one address cannot be signed up again and again: at most 3 new sign-ups per list in 30 days, counting the ones that left; the 4th is refused (429 on the route, no e-mail); the other list is not affected; after the period it works again.AC28— The Monday e-mail for company followers carries the material facts (Fatos Relevantes) the company filed with the CVM and the radar saw in that week (subject, filing date, link to the original CVM document) next to watcher events and dividends; a week with only a material fact still sends; facts go by company (following PETR3 gets the Petrobras fact), land in exactly one week (by the date the radar saw them, since the CVM file lags), notices to the market and other companies stay out, the leave link stays, and a missing radar file breaks nothing.LE01— resolver finds the company by any of its codes and returns the MAIN ticker (or the registry one when there is no main); unknown ticker is None. por_ano, multiplos_por_cd and serie put every column in its own field, and the series only carries multiples for years with indicators — the full list carries all.LE02— retrato takes the last year with an indicator, counts its flags, summarises restatements (total, years, the largest per year) and measures staleness: last filing received, last quarter and how many years the regulator holds beyond what was computed.LE03— faixa_propria uses only clean values and states the exact position (0 = lowest, 1 = highest), None below 2 years; mediana_setor excludes the company itself, requires 3 peers and is None without a sector; distribuicao takes each company's last CLEAN year (or all in a given year, multiples included) and the right quantiles; the fraction helpers are strict.LE04— liquidez is the average volume over the last N sessions, with the window stated; without prices, None.LE05— recebimentos separates annual from quarterly filings and puts each quarter in the right slot; reapresentacoes orders accounts by the largest difference, names the item in the chart of accounts of the company's TYPE and dates each year by the FOLLOWING annual filing.LE06— retrato_em builds the snapshot of the day that year's annual filing became public: that year's indicators and multiple, only restatements of EARLIER years, the year's average Selic and 'today' = the filing date. A year without indicators, or an unknown ticker, is None.LE07— The quarterly series respects the company's type: a bank gets no gross margin (it does not exist in its chart of accounts); with no type, the general one applies.LE08— Dividends newest first with every column in place; trading dates in order; the payment date announced to the regulator keyed by class, record date and value to 6 decimals, and empty without the table; last close of each year; coverage or None.LE09— A company that does not consolidate (publishes only the individual statements) restates like any other: the reading gives the year’s aggregation (ind, nao_consolida) and which side came zeroed in the CVM file; the page says “individual — company does not consolidate” and “no value in the file”, never R$ 0 nor the 100% the empty side produces; the flag counts every row and measures the difference only where both sides have a value.LE10— The list of curated sector comparisons comes from the data: in each sector, the three with the highest average traded value over the last sessions (the liquidity calculation), only among those that traded recently; it changes when liquidity changes; the “Sem Setor Principal” activity is left out; a trio already used by another sector is not repeated; a repeated slug gets the level; it never exceeds the page cap. The data puts the three side by side in the most recent year common to them, with the caveat, and says when there is no common year.LE11— The fourth slice of reads, on a synthetic base: the company profile (with the FCA registry when it exists), share classes with the main one first, the fact-page signals (a ticker change means distinct roots), price range and last close, the year fact in that year’s aggregation (with and without empty items), an item series each year in its own aggregation, the last quarter, sector peers with clean figures only and each one’s latest year, monthly and daily series, recent volumes, and refusal of an unknown price table or column.LE12— The company page reads only through the reading layer: no SQL query in dados_b3/pagina_empresa.py, and api._pagina_empresa opens ONE connection and writes no SQL.LE13— The hot queries have indexes and the planner uses them: the sector median starts from the sector companies and finds each one latest year by (name, company); the last trading day of each ticker comes from a covering index without reading the table. An old database without the indexes gets them at boot, statistics are recomputed only when missing (or at the end of the build), and an index never changes a result.LE14— The daily price series of one company (close and volume per trading day) is read from a covering index without touching the price table, whose rows for one company are spread over thousands of file pages; an old database gets the index at boot together with its planner statistics, the per-code DISTINCT keeps its own index, and results are identical with and without it.LE15— The whole-universe distribution of an indicator is kept in memory keyed by the database file signature: the second call reads nothing, returns the same numbers as a copy that callers cannot corrupt, a commit recomputes it, and in-memory databases or open transactions never use the memory.I-P27— With round numbers, every field of a multiple comes out exact: market value = price × shares; P/E = value ÷ controlling-shareholder profit; P/B = value ÷ (total equity − minorities); EV/EBITDA = (value + net debt) ÷ EBITDA; FCF yield = (operating cash flow + capex) ÷ value — and no flag. The suite tested the BUILT database, not the code that computes it; code mutation showed that swapping any of these operations went through.I-P28— Each substitution comes with its own flag and a number that does not exist stays empty: without minorities, total equity; without controlling profit, total profit; negative profit gives a negative P/E flagged, zero profit an empty P/E; non-positive equity has no P/B; missing EBITDA, negative EBITDA or unknown debt silence EV/EBITDA without inventing it; without capex, no FCF yield.I-P29— The rulers fall on their declared side: a P/E of exactly 200 is not extreme, above is; market value BELOW 1% of equity is a share count in thousands (fixed ×1000 and flagged), exactly 1% is not fixed and falls outside the plausibility band; 0.02× and 50× equity are inside; above the market-value ceiling nothing is published.I-P30— A unit ticker divides the price by its basket (and says so), an unknown basket is flagged; a year with divergent scale loses the four multiples; without shares outstanding the row is empty with the reason; the share count used is the last PUBLIC snapshot up to the price date (never the future), flagged when it came from the reference form and when it is more than 200 days old.I-P31— A year's share count against the previous priced year: up to 3× either way passes; beyond, the year has no multiples — unless the previous year was the FIRST with a price (IPO), the ratio is the ~1000× of a thousands scale, a corporate action is recorded between the previous and the next year, or the count was already adjusted.I-P32— A synthetic COTAHIST line with a distinct value in every field records close, open, high, low, average (two decimals), volume, quantity divided by the quotation factor, trades, date and the source code. Other record types, odd-lot market, unknown codes, zero price and unreadable lines are not recorded.I-P33— In the week a company trades under the old and the new code, the day's record is the main ticker's — all eight fields of the same line and its code — whatever the order in the file; a day only the old code traded keeps the old one. Large batches are written in full.I-P34— The code map gives the main ticker priority 0 and history of the same class priority 1; history of another class is not stitched; a code claimed by two companies leaves both; a company without a main ticker is left out.I-P35— The main ticker comes only from the company's most recent registration file and only from an active, exchange-traded, valid code, common before preferred; the code history gathers every year without placeholders.I-P36— Trailing-twelve-month profit by the identity prior annual − prior Q1..Qk + current Q1..Qk; total net income enters flagged when controlling-shareholder profit is missing; a missing piece makes it null with the missing piece named.I-P37— The quarterly multiple uses the first session from the quarterly filing's receipt within 45 days inclusive; market cap = price × shares, P/E TTM = cap ÷ TTM profit, P/B = cap ÷ (equity − minorities). Each absence — no filing, no session in the window, no share count — is recorded by name.I-P38— The share count is the latest already public up to the date (inclusive), never a later one; on a tie DFP/ITR beat the FRE; FRE alone is flagged; zero counts are ignored; above 100 billion no number; staleness above 200 days is flagged, 200 is not.I-P39— The annual calculation writes one row per year with facts: no filing received gives sem_data_publicacao, no session in the window gives the receipt date and sem_preco_apos_publicacao, otherwise the first session from the receipt (never the one before) and the full computation; it returns how many rows it wrote.I-P40— One step above each cut-off changes the result: P/E 200.5 is extreme; market cap 50.5x equity leaves the band; equity of exactly zero has no P/B and does not divide; half a real of EBITDA still gives EV/EBITDA; half a real of profit is not negative; the market-cap ceiling and the implausible share count at the exact number and just above.I-P41— A split with ex-date on the share-count date is applied; on the price date it is not; the day before the price it is. A suspicious event without a factor inside the window silences the count; outside it, it does not.I-P42— Q2 and Q3 find the quarterly filings of 30 June and 30 September; quarterly P/E of exactly 200 is not extreme and 200.5 is; P/B 50 is not and 50.5 is; market cap at exactly 1% of equity is not rescaled; zero equity does not divide. In COTAHIST a zero quotation factor counts as 1 and a 50-cent price is a price.I-P43— A COTAHIST line with every byte filled — digits next to each field, every number using its full width — is read exactly, so a slice shifted by one byte changes the number. Share jumps of 950x and 1,050.5x fall outside the thousands band and are flagged; a prior year declared in thousands is multiplied by exactly 1,000. In the quarterly path, equity of 50 cents still gives P/B and TTM profit of 50 cents is not negative.I-P44— Share count from the reference form (FRE): the issued capital line counts; a form that does not declare it falls back to subscribed capital, the same shares under the corporations law, and only that: paid-in capital (a subset) and authorised capital (a ceiling) never become a count. A company that declares issued capital in ANY form never gets the fallback, and one with a count from the annual statements only gets it within 3x of the first of those. In the build, the FRE count is loaded BEFORE the indicators, because the Piotroski dilution test needs it.I-P45— Quarterly EPS (TTM) is TTM profit attributable to controlling shareholders divided by the share count that formed THAT quarter’s market cap, recovered as market cap over price per share: EPS × shares = TTM profit and TTM P/E = price per share / EPS. A unit divides the price by its basket but leaves the share count alone; a count filed in thousands is already corrected; a quarter with no filing, no share count or an implausible one has no shares and no EPS; a quarter without TTM profit keeps its shares and has no EPS; negative profit gives negative EPS. Annual EPS uses the fiscal-year profit (controlling shareholders, or total net income when that is all there is). Attack: dividing profit by the unit price without the basket, multiplying instead of dividing, using another quarter’s count, or inventing EPS where TTM is empty.PCL01— The COTAHIST stores the daily close of EVERY other class of the company, from any year (not just the last 60 days): the common or preferred class that is not the main one, the class that exists only by ticker root (the ticker table does not list it) and the unit with an inferred basket. Left out: the main ticker, a unit without a basket (it could be a fund with the same root), a root shared by two companies and a code the table gives to another company. A new company changes the signature of the map, which is what makes the build redo the history.PCL02— /empresas/PETR3/multiplos returns P/E and P/B using the PETR3 close (R$ 30) on the same trading day as the main ticker: P/E 10 against 9 for PETR4 (same EPS). The answer names the class, the price and the day, and keeps the main ticker's value beside it. A class close up to 7 days earlier is used (with a flag); older than that the multiple is null with the reason. Quarterly rows follow the same rule. PETR4, the main ticker, answers as before without the class block. Unit: unit price divided by a basket of 3 (33 becomes 11); with a unit as the main ticker, the common class is compared per share (10 against 11). Market cap, EV/EBITDA and FCF yield stay the company's.PCL03— The class multiple is null, with the reason in the flag, when: the unit has no inferred basket; the main ticker is a unit without a basket; the class never had a price in the database. Another code of the SAME class as the main ticker (the old code of a renamed company) is not treated as a class: nothing changes. The snapshot for today uses the company's EPS and book value per share over the latest close of the class (divided by the basket if it is a unit) and marks an EPS that is not positive.PCL04— The build reuses the database: a COTAHIST year already ingested used to be skipped whole, so the history of the other classes would never reach old years. The price step has its own dataset (cotahist_classes), keyed by the zip hash PLUS the signature of the class map; it redoes only what is missing and deletes the year's rows before rewriting, so no code that left the map stays behind. Today's market value does not count the unit among the classes.I-B100— Companies that file the cash flow statement by the direct method have their operating cash flow (6.01) read from it, with the origin saying `DFC_MD:6.01`; when the indirect method exists it rules. D&A and capex, which sum lines only the indirect method publishes, stay empty under the direct method: nothing is estimated. Before this, 134 operating-cash-flow facts in 29 companies were empty, and P/OCF and free cash flow vanished from them.I-B101— A controlling-shareholder profit line (3.11.01) filed as ZERO while net income (3.11) is not zero becomes MISSING, with the reason in the origin, annual and quarterly; ROE then uses total net income with the `lucro_total_sem_minoritarios` caveat instead of showing 0.0%. A true zero stays zero and a filled line stays as filed. Found by a blind test with Claude on 12/10/2026: Sabesp 2025 (R$ 8.5 bn profit) showed ROE 0.0% with a clean-data seal; 69 companies in 2025.I-B76— The chart of accounts comes from the text of line 3.01 in the most recent year, case-insensitive: “intermediation” is a bank; “insurer” or “reinsurance” (either alone) is an insurer; any other text, empty text or no 3.01 at all is general. The returned count is exact.I-B77— Revenue + cost = gross profit, and pre-tax income + tax = continuing income: a positive cost that only closes when negative is flipped (and the origin says so); one that already closes, or closes neither way, stays. The tolerance is 0.5% of the larger end (floor of R$ 1): a gap of exactly the tolerance closes, one real more does not, on both sides. A missing item or empty value is a no-op.I-B78— Cost with the parent account 3.02 at zero (or missing) becomes the sum of its FIRST-level children, never grandchildren, and the origin says so; a parent with a figure keeps it even when the children add up to something else.I-B79— D&A adds the matched LEAVES of 6.01 (a parent with a matched child stays out), includes depletion, excludes amortisation of debenture transaction costs and unlabelled lines; capex adds only OUTFLOWS (value below zero; zero is not an outflow) of fixed or intangible assets in 6.02, leaves only, without asset sales or financial investments. The origin lists exactly the accounts added; with no match both are empty, never estimated.I-B80— For banks, equity and profit come by LABEL: level-1 equity (lowest code) outside attribution lines; profit from the LAST “profit for the period” line (highest code, not highest value) outside per-share lines; the controlling one outside non-controlling. Fixed items follow the financial chart (3.05 is pre-tax income, there is no cost), and restatements are checked on the financial accounts.I-B81— Closing equity = opening + transactions with owners + comprehensive income + internal changes, each term with its sign. It closes: nothing changes; a gap of exactly 0.5% of closing equity closes, one real more does not; the denominator floor is R$ 1. It does not close: the six bridge items (only those, and missing ones are not created) go empty with the reason. An empty or missing term counts as zero; without opening or closing equity nothing is concluded.I-B82— ONE conflicting part of a summed origin (before the semicolon) is enough for the item to go empty with the reason; an empty origin or one without a statement prefix is not conflicting. In a year of divergent scale, every item with a value AND an origin is stamped; empty or origin-less items are not.I-B83— A file multiplied by a thousand has the ratio of BOTH columns near a thousand and near a thousandth: it becomes a factor-0.001 correction (and divided by a thousand, factor 1,000); the median of an even number of accounts is the mean of the two central ones; 20 accounts diagnose, 19 do not. A single column near a thousand (or a thousandth) stays as a doubt, ratio rounded to 6 places, and the bands are 5% around 1,000 and 0.001, not 1,001. The quarterly path does not diagnose.I-B84— The sum of the three quarters against annual revenue: above 100x or below 0.01x is a doubt (ratio to 4 places, 3 quarters); exactly 100x and exactly 0.01x are not; a zero sum is not compared; a year without all three quarters is left out; the quarterly path does not run it.I-B85— Equity that falls at least 100x against EACH neighbour and comes back, with an implied factor (geometric mean) between a third and three times a thousand or a million, is flagged (ratio to 2 places) and the fact origin carries the warning; neighbours of opposite signs (crossing zero) are not; a 100x drop exactly flags, less does not; a drop against one neighbour only does not; a factor of exactly 3,000 flags, 3,500 does not.I-B86— A year that appears as LATEST and PREVIOUS with a relative gap above 0.5% (over the larger of the two, floor R$ 1) is a restatement, gap to 6 places; exactly 0.5% is not.I-B87— Standardisation writes one fact per item per year from the minimum year (inclusive), returns how many it wrote, annotates the zeroed consolidated block and only it (a company with only individual statements gets no annotation), records divergent scale from both detectors; the quarterly path does the same for quarters 1, 2 and 3.I-B88— With round numbers, every general-plan indicator comes out exact and unflagged: gross, EBIT and net margins over revenue; ROIC = EBIT × (1 − tax / pre-tax income) over AVERAGE invested capital (debt + equity − cash − financial investments); ROE = controlling profit over average controlling equity; EBITDA = EBIT + D&A; net debt; net debt / EBITDA; growth; ROA = TOTAL profit over average assets (6 places); current ratio as a snapshot; Piotroski 8 of 9 (equal turnover does not score; an equal share count does).I-B89— Effective tax rate = −tax / pre-tax income. Pre-tax income zero or negative, or either missing: 34% with a fallback flag. Tax credit (negative rate): 0% with a clamp flag; a rate of exactly zero is not clamped. Above 45%: 45% with a clamp flag; exactly 45% is not clamped.I-B90— Without the prior year, capital is the closing one (flagged); average capital zero or negative has no ROIC; |ROIC| above 2 is extreme, exactly 2 is not; missing cash counts as zero and warns (also on net debt and net debt / EBITDA); missing financial investments count as zero without a warning; without debt there is no capital.I-B91— Without controlling profit, total profit with a flag; without a non-controlling line, the whole equity; average equity zero or negative has no ROE. Without D&A, EBITDA is empty with a flag; EBITDA zero or negative has no net debt / EBITDA.I-B92— Zero revenue: margins empty with a flag, and growth of −100% says revenue is zero; an EBIT margin above 100% is flagged as implausible, exactly 100% is not; zero average assets, zero current liabilities: flagged; assets below R$ 1 million flag EVERY indicator of the year as a shell, exactly R$ 1 million does not.I-B93— The growth denominator is the RESTATED comparative of the prior year (previous-order account, same aggregation as the fact), without a flag; without it, the revenue as filed with a flag. Five-year CAGR = (revenue / revenue five years earlier)^(1/5) − 1, also on the comparative when there is one; a non-positive base has no CAGR.I-B94— Divergent scale in the year: EBITDA and net debt (the ABSOLUTE figures) go empty, ratios stay, all flagged. Equity isolated out of scale flags the ROE of the year AND the next (and only ROE); isolated assets flag ROA and Piotroski of the year. A zeroed consolidated block flags the year. Missing cost with gross profit less than R$ 1 from revenue flags an incomplete income statement (R$ 1 exactly does not). A quarterly sum between 1.8x and 3.5x of the year (inclusive) flags a contradiction.I-B95— A comparison whose TWO ends sit on different bases (consolidated × individual) is flagged: one-year growth, ROE, ROA and Piotroski on a one-year window; CAGR on five. A year without a neighbour does not compare, and the margin (no comparison) is never flagged.I-B96— Banks: net margin over intermediation revenue, ROE over average consolidated equity when the consolidated balance sheet has no non-controlling interests line (flagged as including them; without controlling profit, total profit and the flag), ROA, growth on the restated comparative, and NO ROIC, EBITDA, gross margin, current ratio or Piotroski. Non-positive average equity has no ROE.I-B97— The share count of each year is the one of the MOST RECENT document of that year (December beats September, whatever the write order); an empty count is left out. With shares rising, the dilution test does not score.I-B98— Every “at most zero” cut-off is about zero, not R$ 1: pre-tax income, average capital, average equity, EBITDA, revenue and the CAGR base of 50 cents still compute. Cash, financial investments and non-controlling interests published as ZERO count as zero, not 1. Zero ROA and zero operating cash flow do not score in Piotroski, 50 cents of cash flow does; equal leverage is not a fall. For banks, the CAGR uses the restated comparative of five years earlier when it exists, zero revenue has no CAGR and average equity of 50 cents has ROE.I-B99— An annual statement filed without the cost line (custo_ausente_na_dre) flags what reads the income statement: margins, ROE, ROA, EBITDA, growth and Piotroski. It does NOT flag the current ratio or net debt, which are balance-sheet snapshots and read no income-statement line. The values do not change, only the flag.CP01— Thousands and decimal separators follow the language: 1.234.567 and 1.234,57 in PT; 1,234,567 and 1,234.57 in EN; the requested decimal places exactly; HTML escaped.CP02— The verdict flag states its colour through the language label, with the dot in the token colour; text and reasons come out in the language (falling back to PT without an EN version); no reason, no list; everything escaped.CP03— The verdict block: the top sentence, the flag, each question with its items (an unavailable item carries the nd class), the right methodology link for each number in the language, the note, the JSON and the verdict link.CP04— The showcase cell: margin and ROIC in % with 1 decimal, the others with 1 decimal, a dash when unavailable; liquidity in millions; name cut at 28 characters; the flag dot in its colour. The active column header carries the sort arrow and links to the opposite direction; sector and language travel in the link.CP05— The showcase sorts by any column both ways, the flag in the order green, yellow, red; an unknown order becomes ticker; the sector filter is exact; a company without the number goes last in BOTH directions.CP06— The showcase includes companies whose average liquidity is in the window [floor, ceiling) and that have a verdict, in ticker order. The list is kept per data and window version, a new version drops the old one, and without a version nothing is kept.CP07— The showcase page: the window in millions in the intro and the description, the base total, sectors as a filter (the active one in bold, all becomes a link), the count of the filtered table and the default order; PT and EN.CP08— The company watcher page: the list is cut at the configured limit while the N of M count is the total; filters by type and by ticker (upper case); the counter shows only the configured recent months but counts the whole base; dates dd/mm/yyyy (EN yyyy-mm-dd; none, a dash); filter links carry the language; the ticker links to its page in the language.CP09— The fund and ETF watcher pages: their own event types and counter, the ticker linking to /fiis or /etfs, the form with the hidden class, the class JSON, the configured ranges in the text and the class tab in bold; an unknown class is the company one.CP10— /vigia/{T}: the count of all the ticker events in the lede (or the no-event sentence), the type filter with the language, the JSON with the class (none for a company), links to the right ticker page and to all events of the class. The tabs mark the current class and link the others with the language and the week.CP11— On the fund and ETF watcher pages: counter header and type column carry the class labels in the language; dates dd/mm/yyyy in PT; the P/B, yield, vacancy, fee and premium ranges come out WHOLE (one label more than the cut-offs) and configured percentages times 100; an unknown type is ignored; the ticker is cut at 12 characters; with the filter only when filtered.CP12— Values right at the rounding edge: a window of R$ 1,500,000.50 shows as 2 million (not 1); liquidity of R$ 1,050,000.50 as 1.1 mi; an empty sector does not become None. Without sessions in the configuration, the showcase uses and states 63 sessions. The page frame marks the URL section in the menu.CP13— A date with time shows as 15/09/2026 on every watcher page; the ticker filter is cut at 12 characters on the company and ETF pages too; a ticker-only filter already counts as filtered on fund and ETF pages; PT type labels on companies; configured cut-offs at the rounding edge come out by the exact computation.CP14— The share class comes from the last digit (3 ON; 4 to 8 PN; 11 UNT; anything else, none). In the dividend list the ex-date is the first session AFTER the record date (none without a later session); payment date from B3, else from the CVM notice by key (class, record date, value to 6 places), with the source; newest first; the summary counts the last 365 days including the one-year edge and excludes future dates.CP15— P/OCF = market value / operating cash flow; P/FCF = value / (OCF + capex, which is negative); only with positive cash flow (zero does not count); banks, insurers and missing or zero market value get nothing.CP16— Each indicator by theme with round numbers: P/Revenue, EV/Revenue, P/EBITDA and P/EBIT (positive denominator only), EV/EBIT, earnings yield, net debt/equity, gross debt/equity (short plus long; one of them is enough), equity/assets, liabilities/assets, EBITDA margin, turnover, payout and the 5-year CAGRs (both ends positive). Themes in order, an empty theme disappears, banks lose what does not apply.CP17— Sector rank (1 = best, each key in its direction; without a direction rule or with fewer than 3 peers, nothing); the median only with 3+ peers; the year yield = sum / last close of the year; average yield only over the requested years that have one; the record-date radar of the last 5 years from day 1 of the month; the checklist with each threshold and what does not apply to banks; the 12-month snapshot only with a positive denominator.CP18— The rulers half a real away from zero and the thresholds as NUMBERS: 0.50 of cash flow still gives P/OCF; a CAGR end of 0.50; EBITDA, EBIT and revenue of 0.50 to 1.00 still divide; ROE 0.5, yield 0.05 and exactly R$ 10 million of liquidity pass the checklist; zero profit is not profit; a tie in P/E is not better; a past today is used, not the clock.CP19— A synthetic company with round numbers through the whole page path: market value = shares x close; EPS and BVPS on controlling equity; net debt and EV; 12-month yield only from the main class; payout of the fiscal year; P/OCF and P/FCF. Shares in thousands are corrected x1000 and said so; a count outside the [0.02; 50] band of equity gives no market value; banks have no EV. Values in reais, TTM by identity, the 12-month snapshot, liquidity, sector median, returns and the page cache per (version, company).CP20— The market value rulers at exact numbers: shares in thousands only below 1% of controlling equity (exactly 1% is not corrected); the [0.02; 50] band with both ends INSIDE and 50.5 outside. A unit divides the price by its basket; a count jumping 4x without an event gives no market value. Cash and investments published as ZERO count as zero. The 12-month yield counts exactly one year back, not one year and a day. On the page: dividends per year of the main class only, the 8 restatements with the largest absolute difference, the checklist ROE and debt from the fiscal year.CP21— What CP20 did not reach: a 1-year CAGR; the theme block year is the valuation year; controlling equity of 50 cents still passes the band; no dividend in the fiscal year means no dividends of the year; no profit means no payout; EPS of 0.50 still gives payout. In the quarter: cash plus investments, short-term debt only, quarterly and TTM FCF (none when a piece is missing), controlling equity without a minorities line. On the page: 10 years of dividends (not 11), the average yield of the 5 previous full years, roe_ttm really being ROE, and the controlling profit series in the checklist.I-N651— On the real database, the ITUB4 page shows the exact-definition block (ROE, base date, DFP) linked from the anchor bar, and on a database of the current version the Itaú ROE is over controlling shareholders' equity (2.08 minus 2.08.09) with the dated correction and the card says so; VALE3 (PT and EN) shows net debt with accounts 2.01.04 and 1.01.01, and the VALE3 debt answer page and its .md carry the definition and base date; no advice terms.I-N682— On the real database, MXRF11, PETR4, BOVA11 and the three lists, in PT and EN: one valid JSON-LD block with one FAQPage identical to the visible questions (H2 and one H3 per question) whose dateModified is the database load time; the page shows that time; every figure of the new answers and of the number strip appears in the page body outside the FAQ and the strip; the title figures are in the strip; no advice terms; asset pages link the primary source and the debt answer page lists the CVM files.RC01— /fiis/screener: the investor floor includes whoever sits exactly on it (5,000 in, 4,999 out; a null count is zero); a flagged P/BV or yield is not a filterable number (the fund appears without it and passes no filter on that field); each bound (pvp_min/max, dy_min/max) includes the equal value; segment matching ignores case; order is by ascending P/BV with funds without a clean P/BV last; `limite` cuts the list but not `n`, default 50, ceiling 200; an unknown parameter or a non-numeric value is 400; an empty parameter counts as absent; a database from another version is 503 with Retry-After.RC02— /scores: Piotroski compares the latest fiscal year with the PREVIOUS one (year − 1), with the share counts of both years, and states `de: 9`; banks get no Piotroski and their type comes out as stored; Graham uses the latest multiple with both P/E and P/BV, only when both are positive, and P/E × P/BV = 22.5 still passes; rounding is 2 places (P/E, P/BV) and 1 (product).RC03— /dividendos: `proventos` carries every share class; the yearly summary and the 12-month yield only the class of the requested ticker (ALFA3 → ON), summed to 6 places; a distribution without a record date stays out of the summary; one whose record date is EXACTLY one year ago still counts for the yield; the yield exists only with a price and a positive sum; without a coverage row the status is “nao_verificado” with zero distributions; the note names the class.RC04— /empresas/{T}: a request by a secondary code answers with the PRIMARY one; name, tax id, CVM code, sector and type each come from their own column; coverage is the first and last indicator year plus the restatement count; a company with no row in the ticker table lists the primary code; every resource says `exige_chave` = false only for the free sample.RC05— /indicadores: banks and insurers get the intermediation note and the chart-of-accounts warning, never the minority-interest alert; a general company gets the alert only when controlling-shareholder income differs from total income by MORE than 1% in the latest consolidated year, with the relative difference to 6 places.RC06— /screener: `_min` and `_max` include the equal value; a flagged number does not pass; order is by the FIRST filter, descending (also with a single filter); `limite` defaults to 100 and cuts the list, not the total; `as_of` equal to today is valid (only the future is 400); a non-numeric value and an unknown parameter are 400; a receipt over the daily limit states the limit.RC07— The JSON restatements feed returns 100 by default, at least 1 and at most 500; without a key and without asking for JSON it is the page, in the requested language.RC08— /dump/{set}.csv carries the header with the column NAMES and every row of the set, with no null byte between chunks; an unknown set is 404.RC09— /etfs/{T}/diario returns the whole series without `desde` and from it (inclusive) with `desde`, with the closing price and premium of the same day; an unknown ETF is 404; a database from another version is 503 with Retry-After.RC10— /etfs/{T}/carteira: without `competencia`, the latest one; the NAV is the one of the largest position and the weight is value ÷ NAV (null for a position without value); available months come out as YYYY-MM, newest first; with `competencia`, that one; an unknown ETF is 404; a database from another version is 503.RC11— /empresas/{T}/indicadores/{name}: for a MULTIPLE the series comes from the multiples (with the price date); for an indicator, from the indicators; the “current” value compared with the universe is the latest one WITHOUT a flag — a newer flagged point stays out. On the real database, with a company whose latest point is flagged.RC12— /empresas/{T}/linhagem for an indicator the company did not publish in that year is 404 (not another code), with the year in the message.RC13— /empresas/{T}/eventos returns every corporate action (spin-offs included), newest first, with the SOURCE (declared by the exchange or found by us) and the SITUATION (what the price said) in separate fields, the declared factor, the approval date and the net effect of the day: the two TIMS3 events of 02/07/2025 (0.01x and 100x) are 1.0 together. A real backtest read inferred / not checked and treated a declared event as unproven.RC14— The corporate-actions table on the company page says where an event comes from only when it is NOT the exchange (found by us), shows the net effect of the day when there is more than one event in it, and spells out the situation instead of a single not-checked for everything. It used to say inferred for any flagged event, declared ones included.RC15— The events set of the dump has one row per event with ticker, declared factor, approval date, net effect of the day, the price-check situation and the source (declared by the exchange / found by us), spin-offs included, and is listed in the dump index.RC16— /empresas/{T}/precos returns the full DAILY series (open, high, low, close, average, financial volume, quantity, trades) oldest first in one call; de/ate cut it (inclusive at both ends); limite cuts from the oldest and says so; the corporate actions INSIDE the period come along (those outside do not) with the net effect of the day; and the answer says the price is NOT adjusted. A real backtest said the road to the full daily series was the biggest problem.RC17— precos with formato=csv returns the CSV with header and the same rows as the JSON (and signals a cut by header); a date outside YYYY-MM-DD, `de` after `ate` and an unknown format give 400 with the cause; an unknown ticker gives 404.RC18— `/empresas/{T}/desempenho` returns buy-and-hold and moving-average-crossover return, drawdown and volatility in a few numbers, on the split-ADJUSTED price (a 2:1 split is not a 40% drop), with the premises written in the answer and the crossover signal applied to the NEXT session only; invalid dates, a short average not below the long one and unknown tickers are rejected.RC19— `llms.txt` opens (within the first 2,500 characters) with the open performance route, stated as NO KEY and for any ticker, before the long sections: AI page readers summarise long files and lose what sits in the middle.RC20— The company page `/acoes/{T}` links to `/empresas/{T}/desempenho` (open, no key) and `llms.txt` no longer says only WEGE3 and the methodology are open: it names the any-ticker performance route in the closing sentence and in the key section. AI page readers summarise `llms.txt` and read the company page, so the hint must live there.RC21— `/etfs/{T}/desempenho` and `/fiis/{T}/desempenho` return return, drawdown and volatility for buy-and-hold and the 21x100 crossover with NO key, with fund premises (distributions left out, no split adjustment); a daily jump above 35% (an unregistered split) is listed in `saltos_suspeitos` with `confiavel: false` instead of silently becoming a -90% return; unknown tickers 404, bad dates 400.RC22— The FII and ETF pages show the buy-and-hold vs crossover table with the open route link and fund premises (no distributions); a suspicious daily jump shows a 'not reliable' notice; with no result the block disappears; `llms.txt` names the FII and ETF routes at the top.RC23— Performance now carries `retorno_total_pct` (and annualised and total drawdown): a dividend buys more shares at the close of the EX-DATE (first session after the record date); in the crossover only whoever is long on the ex-date receives it; price fields do not change; with no recorded dividends the answer stays price-only; FIIs count RENDIMENTO and leave AMORTIZACAO out.RC24— `retorno_total_liquido_pct` (and annualised and net drawdown) discounts the 15% withholding tax on JCP only; dividends and REIT distributions come in whole; the GROSS total and price returns do not change; with no JCP the net equals the gross and the page shows no extra column; with JCP the column appears with the tax label; premises and page say the gross is gross.RC25— A distribution paid in TWO equal installments with the same ex-date (PETR4: JCP of R$ 0.35048636, ex-date 01/06/2026, paid on 20/08 and 21/09) stays as two rows: each repetition in B3's list is an installment and the company supplement matches each one by payment-date order; running again does not duplicate; a dividend and a JCP of the same value on the same date are not installments. Found by a blind test with Claude on 12/10/2026 (PETR4 yield was 6.2% against 6.5% elsewhere).RC26— A database from the previous week (the rebuild REUSES it) with the old `dividendo` key, without the installment: on opening, the table is rebuilt with the installment in the key, every row stays, the index comes back, and from then on two equal installments coexist; opening again changes nothing. Found when the 12/10/2026 build kept the old key.IX01— The B3 index response (one row per day of month, `rateValue1..12` per month, Brazilian-format text) becomes ordered (date, points) pairs; nulls and non-existent days (Feb 30) are dropped.IX02— Index ingestion skips a closed year that already has enough sessions, and a year B3 did not return stays as it was: nothing is deleted and nothing raises.IX03— The versioned index file completes the table (the fresh row wins) and is rewritten only when the table has at least the rows the file already has.IX04— A from-scratch build inherits `indice_preco` from the previous database when B3 did not answer, and the table is in the shrink-watched counts.IX05— `/indices` lists the B3 indices with coverage; `/indices/{code}/precos` serves the daily series (date range, CSV, lower-case code); invalid code 400, index without data 404, bad date 400.IX06— Without a key the index price route returns 200 with the LAST 30 sessions and a notice (not a bare 401); performance and the list stay open.IX07— Index performance matches the hand calculation (buy-and-hold +40.0%, crossover +17.48% on 10,11,12,12,13,14 with averages 1x2), carries no total-return field and says price indices exclude dividends.IX08— The dump has an `indices` set with (indice, data, fechamento), ordered.IX09— The NIGHTLY run asks only for the current year of each index (the full series is the Sunday build); if B3 raises, it becomes `erro_indices` and the next steps still run.IX10— A market name (IBRX) resolves to the B3 code (IBXX) in the API and connector, and an unknown index 404 lists the codes we haveIX11— At boot a whole index missing from the list is filled from the versioned file without overwriting rows already in the basePX01— Validity: renewing BEFORE expiry adds to the end of the current period (no day lost); coming back AFTER expiry restarts on the payment day; Jan 31 + 1 month lands on the last day of February.PX02— The resync reads PAID Pix package checkout sessions: in date becomes Pro with `pro_ate`; expired keeps the key on the free plan; unpaid, non-payment-mode or package-less sessions do not count; `pro_ate` is written to the Stripe Customer metadata.PX03— An active card Pro subscription stays Pro even with an old expired Pix; a paid Pix Customer without a key gets one, written to Stripe.PX04— With 5 days or less left, ONE renewal e-mail per period is sent (marked in `pix_aviso`); far from the end, or with no e-mail on the Customer, nothing is sent; the text gives the date and says the key stays on the free plan.PX05— The Pix checkout is a one-time payment (`mode=payment`) with Pix only, at the package price, with the package in metadata; with a known customer's key it goes to the SAME Customer; an invented package opens no charge.PX06— `/assinar?plano=pix&pacote=1m` goes to checkout; the renewal POST takes the key from the FORM (never the URL) and answers 303; a bad package is 400; a crawler sees the plans page and creates no session; the plans page shows Pix with the three packages and the renewal form.BK01— `/admin/backup` lists the three databases of the MOST RECENT daily backup (not the partial folder nor the previous day), with bytes and the sha256 of the uncompressed file.BK02— `/admin/backup/{name}` delivers ONE database, gzipped, which decompresses exactly into the latest backup file and matches the listed sha256; the day goes in a header.BK03— Without X-Admin-Token, with a wrong token or with the token in the URL: 404. A name outside the three databases (the main database, `..`, a stray file inside the backup folder, a missing file) is also 404, and stray files are not listed.BK04— Before the first backup the list comes with `dia: null` and no files, and a download is 404 (not 500).BK05— Encrypt then decrypt returns identical bytes (empty file, smaller than a block, exact multiple of a block, many blocks) and a real SQLite database passes `PRAGMA integrity_check` after the round trip.BK06— A wrong key does not decrypt. A missing, short or malformed key raises an error whose message does not contain the value received.BK07— AES-GCM authenticates: one changed byte, a cut end, swapped blocks, a file delivered under another name or garbage makes decryption FAIL.BK08— Without BACKUP_CHAVE (or with an invalid key) both encrypted-backup routes answer 503, deliver no byte and write NOTHING to disk.BK09— No token, a wrong token or the token in the URL: 404, like the other admin routes. If ADMIN_TOKEN is not set on the server nobody gets in (fail closed).BK10— The manifest lists name, bytes, sha256 of the plain file, encrypted size and rows per table, and never contains the token, the key or the stored e-mails.BK11— `/admin/backup/arquivo/{name}` only serves the backup databases: `../`, encoded slashes, the main database, stray files and unknown names are 404.BK12— The delivered file has no SQLite header or plain e-mail, decrypts exactly into the backup database with the manifest sha256, the Content-Length matches and the original is byte-for-byte unchanged.BK13— More than 60 requests a minute to the encrypted-backup routes: 429 with Retry-After, wrong tokens included.BK14— The pull script stores files still encrypted, checks sha256, never overwrites, keeps N days and prints neither secret; the restore test decrypts in a temporary folder, checks sha256, integrity_check and row counts, and fails on a changed byte.PQ01— Ibovespa or Selic: the index return is end/start - 1 and the Selic target compounds as (1+target)^(n/252) over the sessions after the start; horizons the series does not cover are not shown; the page says Ibovespa is a TOTAL RETURN index (B3 methodology) and the target is not the CDI.PQ02— The crossover question lists the most traded stocks in volume order, each row with the SAME number as the open /empresas/{T}/desempenho route, and the answer sentence counts how many times the crossover won (total return when dividends are recorded).PQ03— /perguntas lists both questions in PT and EN; an invented slug is 404; the sitemap and llms.txt point to them; with no series the page does not crash.PQ04— /perguntas/fiis-maior-dividend-yield lists the same funds, in the same order, as the FII hub ranking (5,000+ shareholders, no flag), with DY and P/BV, the median by segment and the rule written down; a fund with a flagged yield or few shareholders stays out; the .md has the same table; with the FII tables out of version the page says so instead of crashing.FR01— With distributions only from day 100 of a 200-day window, an FII's total return is NOT shown (it would be understated) and `rendimentos_cobertura.rendimentos_desde` says from when; asking for a window from that date brings the total back; a first distribution within 45 days of the start shows the total directly.FR02— A build inherits only the FII distributions OLDER than the first one the fresh source still shows, so a value B3 corrected is not counted twice; `fii_provento` is among the series inherited by default.FR03— An FII's type comes from the asset block of the monthly report: brick-and-mortar (properties and real-estate companies at 50% or more of the invested total), real-estate debt (CRI, CRA, LCI, LH, LIG), fund of funds (other FIIs) or hybrid; with no investment, no type. The highest version per fund and month wins.FR04— The FII page shows the type by portfolio with its percentage and report month, next to the declared segment; the hub has median P/BV and yield by type (liquid funds only); a database without the table does not break.FR05— When the CVM registry gives the SAME ISIN to two funds, the yearly build asks B3 how many units the ticker has and keeps the fund whose report declares the closest number; prices, distributions and ticker move from the loser to the owner. The daily update (no network) keeps the owner the build chose; without B3, the fund with most unitholders wins; codes that are not an ISIN match nothing. On 12/10/2026 TRXF11 showed one fund's price and another's book value (P/BV 98).FR06— The 12-month yield of FIIs summed 13 monthly distributions when the month-end date shifted from one year to the next: KNRI11 showed 9.5% instead of 8.8%, and 216 of 329 FIIs were overstated by more than 0.3 percentage points; the 12-month windows of stocks and BDRs had a similar edge case. Fixed on 2026-11-03: the FII yield counts 12 calendar months, and values are recalculated on the next nightly update. The test runs KNRI11's distributions from the local base through the build's yield step and gets 12 months, R$ 13.31 and 8.84%.FR07— A synthetic fund paying on the last business day of every month sums exactly 12 months even when that day moves between years; two distributions in one month add up but count as one month; a skipped month gives 11 months; a fund paying on the 15th is not counted twice.FR08— For stocks the 12-month window is record dates in (anchor minus one year, anchor], and when the anchor is the last business day of its month the lower bound moves to the last day of the same month a year earlier (2026-10-30 excludes 2025-10-31; 29 February becomes 28 February); a monthly month-end payer sums 12 payments, not 13, in the DY 12m card total.FR09— FII routes stay up on a base of another version that has every fii* table and column of the current schema with funds and prices, and drop to 503 when a column is missing or the tables are emptyI-N411— For five liquid securities (ITUB4, PETR4, VALE3, KNRI11, BOVA11), in Portuguese and English, the how-much-did-it-return page has the "Reading the figures" block with six paragraphs (windows, best and worst year, largest drop, comparison with the index and Selic, share from dividends, method) and 250 to 400 words; both languages carry the same six paragraphs; no two securities share 60% or more of their sentences; the whole page is under 60 KB, has no script besides JSON-LD and keeps the not-advice notice; no sentence claims a licence or free distribution or recommends anything.I-N412— The figures in the "Reading the figures" text match pagina_quanto_rendeu.dados and an independent SQL computation that does not use the performance module (traded close and dividends reinvested at the ex-date, for stocks with no split or bonus in the window): the BRL value of every window (within BRL 1) and start dates, the largest price drop with peak and trough dates, the best and worst calendar year (within 0.06 point), and the side (ahead or behind) of the Ibovespa and the Selic target in every window, against index returns and the day-by-day Selic built straight from the tables. Holds for at least three stocks.I-N413— The prose block appears only on liquid securities by the same ruler as the sitemap (stock: 63-session average of BRL 5 million a day or more; REIT: 5,000+ shareholders; ETF: 60-session average of BRL 1 million or more): the one-security functions agree with the sitemap sets for every stock and a sample of funds; an illiquid stock keeps the old page (200, canonical, no noindex, no block); the .md version carries the same six paragraphs as the page; the sitemap is unchanged.QR01— For 1 and 3 years, 'R$ 1,000 became' equals 1000 × (1 + return) from the open /empresas/{T}/desempenho route with the same start (price and with dividends); Ibovespa and Selic for the same period appear; horizons without price history do not; the answer opens with the longest available horizon.QR02— A secondary ticker redirects to the main one with 301; an unknown ticker is 404; the stock page links to the question; the sitemap lists one per company; llms.txt mentions it.QR03— How-much-did-it-return also answers for funds: KNRI11, HGLG11 (FIIs) and BOVA11 (ETF) give 200 with the 'R$ 1,000 became' figures equal to 1000 × (1 + return) of the open fund performance route with the same start; a FII is compared with the IFIX and an ETF with the Ibovespa; where we hold no distributions for the whole window the page shows price only and says so, and only says the real return is higher when some distribution is recorded (an ETF with none, or a FII with none, gets a neutral sentence instead); an unknown ticker is 404, plain text (.md) carries the same numbers; the fund page links to the question, the sitemap lists it, and the fund distributions address redirects 301 to the distributions section of the fund page (404 for an unknown fund).QR04— The link to the fund distributions on the how-much-did-it-yield page points straight to the fund page section, not to a path that redirects with 301.TX01— /empresas/{T}/desempenho carries comparacao_mesmo_periodo with Ibovespa and the compounded Selic target in the response's own window, plus pagina_pronta; FIIs also carry IFIX; asking another window moves all three together.TX02— The .md versions of the answer pages return text/markdown with no HTML and the same table as the page; a secondary ticker redirects to the main .md; unknown is 404; the HTML page links to the .md; llms.txt teaches the .md and the ready comparison.BD01— From COTAHIST only BDR rows (CODBDI 34, 35, 36) of the spot market (010) enter, with the five prices, volume and trades of the same row; the ticker keeps the ISIN and type of its most recent row.BD02— The BDR registry reads every page of B3's list; "Não Classificados" is not stored as a sector; if one page fails nothing is written and the previous registry stays.BD03— B3's company supplement becomes distributions per ISIN (Brazilian number and date format; a 9999 date means no date) and split/reverse-split events; labels that do not change quantity and zero values are left out.BD04— A split the price confirms adjusts the returns (no fake -50%); a declared split the price contradicts is left out and flagged; distributions enter the total return and the 12-month sum.BD05— /bdrs lists only BDRs that traded in the last year, in HTML and JSON, with a type filter (invalid type 400); /bdrs/AAPL34 is open as page and JSON and says what it lacks; another BDR as JSON without a key is 401; unknown is 404.BD06— /bdrs/{T}/precos without a key returns the LAST 30 sessions with a notice (AAPL34 in full); with a key the whole series, also as CSV; the sitemap carries /bdrs and only BDRs above R$ 50 thousand a day.BD07— The top menu has BDRs; search takes "AAPL34" straight to the BDR page and finds "apple" by name; a BDR idle for 900 days (no page) does not show; the results table says BDR (and ETF), not "Stock".RO01— An indicator caveat shows as a sentence with the code in brackets (the same code as the API); an unknown flag shows as the code only; a partial Piotroski says how many criteria were checked; charts and tables use the sentence.RO02— The ETF page shows shares outstanding (net assets / NAV per share of the latest daily report, with its date), the administrator from the B3 registry and the year the class was registered; the old keys gestor, cotas_emitidas and data_inicio no longer exist (page, JSON, MCP); the approved quantity stays, under its own name.RO03— The FII page says the segment is the one declared by the administrator, and the P/BV card shows, next to the point-in-time figure, the P/BV at the latest close (close / book value per share) with its date.RO04— The home page 'largest market value' list comes from the same calculation as each company page (today's close x shares of the latest filing), re-ranked by it; a candidate without today's value is left out; only the first N.RO05— The same corporate event in the ON and PN classes (same date, type, multiplier and status) is ONE row naming the classes; a single-class event gets no label; a multiplier below 0.1 keeps the decimals it needs (0.001x, not 0.00x).RO06— The 5-year average dividend yield (the checklist criterion) comes with the median beside it; the criterion stays the average.RO07— Every English page announces Pro at the SAME price (the one for the language), with no hand-written R$ 49.90.RO08— The search title and description use the latest figure when its caveat only declares the method (total profit without minorities, year-end equity...); a caveat that puts the figure in doubt removes that year; a figure more than one year older than the latest published does not enter (better no ROE than the one from 2012).RO09— Every marker that can silence a multiple (P/E, P/BV, EV/EBITDA...) has a sentence in Portuguese and in English; the verdict writes the caveat as a sentence with the code in brackets; patterns with a number (unit of N shares, missing quarter, price N years later) become a sentence with the number; so does the dividend coverage status; FII and ETF markers show the sentence on screen and the code in the tooltip.RO10— Two distributions on the same ex-date are reinvested TOGETHER (1 + (a+b)/P) in the performance block and in the returns table; both 5-year windows start at the same session (the last one up to the same day 5 years earlier) and give the same number; a share-count suspicion that the price refuted is not a company event nor counted in its signals, and what our detector finds is never labelled as declared by the exchange.RO11— A company with common and preferred shares: today's market value is common × common close + preferred × preferred close (the split comes from the capital composition of the latest annual/quarterly filing; the other class's close from COTAHIST, up to 7 days older); EV uses it; the per-share market cap used by multiples does not change. Without the other class, with two preferred classes, or with a stale close, it stays as before. COTAHIST stores the full close history of the other class (a unit only with an inferred basket), and today's market value never counts the unit.RO12— /en leads to the English home and /en/<page> to that page in English (301); /llms-full.txt leads to /llms.txt; /.well-known/mcp.json says where the MCP connector is; the home counts the REITs WITH A PAGE, like /fiis and /para-quem. On 16/10/2026 /en and both agent doors returned 404 and the home said 484 REITs against 472 elsewhere.RO13— The checklist says "no losses" (not "none", read as "no profit") and "years of trading in our data" (not "years listed"); the home says "New at the regulator" (the block shows 30 days, not "yesterday") and cuts names at a word boundary; the free-key offer links to /chave-gratis; the price chart says it is not adjusted for dividends.I-N230— The daily snapshot stores, per share and per REIT fund, the figures the page shows that trading day, and says which filing they came from (fiscal year, filing date at the regulator, quarter).I-N231— The daily snapshot is append-only: running twice for the same trading day duplicates nothing, the database itself refuses UPDATE and DELETE, and a new day is added beside the old one.I-N232— The snapshot lives in its own file: replacing the main database (nightly swap, Sunday rebuild) does not touch a single row, and the daily backup copies the file.I-N233— If the snapshot fails, the nightly update goes on, nothing is half-written, and /saude shows the block with the failure message.I-N234— /saude carries a `foto_diaria` block: last date stored, number of days, rows and file size.I-N235— The snapshot read route answers only with the admin token (404 otherwise), returns the ticker's series in the requested date range and rejects a malformed date.I-N236— The nightly loop takes the snapshot AFTER the price update, inside its own try/except, and never during the test battery.I-N270— The public pages /foto-diaria and /foto-diaria/{ticker} (Portuguese and English) show only CVM fields and metadata: snapshot date, database version, the statement in force, the CVM filing date, the 12-month profit (or the REIT's net asset value per share) and a hash per row. No closing price, market value, P/E, yield, dividend, EV, EPS or share count appears (B3 or mixed fields), the page has a How to cite block with the series hash and one Dataset JSON-LD block, the row hash can be recomputed from what the page shows, and opening the pages leaves the snapshot file byte for byte as it was. Why: the B3 licenses prices, price history, indices and dividends, and the owner has no licence. Attack: SELECT * in the page, a B3 column added to the table, a route that opens the file for writing.I-N271— Every column of the snapshot tables is classified as metadata, CVM, B3, mixed or internal, and the public columns are exactly the CVM ones; a new column nobody classified fails the test.I-N272— An unknown ticker gets a clean 404 (HTML with the site frame, plain JSON for JSON clients, the raw text never echoed); a known ticker with no recorded day gets a page saying so, with no table and no invented date; with no snapshot file the index says no trading day is recorded yet and the route does not create the file.I-N273— With 2 recorded days the page says 2 trading days recorded, never more; the notice that the series starts on 2026-10-21 is in both languages, and both languages have the same anchors, the same links and the same number of table rows.I-N274— /foto-diaria is in the sitemap (lastmod equal to the date of the latest snapshot), in the dashboard list of fact pages and in the facts hub; the per-asset pages are not in the sitemap and are measured by the dashboard.I-N237— SECURITY.md exists at the repository root, says how to report a vulnerability (the contact e-mail), the response time and the scope.I-N238— requirements.lock pins every package of requirements.txt to an exact version (==), so the dependency set can be reproduced and audited.I-N112— In a fresh interpreter (cold process cache), with the real database and no network, every route in custo_config.json answers 200 under its own ceiling, on the first request and on the second. The watcher events are not part of the request cost: they are computed once per data version, outside the web process, and timed separately.I-N112d— With the real database, every shortcut that brought the cold home page and sitemap under their ceiling gives the SAME result as before: the BDR count equals len(universe); /hoje without the ticker changes has the same keys, in the same order, as the full /hoje minus them; the market value of a sample of companies computed in a batch equals one at a time; the price lines of the rankings in one query per table equal one query per ticker; the last trading day of each company and each BDR via index skipping equals the GROUP BY; the watcher week with the first event already counted equals the full count; and the sitemap rebuilt with the route-text memories filled equals the one built from scratch.I-N113— The real server (uvicorn, one worker, hourly loop and warm-up on) boots on the real database, and /saude answers in under 1 s throughout the first 60 s of the process life while four robots keep requesting the routes that depend on the watcher events (sitemap, /vigia, weekly summaries, one company watcher). Each robot request ends within 3 s, with 200, or 503 with Retry-After while the events are being computed outside the process; none hangs. This is what went down in production on 2026-09-27. When it fails, it names the cause from the server snapshot taken while the request was hanging: stuck INSIDE the app (and where), or the connection dropped OUTSIDE it with the server idle (and which child processes were alive).I-N437— With the real numbers from limites_config.json an AI assistant makes 151 calls in one hour from one address without refusal while an ordinary anonymous caller is refused at call 61; over REST, ChatGPT-User and Claude-User get 200 past the ordinary limit and the browser on the same address is still refused; AI calls do not count toward the global ceiling of humans.I-N438— AI assistants have their own global ceiling: past it a ChatGPT-User call from a new address gets 429 with Retry-After and a body naming the server limit for AI assistants and the free key link; a browser still passes, a key bypasses both, the per-address AI limit also applies, and the defaults are 1000 per hour, 10000 per day, 5000 and 100000 in total.I-N439— Every call to /mcp counts as an AI assistant whatever the User-Agent, and REST calls count only when the dashboard classifies the User-Agent as AI on request or AI indexer; browsers, training crawlers, Googlebot, curl and internal calls stay in the ordinary count; the public text states both limits with the gate numbers and the environment overrides the config.I-N440— Ticker normalization on a fake base: uppercase, no spaces, no BVMF prefix, no .SA suffix, the fractional F is dropped only when the code without F exists, BVSP and IBOVESPA become IBOV only when IBOV exists, an old code becomes the current one only through the change map; only a short alphanumeric code may reach the dashboard.I-N441— On the real base, routes with the code in the path accept PETR4.SA, petr4f and BVMF:PETR4 and return the same JSON as PETR4 with the X-Codigo-Atendido header; the REIT sheet accepts HGLG11.SA, index performance accepts ^BVSP as IBOV, a browser asking for /acoes/PETR4.SA gets 301 to /acoes/PETR4, and fixed routes are untouched.I-N442— An unknown code returns a 404 that starts as before, suggests the close code and links the name search; the dashboard stores only the normalized code, the route and the day (no IP, no User-Agent, no free text), and /admin/stats shows the top unknown codes of 7 days.I-N581— The per-address limit for AI assistants on open tools is doubled to 2,000 per hour and 20,000 per day, the global AI ceiling, the human limit and the free key stay as they were, public text states the new numbers in Portuguese and English, and key-only routes still return 401 even for an AI User-Agent.I-N320— With the commercial gate on and no key, the open-shop routes answer 200 for any ticker, not only the trial one: accounting facts (annual and quarterly), annual indicators, multiples and dividends for PETR4, VALE3 and ITUB4, the REIT sheet in JSON and the REIT ranking. Every answer carries a ready-to-cite source line with the data version; an unknown ticker is a 404 before anything is counted.I-N321— The vault stays shut: a past date (as_of) on facts and multiples, filing versions, per-company restatements, the whole-database export (JSON and CSV), the stock screener and the REIT screener answer 401 without a key. When the request is for the paid part of a route that also has an open part, the 401 says what is open, what needs a key and where to subscribe. A Pro key still opens the point-in-time answer.I-N322— Without a key, an address past its hourly cap gets a 429 with Retry-After (seconds until the hour turns) and a body naming the limit, the free-key link and the subscription link; the refused request does not spend quota; another address still passes; a request with a key skips the anonymous limit, and a bad key gets a 401 telling the caller to repeat without it. The daily cap works the same way.I-N323— A ceiling on all anonymous callers together protects a one-CPU server from someone who changes address on every request: past it the answer is a 429 with Retry-After saying it is the server limit, while key holders keep passing. The default per-address daily cap is below the free key's daily cap, so holding a key is never worse than not holding one.I-N324— The hourly window resets when the hour turns and the daily one when the UTC day turns; the same address hashes to the same value within a day and to a different value on the next day (daily salt), and different addresses hash differently.I-N325— After anonymous queries from a known address, that address (and the socket address) appears nowhere in clear: not in the counting dictionary, not in the analytics file, not in the captured log, not in the answer. Only a 16-hex-digit hash exists, and the module writes nothing to disk.I-N326— Behind the proxy the socket is always the proxy's, so the limit reads the Cloudflare header first, then true-client-ip, then the FIRST entry of x-forwarded-for, and only then the socket. Two visitors with different headers do not share a quota; the same visitor in equivalent headers does.I-N327— The open shop adds nothing to what the B3 licence restricts: a closed ticker's daily price series is still a 30-session sample without a key, the index series too, a non-trial ETF in JSON still needs a key, quarterly series, events and scores still need a key, and the REIT sheet carries no daily price series, only the monthly points the public page already shows. The old gate function is untouched.I-N328— The company index marks indicators, multiples, facts and dividends as open without a key (with a per-address limit) and the rest as needing one; its key block names what is open and what needs a key, with the same limit numbers the gate uses.I-N329— llms.txt, the /api page and the subscription page (Portuguese and English) tell the same rule with the same numbers: what is open without a key, what needs one, and the per-address limit, read from the gate and not typed by hand. llms.txt has the open-JSON section with the source line and the key section naming as_of and the export; the subscription table has the point-in-time and whole-database rows; no template marker is left unresolved.DI04— Invalid input (rate outside -5 to 100 percent, non-numeric text, maturity after 2040, in the past or not a business day, impossible reference date, contracts not an integer from 1 to 1,000,000, unknown position) answers 400 in JSON with the field and message and 400 on the page with the same message, the form filled in and no result.DI05— The calculator page (Portuguese and English) is a GET form with no script, shows the formula, the 252 basis, the month letters, the worked example matching the calculation and the notice that the figures are typed by the person and no DI quote or CDI is published; pages with a calculation are noindex with a clean canonical, and it works without the database.DI06— The calculator has entry points: a card in the home tools grid (both languages), an llms.txt entry with the JSON route and the no-quote notice, a section on the API page, and the clean page in the sitemap without calculation URLs.JC04— Without a database or with an empty macro table the page answers 200 and says the official rate was not collected yet and asks for a typed rate; the typed rate works; the Selic and IPCA shortcuts answer 400 with that message in JSON and on the page; the comparison marks those rows as not collected.JC05— Invalid input (missing, negative, non-numeric or above R$ 1 trillion amounts; missing, zero, fractional or longer than 60 years terms; unknown unit or mode; rate outside 0 to 100 percent; real rate or inflation out of range) answers 400 in JSON with the field and message and 400 on the page with the same message, the form filled in and no result.JC06— The calculator page in Portuguese and English has a title and H1 with the query, meta description, clean canonical, a FAQPage JSON-LD whose questions and answers are visible on the page as the single script; the formula and monthly rate are explained with worked examples; a calculation shows a server-side SVG chart with integer coordinates and an accessible label plus the year-by-year table, and is noindex with a clean canonical.JC07— The calculator has entry points: an llms.txt entry with the JSON route, a section on the API page, a link from the DI future calculator in both languages, and the clean page in the sitemap without calculation URLs or the JSON route.JC08— The page opens with 4 fields (initial amount, contribution, rate a year OR a month, term) and the button; Selic, IPCA + X%, inflation and income tax sit in a closed "More options" that opens when the URL already asks for one. 1% a month = 12.6825% a year.I-X15— The recorded SGS answer (series 1, 21619 and 1178, 27 to 29 Oct 2026) becomes ISO dates and numbers in order with the published values (dollar ask 5.3744 and euro ask 6.0000 on 29 Oct, effective Selic 13.65); non-numeric text and malformed dates are dropped.I-X16— The recorded SIDRA answer skips the header row and reads the period from the column named month, quarter or rolling quarter (IPCA 12 months, GDP on the previous quarter, unemployment); rows of another variable are ignored and month 13 is rejected.I-X17— Isolated failure: with only SGS series 1 answering, IBGE down and series 10813 with a misplaced decimal separator, collection writes series 1, rejects the whole out-of-range batch, deletes nothing, does not raise and records each error in meta macro_coleta.I-X18— Full collection asks SGS from 1999 in contiguous windows of at most 9 years and SIDRA for all periods; the nightly run asks only the last 45 days and the last 24 periods; new dollar days reach the cambio table without rewriting old ones.I-X19— In the nightly run a macro collection that raises becomes erro_macro and the following steps still run; a from-scratch build inherits macro_serie from the previous database without overwriting fresh values, and macro_serie is watched by the shrink lock.I-X20— The route /macro/series/{codigo} returns one row per period with one column per series, the source with its licence (ODbL for the Central Bank, citation of the source for IBGE), the citation line with version and latest date, date filters and CSV with the same columns; unknown code is 404 and malformed period is 400.I-X21— In commercial mode without a key, the daily series (dollar, euro, Selic) return a sample of the last 30 points with a notice, also for CSV, while IPCA, GDP and unemployment come in full and open; the catalogue states the access rule of each.I-X22— Before the first collection (no macro_serie table) the series route answers 503 with Retry-After, the catalogue answers 200 with null coverage, every page answers 200 noindex with a collecting notice and no number or JSON link, and /saude still answers.I-X23— Each macro page (dollar, euro, Selic, IPCA, GDP, unemployment) in Portuguese and English shows the latest number with the date in the page language, the source with the licence, the JSON and CSV links, the citation line in the page language, canonical and a Dataset with the licence of the source.I-X24— The macro block of /saude shows for each series the latest period, the days since the end of that period and the limit of its frequency, plus the latest collection; the warning lights up only for an empty or late series and not at the exact limit.I-X25— The Macro hub links the six pages and the catalogue in both languages; the sitemap lists only the pages whose series exists, with the end of the latest period as lastmod and never in the future; llms.txt lists each page, the route and the licences; the API hub links the catalogue; the home pulse links dollar and Selic to the new pages.BK51— Bank matching and column reading: a listed bank is matched to the Central Bank IF.data by the 8-digit root of the CNPJ of the group leader, never by name; the group code wins over the CNPJ-root code; the five columns are read by accent-free name; conflicting readings are dropped and a capital ratio published as a fraction is scaled to percent.BK52— Collection: only quarters already published are requested, with a pause and an identified robot; our banks are stored by quarter; a quarter the source does not answer keeps what was there, a quarter with an impossible value is rejected whole, a failing register does not raise, and the result is saved in meta.BK53— Request shape and fallback: the Olinda address follows the service contract (prudential type, Summary report, encoded filter); AnoMes maps to year and quarter; when no column matches an indicator the collection stores nothing and reports the indicator and the column names it saw.BK54— Publication is OFF by default because the Central Bank service gave no real data at build time: the pages and JSON routes answer 404 even with the table full, and the stock page reader returns nothing; turning the flag on enables it for banks only.BK55— With publication on: the JSON of a bank (any of its tickers) and of all banks carries the source, the ODbL licence with its link, the prudential-group basis, the unit and a citation with the latest quarter; the comparison page in Portuguese and English shows where each number comes from, the licence and the citation, orders by loan book size and says so, and has no recommendation wording.BK56— Before the first collection the table does not exist: the JSON answers 503 with Retry-After and the page answers 200 with a neutral notice, noindex and no number.BK57— The Central Bank indicators block on a bank stock page lists the quarterly series newest first with the source and licence next to the table, the caveat about the perimeter versus the CVM consolidated statements, links to the comparison page and the JSON, and a section anchor; without data neither the block nor the anchor exists.BK58— Build inheritance: the bank indicators table is in the list of tables inherited from the previous database, and the fresh value wins over the old one.BK59— Delinquency and problem assets come from IF.data report 16: the delinquent portfolio (or the problem-asset portfolio) divided by the Total Geral of active credit, times 100, with balances in reais; a missing or zero total, or a line from a different institution code than the total, produces no number; delinquency above the total is out of range.BK60— The collection also asks for report 16; if it does not answer, the quarter keeps the Summary figures, notes a warning and the delinquency already stored is not erased; delinquency above the total rejects the whole quarter batch.BK61— The /bancos page labels net income as accumulated within the semester, shows the delinquency and problem-asset columns and a section on what each number means that states our only calculation and admits the full official definition was not found.BK62— On /bancos the first sentence gives the over-90-day delinquency of the (up to) four largest loan books in the latest quarter, in table order and with the same figure the table shows, in Portuguese and English.PS02— Spreadsheet route: petr4, PETR4.SA, BVMF:PETR4, PETR4F and a padded ticker are served as PETR4 with the same figures, HGLG11.SA as HGLG11 and an old ticker with a known change as the current one, using the same normalization as the other routes.PS04— Spreadsheet route: a key is always required, even for the open sample tickers, with a 401 that says where to get the free key and where to store it in the spreadsheet; an unknown key gets 401, free and Pro keys get 200 with the plan, production without Stripe answers 503 and responses are never publicly cached.PS05— Spreadsheet route: at most 300 tickers and 20 fields per call, an unknown field, a malformed, impossible or future date and an empty list answer 400 with the reason and use no quota, and exactly 300 tickers pass.PS06— Spreadsheet route: a call uses 1 quota unit per 100 cells (non-blank ticker times field, minimum 1), so 30 assets with 10 fields use 3 units in the same per-key table as the dashboard, the whole template (about 30 units per full refresh) fits the free plan six times a day, and a call that would pass the limit gets 429 with Retry-After and the remaining count before using anything.PS07— Spreadsheet route: the Google Apps Script user agent is not in the refused crawler list and gets 200, and robots.txt does not block the spreadsheet page.PS10— The spreadsheet page in Portuguese and English shows the three steps, every field of the route and the macro codes, the limits taken from the gate with the Pro price, the per-100-cells rule and that the price comes from Google; without a template link it says it is being prepared, a Google Sheets link becomes a button and any other link is ignored; it has no licence claim, sits in the API hub, the API page, llms.txt and the sitemap, and the home page did not grow.PS11— Static check of the Apps Script file: the key is only read and written through PropertiesService and no function that touches the key writes to a cell, the Dados B3 menu has the key, refresh and help entries, the custom function calls the batch route with the X-API-Key header in blocks of up to 300 tickers under 2 kB, caches up to 6 hours, has Portuguese messages for 401, 429 and network failure and no external library; the manifest is V8 with no dependencies and the template formulas use only known fields across the seven tabs and the brand colours are specified.PS12— The logo field (link unit, all three classes) returns the absolute dadosb3.com address of the file the asset page shows: the company logo by CVM code for stocks and the fund manager logo for FIIs and ETFs (HGLG11 is Patria, BOVA11 is BlackRock from the CVM fund registry, not the B3 administrator); every address answers 200 with a cached PNG, a missing file returns the short reason sem logo, the field also works with a date, and the manager route only accepts 14 digits.PS13— The manager logo collection maps each fund to its manager through the CVM fund registry (class to fund to manager tax ID) and takes the website the manager declares in the CVM portfolio manager registry; the icon comes only from that site with no Google favicon fallback, icons under 32 px or invisible on a light background are dropped, a blocked domain gets no request at all, an individual manager gets no logo, a withdrawn manager loses file and source line, and the source file records URL and date.PS14— The FII and ETF pages show the manager logo like the stock page: an img with class logo-cia before the h1, served from our own domain, with a title in Portuguese and English saying where it came from and when; no third party image is loaded on visit and a fund without a file shows no image.RD01— The rule that gives an AI assistant fetching on a user behalf the final address: a trailing slash is dropped, /en becomes the same page with lang=en keeping the query and any language already asked for, and /llms-full.txt becomes /llms.txt; the root, /mcp and /mcp/ are left alone.RD02— ChatGPT-User and Claude-User asking for HTML at a trailing-slash address, at /en/acoes/WEGE3 and at /llms-full.txt get 200 in the same request, with the same canonical link and text as the final page; a regular browser and the AI search indexer still get the 307 and 301 redirects.RD03— A stock page asked with a suffixed code (WEGE3.SA) gives an AI assistant fetching on a user behalf a 200 with the WEGE3 page, its canonical link and the X-Codigo-Atendido header, while a browser and the AI search indexer still get a 301 to the canonical address.RD04— The admin dashboard lists the redirects AI assistants received by class, path and status, largest first, only for status 300 to 399 and only inside the time window, joining the measured pages and the paths outside that list.RD05— llms.txt never points to the old Render host or to the www host, announces the connector as /mcp/ with the slash, and every concrete dadosb3.com address it cites answers a browser without a redirect.MT06— A wrongly formatted date, an impossible date, a future date and a date before the base each return 400 with a plain sentence saying why and how to fix it, a back to today link, suggested dates, noindex and the canonical of the undated page.MT07— A valid date before the first filing of that particular company returns 400 saying it had no filing yet and when its first one was received.MT08— The dated page carries meta robots noindex follow, the X-Robots-Tag header and a canonical pointing to the undated page without the date parameter, also in English; the normal page gains no noindex.MT09— The sitemap lists no URL with the date parameter, so the time machine creates no pages for search engines to index.MT10— The dated page stays open without an API key like the normal stock page, an old ticker code redirects with 301 carrying the date, an unknown ticker is 404 and the page contains no buy or sell recommendation.MT11— An empty date parameter behaves as no date and shows the normal stock page without the viewing notice.MT12— Date validation accepts only a real YYYY-MM-DD that is not in the future and not before the base, with the edges (first day of the base and today valid, the next day and the day before the base not), and the suggested dates are semester ends inside the base.MT13— The changed test is true with a newer version after the date or a different figure today and false when nothing changed, and the no price sentence is exactly the approved one.PF01— The monthly cap of each tier of the paid connector fake door comes from limites_config.json (daily Pro cap times 30 times the tier multiplier), nothing typed in code; the block shows both prices, says the caps are provisional and that ordinary AI stays free, and mentions neither card nor Stripe.PF02— The connector block appears on the companies page with both tiers; the PORTA_FALSA_CONECTOR=0 switch removes the block, makes the routes answer 404 and the panel report it as off, while someone already on the waiting list can still leave.PF03— The click on the plan button charges nothing and shows an honest page (plan in preparation, nothing charged, provisional price and cap, work e-mail, ordinary AI free, noindex, no cache) and counts one click per tier; a crawler does not count, the same connection on the same day does not count twice, an unknown tier is 404 and no IP reaches the file.PF04— A work e-mail joins the waiting list at once with one welcome message that says nothing was charged and carries leave and not me links, counts one e-mail for the tier and is never repeated on the page; a repeat sends nothing, personal providers and invalid addresses get 400, crawlers and the hidden field get success without a row, and the per connection daily cap holds.PF05— The not me link cancels the waiting list row and blocks the address for 30 days, leaving works in one click and an unknown link is 404, the waiting list older than six months is deleted, and the erase my data flow covers the table while the anonymous counter stays.PF06— The admin metrics panel shows clicks and e-mails per tier for today, the last 7 days, the 7 days before and a 14 day daily series, with only numbers: no at sign and no address anywhere in the JSON.SD01— The Sunday Desk page shows the cover, the issue summary and a form with only e-mail, origin and a required consent box; without consent nothing is saved or sent.SD02— Signing up saves token, issue, origin and consent date and sends one e-mail with the download, leave and not-me links; asking again resends the same link.SD03— The e-mailed link serves the PDF as an attachment and counts the first and total downloads per token; bots download without counting.SD04— An unknown token gets a 404 page with a link back to the form, with no PDF, on download, feedback and leave routes.SD05— The admin panel counts Sunday Desk visits, sign-ups, sent e-mails, people who downloaded, total downloads and answers per origin without exposing e-mail or token.SD06— The feedback question route does not exist without the admin token, sends nothing without the owner's OK, goes once only to those who downloaded, and stores the 1 to 5 answer by token.SD07— Leaving cancels the download link and the question; the table is covered by not-me, deletion on request and the 12-month retention.SD08— The download-link e-mail counts toward the same daily e-mail cap; when the day is full the request is refused and nothing is saved.TD01— The recorded excerpt of the Treasury public CSV (semicolon separator, decimal comma, day first dates) becomes ISO dated rows with numbers: the fixed rate bond maturing 2027-01-01 has buy rate 13.48 and sell rate 13.60 on the 2026-10-30 base date, the oldest row of 2004 is read, a header different from the checked one or an empty text returns no rows and the reason, and an empty cell becomes none and never zero.TD02— The full collection stores the whole file and the nightly one only the last 45 days; a source that is down, a swapped header, a batch with a rate outside the plausible range or an exception stores nothing, deletes nothing, never raises, and leaves the reason in the collection record of the meta table.TD03— The Tesouro Direto table is part of the inheritance of external series: in a from scratch build where the source failed the rows of the previous database come back, the fresh row wins over the old one, and the shrink guard counts the table.TD05— With no Tesouro Direto table in the database the JSON route answers 503 with Retry-After, the page answers 200 in both languages with the series being collected notice, noindex, no number, no chart and no link to the JSON route, the health route does not fail and shows the Tesouro block as null, and the sitemap does not announce the page.TD07— The yield curve SVG has an image role with a label summarising the series, one marker per point (circle for one family, square for the other, so colour is not the only signal) each with a title giving the term in years and the rate, a legend, axes in years, and it fits a negative rate; an empty series returns an empty string.TL01— The recorded Anatel file (one row per tax ID and product, one column per month) becomes the sum per operator and month: Vivo in August 2026 has 85,818,766 mobile accesses WITHOUT M2M and service points (which go to the other column: 20,141,773), and the August TOTAL adds up to 220,921,381, equal to Anatel's official total; an operator under 100 thousand accesses stays out (only in the TOTAL); an empty file, one without the checked fields or without month columns returns nothing and the reason; an empty cell is "no data", not zero; a malformed tax ID drops the row.TL02— The Anatel ZIP is read by byte range: the central directory comes from the end of the file and only the requested member is downloaded and decompressed as a stream; a missing member returns None; ZIP64, an unknown compression method, a file that is not a ZIP and a server that answers HTTP 200 instead of 206 all stop with an error instead of downloading gigabytes.TL03— The collection writes both services, checks the TOTAL against Anatel's official total month by month (0.0% difference) and stores the result in the telecom collection record; a source that is down, a file without the checked fields, a value outside the plausible range, a summed total more than 5% away from the official one or an exception writes nothing for that service, deletes nothing, never raises, lets the other service go on and leaves the reason in the record; repeating the collection does not duplicate rows.TL04— The batch of files: the history reads from 2023 (mobile by semester, broadband by year), the recent run reads the current period (broadband also reads the previous year in January and February); the night only collects again when it never collected, the last run had an error or it is more than 6 days old.TL05— The telecom accesses table is part of the inheritance of external series: in a from scratch build where the source failed the rows of the previous database come back, the fresh row wins over the old one, and the shrink guard counts the table.TL09— With no telecom accesses table in the database the filtered JSON route answers 503 with Retry-After, the unfiltered one answers 200 with a notice, the page answers 200 in both languages with the series being collected notice, noindex, the licence and the agreed use but no number, no table and no link to the JSON route, the health route does not fail and shows the telecom block as null, and the sitemap does not announce the page.EL01— The recorded excerpt of the ANEEL datastore response (decimal comma values) becomes rows with numbers: the CEMIG-D period starting 2026-05-28 has TUSD 593.08 and TE 310.21, the source duplicates collapse (56 raw records into 27 rows), and when two revisions share a distributor and a start date the one from the higher act number is kept (EMT, 2010-07-15, act 1030); a response without the checked fields, an empty one or a truncated one (fewer records than the announced total) returns no rows and the reason, and an empty cell or a unit other than MWh drops the row.EL02— The collection writes the 27 rows of the excerpt and the result in the collection record of the meta table; a source that is down, a truncated response, a value outside the plausible range or an exception writes nothing, deletes nothing, never raises, and leaves the reason in the record; repeating the collection does not duplicate rows.EL03— The electricity tariff table is part of the inheritance of external series: in a from scratch build where the source failed the rows of the previous database come back, the fresh row wins over the old one, and the shrink guard counts the table.EL07— With no electricity tariff table in the database the filtered JSON route answers 503 with Retry-After, the unfiltered one answers 200 with a notice, the page answers 200 in both languages with the series being collected notice, noindex, no number, no table and no link to the JSON route, the health route does not fail and shows the energy block as null, and the sitemap does not announce the page.FD01— The CVM daily fund report becomes one row per fund and month: unit value, net assets and unitholders of the last reported day and the monthly sum of inflows and redemptions; when the class row exists the subclasses are ignored, with subclasses only net assets and flows add up and the unit value is blank for two or more; malformed tax IDs are dropped, the old CNPJ_FUNDO layout is accepted and a file without a checked column is refused.FD02— The fund collection covers the 13 months ending in the current one; the 3 newest are downloaded on every build and older ones only without the aggregate on disk; an unavailable month neither crashes nor erases; the zip is deleted after reading; months leaving the window are dropped; the register gives the class name with accents, the manager through the parent fund and the name of funds not yet adapted, and a register outage keeps the stored names.FD05— The /fundos/informe route returns the three rankings of the reference month with the CVM dataset page, the ODbL licence and a citation, the series of one fund by tax ID, a name search and a CSV with a fixed header; bad parameters return 400, an unknown fund 404, and without the table the plain address returns 200 with a notice and the filtered one 503 with Retry-After.FD06— The /fundos page in Portuguese and English shows the reference month, the three rankings linking to each fund, the warning that net assets are not added up, the JSON and CSV links, source, licence and citation with no script; search results are noindex; each fund page shows the register and the month-by-month table; an unknown fund is 404; without the table both pages are a noindex notice with no number, never an error.FD07— The health route has the funds block with an alarm after 21 days, the sitemap announces /fundos with the last reported day never in the future, llms.txt cites the page and the route, the API hub has the route card, the footer of every page links to /fundos, the Portuguese and English methodology pages cite the source and licence, and the FD family exists.FD08— The MCP tool fundos_investimento calls the same route: rankings with source and licence with no argument, the series with a tax ID and a name search; a bad request comes back as an error field, never an exception.SC01— The checked against CVM badge is an SVG with the value, the filing date and the phrase, served with an ETag and a 304, and its HTML code links to the lineage page of that exact number.SC02— No badge exists for an unknown indicator, a value in reais, an unknown ticker, or a number with a caveat flag or incomplete lineage: all return 404 or None.SC03— The badge page explains the use with copyable code, and each badge request is counted by the Referer host in selos_embutidos, separate from the embedded cards, with the own site and missing Referer counted apart.I-CR01— An unnamed crawler hammering the search pages gets 429 with Retry-After and the cuts are counted per familyI-CR02— Named AI crawlers and search engines such as GPTBot ChatGPT-User ClaudeBot Googlebot and Bingbot are never rate limited by the anonymous crawler cutI-CR03— Ordinary browsers and requests carrying an API key never get 429 from the anonymous crawler cutI-CR04— Only expensive read pages count toward the anonymous crawler cut and other paths and methods are untouchedI-CR05— Thresholds come from environment variables with conservative defaults each IP has its own bucket and one switch turns the cut offI-CR06— Through the real app an unnamed crawler gets 429 while browsers named AI crawlers and key holders passI-H41— The memory watchdog lists the requests in flight from oldest to newest, with method, path and query without the secret parameters (api_key, chave, token) and the agent family only; never the raw User-Agent or an IP. A request that ended leaves the list.I-H42— The memory reading adds the web process and its child processes (and their children) from /proc, reads the container from the cgroup, survives a child that died mid-reading and returns nothing where there is no /proc.I-H43— Crossing the memory threshold writes ONE line with memory, background phase, oldest request and the in-flight list; at most one line every 30 s while it stays high, and one on recovery. The last 20 events stay for the admin panel.I-H44— Above the cut threshold, NEW requests to heavy pages (stock pages, lineage, preview image, MCP) get 503 with Retry-After for a short window; the health check and the admin are never cut, and the watchdog is the outermost middleware.I-H45— The watchdog thread does not start in the test battery, when switched off or without /proc; thresholds come from environment variables with safe defaults, and the admin panel shows the watchdog block only with the token, without IP or key.I-H52— With the app full (teto_em_curso requests already inside), a new request gets 503 with Retry-After without entering the app; /saude and /admin always pass; one "teto" event per floodI-N594— soltar_cache_de drops the file cache of each file (and its -wal), ignoring missing files and any error; without posix_fadvise it does nothing.I-N595— The daily update (work copy, checkpoint, swap) and the backup release the file cache after the heavy steps; a failure there never breaks them.CF01— From the CVM fund portfolio file (CDA block 4) only ordinary and preferred shares and units are kept; lent shares, BDRs and debentures are left out; two lines of the same fund and ticker add up; accented fund names are read correctly.CF02— The fund portfolio ingestion covers the 13 calendar months before today; the 4 most recent are downloaded on every build and older ones only when the aggregate is missing on disk; an unavailable month neither crashes nor erases what was there; the downloaded zip is deleted after reading; months older than the window are dropped.CF05— The stock page shows the block Funds that hold this stock with the reference date, the publication delay warning, the link to the CVM dataset and the ODbL license; the JSON route returns the same with source, license and citation; without a portfolio the page stays as it was.CF06— The count of funds per month is read once per stock page and kept in memory per database file, so the next request does not scan the whole fund portfolio table again; a commit that changes the file, an open transaction or an in-memory database always count again, and the answer equals the direct count.FO01— The recorded Olinda response for IPCA expectations becomes one snapshot per week and reference year, the last survey of the week; the ISO week that crosses New Year is split at the calendar year so the last December survey (31 December 2024) survives next to the first January one, since it is the base of the accuracy block.FO02— In the recorded response of 12 to 23 October 2026 the four indicators (IPCA, Selic, Cambio with its accent, PIB Total) are each read by their own source name, with reference years 2026 to 2030 and the newest snapshot of 23 October 2026, and the values of the day match the source.FO03— A row without a median, with a reference year that is not four digits, with a malformed date, of another indicator or of calculation base 1 (the short one) is dropped; only base 0, the one the Focus Report uses, is read.FO04— Storing a Friday survey replaces the Thursday snapshot of the same week without touching the previous week or another reference year, and storing the same day twice does not duplicate it.FO05— The collection stores each indicator on its own: a failed window leaves that indicator with its error and its previous history intact, the others are stored, the outcome is recorded in meta focus_coleta, and a source outage never raises.FO06— A median outside the plausible range (for example IPCA 500) refuses the whole batch of that indicator, stores nothing and keeps the previous history, while the other indicators carry on.FO07— The request to the source filters by the source name, calculation base 0 and a window of days, encodes spaces as %20 and the accent of Cambio in UTF-8; the full collection asks in 3-year windows from 2000 and the nightly one asks a single 21-day window.FO10— The JSON catalogue and the per-indicator series carry the source with the ODbL licence, a citation line with date and data version, the accuracy table with its rule and the coverage; the series is open, filterable by date and reference year, with CSV equal to the JSON, 404 for an unknown indicator and 400 for malformed parameters.FO12— Before the first collection (database without the Focus table) the JSON routes answer 503 with Retry-After, the page answers 200 with a series-being-collected notice, noindex, no number and no link to the JSON, the health endpoint does not fail and the sitemap does not announce the page.FO13— The Focus page is linked from the Macro hub, announced in the sitemap only when the database has the survey (lastmod never in the future), cited in llms.txt with the licence, linked from the API hub, reported in the health endpoint with its age, and its table is part of the build inheritance and the shrink guard.PV01— Link preview image: every public HTML page gets one og:image (1200x630, pointing to /og.png for its own path), twitter:image and a summary_large_image card (the old summary card is replaced, never duplicated); the PNG opens at 1200x630 with the page title taken from the h1; malformed paths return 404.DL01— Delisted-company price file reader: only share lines (BDI 02), spot market, class suffix 3-8 or 11 are read, with the same slices as the main price ingest and the quotation factor dividing the QUANTITY, never the price; forward, odd-lot, subscription rights and zero closes are rejected.DL02— The ISIN issuer code is taken only from share (ACN) or unit (CDA) ISINs; ETF, fund and foreign ISINs have none and never enter linking or the gap list.DL06— Building the delisted tables is idempotent: it deletes and rebuilds, so no duplicated sessions and no ticker survives a link that no longer exists.DL07— The list of tables purged when a company leaves the universe excludes the delisted-company tables, otherwise each build would recreate the survivorship bias they exist to fix.DL09— A trading code the company itself declared to the CVM counts even outside the usual format (GOL traded as GOLL54 until 2026) when the B3 quote line is a share; declared rights and undeclared odd codes stay out.DL10— Asking for the dividends of a stock that left the exchange (AESB3, ENBR3) never returns 200 with an empty list: with dividends declared to the CVM it returns them with the left-the-exchange block, otherwise 404 with that block and a retry hint saying the absence is unknown.DL11— A code the database does not have stays 404 with the usual message plus structured suggestions (same root or close spelling, only known codes) so an assistant can retry by itself; retrying with the first suggestion works.DL12— Prices and indicators of a company that left the exchange stay 404, but the error says it left, gives the last trading day and the routes that do answer, and the panel still counts the request.DL13— Corporate events of a company that left the exchange answer 200 with the left-the-exchange block even without a key and a null (not empty) B3 event list; the events declared to the CVM reference form follow the key rule and always carry the no ex-date flag.DL15— A code of a listed company never turns into a company that left the exchange, and a reused code goes to the listed one.DL20— Dividends a delisted company declared in the CVM Reference Form (item 3.5) enter with fiscal year, share class, type, amount and payment date; a re-declared year counts once (newest form), zero amounts and listed companies stay out.DL21— The dividends answer for a company that left returns what it declared to the CVM with the left-the-exchange block and a coverage naming source and period; a company with nothing declared stays 404, never an empty list.DL22— The IPE dividend report of a company that left enters per share with record date, only for ISINs of its own issuer; free-form files are kept without value, nothing is downloaded twice, and an unreachable CVM stops after 5 failures.DL30— The page of a stock that left the exchange (/saiu-da-bolsa/ENBR3, BIDI4, AESB3) opens in Portuguese and English with the direct answer (name, last trading session, total declared to the CVM as a total amount, not per share), the per-year table equal to the JSON /empresas/{T}/dividendos, the CVM code, a citation line with the data version and the page URL, and follows the asset-page rule (English noindex, no hreflang).DL31— On /saiu-da-bolsa every ticker of a company that left links to its own page (or to /acoes/{T} if still in the universe), every such link answers 200, the sitemap has exactly one URL per delisted ticker, and llms.txt cites the per-ticker page with the count.DL32— The English page of a stock that left the exchange has the same numbers, anchors, internal links and table rows as the Portuguese one.DL33— On the synthetic database, the page of a delisted stock with dividends shows the JSON totals and links its other ticker; one with no declared dividend answers 200 saying the absence is unknown, without a table and without linking the 404 dividends route; an active company is 404.DL41— The internal search finds the yearly pages and the main list by deslistagem, OPA, fechamento de capital, cancelamento de registro and saiu da bolsa (the year typed comes first), plus the companies of that year with working links; petz finds the PETZ3 page and AUAU3; the exact code PETZ3 goes straight to its page; /buscar?q= goes to the search; active tickers still go to their own page.DL43— The home block states how many companies left the exchange in the latest year with a page, through the last session date, linking the yearly page and the main list; with no data it says nothing; every page footer links /saiu-da-bolsa.DL44— Performance of a delisted stock (NTCO3) runs up to its last trading day on the as-traded price, without total return, and says distributions are not includedAN01— The recorded excerpts of ANP's per-well files (December 2023 in the new layout, latin-1; March 2022 in the old layout with a quoted multi-line title and thousand-dot numbers) become sums per environment, basin, field, operator and contract: Tupi adds up to 804,443.7557 bbl/day and 40,009.19 Mm3/day, all of it pre-salt held in a COLUMN (adding the pre-salt file again would double Tupi); the month is rejected, with the reason, for a missing onshore or offshore file, a different header, a period inside the file that is not the requested one, an unreadable or negative number, a pre-salt field that offshore does not have, or a national total outside the plausible range; numbers with thousand dots and decimal commas are read correctly.AN02— The links on ANP's page (recorded excerpt) give the months under the four file name patterns ANP has used; the yearly 2005-2020 zip and months before 2021-01 stay out; a new file on the page would enter on its own.AN03— The tax ID comes from ANP's list of concessionaires by contract number and links to the production only when the operator name in the production equals the contract operator's current name: Petrobras (Tupi), Eneva and PetroRecôncavo link; Atlanta (Enauta Energia against Brava Energia S.A.), Peregrino in 2022 (Equinor Brasil against Prio Tigris), Frade (Petro Rio Jaguar against Prio Forte S.A) and a contract missing from the list do not; a truncated or different-header list is refused.AN04— The collection reads the page, the months it links and the concessionaires list, writes month by month and the result in the collection record of the meta table; a month missing on the server is an error for that month only; repeating does not duplicate and a re-read month replaces the previous one; the nightly run only downloads missing months and re-reads the last 3, the weekly one re-reads everything; an unreadable zip, a source that is down, a page with no link or an exception writes nothing, deletes nothing, never raises and leaves the reason in the record.AN05— The ANP production table is part of the inheritance of external series, but only for months the new build did not bring (ANP renames operators in old months and the key includes the operator, so inheriting the same month twice would double it); the shrink guard counts the table.AN08— With no ANP production table in the database the filtered JSON route answers 503 with Retry-After, the unfiltered one answers 200 with a notice, the page answers 200 in both languages with the series being collected notice, noindex, the licence, no number, no table and no link to the JSON route, the health route does not fail and shows the anp block as null, and the sitemap does not announce the page.AN12— The ANP Monthly Bulletin (recorded one-page PDF excerpts of the by-operator table) gives every operator from 2024 on: August 2026 has 50 operators, Petrobras at 4,019,651.38 bbl/day and 196,391.44 Mm3/day, and the name SHELL BRASIL that ANP lists twice becomes one operator (summed); the decorative symbol glued to names is removed; a PDF that is unreadable, from another month, with no table or with a row whose total does not match oil plus gas times 6.2898 is refused; only monthly PDFs from 2024-01 are discovered; the collection only takes months after the last OFFICIAL per-well month, writes month by month, re-reads only the last 2 and links the tax ID by name through ANP's list (Petrobras, Brava and the SPE Prio Forte link; the SPE is not PRIO S.A.).I-N480— An unknown-code 404 now says what the code is: a FII asked for under /empresas points to /fiis, an Azul unit points to AZUL3 as ANOTHER class, a name or URL-template word is called that, and fund quotas missing from the base say it is a coverage gap; nothing is invented and nothing redirects.I-N481— Every curated code-to-company link is checked against the database (target exists, delisted paper exists, CVM registry exists) and each ticker change shows trading continuity within a weekend.I-N482— In the REST routes each group of unknown codes keeps the 404 and the "ticker desconhecido" prefix and adds the code type, where it is and related fields; similar-spelling suggestions are labeled as possibly a different asset and never include the requested code itself.I-N483— In the MCP connector every tool returns the same suggestions, left-the-exchange block, code type and location fields on an unknown-code error, not only dividends and events.I-N540— A fund-quota code seen in B3's COTAHIST is linked to a CNPJ only when that exact ISIN was declared in the CVM FII monthly report, reading both CVM layouts (2016-2020 and 2021 on); an ISIN declared by two CNPJs is not guessed, a same-prefix ISIN is not a match, and rerunning does not duplicate rows.I-N541— An old FII code redirects to the fund's current code only when it stopped trading before the current one (RBFF11 to RBFM11, not AVUR11 to AVUR15); unknown quota codes say what they are with the source (other code of a covered fund, fund in the CVM report without a linked code, ISIN claimed by two CNPJs, or a quota outside the FII report), never 'does not pay', and an old database keeps the previous behaviour.I-N542— On the real database, codes that returned 404 in the 7-day panel resolve to the covered fund (RBFF11, SHOP11, EVBI11, TRXL11, SIGR11 and others), checked against the same CNPJ and trading dates; Fiagro and FI-Infra quotas outside the FII report say so with the COTAHIST name.I-N543— The FII page and dividends routes send an old fund code to the current one with a 301 and the page says where the visitor came from (not forgeable by the URL); machine requests under /empresas stay 404 and point to /fiis/{current code}.I-N544— The 2016-2020 CVM FII monthly reports, which use the old column names (CNPJ_Fundo, Nome_Fundo), are ingested like the newer ones: a fund that left before 2021 gets its registry and monthly facts instead of being silently skipped.I-N545— One month of the CVM Fiagro monthly report is ingested into the fund tables: digits-only CNPJ formatted like the FII report, highest version kept, segment 'Fiagro', fund-months already in the FII report untouched, idempotent; the fund page of a Fiagro says its net assets come from the CVM Fiagro report.I-N546— For funds that file the Fiagro report, a COTAHIST quota is linked by the same ISIN issuer code when the declared ISIN is another series of that issuer (link type emissor_isin), only when a single CNPJ declares that issuer and a single quota code has it; FIIs still need the exact ISIN.I-N547— A fund with no linked trading code (its newest report declares an ISIN that does not trade) gets the code linked to it, its ISIN becomes the traded one and that ISIN's prices are loaded; codes of other funds and ties are never used.I-N548— On the real database, panel codes that returned 404 for lack of a link now have a fund page (KNCA11 and CPTR11 as Fiagro with the source note, FATN11, MGFF11, SCPF11, GRLV11, XPGA11), and DOMC11 redirects to the fund's current code.I-N590— The B3 bridge records code, ISIN, CNPJ, fund name, type, the public B3 fund-record URL and the date only when the code equals the record's main trading code and trades in COTAHIST; receipts, codes on two records and codes that do not trade stay out, and the raw B3 download is reused within the week and when B3 is downI-N591— Applying the B3 bridge gives a FII with no code the record's code, swaps a code that stopped before the new one started (TVRI11 replaces BBPO11, which becomes an old code), adds an FI-Infra outside the FII report with its type, CVM register name and CVM Daily Report net assets, settles a disputed ISIN by the record, and never overrides a CVM link to another fund or a code still tradingI-N592— On the real database IFRA11 has a fund page with the CNPJ from the B3 record, says it is an FI-Infra and not a FII, cites the CVM Daily Report and the B3 record, and has P/BV; TVRI11 is fund 14.410.722/0001-29 and BBPO11 redirects to it; KNDI11 (FIP) has a page saying there is no daily report; the disputed SARE11, delisted, stays 404 without picking an ownerI-N593— Invariant on the real database: every fund trading code has its link source recorded (CVM ISIN or B3 bridge) with the same CNPJ, and every B3-bridge link has its row with the public B3 record URL and the retrieval dateI-N597— Applying the B3 bridge on a rebuild still swaps the code when the new code's prices are already under the fund (only the current code's last trade counts), and a fund that already has the record's code but no ISIN gets it, so its old codes redirect to itI-N708— Letters that open the company name (KLAB3 -> KLABIN) put the company ahead of mere look-alike codes; the same code root still winsI-N709— A delisted code asked under /fiis points to the delisted page, and the ChatGPT app tools record the unknown code like /mcp/ doesI-N712— A share class the company does not declare in its registration form (KLBN4, ALUP4) resolves to the company through its B3 price series; a class shared by two companies or with no series does notI-N713— On the real database KLBN3/KLBN4/ALUP3/ALUP4 answer like SAPR4 in the REST routes, the MCP tools and the ChatGPT app, with their own price series (close only, no OHLC copied from the unit)I-N714— Prices and performance come from the requested code's own series; a class with only closing prices says so, and falling back to the main share is always flagged as another securityI-N700— The same URL requested with Accept-Language en-US and pt-BR returns the SAME HTML, in Portuguese, with the canonical on the URL itself, no noindex and no Vary: Accept-Language (stock and REIT pages, lists, delisted, lineage, guides, home); an invalid ?lang=xx is Portuguese too; _idioma ignores the header. Since 06/11/2026 the language comes only from ?lang=.I-N701— ?lang=en returns English. Pages with both languages carry a self canonical in each language and the same reciprocal hreflang trio (pt-BR, en, x-default = PT); asset pages keep English as noindex,follow with a self canonical and no hreflang on either side. The footer offers the other language as a static link.I-N702— A page marked Portuguese-only gets no hreflang (neither in the HTML nor in the sitemap) and no footer language link; every route family in the sitemap has a real English version at ?lang=en.I-N703— Googlebot, bingbot, GPTBot and OAI-SearchBot, sending Accept-Language en-US, receive the Portuguese page on the Portuguese URL, indexable, with the canonical on that URL.I-N704— /sitemap_index.xml is a valid sitemap index; each child /sitemaps/{type}.xml is a valid urlset; the union of the children equals /sitemap.xml with no repeated URL; no BDR without data of its own (no declared distribution and no issuer record); lineage pages of liquid stocks are in, return 200 and carry a canonical; lists and the delisted page have priority 1.0; robots.txt points to the index.I-N705— A trailing slash (/acoes/PETR4/) answers 301 to the address without it, on GET and HEAD; /mcp, the 404 pages, JSON and .md responses do not change.I-N706— Public HTML pages and the sitemap carry a strong ETag and an HTTP-date Last-Modified; If-None-Match with the same ETag returns 304 with no body; PT and EN have different ETags; private routes and keyed responses get no public ETag.I-N707— python -m dados_b3.indexnow --tudo reads the sitemap index, follows the children and, without --enviar, only counts and shows the batches; with --enviar it refuses a non-production sitemap or a key that is not served, and posts in batches of at most 10,000 URLs with host, key and keyLocation.FA01— /fontes-abertas answers in Portuguese and with ?lang=en (right html lang), with a canonical of its own URL in the language served, is in the sitemap and is linked from every page footer, the home, /guias and llms.txt.FA02— The FAQ is visible in the initial HTML and in the FAQPage JSON-LD with the SAME questions and answers, among them the literal question about an open, free database with the dividends of companies that left the exchange; same in English.FA07— Database without the delisted tables: the sentence points to /saiu-da-bolsa and publishes no zero. Database without construido_em: the frequency sentence points to /saude instead of inventing a date.FA08— On the company page, every historical indicator with a declared chain (linhagem.INSUMOS) and a value in the last year has a link reading Memoria de calculo do/da ... (EN ... calculation trail) to /linhagem/T/year/indicator, which opens; on /acoes/T/divida the two table indicators too.LS02— Q4 earnings are the fiscal year's minus Q1 to Q3 of the same item, with controlling-shareholder earnings taking priority over the total.RK01— B3's 'REST CAP DIN' (return of capital) reads as its own type, RESTITUICAO_CAPITAL, in the function and in SQL alike; JCP and dividends are unchanged; the stored type is unchanged; the per-type split puts it in restituicao_capital, outside dividends + JCP + other.RK02— Every row whose B3 text starts with REST CAP leaves the read layer typed RESTITUICAO_CAPITAL, and the dated reader carries the type too.DM01— The parallel-run guard lives INSIDE construir_local.py: a lock file (exclusive create, with pid) plus the process list; another live run, through the lock or another construir_local.py from ANY folder, exits with code 75 before touching anything; a lock from a dead process is taken over with a warning; the venv launcher, the build's own children, a grep or an editor do not count; only the owner removes the lock; main takes it before any action.DM02— The Sunday script waits for CVM: all three yearly zips (DFP, ITR, IPE) dated today in Brasilia time means go now; otherwise it asks again every 30 minutes; at 3 pm it goes on if at least one is from today and STOPS with code 10 if none is; only HEAD requests, nothing is downloaded.DM03— The DATA_VERSION in the code must equal the one /saude publishes: equal goes on, different stops with code 20 (a new version is published by hand), and an unreadable /saude or code stops with code 21.DM04— Only the dedicated Sunday folder is reset: created as a git worktree from origin/main when missing, reset to origin/main and cleaned every Sunday, and only if the path is exactly that folder, it is a worktree and git confirms its root; any other path, the main checkout or a subfolder of another repository stops with code 40 and the owner's local work survives.DM05— Rehearsal mode runs construir_local.py with --ensaio (build and gate, nothing sent); normal mode runs without it and requires the admin token BEFORE waiting for CVM (code 60); the lock becomes code 30 and any other error code 50; every line goes to the log, which is copied to the NAS by name (TRUENAS, never an IP); a NAS outage only warns and the token never reaches the log.DM06— The Sunday script creates no scheduled task and changes no Windows setting; the owner's step-by-step lives in ferramentas/domingo_cvm_tarefa.md with Sunday 9:30, wake the computer, the v2 wrapper that returns the exit code, the dedicated folder, the project venv python and the first Sunday in rehearsal mode.I-N710— The llms.txt page is served from process memory: the cached text is identical to the freshly built one, and it is rebuilt when the data version changes
Level 2 — Semantics: does the account mean what the calculation assumes? (123)
I-B07— Every LATEST × PREVIOUS divergence above 0.5% in the key accounts must be recorded in `reapresentacao`.I-E05— The trading session used in each multiple falls INSIDE the window after the filing was published.I-F02— The same central invariant as the equity multiples, applied to REITs: the price behind P/B is never earlier than the filing's delivery date.I-F07— Vacancy is a fraction in [0, 1] (INVARIANT) and the source's ambiguous semantics is always flagged (HEURISTIC, in the same test).I-F08— No CLEAN P/B (flag NULL) pairs a book value from one reporting period with a trading price far later than the filing's publication.I-B24— Every bank and insurer that has a fact has both profit AND equity extracted in the most recent year.I-X02— Inflow and outflow are GROSS flows: negatives cannot exist.I-X03— The series skips no month between the first and the last.I-L03— An indicator published WITH A VALUE and WITHOUT A FLAG cannot have a required input with no backing in the database.I-L04— Every indicator the database publishes is declared in `INSUMOS`.I-B15— The price used in a multiple is never earlier than the filing's receipt date — the central INVARIANT of the whole multiples block: without it, the P/E 'knows' a balance sheet the market had not yet seen.I-B16— Counter-proof: market cap recomputed outside the calculation code (price × the most recent share count AS OF the price date) matches what was stored.I-B18— The same central invariant as I-B15, in the quarterly block: the multiple's price is never earlier than the quarterly filing's receipt date.I-P05— `codigos_negociados` works with ZERO COTAHIST files present: it returns an empty set instead of raising.I-P02— Every code feeding a company's price series belongs to the SAME share class as today's primary ticker.I-P04— Every market cap uses a share-count snapshot that WAS ALREADY PUBLIC on the price date — never a later one.I-P12— The quotation factor corrects the QUANTITY; the price passes through untouched.I-P14— The swap only ever happens between different share CLASSES — never between a code and the retired name of the same class. Without this, MOTV3 would lose to CCRO3 and EMBJ3 to EMBR3: the old code has more historical volume purely because it existed longer, and the site would start calling Embraer and Motiva by the codes they abandoned.I-P15— The liquidity swap elects a unit only when its basket is recorded in unit_composicao, and never dethrones one. A unit packs N shares into one instrument; without N, market cap comes out multiplied. The basket is now inferred from the unit’s own price and market cap already divides by it, so keeping units out left Energisa represented by ENGI3, which trades 2,669 times LESS than ENGI11 — Taesa, Iguatemi, Santander and Sanepar likewise, four of them vanishing from any liquidity-screened universe. A unit with no recorded basket still cannot win, however liquid.I-P23— A company that changed its ticker root (Marfrig to MBRF3 in January 2026) gets the new code even while the annual registration form still lists the old one, and the old code, with zero recent volume, yields without the 3x factor over the sixteen years of history it accumulated. Same root is never a rename; a root claimed by two companies is discarded; a living incumbent keeps the factor.I-B32— Every indicator the database emits has a provenance entry in the DICTIONARY — the formula, the CVM accounts and the profit BASE that /indicadores now ships ALONGSIDE the number.I-Q18— Asking FNET for an OLD month does not shrink the filing window. A fund's daily report for a month is filed in the first days of the next one, and FNET's search is by filing date. One parameter used to do two jobs — say which month is the last closed one, and stop the window from asking for the future — so pushing it back to fetch history collapsed the window to a single day and the source returned nothing. A seven-hour backfill stayed stuck on the newest month because of it, and the source took the blame it did not deserve.I-Q19— Asking only for balance sheets does not download daily reports. An ETF's effective fee comes from the difference between TWO balance sheets of consecutive months in the same financial year, because the chart-of-accounts figures accumulate across the year. The base held 153 balance sheets piled into two isolated months — 138 funds, only 4 with the pair — and a run aiming at the missing month spent its whole budget on that month's daily reports, which are useful but are not what blocks the calculation.I-Q20— The backfill's time budget applies INSIDE the document listing, not only between months. FNET's search pages 200 at a time, up to 30 pages, and each request waits up to 240 seconds when the source is throttling — two hours for a single listing. The budget used to be checked only between one month and the next, so a 30-minute round spent three hours without finishing its first one, and the easy reading would have been 'FNET is down'. It was not: the limit was ours, in the wrong place. Half a listing is an honest result — it saves what arrived and the next round continues.GC23— A free key past 80% of its daily limit gets aviso_do_plano in the JSON (used, limit, remaining, and the Pro summary with price and the 7-day guarantee) plus the X-DadosB3-Restam-Hoje header; never before 80% and never on a Pro key; the same Pro summary is in llms.txt and in the bulk-export refusal.I-N77— The three pages that lacked JSON-LD in the 'as an AI' test (/hoje, /buscar, /comparar) now carry ONE valid schema.org block in the head: the screeners declare a SearchAction pointing at /procurar?q=, /hoje is dated by the DATA VERSION (not by today), and /comparar/A/B lists both companies with ticker, resolved name and the URL of each company page. Portuguese and English.I-N78— Every public read-only response without a key carries Cache-Control: public, max-age=300 and Vary: Accept-Language (the same URL serves PT or EN by browser header); no response carrying an API key, and no session, billing, protocol or keyed-data route, is ever publicly cacheable; 404 and 503 are not cached.I-N79— `pip install dadosb3` is the first move anyone who writes code makes, so it appears at the three doors that person arrives through: the /api hub, llms.txt (the door AI assistants read) and the /para-quem page, in both languages. The link points at PyPI — the source the reader can check — not at a page of ours.I-N81— When the filing to the regulator declares an instalment payment, the asset's calendar says so and links to the document, instead of going silent about the payment. The form carries the FORM of payment (lump sum or instalments); an instalment payment has no single date, it has several, inside a document we do not parse. Before, such a line produced no payment event and looked exactly like a dividend with no date at all — the reader concluded there was no scheduled payment, when what exists is a payment in instalments we have not opened.I-Q07— A Brazilian-formatted number ('13.919.737.980,15') becomes a float; empty or invalid becomes None, never an exception.I-Q08— A Brazilian date ('11/09/2026 20:27') becomes ISO; an invalid one becomes None.I-Q13— B3's ETF list is read even when the JSON arrives double-encoded.I-Q14— B3's fund detail yields ticker, CNPJ, manager, registrar and share count; an empty body yields None.I-Q15— The FNET document search yields id, type, reference date (a balance-sheet month becomes its first day) and filing date.I-Q16— The index is inferred from the fund NAME by declared rules, most specific first; a name with no clue yields None, not a guess.I-V04— The items rebuilt from an archived version are the SAME ones the standardisation writes from the same accounts, for an ordinary company and for a bank; only the origin gains the version suffix.I-V06— The versions route lists each filing with every version, its date, the link to the original and whether its values are in the base, archived or not kept; between two versions with values it lists only the accounts that changed (before and after).I-V09— The routes: /fatos without `as_of` is unchanged; with `as_of` it answers the version in force; an invalid date is a 422; /versoes lists the filing with both versions and filters the re-filed ones.I-B110— Bank chart: non-controlling interests are the DIRECT child of equity (code taken from the equity origin) whose label says non-controlling (2.08.09 or 2.07.02 labels); a grandchild or a similar label outside equity does not count; without the line the item is null; annual and quarterly; the account is kept in the version archive.I-B111— Quarterly item dep_amort_acum is year-to-date D&A: in Q1 it is the stand-alone D&A; in Q2 and Q3 it comes from the DFC_MI_ACUM lines with the same D&A rule (leaves only, no debt-cost amortization) and that origin; without year-to-date lines it is null, never derived from stand-alone quarters; the annual series does not get the item.I-B114— The rule that decides whether a line of the indirect cash flow statement (block 6.01) is added to the D&A used for EBITDA matches a GROUND-TRUTH file of real CVM labels in both directions: misspelled and abbreviated depreciation/amortization labels now count, goodwill, fair-value step-up, depletion, right-of-use and mixed D&A-plus-impairment lines keep counting, and amortized-cost financial instruments, loan interest and charges, funding costs, instalment taxes, deferred revenue and deferred tax, PIS/COFINS credits on depreciation, lines mixed with equity-method results and lines in 6.01.02 that do not name an asset are left out. Every label that changes any company-year or quarter in the base has a line in the file (pilot P2, 07/11/2026: the JEV model screened 886 candidate labels for US$ 0.02 and every disagreement was decided by hand).I-B115— The annual D&A item and the quarterly stand-alone and year-to-date items all apply the same single rule (misspelling counted, amortized-cost line in 6.01.02 out, PIS/COFINS credit and loan-interest lines out, mixed impairment line kept), parent and child never count twice, and the versions archive keeps exactly the same lines.I-B113— Bank ROE is profit to controlling shareholders over average controlling shareholders' equity (equity minus non-controlling interests), with no flag; a parent-only bank without the line uses its equity with no flag; a consolidated bank without the line at either end of the average keeps total equity with flag pl_consolidado_inclui_minoritarios; non-controlling interests reported as zero count as zero.I-N650— The calculation note for ROE and debt rebuilds the published figure from the facts and shows profit, both equity figures, the average, the CVM accounts, the base date and the DFP filing date; if the rebuild does not match the published number, the components are not shown (only the formula); a bank with the non-controlling interests line uses controlling shareholders' equity and says this is the correction of 2026-11-06; a consolidated bank without the line says its equity includes them; latest-quarter net debt (ITR) uses the same accounts and, without the 12-month pieces, says the 12-month ratio is not shown; the text says what the number is not; the module writes no SQL.I-N660— Net debt/12-month EBITDA to the end of the latest quarter: 12-month EBIT and D&A by the TTM identity (year-to-date D&A from the ITR), every piece with its account in the text (PT and EN); shown only with all pieces, on one basis, without scale marks, with prior-year pieces matching the comparative column of the current ITR and positive 12-month EBITDA; otherwise the text says why and points to the closed-year ratio.I-N670— A company whose EBITDA changed with the 2026-11-07 review of the D&A rule carries the note "Correction on 2026-11-07" in the debt block of the stock page, the debt answer page and its markdown, and in the indicators JSON, in Portuguese and English: what changed in the rule, the years, the labels that left or entered and the EBITDA of the last affected year before and after; a company outside the frozen table has no note (real base: UGPA3 has it, WEGE3 does not).I-N671— The frozen table of companies affected by the D&A rule review holds the 41 companies and 124 company-years measured on 2026-11-07; each entry has its last year among the affected years and a different EBITDA before and after, and every label it cites is in the rule ground truth with the right direction (removed labels are out, added labels are in).I-N672— A year whose published D&A includes a mixed depreciation-and-impairment line gets the notice "D&A for this year includes a line that combines depreciation and an impairment loss, as the company filed it" in Portuguese and English: a dagger on that year EBITDA in the "In reais" table with the sentence and the account, the sentence in the debt block when the year matches, and aviso_da_impairment in the indicators JSON; no notice without the line, the figure does not change and the indicator gets no flag (real base: ABEV3 has it, WEGE3 does not).I-N680— The "Updated" time and the FAQPage dateModified come from the database meta table: the latest of construido_em (end of the build) and diario_ok_em (last daily update that stored prices), in Brasília time (UTC-3); a run with nothing new does not count; without those keys or without the meta table there is no time (the line shows only the source, never an invented time); the module reads only meta.I-N681— Fund, stock, ETF and list pages: with complete data they get 5 to 8 questions, each answer with the figure in the page's own format, and the number strip carries the base date of each figure; when a datum is missing its question disappears (no price means no quote and no current P/BV; a flagged yield is left out; no monthly report means no unitholder count); with nothing, no question, no strip and no new title. No advice terms.RO14— The fund page shows two labelled cards, P/BV (monthly report) (report-day price over the report's NAV per share) and P/BV today (last close over the latest report's NAV per share), each with base date and calculation, both equal to the calculation done in the test, with definitions below; in English the labels are P/BV (monthly report) and P/BV today; no formula changes.DI01— DI future calculator: the PU matches hand-computed cases (10 percent with 252 and 126 business days, DI1F28 at 13.50 percent on 2026-10-30 with 293 business days and PU 86309.06, basis point value R$ 8.84) and the JSON route returns the same figures, contract code and conventions.DI02— Business days count the reference date and not the maturity and skip holidays generated by rule: Carnival and Corpus Christi 2027, Easter dates, Nov 20 only from 2024, Dec 24 and Dec 31 as business days, a Saturday reference starting on Monday; every accepted maturity format gives the first business day of the month.DI03— Gain or loss sign: long the rate gains when the rate rises and loses when it falls, short the rate is the mirror with the same size; 13.50 to 13.20 percent on DI1F28 loses R$ 266.01 per contract when long, the total multiplies before rounding and the page says gain or loss and explains the rule.JC01— Compound interest calculator: hand-checked cases (R$ 1,000 at 12 percent a year for 1 year with no contribution gives R$ 1,120.00; R$ 100 a month only gives R$ 1,264.65; R$ 1,000 plus R$ 100 a month for 10 years gives R$ 25,298.85; monthly rate 0.948879 percent; zero rate returns the amount invested), the yearly table closes on the final value, Brazilian thousand separators are read, and the JSON route returns the same figures with the formula.JC02— Regressive income tax table (22.5, 20, 17.5 and 15 percent by days held) applied to each contribution with its own term: R$ 1,000 at 12 percent for 1 year pays R$ 21.00 and nets R$ 1,099.00; contributions only pay R$ 13.29; the value in todays money divides by inflation (R$ 1,071.77 at 4.5 percent); the page labels the tax as simplified.JC03— With the macro table filled, the Selic shortcut uses the latest Copom target (15 percent, not the older target nor the effective rate) and IPCA plus X compounds the latest 12-month IPCA with the real rate; typed inflation takes precedence; the comparison shows Selic, inflation only and the simulation rate on the same money; the page states each source and date and that the CDI is not published.I-X26— On the real database, PTAX ask is never below bid for dollar and euro, every value is in the plausible range, the 12-month IPCA equals the product of the 12 published months within 0.02 points, and IBGE monthly IPCA matches the Central Bank series 433 within 0.005 points.PS01— Spreadsheet route: one request with stock, REIT and ETF tickers and several fields returns one row per ticker in the same order, a blank row where the cell was blank, and each figure equals what the site already publishes (latest-year ROE from the indicators route, the page P/E and P/B cards, the latest EV/EBITDA and EPS from the multiples route, the stock DY and 12-month payouts, the REIT P/B and DY of the fund page).PS03— Spreadsheet route: every missing value comes with a short reason (a REIT has no P/E, an ETF has no ROE, not applicable to a bank, ticker not found and logged on the dashboard, index out of scope, statement not out yet on that date, today only for DY with a date, the right field for macro codes), never a blank cell, each reason within 60 characters.PS08— Spreadsheet route with a date: the statement year is the latest whose annual filing the CVM had received on that date, ROE is that year value and P/E is the trailing figure of the screener point-in-time rule, so 2019-06-30 uses 2018 and 2015-06-30 uses 2014.PS09— Spreadsheet macro codes on a database built in the test: latest period without a date, latest period up to the date for daily, monthly and quarterly series, percent as a fraction, the dollar in reais, the date field returning the period, and a short reason for a missing table, an empty series or a date before the first point.MT01— The dated stock page (?em=) shows the notice You are viewing the date with a back to today link, the filing in force on that date with version, CVM source account and original document link, and ROE, margin and net debt over EBITDA of the latest annual filing already received.MT02— The filing shown is the one with the latest receipt date on or before the chosen date and never a newer one: the day before a quarterly filing arrives the previous annual filing applies, on the exact day the quarterly one does, and the figures equal the point-in-time facts function.MT03— The then versus today block appears only when the filing in force was re-submitted or its figure was republished later, and the indicator note says when it was computed on a newer version; a filing never re-submitted shows no block.MT04— At the edge of a re-submission the day before the new version arrives still shows the old version with the then versus today block, and on the day it arrives the new version applies with no block.MT05— Nothing that depends on price appears on the dated page (no P/E in a card, no P/B, EV over EBITDA, market value, price, dividend yield or price chart), in Portuguese and English; one short sentence says the price of the day will enter once the quotation licence is resolved.TD04— The rates route returns every bond of the latest base date with source, ODbL licence and a citation line, filters one family, returns the daily series of one bond cut by from and to dates, falls back to the last base date until a given date with a notice, gives CSV with a fixed header, and answers 400 for a bad parameter, 404 for an unknown bond or no data.TD06— The Tesouro Direto page in Portuguese and English opens with the base date sentence for Selic, fixed rate and IPCA+ bonds, the Selic target from the Macro section, two server side SVG yield curves (nominal fixed rate and real IPCA+ rate, never on the same scale) with one point per maturity and a title on each, no browser script other than JSON-LD, the table of each bond, the nominal versus real explanation, the ODbL licence, the citation line, the canonical link, a JSON-LD dataset with the licence of the source, and the notice that it is not investment advice.TD08— The health route carries the Tesouro block (latest base date, age, 7 day limit, last collection) and raises attention only past the limit, not at it; the Macro hub in both languages and the API hub carry the page and route cards; the sitemap announces the page with the base date as last modified and never in the future; llms.txt cites page, route and licence; the methodology pages exist in both languages with the licence and the source.TD09— The Tesouro Direto connector tool calls the same route as the page: with no argument it returns the latest base date with source and licence, with a bond and a maturity it returns the daily series, and a bad request comes back as an error field and never as an exception.TL06— The ranking uses the consolidated month (the one before the newest): TOTAL first, then Vivo 85,818,766 (38.85% of 220,921,381; +1.60% against 84,463,402 a year earlier), Claro, TIM (-2.60%), Surf; the preliminary month comes apart and is not in the calculation; broadband falls in September (as in the official total) so the ranking stays in August; the monthly series flags the newest month as preliminary.TL07— The link to a stock exists only when the full tax ID of the operator is the one of a company with a ticker: Telefonica Brasil links to VIVT3 from the register, TIM S.A. links to TIMS3 from the main row of the ticker table, Claro never links, and a company with the same name, the same 8 digit root (another branch) or no ticker never links; with no register row nothing is invented.TL08— The accesses route returns the ranking of both services with source, the CC BY-ND 3.0 licence (never CC BY), the use arranged with Anatel, the required attribution and a citation line, with no municipality, technology or product rows; with a service and a tax ID (with or without punctuation, or TOTAL) it returns the monthly series cut by from and to months; CSV has a fixed header; bad parameters give 400 and an unknown operator 404.TL10— The telecom page in Portuguese and English opens with the sentence of totals and leaders, the attribution to Anatel at the top, the two tables with the Vivo line (85,818,766, 38.85%, +1.60%), the preliminary month apart, the notice that M2M and service points are left out, that the sums, the TOTAL, the share and the change are our calculations and that an operator is not the stock, the source with both files, the CC BY-ND 3.0 licence, the use arranged with Anatel, the citation line, the canonical link and a JSON-LD dataset; only Vivo and TIM link to a stock and only with the identical tax ID; no browser script other than JSON-LD.TL11— Without a register row with a ticker and an identical tax ID the page links to no stock at all, neither by the name Vivo or TIM nor by the group.TL12— The health route carries the telecom block (newest month, age per service, 75 day limit, last collection) and raises attention only past the limit, not at it; the Macro hub in both languages and the API hub carry the page and route cards; the sitemap announces the page with the first day of the newest month as last modified and never in the future; llms.txt cites page, route, licence and the agreed use; the methodology pages exist in both languages with the source, the CC BY-ND 3.0 licence (never CC BY), the agreed use and the attribution.TL13— The telecom connector tool calls the same route as the page: with no argument it returns the ranking with source, licence and attribution, with a service and a tax ID it returns the monthly series, and a bad request comes back as an error field and never as an exception.EL04— The latest tariff per distributor joins consecutive pieces with the same value (ANEEL splits a period on 31 Dec and 1 Jan), so CEMIG-D shows 903.29 (593.08 + 310.21), +5.21% against 858.58, never a zero change; a distributor whose last tariff expired in 2016 is marked not in force and stays out of the minimum, median and maximum (768.02, 891.92 and 954.39 for the four in force); one day before an adjustment the previous tariff holds.EL05— The link to a stock exists only when the full tax ID of the distributor is the one of a company with a ticker: a company with the same name, the same 8 digit root (another branch) or no ticker never links.EL06— The tariffs route returns the latest tariff of the six distributors of the excerpt with the summary, source, ODbL licence and a citation line, returns every period of one distributor by tax ID (with or without punctuation) cut by from and to dates, gives CSV with a fixed header, and answers 400 for a bad parameter or a from date without a tax ID, and 404 for a tax ID with no data.EL08— The electricity tariff page in Portuguese and English opens with the sentence of the range (768.02 to 954.39 per MWh, median 891.92), the four cards, the table with the CEMIG-D line and the period in readable dates, the distributor whose tariff expired in a separate section outside the ranking, the notice that taxes are not included and that the total and the change are our calculations, the notice that a distributor is not the stock, the ODbL licence, the citation line, the canonical link, a JSON-LD dataset with the licence of the source, no browser script other than JSON-LD, and no stock link unless the tax ID is identical.EL09— The health route carries the energy block (newest period start, age, 75 day limit, last collection) and raises attention only past the limit, not at it; the Macro hub in both languages and the API hub carry the page and route cards; the sitemap announces the page with the newest period start as last modified and never in the future; llms.txt cites page, route and licence; the methodology pages exist in both languages with the licence and the source.EL10— The electricity tariffs connector tool calls the same route as the page: with no argument it returns the latest tariffs with source and licence, with a tax ID it returns the periods, and a bad request comes back as an error field and never as an exception.FD03— The reference month of the fund rankings is the newest closed month with at least 85 percent of the funds of the fullest month, never the partial current month; the rankings order by net assets, largest net inflow and largest net outflow, and the net inflow is inflows minus redemptions.FD04— The series of one fund runs from the oldest to the newest month with the monthly unit change only between consecutive months that have a unit value; a fund with several subclasses has no change; the search finds a name without accents or case and a fund by the start of its tax ID, largest net assets first.I-NA01— Only on-market purchases and sales of own shares of the company and units enter the sums by type of person (balances, subscriptions, forward trades, options, parent and subsidiary papers and companies outside the universe are left out); only the latest version of each restated filing counts; trades without a reported value count in shares and are tallied separately from reais.I-NA02— The database keeps only sums per company, month and type of person: no column for a name, the free-text description, the broker or the trade date, and the free text of the CVM line never appears in the stored file or in the JSON.I-NA03— The collector asks the CVM for last year and the current year with a pause between requests and an identified robot, respects the minimum interval, sends the ETag back and treats 304 as no change, replaces the year on a restated file, and keeps what is stored when the source is silent or the zip is unreadable.I-NA04— The 12-month series of a company ends at the latest month reported and gives the four types of person per month with net reais and shares and the number of trades; the ranking of the latest month lists the largest net purchase and the largest net sale in reais and counts the companies that filed.I-NA05— The /administradores page (Portuguese and English) has no JavaScript, lists the largest net purchases and sales of the latest month with links to the stock page, cites the CVM dataset and the ODbL licence, says the data is by type of person and not a recommendation; the JSON routes carry source, licence, notice and citation, and an unknown ticker returns 404.I-NA06— The stock page gains the insider trading block (month by type of person for the last 12 months, source, licence, links to the page and the JSON) only when the company filed in the window; the page is in the sitemap and the facts page list, and the collector only fires with NEGOCIACAO_ADMIN_LIGADO=1 outside the web process.CF03— The change in a fund position is measured in number of shares against the previous month: increased, reduced, unchanged (under 0.5 percent), new or sold out; the list is ordered by market value and the counters cover every fund even when the list is cut.CF04— A latest month with fewer than 85 percent of the funds of the previous month is not used as reference, so a file still open to filings does not make every fund look like a seller; with a single month in the database the change is empty and not zero.FO08— The latest survey gives, per indicator and reference year, the newest median and the one from up to seven days earlier with their difference (IPCA 2026 from 5.219 to 5.1891 is minus 0.0299); a year without a previous week has no difference instead of zero.FO09— How accurate the market was: for a closed year the expectation is the last survey of the previous December (not January, not an earlier December day) against the realised IPCA of December and the Selic target on 31 December from the macro series; the error is realised minus expectation, years without data are left out and nothing is estimated.FO11— The Focus page in Portuguese and English shows the headline sentence, the table of medians with the difference from the previous week, evolution and accuracy charts as server-side SVG with titles and no script, the accuracy table flagged as our subtraction, the ODbL source, JSON and CSV links, the citation line, JSON-LD with the source licence and the not-investment-advice note.PV02— On stock pages the preview image shows the last close, the 1-month change (green when up, red when down), ROE and last-year net income, plus the company logo (owner's decision, 01/11/2026); a share class that is not the one in the price series gets no price or change; dividend yield, P/E and index values stay out; other sections show no number.PV03— On stock pages the preview image title comes from the database (TICKER — NAME) when the page h1 was not remembered by the process serving the image, so the card never shows only the section name.DL03— An issuer claimed by two companies through their declared codes is given to neither (no last-wins, no dictionary-order owner); an unambiguous issuer gives its company every share ticker it ever traded under.DL04— End to end on synthetic files: a delisted company gets the code it declared plus the old tickers of the same ISIN issuer, prices stop at each ticker's last session, active companies and their other classes never enter, an unclaimed issuer becomes a gap, an inconsistent OHLC row is kept but flagged, and the old snapshot is rebuilt.DL05— CVM Reference Form splits and reverse splits enter for delisted companies only, once per event (resubmissions do not duplicate), with the declared ratio and the sem_data_ex flag: the form has the approval date, not the ex-date, so it cannot adjust prices.DL08— Delisted coverage is counted from the tables (companies, linked tickers, sessions, declared events, zero dividends because no source exists, gap volume); a database without them reads not built, never zero; the gap table carries class lacuna.DL23— The FRE amount of a company that left is published in reais: when the company typed it in thousands (checked against the DFP net income, which states its scale, or against its own total), it is multiplied by 1,000 and flagged; with nothing to check against it stays as declared.DL40— Each yearly page of companies that left the exchange (/saiu-da-bolsa/{year}) opens in Portuguese and English with the synonyms in title, heading and description (delisting, tender offer, going private, registration cancellation); the count in the direct answer equals the rows and names of the list, the cancellation count equals the second table, 2026 matches the year table of /saiu-da-bolsa, the visible FAQ equals the FAQPage JSON-LD, and the page has the method, primary sources, dividend coverage, its own canonical and the hreflang trio; the sitemap and the main list link every year; a year without a page is 404.DL42— /acoes/{ticker} of a company that left the exchange redirects (301) to its own page, which answers 200; curated ticker changes go to the new ticker; a different successor company stays 404; active and same-company changes are unchanged. The PETZ3 page shows the last per-share dividend (R$ 0.2883, record date 2024-11-13, paid 2024-11-29) equal to the database row, with the CVM filing link, and links AUAU3 as the ticker trading afterwards, with dates checked in the database (consecutive sessions).AN06— Reads sum per operator and field and compute the share and the 12-month change against the same month of the previous year (March 2022 is not it, so no change; a December 2022 row makes it appear); an operator whose name changed has no change; the series of a field ignores case and accents; the link to a stock exists only when the full tax ID is the one of a company with a ticker: a company without ticker, an SPE with its own tax ID or a branch with the same root never links.AN07— The production route returns the operators of the latest month with source, CC BY-ND 3.0 licence (not CC BY), use agreement, attribution and a citation line; fields with nivel=campo; the monthly series by tax ID (with or without punctuation), operator or field cut by from and to months; CSV with a fixed header per level; 400 for a bad parameter, two filters, a month with a series or from/to without a filter, and 404 for a request with no data.AN09— The oil and gas production page in Portuguese and English opens with the sentence of the latest month, the notice that ANP's monthly files stop there (CAPTCHA from 2024), the four cards, the operators and fields tables with Petrobras and Tupi, how to read each column, the licence stated as NoDerivatives and not CC BY, the use agreement, the attribution, the citation line, the canonical link, a JSON-LD dataset with the licence of the source, no browser script other than JSON-LD and no stock link unless the tax ID is identical; the notice disappears when the latest month is recent.AN10— The health route carries the anp block (latest month in the database and in the source, months since the last, last collection) and shows a source stopped in the same month as information without attention, raising attention only when the source has a newer month than the database; the Macro hub in both languages and the API hub carry the page and route cards; the sitemap announces the page with the last day of the month as last modified and never in the future; llms.txt cites page, route, licence and agreement; the methodology pages exist in both languages with the licence, the agreement, the source and the CAPTCHA.AN11— The production connector tool calls the same route as the page: with no argument it returns the operators of the latest month with source, licence and agreement, with a field it returns the series, and a bad request comes back as an error field and never as an exception.AN13— The manual exports folder of the public query (one CSV per month and environment) is read by the same parser: a month with Mar and Terra is complete, one with only Mar is labelled offshore only; Mar and Terra of the same month do not double; without the pre-salt file the pre-salt share is NULL, never zero, and a later pre-salt file becomes a column of Mar only if it is a subset; two files of the same month and environment count once and different contents warn; a month the official file already has stays official with a warning on divergence above 0.5%; no 12-month change is computed against a month of different scope; the page shows the latest loaded month, says 2024 onward is a manual export with partial coverage, lists the months without Terra and a coverage table by month and environment; the route carries source and scope and the manual coverage.FA03— Every fact in the Dados B3 column comes from code or database: with swapped constants the cell changes; the served page shows the API free and Pro limits, the MCP registry tool count, the indicators with lineage, the delisted counts from delistadas.cobertura and the date of meta.construido_em.FA04— Every third-party cell carries the https URL of its proof; an unverified one says not verified on 2026-11-06 (PT: nao verificado em 06/11/2026). The page uses no value adjective and recommends no source.FA05— The old phrases (only N can be tied, every Sunday, none of them is in the base, whoever left is not here, no key needed for WEGE3) are gone from the pages and llms.txt; the new delisted-coverage sentence, with criterion and date, appears on /saiu-da-bolsa, /o-que-nao-resolvemos, llms.txt and /fontes-abertas with the table counts.FA06— The update frequency is the code's (the nightly run fetches dividends by default; statements only at the rebuild) and appears identically on the home, the methodology page, /saude, llms.txt and /fontes-abertas, with the meta.construido_em date.LS03— Each row of the P/E-below-10 list carries the flag computed by lucro_atipico.do_exercicio for the EPS year; the HTML shows the earnings-flag column and the JSON carries boolean and text.LS04— /empresas/{T}/multiplos carries aviso_lucro_atipico (fiscal year and 12 months, boolean) and the valuation fields; when flagged, the text, the quarter and the source (document and CVM filing date); /empresas/{T}/indicadores lists the flagged years.LS06— On a flagged stock's page, the Today block shows the flag, the quarter and the source (ITR/DFP and the link to the quarters table), in PT and EN.RK03— /empresas/{T}/dividendos: each year's total equals dividends + JCP + other, without the return of capital, which comes in restituicao_capital; restituicoes_de_capital lists them all; restituicao_capital_12m holds the last 12 months; the 12-month DY does not add them.RK04— The top-payers list adds only profit distributions (each row's DY matches the sum without the return of capital); the 12-month return of capital is in its own column, with the record date, in the HTML and the JSON.RK05— The company page (yearly table) and /acoes/{T}/dividendos show the return-of-capital column outside the total, in Portuguese and English.RK06— The company page's 12-month DY leaves out the return of capital and lists it separately with amount and dates; the home's top yields use the same rule.
Level 3 — Economic: is the result possible in the real world? (16)
I-X08— Every published correlation carries its own n, and n is never small enough to be noise.I-X09— The SAME-month correlation is the largest of them all.H-B05— Total assets never negative, never above R$ 100 trillion (catches a MIL currency scale applied twice).H-B08— |ROIC| > 200% without the `roic_extremo` flag is a defect until proven otherwise.H-B23— Same pattern as H-B08/H-B17: a payout above 5× the ex-date price is a historical price not adjusted for a split (common in older B3 data), not a parsing error.I-E04— No market capitalisation larger than Brazil's GDP.H-F05— Same pattern as H-B17: a P/B outside [0.2, 5.0] with no flag is a defect until proven otherwise (tiny or negative book value, corporate action).H-F06— A trailing-12m dividend yield above 30%/year is almost always an extraordinary distribution (property sale, capital amortisation booked as income at source), not a recurring yield.H-B25— A bank ROE outside [-100%, 150%] is almost always an extraction error (equity and profit swapped).H-B17— Same pattern as H-B08 (ROIC): |P/E| > 200 or |P/B| > 50 without a flag is a defect until proven otherwise.I-B31— The sector index carries the 10 macro sectors plus 'Market (B3)', each with a full series (same number of weeks), positive levels and base 100 at the start.I-B33— No week of any sector moves more than a stock market can move in a week.H-B14— Q1+Q2+Q3 standalone revenue <= annual revenue (2% tolerance) for the VAST majority of companies — the residual (implied Q4) must be non-negative.I-P07— The B3 average price falls OUTSIDE [low, high] in ~0.1% of rows — and this test exists to record that, not to fail on it.LS01— The atypical-earnings rule flags the ALLD3 Q3 2025 case and a sharp drop, and does not flag a strong seasonal quarter, a negative median or a missing quarter; the flag text is the agreed one in PT and EN.LS05— Among the liquid stocks of the lists, at most one in four is flagged for the EPS fiscal year (measured on 2026-11-06: 17 of 158).
Other product tests (131)
Access gate, navigation and the MCP connector — they test whether the product works, not whether a number is right, so they don't fit the 3 levels above by design. Still counted, still listed:
MED01— Every tool on the MCP connector goes through the counting point. The attack is to reintroduce a bare @mcp.tool() decorator on some tool: it would work perfectly and stay silent in the panel, which is the failure mode this project exists to avoid, because the number simply gets smaller and nothing warns you. The instrumentation is a single choke point rather than a line remembered in seventeen places, since this codebase has already paid three times for rules that depend on somebody remembering to apply them.MED02— Calling an MCP tool actually records the call. A decorator that exists but records nothing is worse than no decorator at all, because the panel then reports a confident zero, so this test calls a real tool and then looks for the row in the database. It records the tool name and nothing else: no arguments, no ticker, no API key, no IP address.MED03— A click in our own funnel is not counted as a visitor. Funnel events land in the same table with bot=0, so every checkout opened was inflating the human traffic count, and the error ran upward in a number used to decide where to invest. The first version of this test PASSED with the defect reintroduced, because the local database holds no event rows at all; an invariant that depends on the database happening to contain the case is an invariant that tests nothing on the day it does not. It now creates the rows it needs and measures the difference they make.PR01— This file's header may not lose the warning that passing the suite is not proof of truth.PR02— No indicator goes live without a methodology page.GC01— With Stripe unconfigured the product serves everything with no key — the development and UVI mode, which must not close by accident.GC02— With the gate on, WEGE3 stays open: the free taster that lets anyone check the data before deciding.GC03— Outside the taster, indicators, multiples and facts all require a key — and the refusal is a 401 on all three routes, not just the first.GC04— A non-existent key is refused. It looks obvious, and it is precisely the test that stops a gate from becoming decoration by accepting any string.GC05— The counter-proof to GC04: a valid key opens the gated ticker. Without both, the gate could be refusing everyone.GC06— The daily cap actually cuts: the call beyond quota returns 429, not a silent 200 with the counter ignored.GC07— Landing, methodology and /saude never sit behind the gate — the product's argument has to be verifiable by someone who is not a customer yet.GC08— Free plan: access to everything, but the daily cap is the free one (lower).GC09— /assinar?moeda=usd passes currency='usd' to checkout (international audience); the default stays BRL.GC10— ?lang=en serves the English landing page; an Accept-Language of en does too.GC11— The JSON dictionary states each indicator's profit base — the audit finding: net margin (total) != ROE (controlling shareholders).GC12— The screener filters by ranges and rejects invalid input (rather than ignoring it).GC13— Dividends follow the gate: WEGE3 (the free taster) opens with no key; PETR4 requires one; a valid key passes.GC14— Scores and restatements follow the gate: WEGE3 open; PETR4 requires a key.GC15— The 401 answers HTML to a browser and JSON to an agent.GC16— The 401 page comes out in English for whoever asks in English.GC17— The bulk dump is what separates the free plan from the paid one.GC18— The Pro 403 answers in the format of whoever asked.GC19— With no key, /dump must not offer the free key.GC20— A ticker that does not exist answers 404 BEFORE the gate asks for a key; HEAD answers like GET with no body; the CORS preflight on the connector endpoint accepts POST. Three findings from an agent testing production the way an AI would: a mistyped ticker got “key missing — subscribe” and the AI relayed the subscription as the fix; HEAD returned 405 on the home, robots and sitemap, which a link checker reads as a site that is down; and the connector preflight refused POST, so a browser-based MCP client could not talk to the server.GC21— The daily cap is per KEY and per plan, from limites_config.json (the environment overrides it). The count is the one in uso_api, so a restart does not give the quota back. Beyond the cap the answer is 429 with Retry-After in seconds until the day turns, and the message points to /assinar. Another key from the same client still works (it is not per IP), and a public route without a key, the free trial or the HTML page, never meets the cap. Through the MCP connector the wait goes in the body. If analytics cannot be read, the in-memory count still holds the cap.GC22— The bulk CSV download survives each block being produced by a different worker thread, and the file is complete (header plus every row). The server iterates the stream on a thread pool with no promise of the same thread; the default SQLite connection refused the second thread and the paid download died midway.I-N01— Crawls every page from the home: no internal link may answer an error, and every page must have a way back home.I-N02— Methodology aliases (URLs people GUESS, like /metodologia/pl) redirect to the canonical page — and a name that does not exist still 404s, so the alias does not become a hole that swallows everything.I-N03— Every HTML page carries a visible language switch.I-N04— The methodology — the product's central argument — exists in English.I-N07— Every configured vertical has a door, and the door speaks both languages.I-N05— Every public page declares a viewport and has a responsive rule.I-N08— The main menu, in both languages, has a door to every vertical.I-N06— A route that depends on a NEW table answers 503, never 500, while the table does not yet exist.I-N09— A table mixing two time bases must label each of them.I-N10— Every example the /screener menu offers must be accepted by /screener itself. Two of the three used to return 400 — the menu exists precisely for someone arriving without knowing how to use the endpoint, and handing them a broken example is worse than handing them nothing. The check runs against the same indicator set the filter consults at runtime, so renaming an indicator without updating the examples fails here.I-N11— The /screener menu answers callers who have NO key. The menu exists, in its own words, for 'whoever discovers the endpoint and opens it in a browser' — precisely the person who does not have a key yet, and for months it sat behind the 401. The test calls the endpoint with no key header and demands 200 with the indicator list, while also demanding that filtering stays closed — otherwise the fix would leak the paid product.I-N12— /saude publishes the version of the data being served, and it matches the database actually in use. Born from a sceptical external review: 'I want to say my backtest used Dados B3 version X and recover that dataset later'. The field existing is not enough — a label that drifts from the database is worse than none, because it grants false confidence. The test reads `meta` straight from the database, outside the route, and demands the two agree.I-N13— The restatements page is public while the per-company feed stays behind a key. This is the first fact page, and the rule behind it comes from measurement: over thirty days the AI assistants fetched the home page 209 times, the comparison page 15, the multiples methodology 14 — and the TWENTY conceptual guides added up to 7. An assistant already knows what a price-to-earnings ratio is; it fetches whoever answers what it does NOT know. Restatements are the one product dataset no competitor publishes, because the regulator overwrites the old version of a statement and we keep both. The split mirrors the rankings page: the AGGREGATE is public because it is acquisition, the per-company detail is paid because it is the product. Publishing the whole feed would give away what sustains the paid tier; hiding the aggregate would hide precisely what differentiates us.I-N14— The data-quality page lists what we do NOT publish, and why. Writing it fixed a defect before anything was published: while assembling the breakdown of the eight thousand withheld indicators, over a thousand turned up with a NULL flag, meaning they were withheld with no stated reason. On a page whose whole subject is why a figure is missing, thirteen percent of 'I do not know' is the page contradicting itself. The cause was zero revenue on the income statement, which happens in holdings whose result comes from equity income and in companies with no operations that year: the withholding was correct and the reason was simply not declared. Those became explicit flags and the silent cases fell to fifty-three. Withholding without saying why is half the house rule; the other half is saying it, and that is the half a public page enforces. The test also caps the silent cases, because without a ceiling the next rule that withholds without declaring would go unnoticed exactly as these did.I-N15— The bank chart-of-accounts page proves that an account code does not define an account. The fact it carries: one large state-owned bank reported profit under one code through 2019 and under a different one from 2020 onward, while the largest private bank stayed on the original code for the entire series. Anyone scraping the regulator with a fixed code gets the private bank right, gets the state-owned one wrong from 2020, and receives NO ERROR at all: they get an empty value, or another line's number. The stable case being the most famous bank is what makes this dangerous, because it is the one everybody tests a scraper against, and it passes. Seventeen of twenty-one institutions changed their equity account, most of them in the same year. This is not our theory: an external auditor found the mirror defect in our own output, where we declared one account and used another, and that correction became its own invariant. This page publishes the map that episode showed was missing, and the test attacks by checking the contrast survives.I-N16— The sustainability-reporting page states that the obligation was REPEALED, with the date. The idea arrived with an out-of-date premise, and that is what made it worth publishing: it came as companies being required to report from 2026 with the first reports in 2027, which was true until 29 May 2026, when a new resolution repealed the requirement outright rather than postponing it. Almost everything written in Portuguese on the subject predates that repeal and still says it is mandatory, so a page that corrects information an assistant would otherwise repeat is exactly the kind of page that gets cited. The page also declares what we do NOT have: we ingest annual and quarterly financial filings, not sustainability reports, so this is regulation rather than our own measurement. Without that admission the page would imply we measure environmental and governance data, which would be more useful to us and less true. The test attacks by removing either the word that carries the correction or the admission of the limit, and it also requires the sources to appear on the page, because a rules page without sources is an assertion.I-N17— The page listing the traps of the regulator's CSV counts them from the database rather than asserting them from memory. Every trap on that page bit us, and several were found by external auditors checking what we publish: the extra dimension in the statement of changes in equity came from one of them, who received six rows where our coordinate promised exactly one. The risk with a page like this is that it becomes a theoretical list of precautions for using public data, the kind anyone can write and nobody verifies. What distinguishes it is the COUNT, and the count has to come from the database: written by hand it goes stale in silence, which is precisely the defect another invariant exists to catch. The test attacks by hardcoding a figure into the text instead of querying for it, and it also checks the thousands separator matches the language, since a Brazilian separator reads as a decimal point in English.I-N18— The look-ahead page measures rather than asserts, and it separates a filing delay from a restatement. The theory of look-ahead bias is everywhere; the Brazilian NUMBER is not — how many days pass between a fiscal year ending and its statements existing at the regulator. It is about a hundred days on average, and a hundred and sixty-one filings took more than a year, which is the figure anyone calibrating a lag actually needs. The extreme case requires a caveat the page makes explicitly: the largest gap in the base is a bank showing an eight-year interval, and that is not a delay in the original filing, it is a restatement being republished years later. Selling that as a filing that took eight years to appear would be technically true and materially misleading, the same error nearly made when ranking restatements by percentage. The page also states what lagging the filing does NOT fix — survivorship, historical universe, delisting — because a correction that implies more coverage than it delivers is its own kind of look-ahead.I-N19— Someone arriving without a key gets the PAGE, whatever their Accept header says. This was a discovery defect found in production: the first version negotiated on an explicit text/html header, so a browser got the page while the wildcard header - the default for curl and for several automated fetchers - got a 401, as did a request with no Accept header at all. The page is listed in the sitemap and in the file we publish for AI agents, so anyone arriving through either got a closed door on the most differentiated page we have, and a 401 reads as nothing-here rather than wrong-format. This is the third time in the same family: the connector endpoint answering 406 in a browser, the audit route missing from the AI index, and now this one - the pattern is always a door that only opens for someone who already knows how to knock. The correct rule is not the Accept header but the API KEY: whoever sends a key wants the feed and existing integrations must not break, while whoever sends none has no access to the JSON anyway, so answering 401 merely hides public content.MCP01— The connector advertises EXACTLY the 14 expected tools — not one fewer (breaks clients) nor one more (drops out of the catalogue).MCP02— Through the connector the taster holds too: WEGE3 answers with ROIC and no key at all.MCP03— A gated ticker does not hand the AI a raw error: it returns an error WITH the way to resolve it, because the reader is an agent that needs to know what to tell the user.MCP04— A key passed as a tool argument opens the gated ticker — this is how an AI client authenticates, without an HTTP header.MCP05— The methodology is free through the connector too: the AI can explain HOW the number is computed without anyone paying for it.MCP06— MXRF11 is the REIT vertical's free taster: it opens with no key, just as WEGE3 does for equities.MCP07— The connector answers clients that send a third-party Origin. A real defect on 22/08/2026: the SDK's anti-DNS-rebinding protection validated Origin against a list holding only our own domains and returned 403 to chatgpt.com, chat.openai.com and claude.ai — the connector we advertise on Glama, mcp.so and X was turning away exactly the clients we invite. An external review hit it; our own probe missed it because curl sends no Origin — tested as a machine, not as a client. So this test sends the header explicitly. The other half matters just as much: opening the shop door is not opening the safe, so it also verifies that a paid ticker without a key is still refused.MCP08— Opening /mcp/ in a browser returns a useful page, not a 406. THREE external reviewers in a row failed to test the connector, and this was why: the MCP endpoint requires `Accept: text/event-stream`, so a plain GET received a 406 carrying a JSON-RPC error. To a human — and to the crawler of whoever is evaluating us — that reads as a BROKEN SERVICE. And it is precisely the URL we advertise on Glama, on mcp.so and in the README. An endpoint that only proves it works to someone who already knows how to use it proves nothing to someone evaluating it.MCP09— The connector's audit tool points at the regulator's archive, not at us. It exists because two of three external auditors had browsing disabled and could not reach a sample by URL at all; the connector is the door that does not need a browser. But it only counts if it sends the auditor to the primary source: an audit tool that points back at the product being audited is the number agreeing with itself. The test also enforces the two rules that keep a result from inflating on its own, that the auditor picks their own seed and that unchecked never becomes passed.I-TR01— Every test whose ID belongs to the data-invariant family (any "I-"/"H-" that is not navigation) must be classified in `_NIVEL_DO_TESTE`.I-TR02— The total collected by introspection is EXACTLY the number of tests `pytest --collect-only` sees in the same modules — not one more.I-TR03— No test disappears from the collection for lack of a docstring or ID — the explicit requirement is 'never vanish silently'.I-TR04— The featured-case lists may only cite IDs the real suite still has — a dangling case (a test renamed or removed) is dead history that should not keep being told.I-TR05— Every test in the suite has a description in Portuguese AND in English — the double gate exists because requiring only the translation would let an empty description come back, translated as empty.MED04— The build alarm lights up on the fact, not on the instant. It went silent through seven consecutive failures in production because it keyed on the state being 'failed', while the retry loop rewrites the state to 'running' at the start of every new attempt: the alarm switched off for the whole duration of each build, and a build retrying hourly is 'running' most of the time. The health endpoint was answering with seven failures, a timeout error and a null alert on the same line while production served two-day-old data. An alarm that blinks is not an alarm.MED05— If the database being served is not the version the code promises, the health endpoint says so. This is the ground truth that depends on no bookkeeping at all, and bookkeeping is exactly what failed: while the build was breaking, production served the previous version for two days with the correction already written, tested and committed, and nothing anywhere said so because nobody compared the stamp in the database against the version in the code.MED06— 'Running' has a deadline, so a hung build cannot pass for a healthy one. Running is the only state without an alarm, which makes it the worst place for a process to die: stalled and working look identical, and the silence reads as health. The ceiling is six hours against the twenty minutes a rebuild takes, deliberately generous so a slow instance never triggers it, because an alarm that cries wolf stops being read.MED07— A page that publishes a live count also says when that count is from. The rule was already written and applied to only half the pages: the stamp helper says it exists for any page carrying a live number, and two of four had it. An external reviewer then read a cached copy of the methodology page showing one test count while the live home page showed another, and concluded a number had been hardcoded in the HTML. Nothing was hardcoded, and both pages serve the same number today: the actual defect was that the methodology page could not prove how old it was, while the transparency page, which does carry a stamp, let the same reviewer spot his own cache and not report it as a data error. A rule declared and half-applied is the failure this project exists to hunt, so it is now structural: any file with a live-count placeholder is rejected until it carries the stamp.PG01— No module that tests DATA is left outside the publication gate. The gate was trimmed for a real reason and trimming is dangerous: four consecutive rebuilds died without recording any failure, the whole service dropping at around fifty-seven minutes, and the navigation module accounted for 185 of the battery's 220 seconds, crawling every page of the site through a test client while the freshly built 1.8 GB database sat open beside it. A broken link does not corrupt data, and it was blocking correct data from going live. But the next temptation is obvious, and this test exists to block it: to keep cutting until the gate is fast and empty. A gate that rejects little is not a cheap gate, it is an ornament. The check also verifies that every module the gate names actually exists on disk, because pytest given a missing target fails, and that would break every rebuild.TR05— Every `testes/test_*.py` file is declared on the transparency page — otherwise the page announces a smaller count than the real suite.TR06— No public page announces a company or test count with a number typed into the HTML.TR07— The global metrics are IDENTICAL in Portuguese and English.TR08— A number on an EN page comes out in the reader's format, with a comma.TR09— Two tests may not carry the same identifier.TR10— The 100+ test list is filterable, and the filter never hides anything from someone without JavaScript.TR11— No analytical page takes more than 2s to build.TR12— Sweeps ALL public pages for any number that coincides with a database count without being current.TR13— No page may claim exclusivity beyond the universe /comparativo declares it compared.TR14— A claim of SUPERIORITY, not merely of exclusivity, must say who it was compared against.TR15— The traffic panel has a count that does NOT rise when it is us browsing or when the suite runs.TR16— Every public HTML page is measured — the funnel pages above all.TR17— A paid click is identifiable, and nothing beyond the campaign is stored.TR18— A refusal by plan or by quota is counted, and the analytics database NEVER stores the key in clear text — counting usage per user without holding a credential is what keeps the published 'no cookies, no IP, no third parties' promise true inside the panel too.TR19— Every expected value in the public challenge is what the API serves right now. The challenge exists for a third party to contradict us — it publishes the hard cases with the number we claim and the recipe to recompute it from CVM filings. That only holds while the claimed number is true. A challenge whose expected value has aged does not look bad on the page; it becomes marketing that looks like an audit. So divergence here BLOCKS PUBLICATION rather than rendering nicely.TR20— Each of the challenge verifiers rejects the defect it watches. The challenge is only worth something if the verifiers bite: one that passes the defect is decorative, and decorative is worse than absent because it buys trust while giving nothing. desafio/atacar.py holds the attack table — one real defect per verification type — and this test runs the whole table.TR21— The transparency page stamps the data version and the moment it was generated. Born from an external review: the reviewer read a stale INDEXED copy of the page, carrying counts from weeks earlier, and reported it as an inconsistency against /saude, which was serving the current ones. The pages were right; the copy was old. But he had no way to know: without a stamp, a cached page is indistinguishable from a live one. In a product whose thesis is auditability that is serious — the reader must not have to guess whether what they are reading is current.TR22— The challenge publishes how many TRAP TYPES it covers, not just how many cases. An external reviewer pointed out that 20 cases package fewer than 20 distinct traps — three look-ahead cases in different sectors, two absent-indicator cases for banks, one declared control — and called the '20' marketing. He is right: the repetition is deliberate and explained in each case, but a reader seeing '20 cases' understands 20 traps. A challenge that inflates its own coverage has exactly the defect it exists to hunt.TR23— The same seed returns the same cases, and without a seed nothing is drawn. Three external reviewers raised the same objection: the challenge proves we can defend cases WE chose, and says nothing about the error rate of the base. One refused to use the challenge as an independent sample, calling it circular. The random-audit endpoint takes the choice out of our hands — but only if two properties hold: determinism, so the auditor can publish a result anyone reproduces; and the seed being theirs, because a server that picks the sample could retry until a convenient one appears.TR24— The path form of the random audit returns the same sample as the query-string form. A fourth external reviewer in a row could not test the API live: their browsing tool refuses non-indexed query-string URLs, so the sample was unreachable while the whole site was up. They rigorously reported 0 of 75 cases checked, inconclusive by access limitation, rather than claiming the base passed. When four reviewers trip on the same step, the step is ours. The second door only helps if it is the same door: diverging samples for one seed would be worse than having no second door at all.TR25— Every page that shows a live count stamps the data version and the generation time. The same external reviewer, two rounds running: first they read an old indexed copy of the transparency page and reported it as an inconsistency; then they saw the home page announcing one test count while the indexed Portuguese copy still showed a much older one. They were right about what was missing: to audit anything you must know which state of the system you are auditing. A live number with no generation date ages in silence, and the stale copy becomes indistinguishable from the live page.TR26— An auditor's sample survives an update to the base. Found against our own interest: running the external reviewer's three seeds on the rebuilt base to send them a case-by-case diff, there was no diff at all — none of the 75 cases recurred. The universe had gone from 63,406 rows to 63,375 and the draw was POSITIONAL, so removing 31 rows shifts every other one. That destroyed the only property this endpoint sells: the auditor publishes a result, the base updates, and nobody can re-check what they claimed. Worse, anyone trying would see different cases and conclude the sample had been hand-picked, which is the accusation the endpoint exists to make impossible. Hashing the key instead of the position gives each case a position of its own, so an update moves only the rows it actually touched. The test simulates the update rather than describing it.TR27— The audit sample is a citable artifact, not just a dynamic page. Suggested by the external reviewer and adopted in full: hashing fixed reproducibility, but three years from now the universe will be a different size, and someone who published an audit could not PROVE a given case belonged to that sample. With the sample key and its score the proof is arithmetic and does not depend on us — anyone recomputes the score from the key and the seed. The audit id ties the four parameters that define one run into a short citable label so two different audits cannot be confused. The test also enforces machine-readable quality: prose serves a human, but an API consumer has to decide without interpreting text, so 'this financial year has a scale mismatch, do not use its absolute value' must be a boolean, not a paragraph.TR28— The audit history separates outside auditors from ourselves and keeps the errors on record. The page exists against commercial instinct: the reflex is to publish no errors found and let the findings die in the chat of whoever audited. But trust us, the base is good is worth nothing coming from whoever sells the base, and nobody who hides errors publishes a table of errors. The test closes the two obvious ways this page could turn into marketing: adding internal and external checks into one number, and recording as checked the whole sample when only part of it was actually redone. One reviewer received seventy-five cases and could redo five; writing seventy-five would have been the comfortable lie, and it was the reviewer who refused to tell it.TR29— Wherever an agreement rate is published, the statistical caveat sits in the same block. Raised by the external reviewer, and he was right: this page can produce exactly the impression of certainty it exists to fight. Publishing eighty out of eighty reads as the base has no errors. What it means is only that no divergence was found in those eighty cases, which supports no claim about the population, especially when most of the checks were run by us. The test demands adjacency, not existence: a true caveat alone in a footer is a caveat nobody reads, and serves only to defend us after someone has already misread the number. It is enforced in both languages, since the reader least able to check the rest of the site is the one reading the translation.TR30— The published audit prompt sends the auditor to the primary source, not to us. The outside advice was to find a human third party. Half of that does not hold: an auditor need not be human, it needs to reach the primary source and have no stake in the outcome — the best external finding so far, a utility's EBITDA matching to the cent against the company's own release, came from an AI reading an independent source. The half that does hold is that we cannot sit in the middle: while we write the prompt and hand it over case by case, the framing is ours, and framing is half the result. So the prompt is published, it points at the regulator's archive rather than our endpoint, it tells the auditor to pick a seed nobody suggested, and it forbids turning could not check into passed.TR31— Every seed in the published range is listed, with no gaps, and the hub that lists them is indexable and in the sitemap. A contiguous, complete range is what makes cherry-picking impossible: publishing only 'some example seeds' would hand the choice of sample back to us, which is the very vice this endpoint exists to kill, and a gap in the middle is indistinguishable from carelessness — nobody counts five hundred links by hand. The sitemap half of this test was revised in September 2026: it used to demand all five hundred sample URLs be in the sitemap, which could never work because those pages are noindex; what delivers the original intent is the indexable hub that links them all.TR32— A failing build becomes visible state, not just a log line. Real incident: a change broke one path of the build, the exception was logged, the job retried hourly, the previous database kept serving, and the site looked perfectly normal for sixteen hours. The design protected users, since nothing half-built was ever published, but it protected too well and hid the problem from us as well. The fix is not email: it is publishing the state where someone already looks. The health endpoint is polled by the daily routine and open to anyone, so a silent failure now requires someone to ignore a field that says failed. The test also demands the error itself and a plain-language line explaining what the failure means, because a bare failure count only alarms whoever already knows how to read it.TR33— The comparison page states when it was checked and admits where we lose. It had been wrong in our own favour for three weeks: the table said a competitor had no MCP connector, only a partial methodology, and a quarterly price around forty reais. In fact it now ships a native MCP connector installable in one line, publishes a methodology that cites regulator account codes, and charges essentially our price with a free tier shaped like ours. That page is the SECOND most fetched by AI assistants, which makes it the worst possible place to be wrong in our own favour, and simply re-dating it without re-checking would have preserved the error - the word verified only means something if someone verified. The test attacks the shape a marketing document takes when it pretends to be a comparison: it requires the section listing where each competitor beats us to exist, requires a concrete admission (one of them has price history going back twenty-four years further than ours), and requires the competitor's connector to keep being acknowledged.TR51— The test registry lives in one-line-per-ID files with merge=union: it is valid, every defect fails, and two branches adding different IDs merge cleanly.MED08— A funnel step taken by a robot is not counted as human intent. The event recorder hardcoded the human flag regardless of user agent, and the arithmetic closed exactly: seventy-two hits on the subscribe route, forty-five human and twenty-seven robot, against seventy-two events, proving none were filtered. Counting crawlers turned noise into the metric that most resembles truth, which is measured purchase intent.MED09— A crawler following the subscribe link does not open a payment session. Every robot that passed through created a live checkout session at the payment provider as well as a funnel event: garbage on both sides, and of the worst kind, noise shaped like intent to buy. The test also checks that the agent check happens BEFORE the session is created, since a check that runs afterward has already opened the session it was meant to prevent.MED10— The free plan is visible in the served HTML, without executing any script. The readers who do not run JavaScript are the ones that matter most here: the AI crawlers and the search engine, three thousand hits a month and the only external channel that grows. An AI summarizing the site was quoting the monthly price with no way to mention the free key. The original design's honesty is preserved and tested: with the plan switched off the blocks stay hidden and the page does not promise what does not exist. The decision only moves from the client, where it depends on running code, to the server, where the fact is known.MED11— End-to-end counterproof for the rule above: the landing page as actually served carries the free plan. It is not enough for the function to work, it has to be wired into the path that serves the page — which was precisely the original defect, where the rule existed in the client script and the served HTML did not carry it.MED12— No page hand-writes a number the database already knows. This is the gap in the stamp rule, found while checking the AI connector page: the stamp rule only binds pages that USE a placeholder, and the page that teaches an AI how to connect escaped it by using none at all, carrying a typed 400-plus companies and sixteen years when the real figures were 456 and seventeen. The damage is specific and about as bad as it gets for that particular page: an AI reading it to answer questions about the product repeated numbers SMALLER than the truth, so we were understating ourselves on the one page whose audience is precisely the reader who will not check. Sixteen occurrences across seven files, English guides included, while the template helper already said in writing that a changing number must be a placeholder and never typed into the HTML — another rule written and never enforced. What the check deliberately does NOT flag is a third party's number: the comparison table cites a competitor's coverage, and that is a fact about them, while our own row in the same table uses the placeholder. A checker that rejected the competitor's figure would only teach people to route around it.PR03— Someone who has not chosen a plan sees the plans, not a credit card form. The subscribe route was a redirect straight into a live Stripe checkout, and the plan parameter defaulted to the paid tier, so eighteen links across the site, including the nav button, threw readers from a content page into a charge they had not asked for. The funnel showed exactly what you would expect: forty-one paid checkouts opened and zero card attempts, not one declined. Nobody balked at the price; people landed on a form they never requested.PR04— The plans page shows the free tier before the price. This is not an aesthetic preference: the free door is the only one with measured conversion, nine out of thirty-one against zero out of forty-one, so a page leading with the price pushes everyone toward the door that does not convert. The check reads the body only, because the meta description names both prices and is not visual order — the test itself once failed for that reason, and the defect was in the test.AUD01— The coordinates we hand an external auditor really do lead to the line. Three of the five external audit attempts died on the same rock: the reviewer's tooling cannot open a zip archive, so they read the published cases and could never compare against the source. Serving our own extracted CSV would have been worthless, since the auditor would then be checking our number against our own extraction, so we publish coordinates instead: the exact file inside the archive and the three-column filter that isolates the row. This test opens the archive, applies the published filter, and checks that exactly one row comes back and that its raw value times its declared scale equals what we serve. A wrong coordinate does not return nothing, it returns a real row belonging to a different account, and the auditor then reports a discrepancy against a correct database, which is the worst possible outcome of an audit.AUD02— The instructions do not promise a check that the source makes impossible. The first draft said to compare the archive checksum and that it must match. Running our own instruction before publishing showed it failing: the regulator REPUBLISHES these archives, and the one for 2024 was modified two days after we read it. Any auditor downloading today would get a different checksum and conclude we had tampered with the data. Publishing a check that fails by design is worse than publishing nothing, because it carries the appearance of rigor while manufacturing a false accusation against our own database. The checksum is still published, but as a statement of which snapshot we read, never as a byte-for-byte proof.AUD03— The route states its own limit instead of leaving it implicit. Two admissions have to survive any rewrite: the currency-scale conversion is OURS, and it is the one step in the chain the auditor cannot see; and anyone whose tooling can open the archive should read the original file rather than trust our coordinates. Without the second, a convenience for limited tooling quietly becomes an invitation to audit our own copy, which is the circularity this whole effort exists to avoid.AUD04— End-to-end check on the route external auditors actually use, which builds its coordinates by a different code path than the lineage does, so one passing does not imply the other. It found a real defect on its first run: the statement of changes in equity carries an extra dimension, so the three-column filter isolates one row in the balance sheet and income statement but returned SIX there, one per equity column. We would have published an ambiguous coordinate for every item sourced from it, the auditor would have picked one of six in the dark, found a number that genuinely exists, and reported a discrepancy against a correct database.AUD05— The methodology says what happens WHEN the reporting basis changes. I claimed the rule was undocumented and I was wrong: I searched the HTML shell and a content directory that does not exist, while the text lives in the sources-and-standardization page and has been live all along. Concluding absence from my own bad search is the same mistake an external reviewer made with a cached page on the same day. What was genuinely missing is the CONSEQUENCE at the boundary: that comparing two years there compares two different consolidation perimeters. This test now requires the rule, the flag name, and the concrete case to all appear in both languages, because an abstract rule with no example is exactly what let this boundary go unnoticed for months.AUD06— A comparison that straddles a change of reporting basis is marked as such. One company published a 268 percent revenue increase because its consolidated figure for one year was compared against its individual figure for the year before; on a consistent basis revenue had actually fallen by a quarter, and the number carried no flag at all. The instructive part is why the existing protection missed it: the routine that picks the restated comparative denominator deliberately matches on the aggregation level, but that only guarantees the DENOMINATOR is internally consistent and never compares the denominator's basis against the NUMERATOR's. The guard was correct and in the wrong place. We mark rather than recompute, because recomputing on the individual basis would discard the consolidated statements, which are the right basis for the year they exist in.AUD07— A browser gets a page; an agent that asks for JSON gets JSON. This is the fourth external audit attempt stopped by the same kind of door: the reviewer read the 47 KB home page and could not read the audit sample route at all. The route answered correctly in under two seconds — the problem was that it was 142 KB of raw JSON, a format browsing tools truncate or refuse. Half that weight was my own defect from the same day: the coordinate block repeated all twenty-odd accounts of the company for an indicator that consumes three, and a coordinate for an account the formula never touches does not help anyone audit, it just pushes what matters past the tool's reading limit. Trimming brought it to 60 KB; serving HTML closes it. Same remedy as the connector endpoint that used to answer 406 in a browser: someone arriving by browser is not speaking the protocol, they are trying to READ.AUD08— The audit sample route is named in llms.txt and the home page links to the audit protocol. The fifth external attempt produced the sharpest diagnosis yet: the reviewer picked its own seed, composed the URL, and its own reading tool REFUSED it, because that tool only opens URLs that already appeared in the conversation or in a search result, and no search engine had indexed the domain, so there was no path by link. Checking that turned up two defects of ours: the home page did not link to the audit protocol at all, and llms.txt — the file we publish precisely so that AI agents can find their way around — never mentioned the audit route, which lives only in the sitemap, a file reading tools do not consult. Publishing a door and hanging no sign on it is the same as having no door, for anyone who does not already know it is there. The file now also lists a concrete indexed URL, because a tool that cannot compose a URL cannot use a template.AUD09— Amortization of debt issuance cost is kept out of the depreciation figure that feeds EBITDA. This is the first data finding to come from an external auditor since the earlier successful one, and it started from a single case worth 0.34 percent: one company added a line labelled amortization of transaction cost to its depreciation and amortization, and the reviewer flagged it as a methodological caveat rather than declaring a discrepancy, because he could not open the source file to show the account. He was right, and the cause was systemic: the matcher accepted any label containing the word amortization. Measured across the base: 279 company-years affected, 226 inflated EBITDA figures, 107 of them by more than one percent and 35 by more than five, with the worst at nearly 39 percent. EBITDA is operating earnings plus depreciation and amortization OF ASSETS, and financing cost is not that, so this is a correction rather than a mark: the rule about marking instead of rewriting applies when the source is ambiguous, and a line that says cost of raising debt is not ambiguous. Half of this test guards the opposite error: amortization of the fair-value step-up from a business combination IS asset amortization and belongs in EBITDA, and the first exclusion pattern I wrote would have silently removed it from 89 lines.AUD10— The audit protocol declares the conflict of interest instead of pretending it away. The prompt used to open with a sentence saying the person asking is not the owner of the database and has no stake in the result — and the people who paste this prompt are overwhelmingly us. The fifth external auditor caught it: the audit opened with a claim of independence and the outcome arrived in the first person, ours, admitting fault and announcing the fix. His finding survives the conflict, because the account he checked sits in an audited financial statement rather than in his trust of whoever asked. But a protocol whose entire thesis is honesty cannot begin with a false sentence, and that sentence was mine, written to sound neutral. Declaring the tie is stronger than hiding it: an auditor who knows who asked calibrates his skepticism, while one who finds out afterwards discounts the whole result, and is right to.AUD11— The sample cache makes a retry instant and never serves a sample built from an older database. Six external attempts have now ended on transport rather than arithmetic: measured in production, the same route ranged from under two seconds to nearly fifty across six consecutive calls, with one exceeding a sixty-second ceiling, while locally it answers in 136 milliseconds. That is not the algorithm, it is contention on a half-CPU instance serving a 1.8 GB database from network-attached disk. Every audit tool retries the URL after a timeout, and the three that tried reported the same symptom under different names: a gateway error, a cache miss, an unavailable endpoint. Caching turns the second attempt into five milliseconds. The danger of the cache is precisely the defect it imitates: one auditor spent twelve reads looking at a frozen snapshot from the previous day and reported that the correction was not live. If WE served a stale sample after a rebuild we would be the cause of that, with the aggravation that the number would be wrong rather than merely old — so the database version is part of the cache key, and this test attacks by removing it.AUD12— For a bank, the generic list of source accounts is not presented as though it applied. The sixth external auditor found that one bank case declared one pair of account codes while the accounts actually used were a different pair entirely: declared did not match used. He classified it precisely as an audit-trail defect rather than a numerical one, and refused to turn a documentation inconsistency into a claim that the ratio was wrong — the accounts actually used are the correct banking structure. But 1,407 indicators, 2.2 percent of the base, were published that way, and the placement is the worst part: the banking chart of accounts is exactly the surface our own prompt tells auditors to attack. A field called declared that does not describe what was used looks auditable and is not. The chart varies even between banks, so swapping in another fixed list would only change whose statement is wrong; the honest output says the generic list does not apply here and points at the field that does.AUD13— An auditor can redo the ORDERING, not merely the individual scores. This is the finest objection we have received: having checked every published sample score and found them all correct, the auditor observed that this proves the key reproduces the scores shown, but not that those are the smallest scores in the whole population, because he never received the population to sort. He is right, and the distinction is subtle — checking each published score detects a fabricated key, but it cannot detect OMISSION. A case with a smaller score could have been left out and nothing in what he received would reveal it, so the claim that the sample is the smallest N rested on our word. A new route publishes the population keys, and this test does exactly what he would do: download them, score all of them, sort, and compare. Its first version had the same blind spot one level up — an attack that dropped one arbitrary line PASSED, because an arbitrary line is almost never in the top N — so the count of published keys is now checked against the table itself.AUD14— The published population declares an arithmetic that has to reconcile against a different route. The external auditor had just proved, independently, that the sample is the top of the ordering over the population file — but the SIZE of that population was still a bare assertion: the health endpoint published one indicator count and the population file delivered a smaller number of keys, with a gap of several thousand that appeared nowhere. He could verify that the sample was the top, and could not verify that the population was the population. Publishing the count of suppressed indicators closes the arithmetic across the two routes. This does not remove the dependency on us, since both numbers come from here, but it replaces an assertion with a SUM, and a sum that has to reconcile is attackable: dropping a key from the population now requires editing the health endpoint too. The attack truncates the population file without touching the other route.AUD15— The excluded depreciation lines are published, not only the ones that were summed. Without this the new rule was not falsifiable from outside: we published which accounts ENTERED the depreciation figure and nothing about which stayed out, so an auditor wanting to attack the exclusion of debt-issuance cost — a change that moved 232 EBITDA values — could see neither what was excluded nor why. The rule matches the NATURE of the label rather than the account code, since the same numeric code is transaction cost at one company and amortization of a sales stand at another, and a rule like that can only be defended by showing the label that triggered it, which means it can only be ATTACKED the same way. The auditor named three companies as targets; checking them showed the rule does not even fire at two of them, so the route exists for him to find the cases where it does fire on his own, rather than depending on the audited party to point at the battlefield.CV01— Every EBITDA definition a human read in a real indenture is one the lens still finds. The covenant lens went blind three separate times, and each time the only symptom was a smaller number: filtering debentures on a substring that the Portuguese accent broke returned zero, and requiring the word 'means' within forty characters of the term missed four real drafting styles, finding twenty-four definitions where thirty-one existed. This gate holds a set of excerpts checked by eye; if the lens stops seeing any of them the suite fails and no figure is published.CV02— Whether an EBITDA definition is discretionary or objective is what the human eye read, not what the lens claims. Each excerpt was labelled by hand before the test existed. On its first run it failed three cases, and in all three the lens was right and the hand-written label was wrong, which is exactly the service a gate is supposed to perform: catching the person who wrote it.CV03— Put the defect back and the discretionary flag has to change. Without this attack the flag could be permanently empty and the two tests above would pass by accident, which is precisely how an earlier invariant in this project was born blind, passing with the defect reintroduced because the local database happened not to contain the case.CV04— A clause saying '40% of consolidated EBITDA' is a use of the term, not a definition of it. That phrasing appears when contracts define which subsidiaries count as material. Counting it as a definition would inflate the whole base and make the published distribution describe documents that define nothing.CV05— A document that never mentions EBITDA does not produce a row claiming a definition. More than half the filings in this category are amendments that never touch the financial ratio. The computable field stays NULL rather than false: when the source does not allow a decision, no decision is made.CV06— A threshold written in a sentence is read; a threshold sitting in a table is not, and the page says so. In one indenture checked by hand, carrying five thresholds in a table, automated reading found none. This test pins the reason the page calls its threshold count a floor, and it fails on the day tables become readable, forcing the page to stop saying floor instead of repeating it out of habit.CV07— Hand-checked in October 2026: adding the EBITDA of acquired or merged companies takes the figure out of the income statement, in its own non-discretionary category; 'not allowing non-recurring adjustments' is not a non-recurring adjustment; 'non-monetary, to the extent not recurring' is discretionary; the page separates discretionary from acquisition-only.CV08— The 9 deeds whose EBITDA definition the rule missed (two-column glossary) and the JEV gatekeeper pointed out enter through the SAME rule applied to the hand-checked text: CVM 156 comes out equal to the income statement, 'before non-recurring expenses' comes out discretionary; only rows without a definition change, and they are marked definicao_conferida_a_mao.I-N340— The material-fact collector reads the CVM response (a fixture of real IPE rows) and stores only Material Facts and Notices to the Market from companies in the universe, with company, ticker, category, subject, filing date, link to the original CVM document and protocol; running again duplicates nothing (the protocol is the key) and the database itself refuses UPDATE and DELETE.I-N341— The CVM is queried at most once per minimum interval (10 minutes, counted on disk), with a conditional request (ETag) so an unchanged file downloads nothing; the attempt is written before the network call so a crash cannot cause a burst; a silent source or an unreadable zip never erases what is stored; January also reads the previous year's file.I-N342— The request to the CVM carries a User-Agent that identifies the robot and the conditional ETag header, and a network error becomes status 0, never an exception; the server only launches the collector as a separate low-priority process (off by default, one at a time) and never queries the CVM inside the web process.I-N343— The sentence is purely descriptive (who filed what, when, the subject as the company wrote it, the original link; the time only if the source gives it; no adjective, no impact reading); /fatos-relevantes (Portuguese and English) has no JavaScript, lists newest first with the original-document link on every row, says the source is weekly and the date has no time, and shows an honest empty state without the radar file.I-N344— The same /fatos-relevantes address returns JSON with Accept: application/json; the open MCP tool fatos_relevantes (no key) filters by ticker and category; both carry the citation, the source (ODbL, weekly) and the no-time warning; /fatos-relevantes.xml is a valid Atom feed with id, title, date, original CVM link and descriptive text per entry.I-N345— A company page shows the Latest material fact block only when the company has a Material Fact from the last 30 days (not a 40-day-old one, not a mere Notice to the Market, not without the radar file); the page is in the sitemap, in the dashboard list of fact pages and in the facts hub.
Reconstruct it yourself — net margin across 5 companies, 4 sectors
We don't ask you to trust the numbers. Here's the net margin of five companies in four different sectors, each rebuilt straight from its annual report (DFP) at the CVM — net income ÷ revenue, matching the published figure exactly. For ANY indicator, of any company, the whole chain down to the line in CVM's file is at /linhagem:
| Company · sector | Net income (CVM acct) | Revenue (CVM acct) | Margin =÷ |
|---|---|---|---|
| WEGE3 · industrial | R$ 6.78 bn DRE:3.11 | R$ 40.80 bn DRE:3.01 | 16.6% |
| VALE3 · mining | R$ 11.81 bn DRE:3.11 | R$ 213.59 bn DRE:3.01 | 5.5% |
| PETR4 · oil & gas | R$ 110.61 bn DRE:3.11 | R$ 497.55 bn DRE:3.01 | 22.2% |
| ITUB4 · bank | R$ 45.85 bn DRE:3.09 | R$ 387.12 bn DRE:3.01 | 11.8% |
| BBAS3 · bank | R$ 16.78 bn DRE:3.11 | R$ 319.46 bn DRE:3.01 | 5.3% |
Download any of these DFPs from the CVM, take the income and revenue accounts, divide — you get the same number. Banks use interest income as revenue (what makes sense for a bank), so the reconciliation is sector-aware; the others use sales revenue. Figures are the latest fiscal year.
Why we may differ from another site (and it's not an error)
A difference between two sites usually isn't one being wrong — it's a method choice. We disclose ours:
- Average vs ending capital: ROE and ROIC use average equity/capital (this year + last ÷ 2), not the ending balance — so they're not a naive single-year division.
- Controlling vs consolidated: we state which one each item uses.
- TTM vs annual: quarterly multiples use trailing-twelve-months earnings.
- IFRS 16, goodwill, cash, exceptional tax: each handled explicitly and flagged when it distorts.
Do the tests bite? Yes — real cases the suite has caught
A test that never fails could mean perfect data — or a weak test. These were born from real errors that slipped through, and now fail — only shown for tests that actually exist in the collected suite above:
- I-B16 · banks' P/E = 0: the CVM reports share count sometimes in units, sometimes in thousands (varying by company and year); market cap came out 1000× too small and P/E was zero. I-B16 (price × shares recomputed) caught it — we fixed 661 annual and 2,027 quarterly multiples.
- I-B24 · the vanishing profit: the profit/equity account varies by bank (Itaú 3.09, BB 3.11); if the label search fails, the number vanishes silently and ROE is born wrong. I-B24 makes that hole fail.
- H-B23 · the 800% dividend yield: old B3 dividends carry a price not adjusted for splits; without H-B23 the yield would look absurdly real. The flag keeps the record and warns.
- I-B20 · the missing COTAHIST 2023: on the first deploy one year of prices failed to download silently; the API shipped with ~50k fewer prices and the suite passed, because nothing checked coverage. We added I-B20 — a year without prices now fails the build loudly.
- I-B33 · Oil & Gas at −89.6% in one week: COTAHIST isn't split-adjusted; a 10:1 split read as a −90% weekly return and stayed in the cumulative return forever, with nothing failing (a blind amplitude filter didn't fix it either — it just flipped which direction was wrong). I-B33 (no sector week beyond ±35%, the ruler is the exchange itself) stops the build.
- I-B31 · 364 of 865 sector-index weeks frozen: extending the series back to 2010, a sector with no company carrying a market cap that week turned into a factor of 1.0 — a flat line that the cumulative return read as 'market stood still', and the 'since 2010' return came out fictional (+527%). I-B31 fails on any week repeated to the cent.
- I-E05 · EMBRAER's 2010 P/E priced with 2025 data: the point-in-time price lookup fell forward with no ceiling whenever the current ticker had no price at balance-sheet time (ticker change, share-class migration); the multiple came out priced 15 years into the future, in 22% of annual and quarterly rows, silently. The 45-day window (and I-E05) blocks it: no session inside the deadline, no multiple computed.
- I-E04 · AZUL at R$ 1.67 quadrillion market cap: the balance sheet carried 54.7 trillion shares from the judicial recovery issuance, but the price used was already on the post-reverse-split (1:150,000) basis — two ends measured on different bases, market cap wrong by orders of magnitude. I-E04 (market cap never above Brazil's GDP) fails the build on that absurdity.
How far back each block of data goes
No asterisks here: this is coverage year by year, counted right now. Indicators (ROE, ROIC, margins, growth) come from the filings alone and cover the whole series. Multiples (P/E, P/BV, EV/EBITDA) need a price and a share count — and the share count comes from CVM's reference form, which the further back you go the fewer companies filed in a usable format. The gap between the two columns is a source limit, not an ingestion hole. If you look for an old P/E and do not find it, it is because nobody has it — not because we hid it. The same numbers as JSON: /cobertura.
| Year | Companies with indicators | Companies with multiples |
|---|---|---|
| 2025 | 441 | 290 |
| 2024 | 443 | 296 |
| 2023 | 441 | 298 |
| 2022 | 428 | 295 |
| 2021 | 422 | 296 |
| 2020 | 410 | 274 |
| 2019 | 371 | 166 |
| 2018 | 326 | 167 |
| 2017 | 317 | 165 |
| 2016 | 311 | 154 |
| 2015 | 303 | 144 |
| 2014 | 301 | 140 |
| 2013 | 292 | 131 |
| 2012 | 291 | 112 |
| 2011 | 284 | 98 |
| 2010 | 277 | 34 |
Live coverage
457 companies · 884,647 price points · 8,650 recorded restatements · last refresh 2026-11-12 01:38:24. Full live counts at /saude.
Sources: CVM (open data, ODbL) and B3 (COTAHIST). Not affiliated with B3 or the CVM. Not investment advice.
Numbers on this page are live. Data version 2026-11-09-ancora-do-fre · page generated 2026-11-12 08:01 UTC. If this does not match /saude, you are reading a cached copy.