presidio-v/presidio-hardened-angellist
GitHub: presidio-v/presidio-hardened-angellist
一款安全加固的早期初创公司交易流筛选与尽职调查工具,从 AngelList 融资邮件中自动提取结构化信息并进行评分定级。
Stars: 5 | Forks: 0
# presidio-hardened-angellist
[](https://github.com/presidio-v/presidio-hardened-angellist/actions/workflows/ci.yml)
[](https://github.com/presidio-v/presidio-hardened-angellist/actions/workflows/codeql.yml)
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
Presidio 安全加固的**交易流筛选与尽职调查工具包**,专为
通过 **AngelList 投资联盟**寻找的早期(pre-seed / seed)初创公司设计。
## 工作原理
```
forwarded .eml ─▶ intake ─▶ extraction ─▶ enrichment ─▶ triage rubric ─▶ memo
(parse) (regex first, (hardened (deterministic (Claude or
LLM fallback) HTTP fetch) scorecard) template)
```
1. **收集** — 将转发的 `.eml`(或粘贴的文本)解析为结构化的 `Deal`。
2. **提取** — 优先使用确定性的 regex/heuristics;仅在解析信息过少
(`is_complete()` 为 `False`)时启用 **Claude fallback**。
3. **补充** *(可选)* — 通过加固的 session 获取公司网站,以回填一句话简介。
4. **筛选** — 根据确定性的 pre-seed/seed 标准进行评分 → 综合得分 +
等级(`Pass` / `Track` / `Dig deeper` / `Strong lead`)。
5. **备忘录** *(可选)* — Claude 辅助的投资备忘录,带有模板化
fallback,因此即使没有 API key,`--memo` 依然有效。
确定性路径**无需 API key**。LLM 步骤仅在
设置了 `ANTHROPIC_API_KEY` 且安装了 `[llm]` 扩展时才会激活。
## 安装说明
```
pip install presidio-hardened-angellist # deterministic core
pip install 'presidio-hardened-angellist[llm]' # + Claude extraction/memo
```
用于开发:
```
git clone https://github.com/presidio-v/presidio-hardened-angellist.git
cd presidio-hardened-angellist
uv venv && source .venv/bin/activate
uv pip install -e ".[dev,llm]"
```
## CLI 用法
```
angeltriage deal.eml # scorecard for one deal
angeltriage deal.eml --memo # + investment memo
angeltriage deal.eml --enrich # fetch the company site for more signal
angeltriage deal.eml --json # machine-readable output (pipe-friendly)
cat deal.txt | angeltriage - # read a pasted email from stdin
angeltriage *.eml # batch, ranked by composite score
angeltriage deals.csv # batch-triage a CSV of deals (one row each)
angeltriage --imap # pull deal emails over IMAP (see below)
angeltriage --watch --interval 300 # poll IMAP every 5 min, auto-triage new deals
angeltriage deal.eml --no-llm # force the deterministic-only path
angeltriage deal.eml --weights w.json # tune dimension weights (see below)
angeltriage deal.eml --rubric r.json # full rubric config (see below)
angeltriage deal.eml --save # persist to the deal queue (see below)
angeltriage --queue # show the ranked, saved deal queue
angeltriage --set-status 4 passed # update a saved deal's workflow status
```
`.eml`/文本输入会被作为电子邮件解析;`.csv` 输入会每次一行地进行筛选。
您可以在一个批次中混合文件 —— 所有内容都会按得分一起排名。
示例输出:
```
Nimbus Robotics [Strong lead · 83.0/100]
pre-seed · SAFE · $10,000,000 cap · lead: Jane Okafor
Warehouse-automation robots for SMB 3PLs.
Scorecard:
Team 4.5/5 2 founders; credential signals: ex-, former, mit
Market 3.5/5 clear one-liner present
Traction 4.5/5 signals: customers, month-over-month, mrr, paying
Terms 4.0/5 cap $10,000,000; SAFE
Syndicate 4.0/5 lead: Jane Okafor; allocation $250,000
```
## 库用法
```
from presidio_angellist import triage_email
result = triage_email("deal.eml", memo=True)
print(result.scorecard.tier, result.scorecard.composite) # Strong lead 83.0
print(result.deal.valuation_cap) # 10000000.0
print(result.memo)
```
调整标准权重:
```
from presidio_angellist import score_deal, parse_email
deal = parse_email("deal.eml")
sc = score_deal(
deal, weights={"team": 0.4, "market": 0.2, "traction": 0.2, "terms": 0.1, "syndicate": 0.1}
)
```
## 筛选标准 (pre-seed / seed)
| 维度 | 权重考量内容 |
|---|---|
| **Team** | 创始人数量、技术联合创始人、资历信号(ex-FAANG、YC 等) |
| **Market** | 一句话简介的清晰度 / 行业定位 |
| **Traction** | 营收、用户数、LOIs、增长率 —— 任何早期信号 |
| **Terms** | 针对该阶段的估值上限合理性、金融工具(SAFE/定价) |
| **Syndicate** | 具名的领投人、分配额度、社会背书 |
风险标记(独立创始人、缺失估值上限、阶段估值上限过高、无业务进展、
无网站)会单独标出。
### 超出范围(成长期)检测
该标准主要针对 **pre-seed / seed**。当一笔交易看起来处于较后阶段 ——
明确的 Series A/B/C、ARR/revenue ≥ $5M,或者规模庞大的定价/风投轮次 ——
它将被标记为 **`Out of scope`** 并附注(例如 *"Likely
growth-stage (~$40M ARR; venture round $20M) — outside pre-seed/seed scope; score
is indicative only"*),而不是给予一个误导性的评级。综合得分
仍会计算,但仅作为指示性参考。该功能通过 `detect_stage_scope(deal)`
和 `Scorecard.scope_note` 暴露。
### 调整权重
权重位于 `DEFAULT_WEIGHTS` 中,可在每次调用时覆盖,或通过
`--weights` 从 JSON 配置文件覆盖:
```
{
"team": 0.5,
"traction": 0.3
}
```
```
angeltriage deal.eml --weights weights.json
```
未指定的维度将保留其默认权重(因此部分覆盖也是可以的),
权重之和无需为 1(综合得分会根据总权重进行归一化),
并且至少有一个权重必须为正数。有效的维度包括:`team`、`market`、`traction`、
`terms`、`syndicate`。通过库调用:
```
from presidio_angellist import load_weights, triage_email
result = triage_email("deal.eml", weights=load_weights("weights.json"))
```
### 完整的标准配置 (`--rubric`)
如果需要配置除权重外的更多内容,请传入 `--rubric` 文件。
所有部分均为可选,并与默认值合并:
```
{
"weights": { "team": 0.4, "traction": 0.25 },
"tier_thresholds": { "Strong lead": 90, "Dig deeper": 75 },
"cap_ceilings": { "pre-seed": 8000000, "seed": 25000000 },
"risk_penalty": 5.0
}
```
- **`tier_thresholds`** — 每个等级标签的最低综合得分 (0–100)。
`Pass` 的最低值始终保持在 0。
- **`cap_ceilings`** — 各阶段的估值上限 (USD);超过该上限将触发
风险标记并扣除 Terms 得分。
- **`risk_penalty`** — **每个风险标记**扣除的综合得分 (默认 0)。
```
angeltriage deal.eml --rubric rubric.json # mutually exclusive with --weights
```
```
from presidio_angellist import load_rubric_config, triage_email
result = triage_email("deal.eml", config=load_rubric_config("rubric.json"))
```
验证采取闭环失败原则 —— 未知的键/维度、超出范围的阈值、
负数惩罚或格式错误的 JSON 会引发 `WeightsConfigError`。
### CSV 批量导入
`angeltriage deals.csv` 会逐行筛选 `Deal`。系统会不区分大小写地匹配
常见的列头别名:
| 字段 | 接受的表头(任意一个) |
|---|---|
| company | `company`, `name`, `startup` |
| valuation_cap | `valuation_cap`, `cap`, `valuation` |
| round_size | `round_size`, `raising`, `round`, `target` |
| website | `website`, `url`, `site` |
| founders | `founders`, `founder`, `team` (以 `;` / `,` 分隔) |
| … | `one_liner`, `sector`, `stage`, `instrument`, `allocation`, `lead`, `deadline`, `location`, `traction`, `links` |
金额单元格接受 `$1.2M`, `1,200,000` 或 `500k`。没有公司名称的行将被
跳过。
```
from presidio_angellist import triage_csv
for result in triage_csv("deals.csv"):
print(result.deal.company, result.scorecard.tier)
```
## IMAP 获取
`--imap` 直接从邮箱拉取交易邮件(将投资联盟邮件归档到
一个文件夹中,然后进行轮询)。它在**任何您运行它的地方**运行 —— 您的笔记本电脑或服务器,
而不是手机。凭证**仅**来自环境变量(绝不通过命令
行)—— 请使用**应用专用密码**(iCloud,开启 2FA 的 Gmail):
```
export IMAP_HOST=imap.mail.me.com # iCloud; Gmail: imap.gmail.com
export IMAP_USER=you@icloud.com
export IMAP_PASSWORD=abcd-efgh-ijkl-mnop # app-specific password
export IMAP_FOLDER=Deals # optional; defaults to INBOX
angeltriage --imap --save # fetch UNSEEN, triage, save to the queue
angeltriage --imap --imap-all --imap-limit 20 # most recent 20, read or not
angeltriage --imap --imap-from deals@syndicate.com
```
| 环境变量 | 用途 |
|---|---|
| `IMAP_HOST` / `IMAP_USER` / `IMAP_PASSWORD` | 必需的连接 + 应用专用密码 |
| `IMAP_PORT` | 可选,默认 `993` |
| `IMAP_FOLDER` | 可选,默认 `INBOX`(或使用 `--imap-folder`) |
| `IMAP_SSL` | 可选,默认开启(`0`/`false` 可禁用) |
Flags:`--imap-folder`,`--imap-all`(不仅是 `UNSEEN`),`--imap-from ADDR`,
`--imap-limit N`。邮箱以**只读**模式打开,因此邮件不会被标记为
已读 —— 重新轮询时会重新获取它们,并且交易队列会根据交易标识去重。
```
from presidio_angellist import imap_config_from_env, triage_imap
cfg = imap_config_from_env(folder="Deals", limit=20) # reads IMAP_* env vars
for result in triage_imap(cfg):
print(result.deal.company, result.scorecard.tier)
```
### 监视模式(连续轮询)
`--watch` 会按间隔轮询邮箱,并自动将新交易筛选进
队列 —— 一个无需人工干预的收件箱到队列 pipeline:
```
angeltriage --watch --interval 300 # poll every 5 min until Ctrl-C, saving new deals
angeltriage --watch --max-cycles 12 # poll 12 times then stop
```
在同一个会话中,邮件会根据 `Message-ID` 去重,因此同一封未读邮件不会
在每次轮询时被重新筛选;在重启过程中,交易队列会根据交易
标识去重。首次轮询遇到配置/凭证错误时会快速失败;后续轮询
会容忍瞬时的网络错误并继续运行。每次轮询都会打印一行
摘要以及任何新保存的交易。对于 cron 式设置,请定期使用 `--max-cycles 1`
代替长时间运行的进程。
```
from presidio_angellist import DealStore, imap_config_from_env, watch
with DealStore() as store:
watch(imap_config_from_env(folder="Deals"), store, interval=300)
```
## 本地 / 自托管 LLM
默认情况下,LLM 层使用 Anthropic (`ANTHROPIC_API_KEY`)。如果需要运行
**本地或自托管**的 OpenAI 兼容模型(如 mlx_lm.server, Ollama,
vLLM, LM Studio 等),请设置 base URL —— 仅此一项即可切换后端:
```
export ANGELTRIAGE_LLM_BASE_URL=http://127.0.0.1:8080/v1
export ANGELTRIAGE_LLM_MODEL=my-local-model
export ANGELTRIAGE_LLM_API_KEY=not-needed # optional; many local servers ignore it
export ANGELTRIAGE_LLM_TIMEOUT=120 # optional, seconds
```
| 环境变量 | 用途 |
|---|---|
| `ANGELTRIAGE_LLM_BASE_URL` | OpenAI 兼容的 base URL(例如 `…/v1`);**设置此项即可使用本地模型** |
| `ANGELTRIAGE_LLM_MODEL` | 服务器期望的模型 ID |
| `ANGELTRIAGE_LLM_API_KEY` | 可选的 bearer token (默认 `not-needed`) |
| `ANGELTRIAGE_LLM_PROVIDER` | 可选,强制指定 `openai`/`anthropic` 后端 |
| `ANGELTRIAGE_LLM_EXTRA_BODY` | 可选的 JSON,会合并到请求体中,用于设置服务器特定参数 |
对于**推理模型**(例如 Qwen3),请禁用其思考过程,以便直接返回
最终答案(否则它可能只输出推理 token 而无实际内容):
```
export ANGELTRIAGE_LLM_EXTRA_BODY='{"chat_template_kwargs":{"enable_thinking":false}}'
```
本地端点通常是回环地址,这会被补充功能的 SSRF 防护拦截 —— 因此
LLM 调用会故意绕过该防护。仅将其指向您控制的
服务器 ([SECURITY.md](SECURITY.md))。如果模型不可访问,筛选将降级为
确定性评分 + 模板化备忘录,而不是直接报错失败。
## 邮件通知 (`--notify`)
`--notify` 会通过 SMTP 将**新加入存储**的交易邮件发送给收件人列表 ——
非常适合每日无人值守的运行。配置仅通过环境变量进行:
```
export ANGELTRIAGE_SMTP_HOST=smtp.example.com
export ANGELTRIAGE_SMTP_PORT=465 # 465 = implicit TLS; else STARTTLS
export ANGELTRIAGE_SMTP_USER=you@example.com
export ANGELTRIAGE_SMTP_PASSWORD=… # app-specific password
export ANGELTRIAGE_SMTP_FROM=you@example.com # optional, defaults to USER
export ANGELTRIAGE_NOTIFY_TO="a@example.com, b@example.com"
# 每日 one-shot: 轮询 al folder 一次,进行 triage、保存,并通过 email 发送新 deals
angeltriage --watch --max-cycles 1 --imap-folder al --notify
```
`--notify` 需要一个存储(在 `--watch` 下隐式开启;否则需传递 `--save`),并且
只会发送真正全新的交易邮件 —— 因此只读邮箱重新获取时不会重发。
结合 `--watch --max-cycles 1`,持久化的 `processed_messages` 表可确保每封
邮件在每日运行中被筛选**且仅筛选一次**。发送失败会抛出明显错误
(非零退出码),因此交易绝不会悄无声息地被丢弃。
## 交易队列(持久化)
`--save` 将筛选后的交易持久化到本地 SQLite 存储中,使筛选成为
一个您可以长期跟进的工作流,而不是一次性操作:
```
angeltriage inbox/*.eml --save # triage + save the batch
angeltriage --queue # ranked list of everything saved
angeltriage --queue --status new # filter by workflow status
angeltriage --set-status 4 tracking # new -> tracking -> passed -> committed
```
```
# tier score status seen company
1 Strong lead 83.0 tracking 2 Nimbus Robotics
3 Track 49.5 new 1 Solo Stealth
```
- **跨运行去重** — 交易以网站域名(或者在无网站时使用规范化的
公司名称)作为键,因此由两个
投资联盟转发的同一笔交易会合并为一行。`seen` 会记录其到达的次数。
- **重新保存时保留状态** — 重新筛选一笔 `passed` 的交易不会将其重置
为 `new`;只会刷新记分卡/得分。
- **存储位置** — 默认为 `~/.angeltriage/deals.db`;使用 `--db
FILE` 或 `ANGELTRIAGE_DB` 环境变量覆盖。数据库在本地;数据不会离开您的
机器。
```
from presidio_angellist import DealStore, triage_email
with DealStore() as store: # default path, or DealStore("deals.db")
saved, is_new = store.save(triage_email("deal.eml"))
for row in store.list(status="new"):
print(row.id, row.company, row.tier, row.composite)
store.set_status(saved.id, "tracking")
```
## 安全加固(保留,并用于数据补充)
| 特性 | 功能 |
|---|---|
| **强制严格的 TLS 1.2+** | 拒绝 TLS 1.0/1.1;仅使用临时 EC 密码;始终开启 `verify=True` |
| **HTTP → HTTPS 自动升级** | 静默升级不安全的 `http://` URL;拒绝非 HTTP(S) 协议 |
| **SSRF 防护** | 拒绝解析为回环/私有/链路本地(包括 `169.254.169.254`)/保留地址的目标 |
| **API key / 密钥脱敏** | `presidio_angellist` logger 上的 `RedactingFilter` 会从**每一条**日志记录中清除 Bearer token、`sk_live_*` / `sk-ant-*` 密钥以及 `access_token=`/`api_key=` |
| **带退避的重试机制** | 在遇到连接错误 / 429 / 5xx 时进行指数退避,并遵循 `Retry-After` |
| **基于主机的速率限制** | Token-bucket 限制器;防止对数据补充目标主机造成意外 DoS |
| **安全事件日志记录** | 对每一项加固操作进行结构化日志记录(`presidio_angellist` logger) |
每一个向外的数据补充请求都会经过 `HardenedSession`。发送到
可选 LLM 层的不可信交易文本会被隔离,且 system prompt 会将其视为数据,
而非指令(防止 prompt injection);除非明确选择启用,否则拒绝明文 IMAP。
有关完整的信任边界模型,请参阅 [SECURITY.md](SECURITY.md)。
## 路线图
| 版本 | 亮点 |
|---|---|
| **0.2.** | 转向交易流筛选:邮件获取、确定性标准、`--weights` 配置、LLM 提取 fallback + 备忘录、`angeltriage` CLI |
| **0.3.0** | CSV/批量导入、完整标准配置(`--rubric`:等级、估值上限、单标记惩罚)、HTML 邮件健壮性、og/title 补充 fallback |
| **0.4.0** | SQLite 交易队列:`--save` / `--queue` / `--set-status`、跨运行去重、工作流状态 |
| **0.5.0** | IMAP 获取(`--imap`,密钥校验) |
| **0.5.1** | IMAP watch 模式(`--watch`:间隔轮询、会话内去重、自动保存) |
| **0.5.2** | 更好的公司/一句话简介提取(正文线索);成长期超范围检测 |
| **0.6.0** | 安全加固版本:SSRF 防护、sink 强制日志脱敏、LLM prompt injection 防御、恢复重试/退避、拒绝明文 IMAP、基于 CVE 的依赖项 + CI 中的 `pip-audit` |
| **0.7.0** | 本地/自托管 LLM 后端(OpenAI 兼容)、SMTP 交易通知(`--notify`)、恰好一次轮询(`processed_messages`) |
| **0.8.0** _(计划中)_ | 可插拔的数据补充提供商(Crunchbase/Harmonic)、队列导出/摘要 |
## 运行测试
```
pytest -v --cov=presidio_angellist --cov-report=term-missing
```
## 项目结构
```
presidio-hardened-angellist/
├── src/presidio_angellist/
│ ├── __init__.py # public API
│ ├── hardening.py # TLS / redaction / rate-limit primitives
│ ├── models.py # Deal, Scorecard, TriageResult
│ ├── intake/email.py # forwarded .eml / text -> Deal (deterministic)
│ ├── intake/csv.py # CSV of deals -> list[Deal]
│ ├── intake/imap.py # pull deal emails over IMAP (key-gated)
│ ├── watch.py # --watch: poll IMAP on an interval, auto-triage
│ ├── enrich/web.py # hardened website enrichment
│ ├── rubric_config.py # RubricConfig + defaults (weights/tiers/ceilings)
│ ├── triage/rubric.py # deterministic pre-seed/seed scorecard
│ ├── triage/memo.py # LLM memo + templated fallback
│ ├── store.py # SQLite-backed persistent deal queue
│ ├── config.py # --weights / --rubric config loaders
│ ├── llm.py # optional Claude extraction/memo (key-gated)
│ ├── pipeline.py # end-to-end triage_email()
│ └── cli.py # angeltriage entrypoint
├── tests/
├── pyproject.toml
├── LICENSE # MIT
├── README.md
└── SECURITY.md
```
## 许可证
MIT — 详见 [LICENSE](./LICENSE)。
## 安全
有关我们的漏洞披露政策,请参阅 [SECURITY.md](./SECURITY.md)。
## SDLC
本仓库在 Presidio 加固家族的 SDLC 下开发:
.
标签:LLM集成, Python, 投资分析, 无后门, 逆向工具, 金融科技