尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A SvelteKit app for comparing valuation multiples from the NYU Stern industry datasets. It is deployed on Vercel.
Pick 1-4 multiples, 1-4 regions and 1-4 industries on the home page and the app draws grouped bar charts:
By default each multiple gets its own panel, industries run along the x axis and regions are the colours. The Chart layout dropdowns let you swap those roles. Bars based on fewer than 10 firms are hatched, missing values show as "n/a", and Show data lists the exact numbers.
Every selection is stored in the URL, so a view can be bookmarked or shared, for example /?m=pe_trailing&m=pbv&r=us&r=europe&i=Steel&i=Total+Market. Parameters: m multiples, r regions, i industry names (repeat a parameter for each value), and p, x, c for the panel, x-axis and colour roles.
The /classification page links Dutch and international industry codes to the NYU Stern industries, in both directions:
Every step maps one code to several, so the result is a rough guide, not an official mapping. The page explains the abbreviations, and docs/crosswalk.md lists the sources and the approximations.
The data for the classification page is generated by scripts/build-crosswalk.ts from official correspondence tables (CBS, UN, US Census) and the NYU company list, and committed as src/lib/data/crosswalk.json. See docs/crosswalk.md to rebuild it.
The /companies page lists the companies behind an industry. Filter by Damodaran industry, region, sector, SIC code, country and name or ticker (50 per page); the filters live in the URL. Clicking an industry, sector, region or country in the table narrows the list to it, Reset filters clears them all, and each row has a multiples link to that industry's valuation multiples on the home page. The status line shows how many companies match and how many industries, sectors, regions, sub groups and exchanges they span. The data also holds NYU's Primary Sector (a column and filter) and Sub Group (counted in the status line). The classification page links to it from each industry and each SIC code. The industry shown for a company is the one NYU assigns, not one derived here. The list is built from the NYU company list by scripts/build-companies.ts into src/lib/data/companies.json (see docs/crosswalk.md). US companies on NASDAQ, NYSE or NYSE American link to FMP; other companies whose ticker maps to a Yahoo symbol link to Yahoo Finance (/financials?symbol=ABN.AS&source=yahoo). US OTC rows have no button (non-US rows with an OTC ticker still link to Yahoo); logged-out visitors see the login form and land on that ticker after logging in. Share classes are converted to the FMP style (BRK.B becomes BRK-B).
/countries and /industries show how large each country or industry is as a share of the global total, as a hand-drawn SVG treemap plus ranked bars with a "rest of world" remainder. Countries can be measured in listed companies or in GDP; industries (coloured by sector) and sectors only in companies, never in dollars. Percentages are always of the global total (all 48,156 companies, or World Bank world GDP). Picking a country shows its industries, picking an industry shows its countries, and one country plus one industry shows the cell with a link to the matching /companies list. Anonymous visitors see the cached default view; the drill-down (measure, country, industry, sector) needs the login. GDP comes from the World Bank (npm run build:gdp, once a year, see docs/size.md). There is deliberately no company level until there is a value per company.
/financials)The password-protected /financials page shows the annual income statement, balance sheet and cash flow statement for the last 5 fiscal years of a company, looked up by ticker (for example AAPL or ABN.AS). The data comes from Financial Modeling Prep (FMP) or Yahoo Finance. Each view uses one data source: FMP for US tickers (the default) or Yahoo Finance (a switch on the page, and the only source for tickers with an exchange suffix such as ABN.AS). Yahoo data comes from the unofficial yahoo-finance2 package, may be delayed and can break without notice. TTM is not available with FMP on the current plan and is grayed out there; it works with Yahoo. The page is server-rendered and works without JavaScript.
It needs four environment variables:
FMP_API_KEY: your FMP key.SITE_PASSWORD: the shared password you give to the people who may use the page.SESSION_SECRET: signs the login cookie. Generate one with openssl rand -base64 32.ISR_BYPASS_TOKEN: random string of at least 32 characters (openssl rand -hex 32). It makes the public pages (Multiples, Find industry, Companies, About) a cached static copy (Vercel ISR, one day) for anonymous visitors; logging in sets a cookie that skips the cache. Unset or shorter than 32 characters turns the cache off: the site still works, but it is simply uncached (the pages are sent private, no-store, so the CDN does not cache them either, and logging in changes nothing about caching). Set it for Production and Preview. The token is read at build time (the ISR config) and at runtime (the cookie check), so build and runtime must see the same value; redeploy after changing it. The token is also Vercel's revalidation secret, and logout does not invalidate an existing cookie value: to revoke every bypass cookie, rotate ISR_BYPASS_TOKEN and redeploy, ideally together with a SITE_PASSWORD change. Vercel drops every query parameter that is not listed in the ISR allowQuery, even for logged-in requests that skip the cache, so allowQuery (ISR_ALLOW_QUERY in src/lib/server/access.ts) lists every parameter the pages read plus SvelteKit's two data-request parameters. Each value of a listed parameter is cached separately (anonymous pages ignore them, so each entry is the same default view); a bot that varies those values can create extra cache entries and function runs, but the anonymous pages no longer link to any query variant. When a page starts reading a new query parameter, add it to ISR_ALLOW_QUERY; a test checks that SIZE_PARAMS in size-nav.ts is covered.On Vercel, add them as shared (team-level) variables and link them to the project for Production and Preview. Locally, put them in a .env file (see .env.example; .env is gitignored). They are declared in src/env.ts and read on the server only through $app/env/private, so a new variable must be added to src/env.ts first. If SITE_PASSWORD or SESSION_SECRET is unset, nobody can log in.
Login sets a signed, HTTP-only cookie that lasts 7 days and applies to the whole site (path /); when ISR_BYPASS_TOKEN is usable, a second cookie, __prerender_bypass, is set with it. Logging out only clears the cookie in that browser; a copied cookie stays valid until it expires. To log everyone out, change SESSION_SECRET; to stop new logins with the old password, change SITE_PASSWORD. Failed logins are rate limited per IP, best effort only, because the counter lives in each serverless instance. When testing the login with curl, send Accept: text/html, or SvelteKit answers form actions with JSON.
/financials is never cached publicly (Cache-Control: private, no-store). Limits: one shared password cannot tell users apart or revoke one person; which tickers work depends on your FMP plan (unsupported ones get a message); FMP's terms may restrict sharing its data; and Yahoo's terms target personal use, so check both before giving anyone the password and do not redistribute the data.
The page has vertical tabs (Financials, News, Stock Price, Company Data) and, under Financials, tabs for the income statement, balance sheet and cash flow statement plus a Latest 5 years / TTM toggle. The choices live in the URL (tab, statement, period, next to symbol), so a view can be bookmarked or shared. Company Data shows the company profile from the active source (cached 24 h) and News the latest 20 articles for the ticker (cached 15 min); links to articles and websites open in a new tab and only http(s) URLs are linked. Stock Price shows the last close, day change, 52-week high and low and volume (end-of-day, not live) with a line chart of the last year of daily closes, from one call to the active source, cached for 1 h. TTM is only available with Yahoo (grayed out with FMP); the TTM balance sheet is the latest annual balance sheet. News on Yahoo is matched by company name and is looser than FMP's.
/financials/valuation)The valuation page values one company with multiples, the way the football-field slides in a valuation report do. It sits under /financials, so it uses the same password and is never cached publicly.
/financials; forward multiples use FMP analyst estimates) or a private company uploaded from an Excel template. Download the template gives an .xlsx with Company, Currency, Net debt, a Year row, a Type row (Actual or Forecast) and one row per figure (Revenue, EBITDA, EBIT, Net income, Book equity, Invested capital). Labels may also be Dutch (Omzet, Eigen vermogen, Realisatie, Prognose). The file is read once and never stored; its figures travel in the page URL (co), so they end up in the browser history and the server's request logs.The page shows the company's figures, a table of each peer's multiples with the peers' 25th percentile, median, 75th percentile and the sector value, a football-field chart and a table of equity values. Each method's range is the company figure times the peers' P25 to P75 (the median is the midpoint), minus net debt for EV multiples. Manual bands replace the peer range with a low and high multiple you type in (for example 3x to 5x). The average line is the mean of the midpoints. Values are in the company's own currency, and zero or negative multiples are left out of the percentiles.
All choices live in the URL: symbol or co, peer (repeated), m (repeated), region, industry, lo.<multiple> and hi.<multiple>, and source. A DCF valuation is planned as a separate page.
Anonymous visitors see the default view of Multiples, Find industry, Companies (the first 20 companies) and About, cached for a day. They cannot use filters, search or paging; the URL parameters are ignored. The same shared password unlocks interactive versions of every page. /financials is unchanged. robots.txt disallows crawlers.
NYU publishes the data as legacy .xls workbooks, one per multiple family and region (32 files, updated about once a year). The server downloads only the files a view needs, parses them with SheetJS, and keeps them in memory for 24 hours. The public pages are cached by Vercel ISR for one day (when ISR_BYPASS_TOKEN is set). If NYU is unreachable, a previously cached copy is served. A January update can take up to about two days to show on the public pages (24 h in-memory + 24 h ISR); logged-in visitors only wait for the in-memory copy.
@sveltejs/adapter-vercel for deploymentxlsx 0.20.3, installed from https://cdn.sheetjs.com (the npm registry copy is unmaintained). Dependabot cannot update it; check for new releases now and then.Pages are served with a Content-Security-Policy (script hashes, no framing, same-origin forms) and the headers X-Content-Type-Options, Referrer-Policy and Permissions-Policy, set in vite.config.ts (CSP) and src/hooks.server.ts. The NYU download is limited to 5 MB per workbook and has a 10 s timeout, URL parameters are checked against allow-lists, and the CI workflow runs with read-only permissions.
The code is TypeScript only (.ts files and <script lang="ts">), type-checked in strict mode. npm run check and npm run check:ts-only run in CI. The same rules for contributors and agents are in CLAUDE.md.
CI also enforces a coverage floor on src/lib (npm run test:coverage) and runs npm audit --omit=dev --audit-level=high on runtime dependencies. A separate, non-required lighthouse job audits the public pages (home, classification, companies, about) on the production build with Lighthouse CI (lighthouserc.json): accessibility, best practices and SEO must score at least 0.9 for the job to pass, performance at least 0.8 as a warning. The is-crawlable audit is skipped (skipAudits in lighthouserc.json) because robots.txt deliberately disallows all crawlers, which would otherwise fail the SEO score.
docs/overview.md gives the design at a glance, the design decisions, the release history, the quality review and the roadmap. The decisions are recorded one per file in docs/adr/, the quality review is in docs/quality/, and CHANGELOG.md lists what each release brought. RELEASING.md describes how a release is cut (CalVer YYYY.0M.0D tags).
Prerequisites: Node.js 24.x (pinned in package.json under engines, and in .nvmrc, so nvm use picks it) and npm. The project stays on one Node LTS line; moving to the next one means changing engines, .nvmrc, the CI workflows and @types/node together (a test checks they agree).
git clone https://github.com/jan-ru/my-svelte-app.git
cd my-svelte-app
npm install
npm run dev
The dev server runs at http://localhost:5173.
| Command | Description |
|---|---|
npm run dev | Start the Vite dev server |
npm run build | Create a production build |
npm run preview | Preview the production build locally |
npm test | Run the unit tests (no network) |
npm run test:coverage | Run the tests with a coverage report; fails below the floor in vitest.config.ts |
npm run check | Type-check the code with svelte-check (strict) |
npm run check:ts-only | Fail if the repository contains JavaScript source |
npm run build:crosswalk / build:companies | Regenerate the data files (see docs/crosswalk.md) |
npm run build:gdp | Regenerate gdp.json from the World Bank (see docs/size.md) |
npm run check:data | Fetch the real NYU files and confirm every multiple still resolves |
npm run check:data also runs monthly in GitHub Actions (.github/workflows/check-data.yml); a failed run emails the repo owner. Run it by hand after NYU publishes new data (each January). If NYU renames a column, update the matching entry in src/lib/catalog.ts.
src/
lib/
catalog.ts Multiples, regions, file names
params.ts URL parameters
layout.ts Reshapes data into chart panels
chart.ts Scales and formatting
crosswalk.ts SBI 2025 to Damodaran lookup, both directions
companies.ts Company list filtering, paging and the anonymous preview
source.ts FMP or Yahoo choice per ticker and period
yahoo.ts Capital IQ to Yahoo ticker mapping
return-path.ts Allow-list of pages the login may return to
size.ts Shares of the global total, rest of world, cross cell
size-treemap.ts Squarified treemap layout
size-nav.ts URL state of the size pages (SIZE_PARAMS)
size-view.ts View models of /countries and /industries
gdp.ts, gdp-match.ts World Bank GDP lookup and country-name matching
valuation*.ts Valuation page: calculation, figures mapping, URL state, template, formatting
football-field.ts Model of the football-field chart
styles/ Design tokens and global CSS
data/ Generated crosswalk.json, companies.json and gdp.json
components/ Charts and the login prompt
server/ Workbook download, parsing, cache, comparison, FMP and Yahoo
clients, session auth, access rules (ISR, interactive)
env.ts Declares the private environment variables
hooks.server.ts Security headers, session and interactive flag, cache header
routes/
+page.server.ts Load function
+page.svelte Home page
classification/ SBI 2025 to Damodaran industry lookup
companies/ Company list with industry, region, sector, SIC and country filters
countries/ Country size versus global (companies or GDP)
industries/ Industry and sector size versus global
financials/ Password-protected financial statements (FMP or Yahoo)
valuation/ Valuation versus peers and sector, template download and upload
+layout.* Nav with Log in / Log out, passes the interactive flag
about/+page.svelte About page
scripts/ Builds the classification data (see docs/crosswalk.md)
docs/ Overview, design decisions (adr/), quality review, crosswalk notes, specs and plans
The app builds with adapter-vercel, configured in vite.config.ts (SvelteKit 3 no longer reads svelte.config.js). Internal imports use the #lib/... subpath alias declared under imports in package.json, which replaces the old $lib alias. Vercel uses the Node version from engines in package.json and the settings in vercel.json. CI (.github/workflows/ci.yml) runs the tests and a build on every pull request and push to main. Dependabot keeps dependencies up to date.
MIT © Jan-Ru Muller