myastro.io 设计文档¶
版本:1.0(定稿)|状态:已评审收敛|后续:roadmap-e.md(E0–E4)
1. 目标与边界¶
把"下载天文数据"变成可信赖的本地操作:幂等(同参数重复调用不重复下载)、可追溯(每次数据从哪来、什么参数)、可复现(任何图能反查出数据来源)。
不造的轮子:
- 不实现任何下载协议与查询语言——委托 astroquery / lightkurve / ztfquery / pyvo / aria2c
- 不管数据格式转换——属 lc / imaging / phot 层
- TB 级批量归档也纳入管理(作为 job),但传输由 aria2c 承担
2. 关键设计决策¶
- 一切网络获取统一为 job;
fetch()只是 submit + wait 的语法糖,缓存命中不产生 job(未碰网络)。 (曾考虑按数据类型三分、按体积阈值二分:类型与体积正交,Gaia 锥查等案例跨档;体积阈值依赖网络环境。均否决。) - 双执行器:python(寄生调用方进程,心跳自报)/ aria2(RPC daemon,后台存活)。 lightkurve 等包内部自带 HTTP 会话,无法整体改走 aria2,故不统一传输层。
- 无中心化后台服务;TUI 直接读 DB + aria2 RPC。 执行基座异构(aria2 daemon + 各调用方进程),TUI 是统一观察面。
3. 总体结构¶
调用侧 观察侧
fetch(spec) = submit + wait myastro top(TUI, textual)
submit(manifest) ├─ jobs:live(aria2 RPC) + DB + 心跳/孤儿
│ ├─ storage:DB + 目录实扫核对
▼ └─ datasets:参数钻取
jobs.py 双执行器
├─ python:寄生调用方进程,心跳自报
└─ aria2:RPC daemon,后台存活
▼
astro.db(datasets / files / jobs)
后台存活性(文档级约定):
- aria2 执行器:调用方死了也活着,重启无损(aria2 控制文件)
- python 执行器:调用方活着才活着;心跳超时 → TUI 标 orphaned → retry 重新提交
4. 目录布局¶
~/myastro-data/ # data root(可放网络盘),MYASTRO_DATA 可覆盖
├── cache/ # 第三方包自身缓存(lightkurve/astropy 重定向目标)
└── <mission>/<target>/ # 我们管理的资产,按源/目标分
~/.local/state/myastro/astro.db # 元数据库:本地盘
重要:astro.db 不放 NFS。SQLite 锁与 WAL 共享内存在 NFS 上不可靠(官方警告,
症状为随机 database is locked 或静默损坏)。数据文件(顺序读写)在 NFS 无碍。
配置项:MYASTRO_DATA(默认 ~/myastro-data)、MYASTRO_DB(默认 ~/.local/state/myastro/astro.db)。
5. 数据库 schema(v8)¶
datasets (id, source, target, params_json, cache_key UNIQUE, created_at,
status, meta_json, -- v6:溯源 meta(缓存命中可回读)
ra, dec) -- v8:入库时解析的坐标 + 位置索引
files (id, dataset_id, path UNIQUE, size, sha256, remote_url,
fetched_at, format)
jobs (id, spec_json, executor 'python'|'aria2', url, dest,
status 'queued'|'running'|'verifying'|'done'|'failed'|'orphaned',
progress, speed, error, updated_at, -- updated_at 兼作心跳
started_at, finished_at, file_id, dataset_id, -- v2
gid, -- v3:aria2 句柄
batch_id, -- v4:批次单元
error_type) -- v7:失败类型(异常类名)
health (source PRIMARY KEY, status, detail, checked_at) -- v5
datasets.params_json:可复现性核心,记录全部查询参数- 迁移 v1→v8 逐版追加(
_MIGRATIONS);已发布的迁移不得修改—— 改历史迁移会让"新库"与"升级库"分叉(真实踩坑:把新列写进 v1 建表语句, 新库再跑 ALTER 直接 duplicate column) - 建表语句与迁移路径的一致性由
tests/kernel/test_db.py::test_fresh_schema_covers_all_code_columns守卫 - 各字段的处理语义(何时写、失败怎么办)见 processing.md §3/§4
5.1 路径不变量(2026-10-10 加)¶
任何落盘产物必须在数据根之下。 两道防线:
1. 输入即拒——会把参数拼进文件名/目录名的字段声明 FieldSpec.name_like(走
io/core.py::safe_name)或 pattern(如 brick 的 8 位格式);
2. 运行时兜底——jobs/store.py::_assert_paths_inside_data_root 在写库之前
校验任务返回的每个路径,覆盖"新源忘了标注"的情况。
背景:此前靠各处自行处理(tess 的 slug 挡不住 ..、mpc 只换空格、legacy 完全没处理),
brick="../../x" 实测能把产物写到数据根之外。
6. 核心组件(现状)¶
| 模块 | 职责 |
|---|---|
core.py |
重试(指数退避 1→60s×5,仅 5xx/网络错)、原子落盘、缓存键规范化 |
config.py |
数据根/DB 路径(MYASTRO_DATA/MYASTRO_DB)、目录初始化 |
db.py |
schema + 迁移(v1→v8) |
jobs/ |
作业基底的包:spec(契约/会话)、store(落库与缓存)、rebuild(历史行→spec)、aria2_exec(aria2 收账)、runner(python 执行/提交/fetch)、lifecycle(僵尸/重试/删除/批次)、query(状态/等待) |
coords.py |
坐标解析(params→meta→源 resolver→文件名兜底)与角距计算 |
index.py |
本地索引:两类来源、进程内缓存、bisect 前缀查询、精确键查询 |
search.py |
本地检索:源/目标/文件名/时间 + 精确圆锥坐标检索 |
integrity.py |
完整性巡检:sha 基线、损坏/缺失检测 |
health.py |
源健康探测(判级 + 落库 + 面板覆盖) |
schedule.py |
定时任务(launchd 代理生成/装卸) |
testmap.py |
测试能力地图生成(能力 → 测试) |
sources/ |
每源一模块:网络层 + 落盘 + 入库(_fn 供任务注册) |
urls.py |
aria2 型源的 URL/文件名派生(manifest 与 legacy/ztf 共用) |
top/ |
TUI(panes/screens/batchscreen/suggest/app),详见 design-top.md |
每个环节的处理规则与失败语义见 processing.md。
7. 支持的数据源与实现方式¶
7.1 矩阵总览¶
已实现清单的权威出处 =
io/sources/__init__.py的 SOURCES 注册表 (TUI info 面板即读它)。本矩阵是规划全集,状态列标注实现情况。
| 数据源 | 说明 | 状态 | 执行器 | 通道 | 产物 | TUI 下单 |
|---|---|---|---|---|---|---|
| tess | TESS/Kepler/K2 光变曲线 → parquet | ✅ 可用 | python · lightkurve | lightkurve | parquet | — |
| exoplanet | NASA Exoplanet Archive 凌星参数直查 | ✅ 可用 | python · astroquery.nexsci | astroquery.nexsci | dict | — |
| gaia | Gaia DR3 锥形星表切片 → parquet | ✅ 可用 | python · astroquery.gaia (ADQL) | astroquery.gaia (ADQL) | parquet | — |
| asassn | ASAS-SN Sky Patrol v2 变星光变 | ❌ 不可达 | python · pyasassn | pyasassn | parquet | — |
| ztf-meta | ZTF sci 帧元数据(视宁度/历元)→ parquet | ✅ 可用 | python · ibe HTTP | ibe HTTP | parquet | — |
| ztf | ZTF sci 图像单帧/多帧栈下载 | ✅ 可用 | aria2 · ibe HTTP | ibe HTTP | fits | ztf |
| legacy | Legacy Surveys DR10 tractor 星表(brick 切片) | ✅ 可用 | aria2 · NERSC HTTP | NERSC HTTP | fits | legacy |
| unwise | unWISE 中红外叠加切片 | ✅ 可用 | python · unwise.me cutout(requests + tarfile) | unwise.me cutout(requests + tarfile) | fits(含辅助) | — |
| manifest | 任意 URL 批量下载(manifest 提交) | ✅ 可用 | aria2 · yaml 清单 | yaml 清单 | 任意 | — |
| mpc | MPC 小行星/彗星星历(Horizons 替代) | ✅ 可用 | python · astroquery.mpc | astroquery.mpc | parquet | — |
| simbad | SIMBAD 锥搜:天区目标认证 | ✅ 可用 | python · astroquery.simbad | astroquery.simbad | parquet | — |
| vizier | VizieR 任意发表星表锥检 | ✅ 可用 | python · astroquery.vizier | astroquery.vizier | parquet | — |
| ztf-lc | ZTF 光变曲线(IRSA 凭据) | ✅ 可用 | python · ztfquery.lightcurve | ztfquery.lightcurve | parquet | — |
| horizons | JPL Horizons 星历 | ❌ 不可达 | python · — | — | — | — |
| (规划未实现,3 项) | ||||||
| panstarrs | Pan-STARRS DR2 stack cutout(MAST URL) | 未实现 | — | — | — | — |
| sdss | SDSS DR18 光谱/测光(astroquery.sdss) | 未实现 | — | — | — | — |
| skyfield | DE440s 历表本地化(一次性下载) | 未实现 | — | — | — | — |
7.2 逐源实现要点¶
tess.py — tess_lightcurve(target, sector=None, cadence='2m')
lightkurve search_lightcurve → 选扇区 → 下载(其缓存重定向到 cache/lightkurve/)→ 转 parquet 存 data/tess/<target>/ → 入库。params_json 记 target/sector/cadence/PDCSAP 版本。项目 1 主入口。
exoplanet.py — exoplanet_params(name)
NexSci Archive 查行星参数(周期、深度、T0),供折叠与 TLS 对比。量级 KB,纯查表。
gaia.py — gaia_cone(coord, radius, columns)
astroquery.gaia 同步 ADQL(小锥)或异步 job(大锥,Gaia 侧 TAP 异步,与我们的 job 模型两层,注意区分文档措辞)。产物 parquet。参考星选取/交叉匹配的基础。
asassn.py — asassn_lightcurve(target) / asassn_bulk(targets_file)
Sky Patrol v2 API。单目标走 python 执行器;成批目标(万级)导出为 URL 列表交 aria2。项目 2 主入口。
ztf.py — ztf_meta(coord, radius) / ztf_cutout(...) / ztf_stack(field, band, jd_range)
元数据与单帧 cutout 走 ztfquery(IRSA);多历元整栈(每帧 ~100MB,单天区 5–10GB)由 ztfquery 拿到 URL 后交 aria2 执行器。项目 3 主入口,最复杂,最后实现。
panstarrs.py — panstarrs_cutout(coord, size, band)
MAST PS1 cutout 服务返回直接 URL → aria2。参考图像与对照星来源。
unwise.py — unwise_cutout(coord, band)
unwise.me 切片 URL 生成 → aria2。红外波段补充。
mpc.py — mpc_orbit(target) / mpc_ephemeris(target, epochs)
astroquery.mpc + MPC Ephemeris API。项目 3 小行星星历交叉验证;未来 N.I.N.A 目标规划复用。
注意:原方案的 JPL Horizons(ssd.jpl.nasa.gov)经实测从本网络连接超时不可达,
故星历以 MPC API + skyfield 本地计算(DE440s 历表一次性下载后离线)双路实现,
Horizons 仅作为网络可达时的可选后备。2026-10 实测各源连通性:除 Horizons 外全部可达,TTFB 0.8–1.9s。
simbad.py / vizier.py —薄封装 astroquery 对应模块,目标认证与临时拉取任意星表。
sdss.py — sdss_spec(coords)
astroquery.sdss,光谱 lite 文件。可选练手。
legacy.py(urls.py 生成器) — legacy_tractor_tile(ra_hi) / legacy_coadd_tile(tile)
按 tile 命名规则生成 tractor 星表与 coadd 图 URL,manifest + aria2 批量拉。URL 生成器是免手写 URL 的关键。
skyfield 历表 — 首次使用自动下载 DE440s(~35MB)到 data root,之后永久缓存。
7.3 各源公共约定¶
- spec 必含:source、params(全参数)、executor、size_hint、dest 模板
- 缓存键 = (source, params_json 规范化哈希);命中即返回,不产生 job
- 限流从宽到严:MAST 正常、IRSA 保守(ztfquery 自带节流配置,透传)
- 凭据:数据本身均为公开档;IRSA(ZTF 元数据/图像)在本机配置了账号以提高配额,ztf-lc 的原生 API 不需要凭据(匿名配额是否够用未实测)
8. TUI(myastro top)¶
见 design-top.md(单一出处,避免两处维护): 三面板 0 数据源 / 1 作业 / 2 存储,模态 F1 新建 / F2 预填充 / F12 批量, 字段标签 + 原生 suggester 补全(→ 接受),身份寻址的光标/详情契约。
9. 测试与验收¶
分层与纪律见 testing.md,能力→测试映射见 test-map.md。
现状:240+ 用例,六层(unit/kernel/sources/facade/ui/guards),全量离线可跑;
push 前由 githooks/pre-push 跑全量门(pytest + pyflakes)。
第一阶段的三条验收(同参数两次只产生一次网络请求;断网重试可恢复; 删文件后库能报告缺失)已由内核层测试长期守着。
10. 实施顺序(第一阶段历史记录,已完成)¶
- core.py + db.py
- sources/tess.py(项目 1 急用,python 执行器路径打通)
- gaia.py、asassn.py(项目 2)
- jobs.py aria2 执行器 + urls.py + ztf.py(项目 3,批量场景真实出现时)
- top.py(TUI,有了真实下载活动才有意义)
每步都是"要用的先写",不预建没人用的部件。