How ANU is built and what you can change

ANU Docs

Everything you can change after you clone the repo: the YAML settings that customize the site without code, how data flows from sources to pages, and where to add something new. The settings sections below are read live from the config the site is running.

Get started locally

# one time
git clone https://github.com/Johnnyohay/anu.git && cd anu
npm ci

# refresh the data (all free, no API keys)
npm run collect        # defense news + today's brief
npm run housing        # Zillow prices, rents, forecast, inventory; Freddie Mac rates; housing news
npm run videos         # new videos and Shorts from config/videos.yaml

# apply config/*.yaml and rebuild the JSON the pages read
npm run build:api

# look at it
npx serve public       # open http://localhost:3000 (Docs tab: /docs.html)
npm run app            # Ask ANU with a free local model (see README)

Push to main and the GitHub Action does the same every morning at 10:00 UTC and publishes the site. A push that only changes config/ or public/ redeploys within a few minutes.

Architecture

Collectors pull free public sources into data/ (committed, so history builds up). build-api checks your YAML, adds your custom attributes, and writes static JSON to public/api/v1/. Every page and Ask ANU read only that JSON. There's no server or database, and nothing costs money.

Orange-edged boxes are the parts you customize without code.

What you can change

FileWhat it controlsApply with
config/anu.yamlTabs and their order, feature flags, the housing map's starting view, budget choices, rental-math defaults, ANU score weights, custom attributesnpm run build:api (score weights and rental defaults: npm run housing first)
config/videos.yamlThe YouTube channels on the Watch tab, their topics, how long videos stay, keywords for mixed channelsnpm run videos, then build:api
config/requests.yamlYour feature wishlist, shown below with statusnpm run build:api
data/sectors.json, companies.json, ideas.json, research.json, catalysts.jsonThe defense desk's hand-written knowledge: sector briefs, company directory, build ideas, reading list, upcoming eventsnpm run build:api
src/collect.ts (FEEDS, SECTOR_RULES)Defense news sources and how stories are sorted into sectorsnpm run collect
src/housing.ts (NEWS_FEEDS, TOPIC_RULES)Housing news sources and topic tagsnpm run housing
src/prompts.tsHow Ask ANU talks, and its modes (Teach me, Housing: where to buy, Something to watch…)restart npm run app
public/desk.cssColors, fonts and spacing for the Housing, Watch and Docs tabs (light and dark)reload the page

Mistakes in a YAML file stop build:api with the file, the setting and what's wrong (for example unknown field "prise"), so a typo never reaches the live site.

Tabs

desks: in config/anu.yaml sets the tab switcher at the top of every page. Reorder the list, rename a label, or set enabled: false to hide one. Current tabs:

idLabelPageShown

Feature flags

Each flag switches one feature on or off. A flag that isn't listed counts as on. To add a flag for a new feature, add a line under flags: and read it in the page with ANU.flag(CFG, "your.flag") (see Adding features in code). Current values:

FlagStateWhat it does

Custom attributes

A custom attribute is a new metric you define in YAML as a formula over the numbers ANU already has for every metro. You don't write any page code. Each one automatically:

  • appears in the housing map's Color by menu, with its own legend;
  • is aggregated to states (median, mean, min, max, sum or count of the state's metros) to shade the map behind the dots;
  • becomes a sortable column in the rankings if column: true;
  • shows in each metro's panel, and reaches Ask ANU (in get_metro and rank_metros results as custom).
# config/anu.yaml → housing.attributes
- id: rentBurden                     # letters and digits; used in URLs and the API
  label: Rent as % of a $90k income
  formula: rent * 12 / 90000 * 100   # + - * / ( ), numbers, min() max() abs() round()
  format: percent                    # money kmoney signedMoney percent percent2 signedPercent number
  color: seq                         # seq = one hue low→high · div = two hues around zero
  aggregate: median                  # how metros roll up to a state
  column: true                       # add to the rankings table
  higherIsBetter: false              # default sort direction

Fields a formula can use: . A metro missing any field in the formula gets no value and shows gray. Formulas are read by ANU's own small calculator (src/config.ts), never run as code.

idLabelFormulaFormat · color · statesColumn

Housing defaults

Video channels

The Watch tab and the housing Shorts strip come from config/videos.yaml. To add a channel, open it on YouTube, view the page source, search for externalId (starts with UC) and add a line. Any new topic name becomes a filter on the Watch tab.

ChannelTopicChannel id

ANU embeds and links videos and never downloads them. TikTok and Instagram have no free public feeds and don't allow scraping, so they aren't collected.

Feature requests

Write what you want next in config/requests.yaml (title, area, status, notes and, if it should be switchable, the flag it will use). Then ask Claude Code in the repo: “Build the planned requests in config/requests.yaml.” Mark them done when they ship.

RequestAreaStatusNotes

Adding features in code

  1. A switchable page feature: add my.feature: true under flags:, then in the page: if (ANU.flag(CFG, "my.feature")) renderMyFeature();. Add a row to FLAG_INFO in docs.html so it's described here.
  2. A new setting: add it to the schema in src/config.ts (it's validated and published in config.json automatically), then read it from CFG in the page.
  3. A new built-in metro metric (from a new data source): compute it in collectMarket() in src/housing.ts, add it to the Metro type and SORTS in src/housing-kb.ts, to FORMULA_FIELDS in src/config.ts, and to METRICS in housing.html.
  4. A new tab: copy videos.html as a starting point (it loads desk.css and desk.js), add it under desks:, and publish its data from src/build-api.ts.
  5. A new Ask ANU lookup: add a query in src/housing-kb.ts or src/kb.ts and a tool in src/tools.ts; the local app, the CLI (src/kb-cli.ts) and the Claude Code agent (.claude/agents/anu.md) can all use it.

File map

config/YAML settings: anu.yaml (site), videos.yaml (channels), requests.yaml (wishlist)
src/config.tsLoads and validates the YAML; the formula calculator and state roll-ups for custom attributes
src/collect.tsDaily defense news collector and free morning brief
src/housing.tsHousing collector: Zillow, Freddie Mac, housing news; rental math and ANU score
src/housing-coords.tsOne-time: places metros on the map from the Census gazetteer (npm run housing:coords)
src/housing-kb.tsHousing data types and read-only queries (used by Ask ANU and the CLI)
src/videos.tsVideo collector from YouTube's public channel feeds
src/rss.tsShared RSS/Atom reader
src/build-api.tsWrites public/api/v1/, including config.json
src/kb.ts, tools.ts, prompts.ts, anu.ts, server.tsAsk ANU: queries, tools, instructions, the agent loop and the local app server
public/The site: index.html, housing.html, videos.html, docs.html, shared desk.css and desk.js
.github/workflows/daily.ymlRuns every collector at 10:00 UTC, commits data/, deploys GitHub Pages