数据分析层(新增 services/{spectrum,timeseries,analysis}):
- 光谱参数提取 parameters.rs:LAMOST/SDSS/APOGEE/DESI FITS header 跨源归一化读取
Teff/logg/[Fe/H]/RV 及 ASPCAP 20+ 元素丰度,rayon 并发批量提取
- 谱线测量 lines.rs:内置真空/空气波长谱线表,窗口内极值搜索 + 梯形法积分 EW + FWHM,支持自定义谱线
- 交叉相关测速 cross_correlate.rs:对数波长重采样对齐,内置 Pickles 模板按光谱型插值,
CCF 峰值位置提取 RV 及不确定度
- 周期搜索 periodicity.rs:Lomb-Scargle 周期图(含 FAP 误报概率)+ BLS 凌星检测 + 相位折叠
- 变星分类 classification.rs:振幅/偏度/峰度/过零率/eta 等统计特征 + 规则分类(RR Lyrae/Cepheid/食双星/AGN 等)
- SED 拟合 sed.rs:多波段测光黑体模型拟合,输出 T_eff/半径/消光 A_V/光度及不确定度
- 运动学 kinematics.rs:视差+自行+RV → 银河系 UVW 空间速度,含移动星群成员概率(Banyan Σ 简化版)
- 化学丰度 chemistry.rs:[α/Fe] vs [Fe/H] 计算,厚盘/薄盘/晕星族判别
- 观测规划 observability.rs:目标升落时间/airmass/月相影响/曝光时间估算
- 赫罗图 hr_diagram.rs:Gaia TAP CMD 查询,新增 GET /api/analysis/hr-diagram 端点
数据获取层:
- JWST:clients/mast/jwst.rs 封装 MAST Portal 锥形检索 + JwstSpectrumFetcher(NIRSpec/MIRI 光谱)
- X 射线:clients/heasarc 封装 HEASARC TAP(ADQL)+ XMM-Newton/Chandra 光谱 fetcher
- 图像 cutout:SDSS SkyServer/STScI DSS/Pan-STARRS 三源 cutout + 发现图(Finding Chart)生成
- Source 枚举新增 Jwst/Xmm/Chandra 并注册 ObservationRegistry,前端 SOURCE_THEME 与筛选器同步三源
Agent 工具集(24→35):
- 新增 9 个分析工具:get_spectrum_parameters / measure_spectral_lines / measure_radial_velocity /
find_period / classify_variable_star / fit_sed / analyze_kinematics / analyze_abundance_pattern / plan_observation
- batch_process:批量样本"查询→下载→分析→报告"流水线,并发控制防数据源速率限制
- literature_monitor:按 ADS 查询式/时间窗/最低引用数检查最新文献
定时文献同步:
- sync_queries 表新增 is_scheduled 列(migration 20260713)
- 新增 POST /sync/queries/:id/schedule 端点
- 服务启动时拉起每小时调度器,对 is_scheduled=1 的检索配置静默执行 ADS(entdate 增量)/arXiv 增量同步
- search_history 工具收敛至 services/search::search_agent_history,消除 FTS 查询逻辑重复
其他:
- plotting skill 由占位填充为完整科研绘图规范:光谱/光变/折叠曲线/CMD/SED/[α/Fe]/周期图/Mollweide/发现图 9 类 matplotlib 模板
- 删除死代码 streaming_executor.rs(929 行,仅剩 mod 声明引用,无调用方)
- 新增 docs/roadmap-research-features.md 科研功能路线图及实现状态
147 lines
4.2 KiB
Rust
147 lines
4.2 KiB
Rust
// src/api/catalog.rs
|
||
//
|
||
// 天文星表查询 HTTP 处理器 —— VizieR TAP + Cone Search
|
||
// 对齐 targets.rs 的 handler 范式:State + Query/Json 参数 → service 调用 → ApiResult<Json<...>>
|
||
|
||
use axum::extract::{Query, State};
|
||
use axum::Json;
|
||
use serde::{Deserialize, Serialize};
|
||
|
||
use super::error::{ApiResult, AppError};
|
||
use super::AppState;
|
||
use crate::clients::cds::vizier::VizierQueryResult;
|
||
use crate::services::analysis::hr_diagram::{build_hr_diagram, HrDiagramParams, HrDiagramResponse};
|
||
|
||
// ── 请求参数 ──
|
||
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct VizierQueryParams {
|
||
/// 自由 ADQL 查询语句
|
||
pub adql: String,
|
||
/// 最大返回行数(默认 50,上限 2000)
|
||
pub max_records: Option<i64>,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct VizierTableParams {
|
||
/// VizieR 表名,如 "I/355/gaiadr3"
|
||
pub table: String,
|
||
/// 列名(逗号分隔,为空时取 *)
|
||
pub columns: Option<String>,
|
||
/// 最大返回行数(默认 50,上限 2000)
|
||
pub limit: Option<i64>,
|
||
}
|
||
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct ConeSearchParams {
|
||
/// RA 坐标(度)
|
||
pub ra: f64,
|
||
/// Dec 坐标(度)
|
||
pub dec: f64,
|
||
/// 检索半径(度,0~20)
|
||
pub radius: Option<f64>,
|
||
/// 目标星表(必填,如 "I/355/gaiadr3")
|
||
pub table: String,
|
||
/// 最大返回行数(默认 50,上限 2000)
|
||
pub max_records: Option<i64>,
|
||
/// 返回策略:"nearest"(按角距离排序,返回最近的)或 "all"(无序返回全部),默认 nearest
|
||
pub strategy: Option<String>,
|
||
}
|
||
|
||
// ── 响应封装 ──
|
||
|
||
#[derive(Debug, Serialize)]
|
||
pub struct CachedResult {
|
||
#[serde(flatten)]
|
||
pub result: VizierQueryResult,
|
||
/// 是否来自缓存
|
||
pub from_cache: Option<bool>,
|
||
}
|
||
|
||
const DEFAULT_MAX: i64 = 50;
|
||
const MAX_LIMIT: i64 = 2000;
|
||
|
||
fn clamp_max(v: Option<i64>) -> i64 {
|
||
v.unwrap_or(DEFAULT_MAX).clamp(1, MAX_LIMIT)
|
||
}
|
||
|
||
// ── 处理器 ──
|
||
|
||
/// GET /api/catalog/vizier —— 自由 ADQL 查询
|
||
pub async fn vizier_query(
|
||
State(state): State<std::sync::Arc<AppState>>,
|
||
Query(params): Query<VizierQueryParams>,
|
||
) -> ApiResult<Json<VizierQueryResult>> {
|
||
let max_records = clamp_max(params.max_records);
|
||
let result = crate::services::cds::vizier::query_adql_cached(
|
||
&state.db,
|
||
&state.sources.vizier,
|
||
¶ms.adql,
|
||
max_records,
|
||
)
|
||
.await
|
||
.map_err(|e| {
|
||
tracing::error!("VizieR 查询失败: {}", e);
|
||
AppError::internal(format!("VizieR 查询失败: {}", e))
|
||
})?;
|
||
Ok(Json(result))
|
||
}
|
||
|
||
/// GET /api/catalog/vizier/table —— 按表名便捷查询
|
||
pub async fn vizier_table(
|
||
State(state): State<std::sync::Arc<AppState>>,
|
||
Query(params): Query<VizierTableParams>,
|
||
) -> ApiResult<Json<VizierQueryResult>> {
|
||
let limit = clamp_max(params.limit);
|
||
let columns: Vec<String> = params
|
||
.columns
|
||
.as_deref()
|
||
.map(|c| c.split(',').map(|s| s.trim().to_string()).collect())
|
||
.unwrap_or_default();
|
||
|
||
let result = crate::services::cds::vizier::query_table(
|
||
&state.db,
|
||
&state.sources.vizier,
|
||
¶ms.table,
|
||
&columns,
|
||
limit,
|
||
)
|
||
.await?;
|
||
Ok(Json(result))
|
||
}
|
||
|
||
/// GET /api/catalog/cone —— 锥形检索
|
||
pub async fn cone_search(
|
||
State(state): State<std::sync::Arc<AppState>>,
|
||
Query(params): Query<ConeSearchParams>,
|
||
) -> ApiResult<Json<VizierQueryResult>> {
|
||
let radius = params.radius.unwrap_or(0.1);
|
||
let max_records = clamp_max(params.max_records);
|
||
let nearest = params.strategy.as_deref() != Some("all");
|
||
|
||
let result = crate::services::cds::vizier::cone_search(
|
||
&state.db,
|
||
&state.sources.vizier,
|
||
params.ra,
|
||
params.dec,
|
||
radius,
|
||
¶ms.table,
|
||
max_records,
|
||
nearest,
|
||
)
|
||
.await?;
|
||
Ok(Json(result))
|
||
}
|
||
|
||
/// GET /api/analysis/hr-diagram —— 赫罗图构建
|
||
pub async fn hr_diagram(
|
||
State(state): State<std::sync::Arc<AppState>>,
|
||
Query(params): Query<HrDiagramParams>,
|
||
) -> ApiResult<Json<HrDiagramResponse>> {
|
||
let response = build_hr_diagram(&state.sources.gaia, ¶ms)
|
||
.await
|
||
.map_err(|e| AppError::internal(format!("构建赫罗图失败: {}", e)))?;
|
||
|
||
Ok(Json(response))
|
||
}
|