sheeply 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -2
  4. package/dist/analysis/analyse.js +28 -0
  5. package/dist/analysis/black-sheep-calibration.js +7 -0
  6. package/dist/analysis/black-sheep.js +167 -0
  7. package/dist/analysis/data-quality.js +90 -0
  8. package/dist/analysis/dataset.js +126 -0
  9. package/dist/analysis/entry-maturity.js +72 -0
  10. package/dist/analysis/interpretation.js +105 -0
  11. package/dist/analysis/investments.js +107 -0
  12. package/dist/analysis/leadership.js +42 -0
  13. package/dist/analysis/partial-date.js +17 -0
  14. package/dist/analysis/ratio.js +28 -0
  15. package/dist/analysis/sector-exposure.js +64 -0
  16. package/dist/analysis/stages.js +30 -0
  17. package/dist/analysis/syndicates.js +70 -0
  18. package/dist/analysis/types.js +1 -0
  19. package/dist/cli/analyse-command.js +106 -0
  20. package/dist/cli/db-command.js +28 -0
  21. package/dist/cli/index.js +39 -0
  22. package/dist/cli/program.js +38 -0
  23. package/dist/config.js +53 -0
  24. package/dist/db/database.js +53 -0
  25. package/dist/db/schema.js +159 -0
  26. package/dist/dealroom/client.js +208 -0
  27. package/dist/dealroom/endpoints.js +17 -0
  28. package/dist/dealroom/pagination.js +25 -0
  29. package/dist/dealroom/schemas.js +121 -0
  30. package/dist/ingest/extract.js +137 -0
  31. package/dist/ingest/fetch.js +80 -0
  32. package/dist/ingest/resolve.js +74 -0
  33. package/dist/reports/json.js +97 -0
  34. package/dist/reports/sheep.js +63 -0
  35. package/dist/util.js +19 -0
  36. package/dist/version.js +9 -0
  37. package/docs/api-limitations.md +36 -0
  38. package/docs/dealroom-field-mapping.md +90 -0
  39. package/docs/methodology.md +203 -0
  40. package/package.json +52 -4
