mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: sync research artifact OpenAPI contract
This commit is contained in:
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- [修复] 同步 ResearchArtifact 与 `AnalysisReport.structured_report` 到静态 OpenAPI,避免公开 API 规格与运行时契约漂移。
|
||||
- [新功能] 支持通过 `main.py --stocks` 一次性分析已登记板块指数,自动使用指数适用的数据与分析能力,并保持报告、历史和决策信号兼容。
|
||||
- [修复] `main.py --stocks` 在解析股票列表前先 best-effort 刷新股票索引注册表,保证首次运行能吃到刷新后的指数 alias/身份;刷新失败、超时或禁用不阻断分析。
|
||||
- [修复] 交易日过滤对市场未知的指数 code(如 `sh000016`/`csi930955`/`930955.CSI`)按 `market=cn` 参与 A 股休市过滤,避免休市日指数被 fail-open 保留;市场仍未知的非指数 code 继续保留。
|
||||
|
||||
@@ -3419,6 +3419,17 @@
|
||||
"description": "市场结构上下文(题材主线与个股市场位置信息,JSON)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"structured_report": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ResearchArtifact"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "结构化研究产物,供看板、个股研究页、监控和 Copilot 复用"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -7805,6 +7816,552 @@
|
||||
],
|
||||
"title": "DataProviderCapability",
|
||||
"description": "Visible capability metadata for one data provider."
|
||||
},
|
||||
"ResearchArtifact": {
|
||||
"properties": {
|
||||
"schema_version": {
|
||||
"type": "string",
|
||||
"const": "research-artifact-v1",
|
||||
"title": "Schema Version",
|
||||
"default": "research-artifact-v1"
|
||||
},
|
||||
"artifact_id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Artifact Id"
|
||||
},
|
||||
"source_report_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Source Report Id"
|
||||
},
|
||||
"source_query_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Source Query Id"
|
||||
},
|
||||
"created_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Created At"
|
||||
},
|
||||
"subject": {
|
||||
"$ref": "#/components/schemas/ResearchSubject"
|
||||
},
|
||||
"thesis": {
|
||||
"$ref": "#/components/schemas/ResearchThesis"
|
||||
},
|
||||
"evidence": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ResearchEvidenceItem"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Evidence"
|
||||
},
|
||||
"invalidation_conditions": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ResearchInvalidationCondition"
|
||||
},
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"title": "Invalidation Conditions"
|
||||
},
|
||||
"next_actions": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ResearchNextAction"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Next Actions"
|
||||
},
|
||||
"data_quality": {
|
||||
"$ref": "#/components/schemas/ResearchDataQuality"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Metadata"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"artifact_id",
|
||||
"subject",
|
||||
"thesis",
|
||||
"invalidation_conditions"
|
||||
],
|
||||
"title": "ResearchArtifact",
|
||||
"description": "Structured report artifact for dashboard, stock detail, monitor and copilot reuse."
|
||||
},
|
||||
"ResearchDataQuality": {
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"good",
|
||||
"usable",
|
||||
"limited",
|
||||
"poor",
|
||||
"unknown"
|
||||
],
|
||||
"title": "Level",
|
||||
"default": "unknown"
|
||||
},
|
||||
"overall_score": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"maximum": 100.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Overall Score"
|
||||
},
|
||||
"source_count": {
|
||||
"type": "integer",
|
||||
"minimum": 0.0,
|
||||
"title": "Source Count",
|
||||
"default": 0
|
||||
},
|
||||
"stale_count": {
|
||||
"type": "integer",
|
||||
"minimum": 0.0,
|
||||
"title": "Stale Count",
|
||||
"default": 0
|
||||
},
|
||||
"missing_blocks": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Missing Blocks"
|
||||
},
|
||||
"limitations": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Limitations"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ResearchDataQuality",
|
||||
"description": "Low-sensitive quality summary for the artifact inputs."
|
||||
},
|
||||
"ResearchEvidenceItem": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Id"
|
||||
},
|
||||
"source_type": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Source Type"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Title"
|
||||
},
|
||||
"summary": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Summary"
|
||||
},
|
||||
"source": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Source"
|
||||
},
|
||||
"freshness": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fresh",
|
||||
"stale",
|
||||
"unknown"
|
||||
],
|
||||
"title": "Freshness",
|
||||
"default": "unknown"
|
||||
},
|
||||
"quality_level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"good",
|
||||
"usable",
|
||||
"limited",
|
||||
"poor",
|
||||
"unknown"
|
||||
],
|
||||
"title": "Quality Level",
|
||||
"default": "unknown"
|
||||
},
|
||||
"as_of": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "As Of"
|
||||
},
|
||||
"url": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Url"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Metadata"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"source_type",
|
||||
"title"
|
||||
],
|
||||
"title": "ResearchEvidenceItem",
|
||||
"description": "One evidence item with freshness and quality status."
|
||||
},
|
||||
"ResearchInvalidationCondition": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Id"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"price",
|
||||
"volume",
|
||||
"evidence",
|
||||
"market",
|
||||
"time",
|
||||
"data_quality",
|
||||
"manual"
|
||||
],
|
||||
"title": "Category"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Description"
|
||||
},
|
||||
"trigger": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Trigger"
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"watch",
|
||||
"warning",
|
||||
"critical"
|
||||
],
|
||||
"title": "Severity",
|
||||
"default": "warning"
|
||||
},
|
||||
"metric": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Metric"
|
||||
},
|
||||
"threshold": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Threshold"
|
||||
},
|
||||
"due_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Due At"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Metadata"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"category",
|
||||
"description"
|
||||
],
|
||||
"title": "ResearchInvalidationCondition",
|
||||
"description": "Condition that should invalidate or force reassessment of the thesis."
|
||||
},
|
||||
"ResearchNextAction": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Action"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Label"
|
||||
},
|
||||
"reason": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Reason"
|
||||
},
|
||||
"due_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Due At"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Metadata"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"action",
|
||||
"label"
|
||||
],
|
||||
"title": "ResearchNextAction",
|
||||
"description": "Suggested next action for a human or workflow."
|
||||
},
|
||||
"ResearchSubject": {
|
||||
"properties": {
|
||||
"stock_code": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"title": "Stock Code"
|
||||
},
|
||||
"stock_name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Stock Name"
|
||||
},
|
||||
"market": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Market"
|
||||
},
|
||||
"entity_ref": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Entity Ref",
|
||||
"description": "Optional EntityLink ref when available"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"stock_code"
|
||||
],
|
||||
"title": "ResearchSubject",
|
||||
"description": "The entity being researched."
|
||||
},
|
||||
"ResearchThesis": {
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"bullish",
|
||||
"bearish",
|
||||
"neutral",
|
||||
"unknown"
|
||||
],
|
||||
"title": "Direction",
|
||||
"default": "unknown"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"title": "Summary",
|
||||
"description": "Concise thesis statement",
|
||||
"default": ""
|
||||
},
|
||||
"confidence": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number",
|
||||
"maximum": 1.0,
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Confidence"
|
||||
},
|
||||
"score": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Score",
|
||||
"description": "Original sentiment score when available"
|
||||
},
|
||||
"horizon": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Horizon"
|
||||
},
|
||||
"action": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"buy",
|
||||
"add",
|
||||
"hold",
|
||||
"reduce",
|
||||
"sell",
|
||||
"watch",
|
||||
"avoid",
|
||||
"alert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Action"
|
||||
},
|
||||
"action_label": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Action Label"
|
||||
},
|
||||
"reasons": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Reasons"
|
||||
},
|
||||
"risks": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Risks"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ResearchThesis",
|
||||
"description": "Structured investment thesis extracted from a report."
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
|
||||
@@ -71,6 +71,15 @@ DATA_CAPABILITY_SCHEMAS = (
|
||||
"DataPriorityView",
|
||||
"DataProviderCapability",
|
||||
)
|
||||
RESEARCH_ARTIFACT_SCHEMAS = (
|
||||
"ResearchArtifact",
|
||||
"ResearchDataQuality",
|
||||
"ResearchEvidenceItem",
|
||||
"ResearchInvalidationCondition",
|
||||
"ResearchNextAction",
|
||||
"ResearchSubject",
|
||||
"ResearchThesis",
|
||||
)
|
||||
|
||||
|
||||
def _collect_component_schema_refs(node: Any) -> set[str]:
|
||||
@@ -245,6 +254,15 @@ def test_decision_signal_static_api_spec_matches_runtime_paths() -> None:
|
||||
assert static_spec["paths"][path] == runtime_spec["paths"][path]
|
||||
for schema_name in DATA_CAPABILITY_SCHEMAS:
|
||||
assert static_spec["components"]["schemas"][schema_name] == runtime_spec["components"]["schemas"][schema_name]
|
||||
for schema_name in RESEARCH_ARTIFACT_SCHEMAS:
|
||||
assert (
|
||||
static_spec["components"]["schemas"][schema_name]
|
||||
== runtime_spec["components"]["schemas"][schema_name]
|
||||
)
|
||||
assert (
|
||||
static_spec["components"]["schemas"]["AnalysisReport"]["properties"]["structured_report"]
|
||||
== runtime_spec["components"]["schemas"]["AnalysisReport"]["properties"]["structured_report"]
|
||||
)
|
||||
schema_refs = _collect_component_schema_refs(static_spec)
|
||||
missing_schema_refs = sorted(schema_refs - set(static_spec["components"]["schemas"]))
|
||||
assert missing_schema_refs == []
|
||||
|
||||
Reference in New Issue
Block a user