Build Rankwise

KINDAreplaces $59/mosaves $708/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local weekly citation tracker for one site across four answer engines, whose hard problems are an immutable run history, normalising citations so two spellings of one URL are one domain, and an on-page audit that gives correct advice about crawlers that do different jobs.

estimated effort weekendthe files for this build are in the project pack

RuntimeNode 22 and TypeScript, one CLI plus a server-rendered dashboard on localhostStorageSQLite with append-only run tablesEnginesThe four vendors' own APIs with their search or grounding tools enabledSchedulingA cron entry calling the CLI, with the run recorded whether it finishes or notBudgetA per-run spend ceiling checked before the fan-out starts

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the CLI and the local dashboard.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. API keypay per call, and it is the main running cost of this build

    Why Each engine is a separate account with its own billing, and the search or grounding tool has to be enabled per provider or you are measuring the model's memory rather than its citations.

    Get it Create an account and a key with each provider, then confirm the web search or grounding tool is available on your plan.

    Verify One question returns an answer with citations from each of the four

  3. decidefree to decide

    Why Questions times engines times weeks compounds quietly, and the ceiling is the only thing standing between a weekly job and a surprise invoice.

    Get it Estimate one run at current published prices, decide what a week is worth, and write the figure into the configuration.

    Verify A run that would exceed the figure stops and says so

  4. decidefree

    Why The question set is the instrument, so changing it mid-history is like moving the goalposts and then measuring the distance.

    Get it Write the questions a buyer actually types before they know your name, and commit to leaving them alone for a quarter.

    Verify The file loads and every question is answerable by a source you could plausibly be

  5. installfree, or the cost of the machine

    Why The value is the series, and a series with gaps where the laptop was shut is a series you cannot read.

    Get it A cron entry on an always-on machine, or your platform's equivalent.

    Verify A scheduled run appears in the runs table without anyone pressing anything

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- site(domain, brand_aliases_json, competitor_domains_json) · one site, because the comparison only means anything for one
- questions(id, text, added_at, retired_at) · retired rather than deleted, so old runs still make sense
- runs(id, started_at, finished_at, questions_count, engines_json, estimated_cost, actual_cost, status)
- answers(run_id, question_id, provider, model_id, search_enabled, raw_answer, latency_ms, error, created_at) · one immutable row per cell, errors included
- citations(answer_id, ordinal, raw_url, host, canonical_path, is_mine, is_competitor) · normalised once at write time
- audits(url, checked_at, findings_json) · the mechanical on-page checks, kept per run so a fix is visible as a change
- No table in this schema has an update path. A correction is a new run, because the point of the history is that nobody edited it.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/rankwise.dbRuns, answers and citations. Append-only and the whole asset.
OPENAI_API_KEYsecretrequiredsk-...redactedWith the web search tool enabled on the account.
ANTHROPIC_API_KEYsecretrequiredsk-ant-...redactedWith the web search tool enabled on the account.
GEMINI_API_KEYsecretrequiredAIza...redactedWith grounding enabled on the account.
PERPLEXITY_API_KEYsecretrequiredpplx-...redactedSearch is the default here, but record the flag anyway.
RUN_COST_CEILING_USDrequired12.00Checked before the fan-out. A run that would exceed it stops and records why.
PROVIDER_CONCURRENCYrequired3Per provider, not in total. One of them is always the slow one.
RAW_ANSWER_RETENTION_DAYSrequired400Raw answers are large. Scores and citations are kept forever regardless.

The build, in order

  1. A question set and a run that cannot be rewritten

    One run writes one immutable row per question and engine, including the failures.

    1. A trigger or a check that turns an update into a failed test rather than a lost week.

      Files src/db/schema.tstest/append-only.test.ts

    2. Providers change defaults underneath you and the comparison has to survive that.

      Files src/providers/types.ts

    3. An empty cell is information; a missing row is an unanswered question about your own data.

      Files src/run.ts

    done when · tick each as it passes
    watch out
    • Storing one current score per question and updating it each week feels tidier and destroys the only thing this tool is for, because a citation rate in week one means nothing and a trend over six months means everything. Append a row per run and never update one.
  2. Fanning out without burning the budget

    Four providers are called concurrently under a spend ceiling with visible failures.

    1. Each has its own limits and one of them is always the slow one.

      Files src/pool.ts

    2. The estimate comes from the current published prices, which you re-check, not from a constant.

      Files src/budget.tsdocs/costs.md

    3. A failed cell with an error beats a run that never ends.

      Files src/retry.tstest/retry.test.ts

    done when · tick each as it passes
    watch out
    • Retrying a rate limit immediately in a tight loop converts one throttled provider into a run that takes an hour and costs twice the estimate, and retrying a request that already produced an answer pays for the same cell twice. Back off, cap attempts, and record a cell as failed rather than retrying it forever.
  3. Two spellings of one URL are one citation

    Citations normalise to a host and a canonical path so counting means something.

    1. Normalising at read time means every report defines its own truth.

      Files src/normalise.tstest/normalise.test.ts

    2. When a count looks wrong the raw string is the only way to find out why.

      Files src/db/schema.ts

    3. Being named and being linked are different outcomes and mixing them flatters you.

      Files src/score.ts

    done when · tick each as it passes
    watch out
    • Counting citations by exact URL string splits one page into five entries because of a trailing slash, a tracking parameter, an http scheme and a www prefix, and your own site then appears to be cited a fifth as often as it is. Normalise at write time, keep the raw URL beside it, and test the five variants collapse to one.
  4. An audit that gives correct crawler advice

    The on-page audit reports mechanical facts and names the right agent for the right job.

    1. Training, search, ads and user-initiated fetching are four different decisions.

      Files src/audit/crawlers.tsdocs/crawlers.md

    2. A malformed block is worse than none and looks identical from the outside.

      Files src/audit/jsonld.ts

    3. Nobody can promise a citation, and a tool that implies it is lying politely.

      Files src/audit/report.ts

    done when · tick each as it passes
    watch out
    • Reporting 'GPTBot is blocked, so you are invisible in ChatGPT' is wrong advice, and it is the single most common mistake in this category. OpenAI documents four separate agents with different jobs: GPTBot is the model training crawler, OAI-SearchBot is what surfaces sites in ChatGPT's search features, OAI-AdsBot validates pages submitted as ads, and ChatGPT-User serves user-initiated actions where, in OpenAI's own words, robots.txt rules may not apply. Report each token separately, say what blocking it actually costs, and re-read the vendor pages before you ship, because these tokens are new and they move.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more seo & marketing.