@@ -0,0 +1,90 @@
1
+ # Dealroom field mapping
2
+
3
+ How each Dealroom response field becomes a column in Sheeply's SQLite tables, and which analysis reads it. Verified against the OpenAPI spec at `https://developers.dealroom.co/openapi.yaml` (spec version `2026-10-22`) and live responses on 2026-10-28.
4
+
5
+ Sheeply pins `API-Version: 2026-10-22`. Every stored response keeps the `API-Version` header Dealroom applied, so a later spec change can be traced to the runs it affected.
6
+
7
+ ## Endpoints used
8
+
9
+ | Purpose | Request | Page size | Notes |
10
+ | --- | --- | --- | --- |
11
+ | Name resolution | `GET /data/search?q=…&types=investor&limit=20` | 20 (max) | Free (0 credits). Returns relevance-ranked hits, `type` is `investor` for firms |
12
+ | Investor profile | `GET /data/investors/{uuid}` | n/a | 2 credits. Returns an investor firm **or** a person who invests (`type: person`) |
13
+ | Portfolio | `GET /data/investors/{uuid}/portfolio` | 500 (max) | 1 credit per row. One row per company, includes companies with `rounds_count: 0` |
14
+ | Funding history | `GET /data/transactions?filter=company_investor_id[in_any]:{uuid}&sort=date&include_total=true` | 500 | 1 credit per row. Every round of every company the investor backed, including rounds it did not join |
15
+ | Fallback history | `GET /data/companies/{uuid}/funding-rounds` | 500 (max) | Used only when the transactions filter answers 400, 403 or 404 |
16
+
17
+ All lists page by cursor: the first request sends `limit`, later ones send `cursor=<page.next_cursor>` with the same `filter` and `sort`. `offset` was retired in API version `2026-10-07`.
18
+
19
+ `company_investor_id` is the documented filter "Every round of the companies a specific investor has backed". It turns the whole funding history into a handful of calls (Seedcamp: 8 requests, 1,777 rounds) instead of one call per portfolio company.
20
+
21
+ ## Raw storage
22
+
23
+ | Table | Holds |
24
+ | --- | --- |
25
+ | `ingestion_runs` | One row per fetch: investor, status (`running`, `complete`, `failed`), API host and version, strategy, requests, credits, error |
26
+ | `raw_responses` | Every response body verbatim, with endpoint, params, `retrieved_at`, `API-Version` header, `X-Credits-Used` and a SHA-256 of the body |
27
+
28
+ Everything below is derived from `raw_responses` by `src/ingest/extract.ts` (`EXTRACTION_VERSION = 1`).
29
+
30
+ ## Investor (`/data/investors/{uuid}` → `investors`)
31
+
32
+ | Dealroom field | Column | Used for |
33
+ | --- | --- | --- |
34
+ | `data.uuid` | `uuid` | Identity |
35
+ | `data.name` | `name`, `name_key` | Report, offline name resolution |
36
+ | `data.type` (`organization` / `person`) | `type` | `investor.analysis_level` (firm or person) |
37
+ | `data.subtype` | `subtype` | Report |
38
+ | `data.types[].name` | `firm_types_json` | Report (e.g. `venture capital`) |
39
+ | `data.launch_date` | `launch_date` | Report |
40
+ | `data.locations[role=hq].country.name` | `hq_country` | Report |
41
+ | `data.portfolio.companies.total` | `reported_portfolio_total` | Portfolio completeness check |
42
+ | `data.portfolio.rounds_count` | `reported_rounds_count` | Stored for debugging |
43
+
44
+ Not used: `stages` (empty for the firms checked), `deal_size` (`{min: 0, max: 0}` for Seedcamp), `investments.preferred_round` (Dealroom's own summary, not reproducible from our data).
45
+
46
+ ## Portfolio (`/portfolio` → `portfolio_relationships`, `companies`, `company_taxonomy`)
47
+
48
+ | Dealroom field | Column | Used for |
49
+ | --- | --- | --- |
50
+ | `company.uuid` | `company_id` | Join key |
51
+ | `company.subtype` | `company_subtype` | Excludes universities, gov/NGO and investors |
52
+ | `rounds_count` | `rounds_count` | `0` means a stake not attributed to any known round |
53
+ | `is_lead` | `is_lead_any` | Stored only. It means "led at least one round", too coarse for per-round analysis |
54
+ | `last_round_date` | `last_round_date` | Stored only |
55
+ | `company.name`, `launch_date`, `dealroom_url` | `companies.*` | Report |
56
+ | `company.taxonomy[]` (`id`, `name`, `type`) | `taxonomy_terms`, `company_taxonomy` | Sector exposure (`type = industry` only; no exclusive sector field exists) |
57
+ | `company.launch_date` | `companies.launch_date` | Company age at entry (year precision) |
58
+
59
+ ## Funding rounds (`/data/transactions` → `funding_rounds`, `round_participants`, `entities`)
60
+
61
+ | Dealroom field | Column | Used for |
62
+ | --- | --- | --- |
63
+ | `id` (integer) | `id` | Dedup key across pages, runs and investors |
64
+ | `company.uuid` | `company_id` | Join to portfolio |
65
+ | `announced_date` (`YYYY`, `YYYY-MM` or `YYYY-MM-DD`) | `announced_date` | Ordering rounds. Precision is kept, never padded |
66
+ | `round_label` | `round_label` | **Entry maturity**. Dealroom's own stage label; null when no stage definition is met |
67
+ | `standardized_round` | `standardized_round` | Reported alongside, never used to fill a missing `round_label` |
68
+ | `round_type` | `round_type` | Self-reported name, stored for debugging only |
69
+ | `amount`, `valuation` + envelope `currency` | `amount`, `valuation`, `currency` | Prior VC capital at entry (disclosed amounts only). `0` and `null` are kept distinct |
70
+ | `is_vc_round` | `is_vc_round` | Entry position and prior VC capital (which company rounds count as VC rounds before the entry) |
71
+ | `is_exit` | `is_exit` | Stored only |
72
+ | `investors` (`null` vs `[]`) | `investors_disclosed` | `null` is a tier redaction, not an empty round |
73
+ | `investors[].investor` (`EntityRef`) | `entities` | Participant identity and type (`organization` / `person`) |
74
+ | `investors[].is_lead` (`true` / `false` / `null`) | `round_participants.is_lead` (1 / 0 / NULL) | **Leadership** and **syndicates** |
75
+ | `deleted_at` | | Rows with a value are skipped |
76
+ | `source_url` | `source_url` | Stored for debugging |
77
+
78
+ `investor_round_scope` records which rounds belong to each investor's latest run, so rounds Dealroom stops returning drop out of that investor's analysis on `--refresh`.
79
+
80
+ ## Field names from older schemas
81
+
82
+ | Older field | Current field |
83
+ | --- | --- |
84
+ | `lead` | `is_lead` |
85
+ | `year`, `month` on transactions | `announced_date` (since `2026-10-15`) |
86
+ | `offset` pagination | `cursor` (since `2026-10-07`) |
87
+ | `tags` | `taxonomy` |
88
+ | `organization_subtype` | `subtype` |
89
+
90
+ Sheeply only reads the current names. Pinning an older `API-Version` would fail schema validation rather than silently map fields.
@@ -0,0 +1,203 @@
1
+ # Methodology
2
+
3
+ Algorithm version `0.3.0`. Every number in the report can be traced to the rounds listed under `contributions` with `--verbose`, and every threshold is printed in the report itself.
4
+
5
+ ## The model
6
+
7
+ Three outputs, computed independently from one investment history:
8
+
9
+ | Output | Role | Question |
10
+ | --- | --- | --- |
11
+ | `entry_maturity` | Metric | How early does this investor enter companies? |
12
+ | `investment_leadership` | Metric | How often does it lead its initial investments? |
13
+ | `sector_exposure` | Descriptive | Which sectors does it enter? |
14
+
15
+ Plus `syndicate_relationships`, descriptive context for leadership (who led the rounds it joined, who it co-invests with), and `black_sheep_score`, the one composite: a rank against a built-in peer calibration, described [below](#black-sheep-score).
16
+
17
+ Within the profile, correlated signals are not counted twice:
18
+
19
+ - Company age at entry, prior capital and entry position measure the same maturity as stage, so they sit inside `entry_maturity.explanatory_attributes` and are never scored.
20
+ - Lead-partner concentration describes the same behaviour as the non-lead rate, so it is context, not a second leadership measure.
21
+ - Sector exposure is context and does not feed any rule.
22
+
23
+ Each section carries `role: "metric"`, `"descriptive"` or `"score"`, and every rate is a `{ numerator, denominator, value }` triple. The black sheep score reads the same investment history; it does not change how the metrics are computed.
24
+
25
+ ## Principles
26
+
27
+ - **Observations, metrics and interpretation are separate.** Raw counts sit next to every share; the interpretation only restates them against fixed thresholds.
28
+ - **Unknown is never zero.** Each share names its denominator. Anything not classified is counted in `data_quality.exclusions` with a reason.
29
+ - **No look-ahead.** A first investment is described only by facts dated at or before it. A company's current valuation, status, total funding or later rounds never describe the entry.
30
+ - **Firm level only.** Dealroom's "investor" is the firm. Fund vehicles carry no link to rounds, so nothing is attributed to a fund.
31
+ - **Benchmarks are explicit.** The metrics compare an investor only with a stated 50% line, so they say "most" or "a minority", never "high" or "low". Only the black sheep score compares investors, and it names its peer set and version.
32
+
33
+ ## The investment history
34
+
35
+ For investor _I_:
36
+
37
+ 1. **Portfolio**: every row from `/investors/{I}/portfolio`.
38
+ 2. **Company rounds**: every round of every portfolio company (`company_investor_id` filter), including rounds _I_ did not join.
39
+ 3. **Participations**: company rounds whose `investors[]` contains _I_'s UUID exactly.
40
+ 4. **Order**: by announced date as a span (`2019` covers the whole year, `2019-06` the month), then the earlier stage, then the lower Dealroom id. Undated rounds sort last.
41
+ 5. **First observed investment**: _I_'s earliest participation in the company. Later participations are follow-ons.
42
+
43
+ This is the earliest round **in which _I_ was observed**, which is not necessarily the earliest round **raised by the company**. Both are kept: `entry_context.company_first_known_vc_round_id` in verbose output, and `entry_position` below.
44
+
45
+ Portfolio statuses:
46
+
47
+ | Status | Meaning |
48
+ | --- | --- |
49
+ | `initial_identified` | _I_ appears on at least one of the company's rounds |
50
+ | `no_round_attribution` | Portfolio says `rounds_count: 0`: Dealroom knows a stake but not the round |
51
+ | `rounds_not_matched` | Portfolio says _I_ joined rounds, but no returned round lists _I_ |
52
+ | `participation_undisclosed` | As above, and some company rounds have `investors: null`, so _I_ may be hidden there |
53
+ | `not_a_company` | University, gov/NGO or investor |
54
+
55
+ Only `initial_identified` companies feed the outputs. Flags warn where the first investment may be wrong:
56
+
57
+ - `initial_ordering_ambiguous`: the next participation's date span overlaps the first one's.
58
+ - `undated_participation`: another participation has no date and could be earlier.
59
+ - `initial_undated`: the first round itself has no date.
60
+ - `earlier_round_investors_undisclosed`: a company round that may be earlier has no investor list. A missing list is not an empty list, so _I_ may have entered there.
61
+
62
+ ## Metric A: Entry maturity
63
+
64
+ **Primary result**: the stage distribution of first investments across Pre-seed, Seed, Series A, Series B+ and Unknown. `share` uses all first investments as the denominator (so Unknown is visible); `share_of_known_stage` excludes Unknown.
65
+
66
+ **Stage source: `round_label`**, Dealroom's own stage classification. Dealroom's filter reference describes `round_label` as "Rounds by Dealroom's standardized round stage" and notes that "rounds that don't meet a stage definition have no label". The similarly named `standardized_round` is not used, because in live data it is two different things:
67
+
68
+ - where `round_label` exists, `standardized_round` is a coarser copy of it (952 of 952 Seedcamp-portfolio rounds agreed);
69
+ - where `round_label` is null, `standardized_round` is the self-reported `round_type` mapped across (`SEED` → Seed, `SUPPORT PROGRAM` → Other), as Dealroom's filter docs say: it "falls back to the reported round type when Dealroom left a round unlabelled".
70
+
71
+ So a round without a label is **Unknown**. `unknown_breakdown` shows what `standardized_round` says about those rounds, and `first_investments_by_standardized_round` is printed as a sensitivity check. Sheeply never infers a stage from round amounts.
72
+
73
+ | Bucket | `round_label` |
74
+ | --- | --- |
75
+ | Pre-seed | Pre-Seed |
76
+ | Seed | Micro-Seed, Seed, Seed+, Seed Extension |
77
+ | Series A | Series A, Series A Extension |
78
+ | Series B+ | Series B to Series F, and their extensions |
79
+ | Unknown | null, or any label outside this ladder |
80
+
81
+ **Explanatory attributes** (descriptive, not scored):
82
+
83
+ - `entry_position.prior_vc_rounds`: how many of the company's VC rounds (`is_vc_round: true`) are dated definitely before _I_'s first round: 0, 1, 2, 3+, or uncertain when partial or missing dates prevent ordering. `entered_at_company_first_known_vc_round` is the rate at which _I_'s first round **is** the company's earliest known VC round; uncertain cases are left out of its denominator.
84
+ - `company_age_at_entry_years`: entry year minus the company's `launch_date` year (year precision). Entries dated before the recorded launch are counted, not summarised.
85
+ - `prior_vc_capital`: sum of disclosed amounts of those prior VC rounds. Only companies with no undisclosed amount and no uncertain ordering are summarised; the rest are counted as incomplete.
86
+
87
+ These use only rounds dated before the entry, so they are free of look-ahead. All three understate maturity when a company's early history is missing: `data_quality.funding_histories.history_starts_at_series_a_or_later` counts companies whose earliest known VC round is already Series A or later.
88
+
89
+ ## Metric B: Investment leadership
90
+
91
+ ```text
92
+ lead_rate = first investments recorded as lead or co-lead
93
+ ─────────────────────────────────────────────
94
+ first investments with a usable lead status
95
+ ```
96
+
97
+ _I_'s lead status on each first-investment round, from that round's own participant flags:
98
+
99
+ | Status | Rule | Usable |
100
+ | --- | --- | --- |
101
+ | `sole_lead` | _I_ `is_lead: true`, nobody else is | Yes, as lead |
102
+ | `co_lead` | _I_ `is_lead: true`, someone else is too | Yes, as lead |
103
+ | `recorded_non_lead` | _I_ `is_lead: false`, someone else `is_lead: true` | Yes, as non-lead |
104
+ | `false_with_no_lead_on_round` | _I_ `is_lead: false`, nobody flagged as lead | No |
105
+ | `null_lead_flag` | _I_ `is_lead: null` | No |
106
+
107
+ `lead_status_completeness` is usable over all first investments. The same breakdown over every participation (first investments plus follow-ons) is the secondary statistic `all_participations`.
108
+
109
+ **Why `false` is only usable when another lead is flagged.** The spec documents `is_lead` as nullable ("Whether this investor led the round, when known"), so `false` should mean a confirmed non-lead. The live data says otherwise:
110
+
111
+ - Not one of 4,652 participant flags in Seedcamp's portfolio rounds is `null`; every flag is `true` or `false`.
112
+ - Only 37% of rounds with participants (556 of 1,521) flag anyone as lead.
113
+ - 591 rounds list a **single** investor flagged `false`. A round's only disclosed investor cannot be a confirmed follower of nobody.
114
+ - Lead coverage climbs with time (14% of rounds before 2015, 53% from 2023) and with syndicate size (15% of single-investor rounds, 64% of rounds with four or more), which fits "not recorded" far better than "not led".
115
+
116
+ So on a round with no flagged lead, `false` cannot separate "did not lead" from "lead not recorded", and a round without an identified lead does not show that everyone followed. Those first investments are excluded from the denominator and reported. `sensitivity.lead_rate_if_every_false_is_non_lead` gives the other bound (Seedcamp: 39% usable, 14% if every false counted). `data_quality.lead_flags` repeats this evidence for every investor analysed.
117
+
118
+ **What the lead flag does not show.** It records Dealroom's view of who led the announced round. It does not show who originated the deal, who committed first or who set terms. Announcement dates are never used to infer any of that; they are only used to order _I_'s own rounds.
119
+
120
+ ## Output C: Sector exposure (descriptive)
121
+
122
+ Dealroom has described 18 broad, mutually exclusive sectors, but the API does not expose such a classification. Checked on 2026-10-28: the OpenAPI spec, the docs index and `/reference/filters?scope=companies` list taxonomy families `industry`, `sub_industry`, `technology`, `sector`, `sdg`, `business_model`, `income_stream`, `client_focus`, `ownership` and `techstack_category`, none mutually exclusive. The highest level is `industry` ("Highest-level industry classification", 31 values), and 182 of 513 Seedcamp portfolio companies carry two.
123
+
124
+ So Sheeply reports **multi-label industry exposure**:
125
+
126
+ - Denominator: first-investment companies with at least one industry label (`labelled`).
127
+ - A company with two industries counts once in each, so shares sum past 100%. `label_assignments` shows by how much.
128
+ - `breadth.effective_number_of_industries`: each company splits a weight of 1 across its industries, then `1 / Σ share²`.
129
+ - `sector`, `technology` and `techstack_category` tags are ignored (Dealroom's own guidance: same-named sector tags are niche, techstack describes a website).
130
+ - This is the company's **current** classification, the one deliberate exception to no-look-ahead, acceptable only because it is context.
131
+
132
+ If Dealroom exposes the exclusive classification later, it becomes a single-label distribution of first investments with the same shape.
133
+
134
+ ## Syndicate relationships (descriptive)
135
+
136
+ - **Lead partners**: on first investments with status `recorded_non_lead`, the other participants flagged as lead. Counts, top-lead share, distinct partners and a Herfindahl index (a multi-lead round's weight is split equally).
137
+ - **Co-investors**: every other participant on any participation with a disclosed investor list, once per round.
138
+
139
+ Context only: no interpretation rule reads it.
140
+
141
+ ## Black sheep score
142
+
143
+ `black_sheep_score` answers: how often does this investor lead or go in first, rather than follow others, compared with early-stage peers? 0 follows the flock the most among the peers, 100 is the most contrarian. The default `sheep` output draws it; `--format json` gives the counts behind it.
144
+
145
+ **Observations.** Every first investment announced in 2020 or later. Recent years only, because lead flags are far more complete since 2020 and peers of different ages are then compared over the same market.
146
+
147
+ - **Led**: _I_ is flagged lead or co-lead, **or is the only disclosed investor on the round**. A sole investor is treated as the leader whatever its flag says.
148
+ - **Followed**: _I_ is flagged `false` and another participant is flagged lead.
149
+ - Anything else has no usable lead status and is left out of the lead counts.
150
+ - **Late**: the company had a VC round dated definitely before _I_'s first round (`entry_context.prior_vc_rounds > 0`). Uncertain ordering is left out.
151
+
152
+ **Two components**, each a percentile among peers, inverted so higher is more contrarian:
153
+
154
+ | Component | Share ranked among peers |
155
+ | --- | --- |
156
+ | `leads` | first investments that followed another lead, over those with a usable lead status |
157
+ | `independent_entry` | late share × followed share among late entries: how often _I_ joins an already funded company behind someone else's lead |
158
+
159
+ A percentile counts peers below, plus half of exact ties, over all peers. `value` is the equal-weight average of the two. There is no evidence for other weights, and both components are printed.
160
+
161
+ **Peers and calibration.** The comparison is internal and built in: users never fetch peers. `scripts/peers.ts` lists the peer set by Dealroom id, with a dated version: UK and European early-stage investors, plus investors whose structure fixes their behaviour (angel networks, high-volume small-cheque funds and accelerators) as a check that the score separates them. `npm run calibrate` fetches any peer missing from the local cache, computes each peer's two shares and writes them to `src/analysis/black-sheep-calibration.ts` as two sorted lists of numbers, with no names or ids. A peer counts towards a component only when at least half of its known-stage first investments are pre-seed or seed and it has at least 10 observations for that component. When the investor being scored is itself a peer, one share equal to its own is dropped, so it is not ranked against itself. Any change to how the shares are computed needs a new calibration, and the calibration records the algorithm version it came from.
162
+
163
+ **Not scored** when any of these hold, with `reasons` saying which:
164
+
165
+ - fewer than half of its known-stage first investments are pre-seed or seed, so early-stage peers are the wrong comparison;
166
+ - fewer than 10 first investments with a usable lead status, or fewer than 10 late ones.
167
+
168
+ **Range.** `range` is a 90% interval from 1,000 resamples of the investor's own first investments, with a fixed seed so it is reproducible. It reflects sampling noise only, not gaps in Dealroom's records. Two investors whose ranges overlap should not be called different.
169
+
170
+ **What it can get wrong:**
171
+
172
+ - Dealroom often lists one investor when it has not recorded the others, so the sole-investor rule can credit funds whose co-investors are missing. Funds writing many small cheques are most exposed. `components.leads.sole_investor` shows how many observations rest on it.
173
+ - With the sole-investor rule the two components move closely together across the first peer set (rank correlation about 0.9), so the score is mostly about leading. Without the rule they were nearly unrelated.
174
+ - It describes announced rounds. It cannot see who committed first, how fast a fund moved or whether it asked "who's leading?" before saying yes, which is much of what founders experience.
175
+
176
+ Only the calibration's numbers ship in the package, never peer records. `calibration.fingerprint` hashes the calibration used, so two reports can be checked for the same comparison.
177
+
178
+ ## Interpretation rules
179
+
180
+ Thresholds live in `src/analysis/interpretation.ts` and are copied into every report.
181
+
182
+ | Rule | Fires when |
183
+ | --- | --- |
184
+ | `*.insufficient_sample` | Fewer than 10 in the metric's denominator; the metric is not described |
185
+ | `entry_maturity.majority_pre_seed_or_seed` / `majority_series_a_or_later` | Pre-seed + seed ≥ / < 50% of first investments with a known stage; the statement also gives the Unknown count |
186
+ | `entry_maturity.entry_position` | Always with a described entry metric: how often the first observed round was the company's first known VC round |
187
+ | `investment_leadership.led_majority` / `led_minority` | Lead rate ≥ / < 50% |
188
+ | `investment_leadership.caveat` | With `led_minority`: a minority rate alone does not establish follower behaviour |
189
+ | `investment_leadership.sensitivity` | Some first investments have `false` with no lead on the round |
190
+ | `data_quality.*_coverage` | Entry-stage or lead-status coverage below 70% |
191
+ | `data_quality.portfolio_incomplete` | Fewer portfolio rows returned than Dealroom reports |
192
+
193
+ ## Reproducibility
194
+
195
+ - The report has no wall-clock timestamp. Given the same stored data and algorithm version it is byte-identical, online or offline.
196
+ - `source.dataset_fingerprint` is a SHA-256 of exactly the records the analysis read.
197
+ - `tool.algorithm_version` changes with any metric or threshold; `tool.extraction_version` with any change to how responses become tables; `report_schema_version` with the JSON shape.
198
+ - SQLite is a cache. `sheeply db rebuild` re-derives every table from stored Dealroom responses; a fresh database re-derives everything from the API.
199
+
200
+ ## Not in this version
201
+
202
+ - A stage-weighted early-entry score or stage-adjusted benchmarks. Peer sets for later-stage investors, so the black sheep score can rank them too.
203
+ - Merging entities that belong to one firm (for example a separately listed fund vehicle).
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "sheeply",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Local-first CLI that profiles how a venture firm invests: entry stage, lead behaviour and syndicate concentration, from Dealroom data",
5
+ "keywords": [
6
+ "venture-capital",
7
+ "investors",
8
+ "dealroom",
9
+ "cli"
10
+ ],
11
+ "license": "MIT",
12
+ "homepage": "https://github.com/build40for40/sheeply#readme",
13
+ "bugs": "https://github.com/build40for40/sheeply/issues",
14
+ "type": "module",
15
+ "bin": {
16
+ "sheeply": "dist/cli/index.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "!dist/**/*.test.*",
21
+ "docs",
22
+ ".env.example"
23
+ ],
24
+ "engines": {
25
+ "node": ">=22.13"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/build40for40/sheeply.git"
34
+ },
35
+ "scripts": {
36
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli/index.js",
37
+ "dev": "node src/cli/index.ts",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "test:smoke": "vitest run --config vitest.smoke.config.ts",
41
+ "prepack": "npm run build",
42
+ "prepublishOnly": "npm run typecheck && npm test",
43
+ "calibrate": "node scripts/calibrate.ts"
44
+ },
45
+ "dependencies": {
46
+ "commander": "^15.0.0",
47
+ "zod": "^4.6.7"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.20.5",
51
+ "typescript": "^7.0.2",
52
+ "vitest": "^5.0.5"
53
+ }
54
+ }