diff --git a/commit.sh b/commit.sh
deleted file mode 100644
index e69de29b..00000000
diff --git a/compress-images.sh b/compress-images.sh
deleted file mode 100755
index 904ecbc0..00000000
--- a/compress-images.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/bash
-
-# 需要压缩的大图片列表
-IMAGES=(
- "CoverImage.png"
- "BasicImage.png"
- "teams-image.png"
- "hand-background.png"
- "basic-auth.png"
- "BgMusicCard.png"
- "Landing2.png"
- "Landing3.png"
- "Landing1.png"
- "smart-home.png"
- "automotive-background-card.png"
-)
-
-IMG_DIR="src/assets/img"
-BACKUP_DIR="$IMG_DIR/original-backup"
-
-echo "🎨 开始优化图片..."
-echo "================================"
-
-total_before=0
-total_after=0
-
-for img in "${IMAGES[@]}"; do
- src_path="$IMG_DIR/$img"
-
- if [ ! -f "$src_path" ]; then
- echo "⚠️ 跳过: $img (文件不存在)"
- continue
- fi
-
- # 备份原图
- cp "$src_path" "$BACKUP_DIR/$img"
-
- # 获取原始大小
- before=$(stat -f%z "$src_path" 2>/dev/null || stat -c%s "$src_path" 2>/dev/null)
- before_kb=$((before / 1024))
- total_before=$((total_before + before))
-
- # 使用sips压缩图片 (降低质量到75, 减少分辨率如果太大)
- # 获取图片尺寸
- width=$(sips -g pixelWidth "$src_path" | grep "pixelWidth:" | awk '{print $2}')
-
- # 如果宽度大于2000px,缩小到2000px
- if [ "$width" -gt 2000 ]; then
- sips -Z 2000 "$src_path" > /dev/null 2>&1
- fi
-
- # 获取压缩后大小
- after=$(stat -f%z "$src_path" 2>/dev/null || stat -c%s "$src_path" 2>/dev/null)
- after_kb=$((after / 1024))
- total_after=$((total_after + after))
-
- # 计算节省
- saved=$((before - after))
- saved_kb=$((saved / 1024))
- percent=$((100 - (after * 100 / before)))
-
- echo "✅ $img"
- echo " ${before_kb} KB → ${after_kb} KB (⬇️ ${saved_kb} KB, -${percent}%)"
-done
-
-echo ""
-echo "================================"
-echo "📊 总计优化:"
-total_before_mb=$((total_before / 1024 / 1024))
-total_after_mb=$((total_after / 1024 / 1024))
-total_saved=$((total_before - total_after))
-total_saved_mb=$((total_saved / 1024 / 1024))
-total_percent=$((100 - (total_after * 100 / total_before)))
-
-echo " 优化前: ${total_before_mb} MB"
-echo " 优化后: ${total_after_mb} MB"
-echo " 节省: ${total_saved_mb} MB (-${total_percent}%)"
-echo ""
-echo "✅ 图片优化完成!"
-echo "📁 原始文件已备份到: $BACKUP_DIR"
diff --git a/concept_api.py b/concept_api.py
deleted file mode 100644
index 1370346d..00000000
--- a/concept_api.py
+++ /dev/null
@@ -1,1562 +0,0 @@
-import json
-import openai
-from typing import List, Dict, Optional, Union, Any
-from fastapi import FastAPI, HTTPException, Query
-from fastapi.middleware.cors import CORSMiddleware
-from pydantic import BaseModel, Field
-from elasticsearch import Elasticsearch
-from datetime import datetime, date
-import logging
-import re
-from contextlib import asynccontextmanager
-import aiomysql
-from decimal import Decimal
-
-# 配置日志
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-# 全局变量
-es_client = None
-openai_client = None
-mysql_pool = None
-
-# 配置
-ES_HOST = 'http://127.0.0.1:9200'
-OPENAI_BASE_URL = "http://127.0.0.1:8000/v1"
-OPENAI_API_KEY = "dummy"
-EMBEDDING_MODEL = "qwen3-embedding-8b"
-INDEX_NAME = 'concept_library'
-
-# MySQL配置
-MYSQL_CONFIG = {
- 'host': '192.168.1.8',
- 'user': 'root',
- 'password': 'Zzl5588161!',
- 'db': 'stock',
- 'charset': 'utf8mb4',
- 'autocommit': True,
- 'minsize': 1,
- 'maxsize': 10
-}
-
-
-# API生命周期管理
-@asynccontextmanager
-async def lifespan(app: FastAPI):
- # 启动时初始化
- global es_client, openai_client, mysql_pool
-
- # 初始化Elasticsearch客户端 - 增加超时配置
- es_client = Elasticsearch(
- [ES_HOST],
- timeout=30,
- max_retries=3,
- retry_on_timeout=True
- )
- logger.info(f"Connected to Elasticsearch at {ES_HOST}")
-
- # 初始化OpenAI客户端
- openai_client = openai.OpenAI(
- api_key=OPENAI_API_KEY,
- base_url=OPENAI_BASE_URL,
- timeout=60,
- )
- logger.info(f"Initialized OpenAI client")
-
- # 初始化MySQL连接池
- try:
- mysql_pool = await aiomysql.create_pool(**MYSQL_CONFIG)
- logger.info(f"Connected to MySQL at {MYSQL_CONFIG['host']}")
- except Exception as e:
- logger.error(f"Failed to connect to MySQL: {e}")
-
- yield
-
- # 关闭时清理资源
- if es_client:
- es_client.close()
- if mysql_pool:
- mysql_pool.close()
- await mysql_pool.wait_closed()
- logger.info("Cleanup completed")
-
-
-# 创建FastAPI应用
-app = FastAPI(
- title="概念搜索API",
- description="支持语义和关键词混合搜索的概念库API,包含概念涨跌幅数据",
- version="1.2.0",
- lifespan=lifespan
-)
-
-
-# 添加CORS中间件
-app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_credentials=True,
- allow_methods=["*"],
- allow_headers=["*"],
-)
-
-
-# 请求和响应模型
-class SearchRequest(BaseModel):
- query: str = Field(..., description="搜索查询文本")
- size: int = Field(10, ge=1, le=100, description="每页返回结果数量")
- page: int = Field(1, ge=1, description="页码")
- search_size: int = Field(100, ge=10, le=1000, description="搜索数量(从ES获取的结果数),用于排序后分页")
- semantic_weight: Optional[float] = Field(None, ge=0.0, le=1.0, description="语义搜索权重(0-1),None表示自动计算")
- filter_stocks: Optional[List[str]] = Field(None, description="过滤特定股票代码或名称")
- trade_date: Optional[date] = Field(None, description="交易日期,格式:YYYY-MM-DD,默认返回最新日期数据")
- sort_by: str = Field("change_pct", description="排序方式: change_pct, _score, stock_count, concept_name, added_date")
- use_knn: bool = Field(True, description="是否使用KNN搜索优化语义搜索")
-
-
-class StockInfo(BaseModel):
- stock_name: str
- stock_code: str
- reason: Optional[str] = None
- industry: Optional[str] = Field(None, alias="行业")
- project: Optional[str] = Field(None, alias="项目")
-
- class Config:
- populate_by_name = True
-
-
-class ConceptPriceInfo(BaseModel):
- trade_date: date
- avg_change_pct: Optional[float] = Field(None, description="平均涨跌幅(%)")
-
-
-class ConceptResult(BaseModel):
- concept_id: str
- concept: str
- description: Optional[str]
- stocks: List[StockInfo]
- stock_count: int
- happened_times: Optional[List[str]] = None
- score: float
- match_type: str # "semantic", "keyword", "hybrid", "semantic_knn", "hybrid_knn"
- highlights: Optional[Dict[str, List[str]]] = None
- price_info: Optional[ConceptPriceInfo] = None
-
-
-class SearchResponse(BaseModel):
- total: int
- took_ms: int
- results: List[ConceptResult]
- search_info: Dict[str, Any]
- price_date: Optional[date] = None
- page: int = Field(1, description="当前页码")
- total_pages: int = Field(1, description="总页数")
-
-
-class StockConceptInfo(BaseModel):
- concept_id: str
- concept: str
- stock_count: int
- happened_times: Optional[List[str]] = None
- description: Optional[str] = None
- stock_detail: Optional[Dict[str, Any]] = None
- price_info: Optional[ConceptPriceInfo] = None
-
-
-class StockConceptsResponse(BaseModel):
- stock_code: str
- stats: Dict[str, Any]
- concepts: List[StockConceptInfo]
- price_date: Optional[date] = None
-
-
-class StockSearchResult(BaseModel):
- stock_code: str
- stock_name: str
- concept_count: int
-
-
-class StockSearchResponse(BaseModel):
- total: int
- stocks: List[StockSearchResult]
-
-
-class PriceTimeSeriesItem(BaseModel):
- trade_date: date
- avg_change_pct: Optional[float] = Field(None, description="平均涨跌幅(%)")
- stock_count: Optional[int] = Field(None, description="当日股票数量")
-
-
-class PriceTimeSeriesResponse(BaseModel):
- concept_id: str
- concept_name: str
- start_date: date
- end_date: date
- data_points: int
- timeseries: List[PriceTimeSeriesItem]
-
-
-# 辅助函数
-async def get_concept_price_data(concept_ids: List[str], trade_date: Optional[date] = None) -> Dict[
- str, ConceptPriceInfo]:
- """获取概念的涨跌幅数据"""
- if not mysql_pool or not concept_ids:
- return {}
-
- try:
- async with mysql_pool.acquire() as conn:
- async with conn.cursor(aiomysql.DictCursor) as cursor:
- placeholders = ','.join(['%s'] * len(concept_ids))
-
- if trade_date:
- query = f"""
- SELECT concept_id, concept_name, trade_date, avg_change_pct
- FROM concept_daily_stats
- WHERE concept_id IN ({placeholders}) AND trade_date = %s
- """
- await cursor.execute(query, (*concept_ids, trade_date))
- else:
- query = f"""
- SELECT cds.concept_id, cds.concept_name, cds.trade_date, cds.avg_change_pct
- FROM concept_daily_stats cds
- INNER JOIN (
- SELECT concept_id, MAX(trade_date) as max_date
- FROM concept_daily_stats
- WHERE concept_id IN ({placeholders})
- GROUP BY concept_id
- ) latest ON cds.concept_id = latest.concept_id
- AND cds.trade_date = latest.max_date
- """
- await cursor.execute(query, concept_ids)
-
- rows = await cursor.fetchall()
-
- result = {}
- for row in rows:
- result[row['concept_id']] = ConceptPriceInfo(
- trade_date=row['trade_date'],
- avg_change_pct=float(row['avg_change_pct']) if row['avg_change_pct'] is not None else None
- )
-
- return result
- except Exception as e:
- logger.error(f"Error fetching concept price data: {e}")
- return {}
-
-
-async def get_latest_trade_date() -> Optional[date]:
- """获取最新的交易日期"""
- if not mysql_pool:
- return None
-
- try:
- async with mysql_pool.acquire() as conn:
- async with conn.cursor() as cursor:
- query = "SELECT MAX(trade_date) as latest_date FROM concept_daily_stats"
- await cursor.execute(query)
- result = await cursor.fetchone()
- return result[0] if result and result[0] else None
- except Exception as e:
- logger.error(f"Error fetching latest trade date: {e}")
- return None
-
-
-def generate_embedding(text: str) -> List[float]:
- """生成文本向量"""
- try:
- if not text or len(text.strip()) == 0:
- logger.warning("Empty text provided for embedding generation")
- return []
-
- # 限制文本长度
- text = text[:16000] if len(text) > 16000 else text
-
- # 检查OpenAI客户端是否已初始化
- if not openai_client:
- logger.warning("OpenAI client not initialized, falling back to keyword search")
- return []
-
- response = openai_client.embeddings.create(
- model=EMBEDDING_MODEL,
- input=[text]
- )
-
- embedding = response.data[0].embedding
-
- # 验证embedding是否有效
- if not embedding or len(embedding) == 0:
- logger.warning("Empty embedding returned from OpenAI API")
- return []
-
- logger.debug(f"Successfully generated embedding for text: {text[:100]}...")
- return embedding
-
- except openai.OpenAIError as e:
- logger.warning(f"OpenAI API error during embedding generation: {e}. Falling back to keyword search.")
- return []
- except Exception as e:
- logger.warning(f"Unexpected error during embedding generation: {e}. Falling back to keyword search.")
- return []
-
-
-def calculate_semantic_weight(query: str) -> float:
- """根据查询长度动态计算语义权重"""
- base_weight = 0.3
- query_length = len(query)
- word_count = len(query.split())
-
- if query_length < 10:
- length_factor = 0.0
- elif query_length < 50:
- length_factor = 0.2
- elif query_length < 200:
- length_factor = 0.4
- elif query_length < 500:
- length_factor = 0.5
- else:
- length_factor = 0.6
-
- if word_count < 3:
- word_factor = 0.0
- elif word_count < 10:
- word_factor = 0.2
- elif word_count < 30:
- word_factor = 0.3
- else:
- word_factor = 0.4
-
- if re.search(r'\b\d{6}\b', query):
- pattern_factor = -0.2
- elif word_count <= 2 and query_length < 20:
- pattern_factor = -0.1
- elif '。' in query or ',' in query or len(query) > 100:
- pattern_factor = 0.2
- else:
- pattern_factor = 0.0
-
- semantic_weight = base_weight + max(length_factor, word_factor) + pattern_factor
- semantic_weight = max(0.3, min(0.9, semantic_weight))
-
- return semantic_weight
-
-
-def extract_stock_filter(query: str) -> tuple[str, List[str]]:
- """从查询中提取股票过滤条件"""
- stock_patterns = []
- cleaned_query = query
-
- code_matches = re.findall(r'\b(\d{6})\b', query)
- stock_patterns.extend(code_matches)
-
- stock_name_pattern = r'([\u4e00-\u9fa5]{2,6})(?:股票|股份|集团|公司)'
- name_matches = re.findall(stock_name_pattern, query)
- stock_patterns.extend(name_matches)
-
- for pattern in stock_patterns:
- cleaned_query = cleaned_query.replace(pattern, '')
-
- cleaned_query = ' '.join(cleaned_query.split())
-
- return cleaned_query, stock_patterns
-
-
-def build_keyword_query(query: str, stock_filters: List[str] = None) -> Dict:
- """构建关键词查询"""
- must_queries = []
-
- if query.strip():
- must_queries.append({
- "multi_match": {
- "query": query,
- "fields": [
- "concept^3",
- "description^2",
- "stocks_reason",
- "stocks_full_text",
- "stocks.stock_name^2",
- "stocks.stock_code^2",
- "stocks.项目",
- "stocks.reason",
- "stocks.行业"
- ],
- "type": "best_fields",
- "analyzer": "ik_smart"
- }
- })
-
- if stock_filters:
- stock_query = {
- "nested": {
- "path": "stocks",
- "query": {
- "bool": {
- "should": [
- {"terms": {"stocks.stock_name": stock_filters}},
- {"terms": {"stocks.stock_code": stock_filters}}
- ]
- }
- }
- }
- }
- must_queries.append(stock_query)
-
- return {
- "bool": {
- "must": must_queries
- }
- }
-
-
-def build_semantic_query(embedding: List[float]) -> Dict:
- """构建语义查询"""
- return {
- "script_score": {
- "query": {"match_all": {}},
- "script": {
- "source": "cosineSimilarity(params.query_vector, 'description_embedding') + 1.0",
- "params": {
- "query_vector": embedding
- }
- }
- }
- }
-
-
-def build_hybrid_query(query: str, embedding: List[float], semantic_weight: float,
- stock_filters: List[str] = None) -> Dict:
- """构建混合查询"""
- keyword_weight = 1.0 - semantic_weight
- queries = []
-
- if keyword_weight > 0:
- keyword_query = build_keyword_query(query, stock_filters)
- queries.append({
- "bool": {
- "must": [keyword_query],
- "boost": keyword_weight
- }
- })
-
- if semantic_weight > 0 and embedding:
- queries.append({
- "script_score": {
- "query": {"match_all": {}},
- "script": {
- "source": f"cosineSimilarity(params.query_vector, 'description_embedding') * {semantic_weight} + 0.0",
- "params": {
- "query_vector": embedding
- }
- },
- "boost": 1.0
- }
- })
-
- return {
- "bool": {
- "should": queries,
- "minimum_should_match": 1
- }
- }
-
-
-def build_hybrid_knn_query(
- query: str,
- embedding: List[float],
- semantic_weight: float,
- stock_filters: List[str] = None,
- k: int = 100
-) -> Dict:
- """构建混合查询(KNN + 关键词)"""
- keyword_weight = 1.0 - semantic_weight
-
- filter_query = None
- if stock_filters:
- filter_query = {
- "nested": {
- "path": "stocks",
- "query": {
- "bool": {
- "should": [
- {"terms": {"stocks.stock_name": stock_filters}},
- {"terms": {"stocks.stock_code": stock_filters}}
- ]
- }
- }
- }
- }
-
- search_body = {
- "knn": {
- "field": "description_embedding",
- "query_vector": embedding,
- "k": k,
- "num_candidates": max(k + 50, min(k * 2, 10000)), # 确保 num_candidates > k,最大 10000
- "boost": semantic_weight
- }
- }
-
- if filter_query:
- search_body["knn"]["filter"] = filter_query
-
- keyword_query = build_keyword_query(query, stock_filters)
- search_body["query"] = {
- "bool": {
- "must": [keyword_query],
- "boost": keyword_weight
- }
- }
-
- return search_body
-
-
-# API端点
-@app.get("/", tags=["Health"])
-async def root():
- """健康检查端点"""
- return {"status": "healthy", "service": "概念搜索API", "version": "1.2.0"}
-
-
-@app.post("/search", response_model=SearchResponse, tags=["Search"])
-async def search_concepts(request: SearchRequest):
- """
- 搜索概念库 - 支持KNN优化的语义搜索
-
- 新特性:
- - 使用KNN搜索优化语义搜索性能
- - search_size参数控制搜索数量,提高排序灵活性
- - 支持混合搜索(KNN + 关键词)
- """
- start_time = datetime.now()
-
- try:
- # 提取股票过滤条件
- cleaned_query, stock_filters = extract_stock_filter(request.query)
- if request.filter_stocks:
- stock_filters.extend(request.filter_stocks)
-
- # 计算语义权重
- if request.semantic_weight is not None:
- semantic_weight = request.semantic_weight
- else:
- semantic_weight = calculate_semantic_weight(request.query)
-
- # 生成embedding(如果需要)
- embedding = []
- if semantic_weight > 0:
- embedding = generate_embedding(request.query)
- if not embedding:
- # 已经在generate_embedding中记录了详细日志,这里只调整语义权重
- semantic_weight = 0
-
- # 【关键修改】:如果按涨跌幅或添加日期排序,需要获取更多结果
- effective_search_size = request.search_size
- if request.sort_by in ["change_pct", "added_date"]:
- # 按涨跌幅或添加日期排序时,获取更多结果以确保排序准确性
- effective_search_size = min(1000, request.search_size * 10) # 最多获取1000个
- logger.info(f"Using expanded search size {effective_search_size} for {request.sort_by} sorting")
-
- # 构建查询体
- search_body = {}
- match_type = "keyword"
-
- # 根据搜索类型构建不同的查询
- if semantic_weight == 0:
- # 纯关键词搜索
- search_body = {
- "query": build_keyword_query(cleaned_query or request.query, stock_filters),
- "size": effective_search_size # 使用有效搜索大小
- }
- match_type = "keyword"
-
- elif semantic_weight == 1.0 and request.use_knn and embedding:
- # 纯KNN语义搜索
- filter_query = None
- if stock_filters:
- filter_query = {
- "nested": {
- "path": "stocks",
- "query": {
- "bool": {
- "should": [
- {"terms": {"stocks.stock_name": stock_filters}},
- {"terms": {"stocks.stock_code": stock_filters}}
- ]
- }
- }
- }
- }
-
- search_body = {
- "knn": {
- "field": "description_embedding",
- "query_vector": embedding,
- "k": effective_search_size, # 使用有效搜索大小
- "num_candidates": max(effective_search_size + 50, min(effective_search_size * 2, 10000)) # 确保 num_candidates > k
- },
- "size": effective_search_size
- }
-
- if filter_query:
- search_body["knn"]["filter"] = filter_query
-
- match_type = "semantic_knn"
-
- elif request.use_knn and embedding:
- # 混合搜索(KNN + 关键词)
- hybrid_body = build_hybrid_knn_query(
- cleaned_query or request.query,
- embedding,
- semantic_weight,
- stock_filters,
- k=effective_search_size # 使用有效搜索大小
- )
- search_body = hybrid_body
- search_body["size"] = effective_search_size
- match_type = "hybrid_knn"
-
- else:
- # 传统混合搜索(script_score方式,作为后备)
- es_query = build_hybrid_query(
- cleaned_query or request.query,
- embedding,
- semantic_weight,
- stock_filters
- )
- search_body = {
- "query": es_query,
- "size": effective_search_size # 使用有效搜索大小
- }
- match_type = "hybrid"
-
- # 添加高亮和源过滤
- search_body.update({
- "highlight": {
- "fields": {
- "concept": {},
- "description": {"fragment_size": 150},
- "stocks_reason": {"fragment_size": 150},
- "stocks_full_text": {"fragment_size": 150}
- }
- },
- "_source": {
- "excludes": ["description_embedding"]
- },
- "track_total_hits": True
- })
-
- # 执行搜索(增加超时时间)
- es_response = es_client.search(
- index=INDEX_NAME,
- body=search_body,
- timeout="30s"
- )
-
- # 收集结果
- all_results = []
- concept_ids = []
-
- for hit in es_response['hits']['hits']:
- source = hit['_source']
- concept_ids.append(source['concept_id'])
-
- # 提取股票信息
- stocks = []
- for stock in source.get('stocks', [])[:20]:
- stock_info = StockInfo(
- stock_name=stock.get('stock_name', ''),
- stock_code=stock.get('stock_code', ''),
- reason=stock.get('reason'),
- industry=stock.get('行业'),
- project=stock.get('项目')
- )
- stocks.append(stock_info)
-
- # 提取高亮信息
- highlights = {}
- if 'highlight' in hit:
- highlights = hit['highlight']
-
- # 构建结果
- result = ConceptResult(
- concept_id=source['concept_id'],
- concept=source['concept'],
- description=source.get('description'),
- stocks=stocks,
- stock_count=len(source.get('stocks', [])),
- happened_times=source.get('happened_times'),
- score=hit['_score'],
- match_type=match_type,
- highlights=highlights,
- price_info=None
- )
- all_results.append(result)
-
- # 【关键修改】:始终获取涨跌幅数据,无论排序方式
- # 这样用户可以看到涨跌幅信息,即使不按涨跌幅排序
- price_data = {}
- actual_price_date = None
-
- if concept_ids:
- # 获取所有结果的价格数据
- price_data = await get_concept_price_data(concept_ids, request.trade_date)
- if price_data:
- actual_price_date = next(iter(price_data.values())).trade_date
-
- # 填充涨跌幅信息
- for result in all_results:
- result.price_info = price_data.get(result.concept_id)
-
- # 根据排序方式排序
- if request.sort_by == "change_pct":
- # 按涨跌幅排序(降序)
- all_results.sort(
- key=lambda x: (
- x.price_info.avg_change_pct
- if x.price_info and x.price_info.avg_change_pct is not None
- else -999
- ),
- reverse=True
- )
- elif request.sort_by == "stock_count":
- all_results.sort(key=lambda x: x.stock_count, reverse=True)
- elif request.sort_by == "concept_name":
- all_results.sort(key=lambda x: x.concept)
- elif request.sort_by == "added_date":
- # 按添加日期排序(降序 - 最新的在前)
- all_results.sort(
- key=lambda x: (
- x.happened_times[0] if x.happened_times and len(x.happened_times) > 0 else '1900-01-01'
- ),
- reverse=True
- )
- # _score排序已经由ES处理
-
- # 计算分页
- total_results = len(all_results)
- total_pages = max(1, (total_results + request.size - 1) // request.size)
- current_page = min(request.page, total_pages)
-
- # 获取当前页的结果
- start_idx = (current_page - 1) * request.size
- end_idx = start_idx + request.size
- page_results = all_results[start_idx:end_idx]
-
- # 计算耗时
- took_ms = int((datetime.now() - start_time).total_seconds() * 1000)
-
- # 构建响应
- response = SearchResponse(
- total=es_response['hits']['total']['value'],
- took_ms=took_ms,
- results=page_results,
- search_info={
- "query": request.query,
- "cleaned_query": cleaned_query,
- "semantic_weight": semantic_weight,
- "keyword_weight": 1.0 - semantic_weight,
- "match_type": match_type,
- "stock_filters": stock_filters,
- "has_embedding": bool(embedding),
- "sort_by": request.sort_by,
- "search_size": request.search_size,
- "effective_search_size": effective_search_size, # 实际使用的搜索大小
- "use_knn": request.use_knn,
- "actual_results": total_results
- },
- price_date=actual_price_date,
- page=current_page,
- total_pages=total_pages
- )
-
- return response
-
- except Exception as e:
- logger.error(f"Search error: {e}", exc_info=True)
- raise HTTPException(status_code=500, detail=str(e))
-
-
-@app.get("/concept/{concept_id}", tags=["Concepts"])
-async def get_concept(
- concept_id: str,
- trade_date: Optional[date] = Query(None, description="交易日期,格式:YYYY-MM-DD,默认返回最新日期数据")
-):
- """根据ID获取概念详情,包含涨跌幅数据"""
- try:
- result = es_client.get(index=INDEX_NAME, id=concept_id)
- source = result['_source']
-
- stocks_reason = {}
- if 'stocks_reason' in source:
- try:
- stocks_reason = json.loads(source['stocks_reason'])
- except:
- pass
-
- price_data = await get_concept_price_data([concept_id], trade_date)
- price_info = price_data.get(concept_id)
-
- return {
- "concept_id": source['concept_id'],
- "concept": source['concept'],
- "description": source.get('description'),
- "stocks": source.get('stocks', []),
- "stocks_reason": stocks_reason,
- "happened_times": source.get('happened_times'),
- "created_at": source.get('created_at'),
- "price_info": price_info
- }
- except Exception as e:
- if "NotFoundError" in str(type(e)):
- raise HTTPException(status_code=404, detail="概念不存在")
- logger.error(f"Get concept error: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-
-@app.get("/stock/{stock_code}/concepts", response_model=StockConceptsResponse, tags=["Stocks"])
-async def get_stock_concepts(
- stock_code: str,
- size: int = Query(50, ge=1, le=200, description="返回概念数量"),
- sort_by: str = Query("stock_count", description="排序方式: stock_count, concept_name, recent"),
- include_description: bool = Query(True, description="是否包含概念描述"),
- trade_date: Optional[date] = Query(None, description="交易日期,格式:YYYY-MM-DD,默认返回最新日期数据")
-):
- """根据股票代码查询相关概念,包含涨跌幅数据"""
- try:
- query = {
- "nested": {
- "path": "stocks",
- "query": {
- "bool": {
- "should": [
- {"term": {"stocks.stock_code": stock_code}},
- {"term": {"stocks.stock_name": stock_code}}
- ]
- }
- }
- }
- }
-
- sort_rules = []
- if sort_by == "stock_count":
- sort_rules.append({
- "_script": {
- "type": "number",
- "script": {
- "source": "params._source.stocks.size()"
- },
- "order": "desc"
- }
- })
- elif sort_by == "concept_name":
- sort_rules.append({"concept.keyword": {"order": "asc"}})
- elif sort_by == "recent":
- sort_rules.append({"happened_times": {"order": "desc", "missing": "_last"}})
-
- search_body = {
- "query": query,
- "size": size,
- "sort": sort_rules if sort_rules else [{"_score": {"order": "desc"}}],
- "_source": {
- "includes": ["concept_id", "concept", "description", "stocks", "happened_times", "created_at"],
- "excludes": ["description_embedding", "stocks_reason"] if not include_description else [
- "description_embedding"]
- }
- }
-
- es_response = es_client.search(index=INDEX_NAME, body=search_body)
-
- concept_ids = []
- for hit in es_response['hits']['hits']:
- concept_ids.append(hit['_source']['concept_id'])
-
- price_data = await get_concept_price_data(concept_ids, trade_date)
-
- actual_price_date = None
- if price_data:
- actual_price_date = next(iter(price_data.values())).trade_date if price_data else None
-
- concepts = []
- stock_info = None
-
- for hit in es_response['hits']['hits']:
- source = hit['_source']
- concept_id = source['concept_id']
-
- stock_detail = None
- for stock in source.get('stocks', []):
- if stock.get('stock_code') == stock_code or stock.get('stock_name') == stock_code:
- stock_detail = stock
- if not stock_info:
- stock_info = {
- "stock_code": stock.get('stock_code'),
- "stock_name": stock.get('stock_name')
- }
- break
-
- concept = StockConceptInfo(
- concept_id=concept_id,
- concept=source['concept'],
- stock_count=len(source.get('stocks', [])),
- happened_times=source.get('happened_times'),
- stock_detail=stock_detail,
- price_info=price_data.get(concept_id)
- )
-
- if include_description:
- concept.description = source.get('description')
-
- concepts.append(concept)
-
- stats = {
- "total_concepts": es_response['hits']['total']['value'],
- "returned_concepts": len(concepts),
- "stock_info": stock_info
- }
-
- concept_categories = {}
- for concept in concepts:
- concept_name = concept.concept
- if '新能源' in concept_name:
- category = '新能源'
- elif '半导体' in concept_name or '芯片' in concept_name:
- category = '半导体'
- elif '人工智能' in concept_name or 'AI' in concept_name:
- category = '人工智能'
- elif '医' in concept_name:
- category = '医药'
- elif '金融' in concept_name or '银行' in concept_name:
- category = '金融'
- else:
- category = '其他'
-
- if category not in concept_categories:
- concept_categories[category] = 0
- concept_categories[category] += 1
-
- stats["concept_categories"] = concept_categories
-
- return StockConceptsResponse(
- stock_code=stock_code,
- stats=stats,
- concepts=concepts,
- price_date=actual_price_date
- )
-
- except Exception as e:
- logger.error(f"Get stock concepts error: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-
-@app.get("/stock/search", response_model=StockSearchResponse, tags=["Stocks"])
-async def search_stocks(
- keyword: str = Query(..., description="股票关键词"),
- size: int = Query(20, ge=1, le=100, description="返回数量")
-):
- """搜索股票名称或代码"""
- try:
- query = {
- "nested": {
- "path": "stocks",
- "query": {
- "bool": {
- "should": [
- {"wildcard": {"stocks.stock_code": f"*{keyword}*"}},
- {"match": {"stocks.stock_name": keyword}}
- ]
- }
- },
- "inner_hits": {
- "size": 10,
- "_source": ["stock_code", "stock_name"]
- }
- }
- }
-
- search_body = {
- "query": query,
- "size": 0,
- "aggs": {
- "unique_stocks": {
- "nested": {
- "path": "stocks"
- },
- "aggs": {
- "stock_codes": {
- "terms": {
- "field": "stocks.stock_code",
- "size": size * 2
- },
- "aggs": {
- "stock_names": {
- "terms": {
- "field": "stocks.stock_name",
- "size": 1
- }
- }
- }
- }
- }
- }
- }
- }
-
- es_response = es_client.search(index=INDEX_NAME, body=search_body)
-
- stocks = []
- buckets = es_response['aggregations']['unique_stocks']['stock_codes']['buckets']
-
- for bucket in buckets:
- stock_code = bucket['key']
- concept_count = bucket['doc_count']
-
- name_buckets = bucket['stock_names']['buckets']
- stock_name = name_buckets[0]['key'] if name_buckets else stock_code
-
- if keyword in stock_code or keyword in stock_name:
- stocks.append(StockSearchResult(
- stock_code=stock_code,
- stock_name=stock_name,
- concept_count=concept_count
- ))
-
- stocks.sort(key=lambda x: x.concept_count, reverse=True)
-
- return StockSearchResponse(
- total=len(stocks),
- stocks=stocks[:size]
- )
-
- except Exception as e:
- logger.error(f"Search stocks error: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-
-@app.get("/price/latest", tags=["Price"])
-async def get_latest_price_date():
- """获取最新的涨跌幅数据日期"""
- try:
- latest_date = await get_latest_trade_date()
- return {
- "latest_trade_date": latest_date,
- "has_data": latest_date is not None
- }
- except Exception as e:
- logger.error(f"Get latest price date error: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-
-@app.get("/concept/{concept_id}/price-timeseries", response_model=PriceTimeSeriesResponse, tags=["Price"])
-async def get_concept_price_timeseries(
- concept_id: str,
- start_date: date = Query(..., description="开始日期,格式:YYYY-MM-DD"),
- end_date: date = Query(..., description="结束日期,格式:YYYY-MM-DD")
-):
- """获取概念在指定日期范围内的涨跌幅时间序列数据"""
- if not mysql_pool:
- logger.warning(f"[PriceTimeseries] MySQL 连接不可用,返回空时间序列数据")
- # 返回空时间序列而不是 503 错误
- return PriceTimeSeriesResponse(
- concept_id=concept_id,
- concept_name=concept_id, # 无法查询名称,使用 ID
- start_date=start_date,
- end_date=end_date,
- data_points=0,
- timeseries=[]
- )
-
- if start_date > end_date:
- raise HTTPException(status_code=400, detail="开始日期不能晚于结束日期")
-
- try:
- async with mysql_pool.acquire() as conn:
- async with conn.cursor(aiomysql.DictCursor) as cursor:
- select_fields = """
- trade_date,
- concept_name,
- avg_change_pct,
- stock_count
- """
-
- query = f"""
- SELECT {select_fields}
- FROM concept_daily_stats
- WHERE concept_id = %s
- AND trade_date >= %s
- AND trade_date <= %s
- ORDER BY trade_date ASC
- """
-
- await cursor.execute(query, (concept_id, start_date, end_date))
- rows = await cursor.fetchall()
-
- if not rows:
- raise HTTPException(
- status_code=404,
- detail=f"未找到概念ID {concept_id} 在 {start_date} 至 {end_date} 期间的数据"
- )
-
- timeseries = []
- concept_name = ""
-
- for row in rows:
- if not concept_name and row.get('concept_name'):
- concept_name = row['concept_name']
-
- item = PriceTimeSeriesItem(
- trade_date=row['trade_date'],
- avg_change_pct=float(row['avg_change_pct']) if row['avg_change_pct'] is not None else None,
- stock_count=row.get('stock_count')
- )
-
- timeseries.append(item)
-
- return PriceTimeSeriesResponse(
- concept_id=concept_id,
- concept_name=concept_name or concept_id,
- start_date=start_date,
- end_date=end_date,
- data_points=len(timeseries),
- timeseries=timeseries
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.error(f"Error fetching concept price timeseries: {e}")
- raise HTTPException(status_code=500, detail=f"获取时间序列数据失败: {str(e)}")
-
-
-# 概念统计相关的数据模型
-class ConceptStatItem(BaseModel):
- name: str
- concept_id: Optional[str] = None
- change_pct: Optional[float] = None
- stock_count: Optional[int] = None
- news_count: Optional[int] = None
- report_count: Optional[int] = None
- total_mentions: Optional[int] = None
- volatility: Optional[float] = None
- avg_change: Optional[float] = None
- max_change: Optional[float] = None
- consecutive_days: Optional[int] = None
- total_change: Optional[float] = None
- avg_daily: Optional[float] = None
-
-
-class ConceptStatistics(BaseModel):
- hot_concepts: List[ConceptStatItem]
- cold_concepts: List[ConceptStatItem]
- active_concepts: List[ConceptStatItem]
- volatile_concepts: List[ConceptStatItem]
- momentum_concepts: List[ConceptStatItem]
- summary: Dict[str, Any]
-
-
-class ConceptStatisticsResponse(BaseModel):
- success: bool
- data: ConceptStatistics
- params: Dict[str, Any]
- note: Optional[str] = None
-
-
-@app.get("/statistics", response_model=ConceptStatisticsResponse, tags=["Statistics"])
-async def get_concept_statistics(
- days: Optional[int] = Query(None, ge=1, le=90, description="统计天数范围(与start_date/end_date互斥)"),
- start_date: Optional[date] = Query(None, description="开始日期,格式:YYYY-MM-DD"),
- end_date: Optional[date] = Query(None, description="结束日期,格式:YYYY-MM-DD"),
- min_stock_count: int = Query(3, ge=1, description="最少股票数量过滤")
-):
- """获取概念板块统计数据 - 涨幅榜、跌幅榜、活跃榜、波动榜、连涨榜"""
-
- from datetime import datetime, timedelta
-
- # 如果 MySQL 不可用,直接返回示例数据(而不是返回 503)
- if not mysql_pool:
- logger.warning("[Statistics] MySQL 连接不可用,使用示例数据")
-
- # 计算日期范围
- if days is not None and (start_date is not None or end_date is not None):
- pass # 参数冲突,但仍使用 days
-
- if start_date is not None and end_date is not None:
- pass # 使用提供的日期
- elif days is not None:
- end_date = datetime.now().date()
- start_date = end_date - timedelta(days=days)
- elif start_date is not None:
- end_date = datetime.now().date()
- elif end_date is not None:
- start_date = end_date - timedelta(days=7)
- else:
- end_date = datetime.now().date()
- start_date = end_date - timedelta(days=7)
-
- # 返回示例数据(与 except 块中相同)
- fallback_statistics = ConceptStatistics(
- hot_concepts=[
- ConceptStatItem(name="小米大模型", change_pct=12.45, stock_count=24, news_count=18),
- ConceptStatItem(name="人工智能", change_pct=8.76, stock_count=45, news_count=12),
- ConceptStatItem(name="新能源汽车", change_pct=6.54, stock_count=38, news_count=8),
- ConceptStatItem(name="芯片概念", change_pct=5.43, stock_count=52, news_count=15),
- ConceptStatItem(name="生物医药", change_pct=4.21, stock_count=28, news_count=6),
- ],
- cold_concepts=[
- ConceptStatItem(name="房地产", change_pct=-5.76, stock_count=33, news_count=5),
- ConceptStatItem(name="煤炭开采", change_pct=-4.32, stock_count=25, news_count=3),
- ConceptStatItem(name="钢铁冶炼", change_pct=-3.21, stock_count=28, news_count=4),
- ConceptStatItem(name="传统零售", change_pct=-2.98, stock_count=19, news_count=2),
- ConceptStatItem(name="纺织服装", change_pct=-2.45, stock_count=15, news_count=2),
- ],
- active_concepts=[
- ConceptStatItem(name="人工智能", news_count=45, report_count=15, total_mentions=60),
- ConceptStatItem(name="芯片概念", news_count=42, report_count=12, total_mentions=54),
- ConceptStatItem(name="新能源汽车", news_count=38, report_count=8, total_mentions=46),
- ConceptStatItem(name="生物医药", news_count=28, report_count=6, total_mentions=34),
- ConceptStatItem(name="量子科技", news_count=25, report_count=5, total_mentions=30),
- ],
- volatile_concepts=[
- ConceptStatItem(name="区块链", volatility=25.6, avg_change=2.1, max_change=15.2),
- ConceptStatItem(name="元宇宙", volatility=23.8, avg_change=1.8, max_change=13.9),
- ConceptStatItem(name="虚拟现实", volatility=21.2, avg_change=-0.5, max_change=10.1),
- ConceptStatItem(name="游戏概念", volatility=19.7, avg_change=3.2, max_change=12.8),
- ConceptStatItem(name="在线教育", volatility=18.3, avg_change=-1.1, max_change=8.1),
- ],
- momentum_concepts=[
- ConceptStatItem(name="数字经济", consecutive_days=6, total_change=19.2, avg_daily=3.2),
- ConceptStatItem(name="云计算", consecutive_days=5, total_change=16.8, avg_daily=3.36),
- ConceptStatItem(name="物联网", consecutive_days=4, total_change=13.1, avg_daily=3.28),
- ConceptStatItem(name="大数据", consecutive_days=4, total_change=12.4, avg_daily=3.1),
- ConceptStatItem(name="工业互联网", consecutive_days=3, total_change=9.6, avg_daily=3.2),
- ],
- summary={
- 'total_concepts': 500,
- 'positive_count': 320,
- 'negative_count': 180,
- 'avg_change': 1.8,
- 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'date_range': f"{start_date} 至 {end_date}",
- 'days': (end_date - start_date).days + 1,
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- }
- )
-
- return ConceptStatisticsResponse(
- success=True,
- data=fallback_statistics,
- params={
- 'days': (end_date - start_date).days + 1,
- 'min_stock_count': min_stock_count,
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- },
- note="MySQL 连接不可用,使用示例数据"
- )
-
- try:
- import random
-
- # 参数验证和日期范围计算
- if days is not None and (start_date is not None or end_date is not None):
- raise HTTPException(status_code=400, detail="days参数与start_date/end_date参数不能同时使用")
-
- if start_date is not None and end_date is not None:
- if start_date > end_date:
- raise HTTPException(status_code=400, detail="开始日期不能晚于结束日期")
- # 限制日期范围不超过90天
- if (end_date - start_date).days > 90:
- raise HTTPException(status_code=400, detail="日期范围不能超过90天")
- elif days is not None:
- # 使用days参数
- end_date = datetime.now().date()
- start_date = end_date - timedelta(days=days)
- elif start_date is not None:
- # 只有开始日期,默认到今天
- end_date = datetime.now().date()
- if (end_date - start_date).days > 90:
- raise HTTPException(status_code=400, detail="日期范围不能超过90天")
- elif end_date is not None:
- # 只有结束日期,默认前7天
- start_date = end_date - timedelta(days=7)
- else:
- # 默认最近7天
- end_date = datetime.now().date()
- start_date = end_date - timedelta(days=7)
-
- async with mysql_pool.acquire() as conn:
- async with conn.cursor(aiomysql.DictCursor) as cursor:
-
- # 1. 获取涨幅榜 - 基于平均涨跌幅排序
- hot_query = """
- SELECT
- concept_id,
- concept_name,
- AVG(avg_change_pct) as avg_change_pct,
- AVG(stock_count) as avg_stock_count,
- COUNT(*) as trading_days
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND avg_change_pct IS NOT NULL
- AND stock_count >= %s
- GROUP BY concept_id, concept_name
- HAVING COUNT(*) >= 2
- ORDER BY AVG(avg_change_pct) DESC
- LIMIT 5
- """
- await cursor.execute(hot_query, (start_date, end_date, min_stock_count))
- hot_rows = await cursor.fetchall()
-
- # 2. 获取跌幅榜 - 基于平均涨跌幅排序(负值)
- cold_query = """
- SELECT
- concept_id,
- concept_name,
- AVG(avg_change_pct) as avg_change_pct,
- AVG(stock_count) as avg_stock_count,
- COUNT(*) as trading_days
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND avg_change_pct IS NOT NULL
- AND stock_count >= %s
- GROUP BY concept_id, concept_name
- HAVING COUNT(*) >= 2
- ORDER BY AVG(avg_change_pct) ASC
- LIMIT 5
- """
- await cursor.execute(cold_query, (start_date, end_date, min_stock_count))
- cold_rows = await cursor.fetchall()
-
- # 3. 获取活跃榜 - 基于交易天数和股票数量
- active_query = """
- SELECT
- concept_id,
- concept_name,
- COUNT(*) as trading_days,
- AVG(stock_count) as avg_stock_count,
- MAX(stock_count) as max_stock_count
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND stock_count >= %s
- GROUP BY concept_id, concept_name
- ORDER BY COUNT(*) DESC, AVG(stock_count) DESC
- LIMIT 5
- """
- await cursor.execute(active_query, (start_date, end_date, min_stock_count))
- active_rows = await cursor.fetchall()
-
- # 4. 获取波动榜 - 基于涨跌幅标准差
- volatile_query = """
- SELECT
- concept_id,
- concept_name,
- STDDEV(avg_change_pct) as volatility,
- AVG(avg_change_pct) as avg_change_pct,
- MAX(avg_change_pct) as max_change_pct,
- AVG(stock_count) as avg_stock_count
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND avg_change_pct IS NOT NULL
- AND stock_count >= %s
- GROUP BY concept_id, concept_name
- HAVING COUNT(*) >= 3 AND STDDEV(avg_change_pct) IS NOT NULL
- ORDER BY STDDEV(avg_change_pct) DESC
- LIMIT 5
- """
- await cursor.execute(volatile_query, (start_date, end_date, min_stock_count))
- volatile_rows = await cursor.fetchall()
-
- # 5. 获取连涨榜 - 基于连续正涨幅天数(简化版本)
- momentum_query = """
- SELECT
- concept_id,
- concept_name,
- COUNT(*) as positive_days,
- SUM(avg_change_pct) as total_change,
- AVG(avg_change_pct) as avg_change_pct,
- AVG(stock_count) as avg_stock_count
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND avg_change_pct > 0
- AND stock_count >= %s
- GROUP BY concept_id, concept_name
- HAVING COUNT(*) >= 2
- ORDER BY COUNT(*) DESC, AVG(avg_change_pct) DESC
- LIMIT 5
- """
- await cursor.execute(momentum_query, (start_date, end_date, min_stock_count))
- momentum_rows = await cursor.fetchall()
-
- # 6. 获取总体统计
- total_query = """
- SELECT
- COUNT(DISTINCT concept_id) as total_concepts,
- COUNT(DISTINCT CASE WHEN avg_change_pct > 0 THEN concept_id END) as positive_concepts,
- COUNT(DISTINCT CASE WHEN avg_change_pct < 0 THEN concept_id END) as negative_concepts,
- AVG(avg_change_pct) as overall_avg_change
- FROM concept_daily_stats
- WHERE trade_date >= %s AND trade_date <= %s
- AND avg_change_pct IS NOT NULL
- """
- await cursor.execute(total_query, (start_date, end_date))
- total_row = await cursor.fetchone()
-
- # 构建响应数据
- def build_concept_items(rows, item_type):
- items = []
- for row in rows:
- item = ConceptStatItem(
- name=row['concept_name'],
- concept_id=row.get('concept_id')
- )
-
- if item_type == 'hot' or item_type == 'cold':
- item.change_pct = round(float(row['avg_change_pct']), 2) if row['avg_change_pct'] else 0.0
- item.stock_count = int(row['avg_stock_count']) if row['avg_stock_count'] else 0
- item.news_count = int(row['trading_days']) if row['trading_days'] else 0
-
- elif item_type == 'active':
- item.news_count = int(row['trading_days']) if row['trading_days'] else 0
- item.stock_count = int(row['avg_stock_count']) if row['avg_stock_count'] else 0
- item.report_count = max(1, int(row['trading_days'] * 0.3)) if row['trading_days'] else 1
- item.total_mentions = item.news_count + item.report_count
-
- elif item_type == 'volatile':
- item.volatility = round(float(row['volatility']), 2) if row['volatility'] else 0.0
- item.avg_change = round(float(row['avg_change_pct']), 2) if row['avg_change_pct'] else 0.0
- item.max_change = round(float(row['max_change_pct']), 2) if row['max_change_pct'] else 0.0
-
- elif item_type == 'momentum':
- item.consecutive_days = int(row['positive_days']) if row['positive_days'] else 0
- item.total_change = round(float(row['total_change']), 2) if row['total_change'] else 0.0
- item.avg_daily = round(float(row['avg_change_pct']), 2) if row['avg_change_pct'] else 0.0
-
- items.append(item)
- return items
-
- # 构建统计数据
- statistics = ConceptStatistics(
- hot_concepts=build_concept_items(hot_rows, 'hot'),
- cold_concepts=build_concept_items(cold_rows, 'cold'),
- active_concepts=build_concept_items(active_rows, 'active'),
- volatile_concepts=build_concept_items(volatile_rows, 'volatile'),
- momentum_concepts=build_concept_items(momentum_rows, 'momentum'),
- summary={
- 'total_concepts': int(total_row['total_concepts']) if total_row['total_concepts'] else 0,
- 'positive_count': int(total_row['positive_concepts']) if total_row['positive_concepts'] else 0,
- 'negative_count': int(total_row['negative_concepts']) if total_row['negative_concepts'] else 0,
- 'avg_change': round(float(total_row['overall_avg_change']), 2) if total_row['overall_avg_change'] else 0.0,
- 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'date_range': f"{start_date} 至 {end_date}",
- 'days': max(1, (end_date - start_date).days + 1), # 包含起始日期
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- }
- )
-
- # 如果某些榜单数据不足,使用示例数据补充
- if not statistics.hot_concepts:
- statistics.hot_concepts = [
- ConceptStatItem(name="人工智能", change_pct=8.76, stock_count=45, news_count=12),
- ConceptStatItem(name="新能源汽车", change_pct=6.54, stock_count=38, news_count=8),
- ConceptStatItem(name="芯片概念", change_pct=5.43, stock_count=52, news_count=15),
- ConceptStatItem(name="生物医药", change_pct=4.21, stock_count=28, news_count=6),
- ConceptStatItem(name="5G通信", change_pct=3.98, stock_count=35, news_count=9),
- ]
-
- if not statistics.cold_concepts:
- statistics.cold_concepts = [
- ConceptStatItem(name="房地产", change_pct=-5.76, stock_count=33, news_count=5),
- ConceptStatItem(name="煤炭开采", change_pct=-4.32, stock_count=25, news_count=3),
- ConceptStatItem(name="钢铁冶炼", change_pct=-3.21, stock_count=28, news_count=4),
- ConceptStatItem(name="传统零售", change_pct=-2.98, stock_count=19, news_count=2),
- ConceptStatItem(name="纺织服装", change_pct=-2.45, stock_count=15, news_count=2),
- ]
-
- return ConceptStatisticsResponse(
- success=True,
- data=statistics,
- params={
- 'days': max(1, (end_date - start_date).days + 1),
- 'min_stock_count': min_stock_count,
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- }
- )
-
- except Exception as e:
- logger.error(f"Error fetching concept statistics: {e}")
-
- # 返回示例数据作为fallback
- fallback_statistics = ConceptStatistics(
- hot_concepts=[
- ConceptStatItem(name="小米大模型", change_pct=12.45, stock_count=24, news_count=18),
- ConceptStatItem(name="人工智能", change_pct=8.76, stock_count=45, news_count=12),
- ConceptStatItem(name="新能源汽车", change_pct=6.54, stock_count=38, news_count=8),
- ConceptStatItem(name="芯片概念", change_pct=5.43, stock_count=52, news_count=15),
- ConceptStatItem(name="生物医药", change_pct=4.21, stock_count=28, news_count=6),
- ],
- cold_concepts=[
- ConceptStatItem(name="房地产", change_pct=-5.76, stock_count=33, news_count=5),
- ConceptStatItem(name="煤炭开采", change_pct=-4.32, stock_count=25, news_count=3),
- ConceptStatItem(name="钢铁冶炼", change_pct=-3.21, stock_count=28, news_count=4),
- ConceptStatItem(name="传统零售", change_pct=-2.98, stock_count=19, news_count=2),
- ConceptStatItem(name="纺织服装", change_pct=-2.45, stock_count=15, news_count=2),
- ],
- active_concepts=[
- ConceptStatItem(name="人工智能", news_count=45, report_count=15, total_mentions=60),
- ConceptStatItem(name="芯片概念", news_count=42, report_count=12, total_mentions=54),
- ConceptStatItem(name="新能源汽车", news_count=38, report_count=8, total_mentions=46),
- ConceptStatItem(name="生物医药", news_count=28, report_count=6, total_mentions=34),
- ConceptStatItem(name="量子科技", news_count=25, report_count=5, total_mentions=30),
- ],
- volatile_concepts=[
- ConceptStatItem(name="区块链", volatility=25.6, avg_change=2.1, max_change=15.2),
- ConceptStatItem(name="元宇宙", volatility=23.8, avg_change=1.8, max_change=13.9),
- ConceptStatItem(name="虚拟现实", volatility=21.2, avg_change=-0.5, max_change=10.1),
- ConceptStatItem(name="游戏概念", volatility=19.7, avg_change=3.2, max_change=12.8),
- ConceptStatItem(name="在线教育", volatility=18.3, avg_change=-1.1, max_change=8.1),
- ],
- momentum_concepts=[
- ConceptStatItem(name="数字经济", consecutive_days=6, total_change=19.2, avg_daily=3.2),
- ConceptStatItem(name="云计算", consecutive_days=5, total_change=16.8, avg_daily=3.36),
- ConceptStatItem(name="物联网", consecutive_days=4, total_change=13.1, avg_daily=3.28),
- ConceptStatItem(name="大数据", consecutive_days=4, total_change=12.4, avg_daily=3.1),
- ConceptStatItem(name="工业互联网", consecutive_days=3, total_change=9.6, avg_daily=3.2),
- ],
- summary={
- 'total_concepts': 500,
- 'positive_count': 320,
- 'negative_count': 180,
- 'avg_change': 1.8,
- 'update_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'date_range': f"{start_date} 至 {end_date}",
- 'days': max(1, (end_date - start_date).days + 1),
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- }
- )
-
- return ConceptStatisticsResponse(
- success=True,
- data=fallback_statistics,
- params={
- 'days': max(1, (end_date - start_date).days + 1),
- 'min_stock_count': min_stock_count,
- 'start_date': str(start_date),
- 'end_date': str(end_date)
- },
- note=f"使用示例数据,原因: {str(e)}"
- )
-
-
-# 主函数
-if __name__ == "__main__":
- import uvicorn
-
- uvicorn.run(
- "concept_api:app",
- host="0.0.0.0",
- port=6801,
- reload=True,
- log_level="info"
- )
\ No newline at end of file
diff --git a/concept_api_openapi.json b/concept_api_openapi.json
deleted file mode 100644
index 7fa96034..00000000
--- a/concept_api_openapi.json
+++ /dev/null
@@ -1,1096 +0,0 @@
-{
- "openapi": "3.0.3",
- "info": {
- "title": "概念搜索API",
- "description": "支持语义和关键词混合搜索的概念库API,包含概念涨跌幅数据。\n\n## 功能特性\n- 支持 KNN 优化的语义搜索\n- 关键词 + 语义混合搜索\n- 概念涨跌幅数据查询\n- 股票关联概念查询\n- 概念统计排行榜\n\n## 技术栈\n- FastAPI + Elasticsearch\n- OpenAI Embedding (qwen3-embedding-8b)\n- MySQL (概念涨跌幅数据)",
- "version": "1.2.0",
- "contact": {
- "name": "ValueFrontier",
- "url": "https://valuefrontier.cn/concept-api"
- }
- },
- "servers": [
- {
- "url": "http://localhost:6801",
- "description": "本地开发服务器"
- },
- {
- "url": "https://api.valuefrontier.cn:6801",
- "description": "生产服务器"
- }
- ],
- "tags": [
- {
- "name": "Health",
- "description": "健康检查接口"
- },
- {
- "name": "Search",
- "description": "概念搜索接口"
- },
- {
- "name": "Concepts",
- "description": "概念详情接口"
- },
- {
- "name": "Stocks",
- "description": "股票相关接口"
- },
- {
- "name": "Price",
- "description": "价格数据接口"
- },
- {
- "name": "Statistics",
- "description": "统计数据接口"
- }
- ],
- "paths": {
- "/": {
- "get": {
- "tags": ["Health"],
- "summary": "健康检查",
- "description": "检查服务是否正常运行",
- "operationId": "healthCheck",
- "responses": {
- "200": {
- "description": "服务正常",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "status": {
- "type": "string",
- "example": "healthy"
- },
- "service": {
- "type": "string",
- "example": "概念搜索API"
- },
- "version": {
- "type": "string",
- "example": "1.2.0"
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "/search": {
- "post": {
- "tags": ["Search"],
- "summary": "搜索概念库",
- "description": "支持 KNN 优化的语义搜索,混合搜索(KNN + 关键词),支持按涨跌幅、相关性等排序",
- "operationId": "searchConcepts",
- "requestBody": {
- "required": true,
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SearchRequest"
- },
- "examples": {
- "semantic_search": {
- "summary": "语义搜索示例",
- "value": {
- "query": "新能源汽车电池技术",
- "size": 10,
- "page": 1,
- "sort_by": "change_pct",
- "use_knn": true
- }
- },
- "keyword_search": {
- "summary": "关键词搜索示例",
- "value": {
- "query": "人工智能",
- "size": 20,
- "semantic_weight": 0,
- "sort_by": "_score"
- }
- },
- "stock_filter": {
- "summary": "股票过滤示例",
- "value": {
- "query": "芯片",
- "filter_stocks": ["600519", "贵州茅台"],
- "size": 10
- }
- }
- }
- }
- }
- },
- "responses": {
- "200": {
- "description": "搜索成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/SearchResponse"
- }
- }
- }
- },
- "500": {
- "description": "服务器错误",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPError"
- }
- }
- }
- }
- }
- }
- },
- "/concept/{concept_id}": {
- "get": {
- "tags": ["Concepts"],
- "summary": "获取概念详情",
- "description": "根据概念ID获取完整的概念信息,包含关联股票和涨跌幅数据",
- "operationId": "getConcept",
- "parameters": [
- {
- "name": "concept_id",
- "in": "path",
- "required": true,
- "description": "概念ID",
- "schema": {
- "type": "string"
- },
- "example": "concept_ai_001"
- },
- {
- "name": "trade_date",
- "in": "query",
- "required": false,
- "description": "交易日期,格式:YYYY-MM-DD,默认返回最新日期数据",
- "schema": {
- "type": "string",
- "format": "date"
- },
- "example": "2025-11-25"
- }
- ],
- "responses": {
- "200": {
- "description": "获取成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ConceptDetail"
- }
- }
- }
- },
- "404": {
- "description": "概念不存在",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/HTTPError"
- }
- }
- }
- }
- }
- }
- },
- "/concept/{concept_id}/price-timeseries": {
- "get": {
- "tags": ["Price"],
- "summary": "获取概念价格时间序列",
- "description": "获取指定概念在日期范围内的涨跌幅时间序列数据",
- "operationId": "getConceptPriceTimeseries",
- "parameters": [
- {
- "name": "concept_id",
- "in": "path",
- "required": true,
- "description": "概念ID",
- "schema": {
- "type": "string"
- }
- },
- {
- "name": "start_date",
- "in": "query",
- "required": true,
- "description": "开始日期,格式:YYYY-MM-DD",
- "schema": {
- "type": "string",
- "format": "date"
- },
- "example": "2025-11-01"
- },
- {
- "name": "end_date",
- "in": "query",
- "required": true,
- "description": "结束日期,格式:YYYY-MM-DD",
- "schema": {
- "type": "string",
- "format": "date"
- },
- "example": "2025-11-25"
- }
- ],
- "responses": {
- "200": {
- "description": "获取成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/PriceTimeSeriesResponse"
- }
- }
- }
- },
- "400": {
- "description": "参数错误(开始日期晚于结束日期)"
- },
- "404": {
- "description": "未找到数据"
- }
- }
- }
- },
- "/stock/{stock_code}/concepts": {
- "get": {
- "tags": ["Stocks"],
- "summary": "获取股票关联概念",
- "description": "根据股票代码或名称查询该股票关联的所有概念",
- "operationId": "getStockConcepts",
- "parameters": [
- {
- "name": "stock_code",
- "in": "path",
- "required": true,
- "description": "股票代码或名称",
- "schema": {
- "type": "string"
- },
- "example": "600519"
- },
- {
- "name": "size",
- "in": "query",
- "required": false,
- "description": "返回概念数量",
- "schema": {
- "type": "integer",
- "minimum": 1,
- "maximum": 200,
- "default": 50
- }
- },
- {
- "name": "sort_by",
- "in": "query",
- "required": false,
- "description": "排序方式",
- "schema": {
- "type": "string",
- "enum": ["stock_count", "concept_name", "recent"],
- "default": "stock_count"
- }
- },
- {
- "name": "include_description",
- "in": "query",
- "required": false,
- "description": "是否包含概念描述",
- "schema": {
- "type": "boolean",
- "default": true
- }
- },
- {
- "name": "trade_date",
- "in": "query",
- "required": false,
- "description": "交易日期,格式:YYYY-MM-DD",
- "schema": {
- "type": "string",
- "format": "date"
- }
- }
- ],
- "responses": {
- "200": {
- "description": "获取成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/StockConceptsResponse"
- }
- }
- }
- }
- }
- }
- },
- "/stock/search": {
- "get": {
- "tags": ["Stocks"],
- "summary": "搜索股票",
- "description": "根据关键词搜索股票名称或代码",
- "operationId": "searchStocks",
- "parameters": [
- {
- "name": "keyword",
- "in": "query",
- "required": true,
- "description": "股票关键词",
- "schema": {
- "type": "string"
- },
- "example": "茅台"
- },
- {
- "name": "size",
- "in": "query",
- "required": false,
- "description": "返回数量",
- "schema": {
- "type": "integer",
- "minimum": 1,
- "maximum": 100,
- "default": 20
- }
- }
- ],
- "responses": {
- "200": {
- "description": "搜索成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/StockSearchResponse"
- }
- }
- }
- }
- }
- }
- },
- "/price/latest": {
- "get": {
- "tags": ["Price"],
- "summary": "获取最新价格日期",
- "description": "获取数据库中最新的涨跌幅数据日期",
- "operationId": "getLatestPriceDate",
- "responses": {
- "200": {
- "description": "获取成功",
- "content": {
- "application/json": {
- "schema": {
- "type": "object",
- "properties": {
- "latest_trade_date": {
- "type": "string",
- "format": "date",
- "nullable": true,
- "example": "2025-11-25"
- },
- "has_data": {
- "type": "boolean",
- "example": true
- }
- }
- }
- }
- }
- }
- }
- }
- },
- "/statistics": {
- "get": {
- "tags": ["Statistics"],
- "summary": "获取概念统计数据",
- "description": "获取概念板块统计数据,包括涨幅榜、跌幅榜、活跃榜、波动榜、连涨榜",
- "operationId": "getConceptStatistics",
- "parameters": [
- {
- "name": "days",
- "in": "query",
- "required": false,
- "description": "统计天数范围(与 start_date/end_date 互斥)",
- "schema": {
- "type": "integer",
- "minimum": 1,
- "maximum": 90
- },
- "example": 7
- },
- {
- "name": "start_date",
- "in": "query",
- "required": false,
- "description": "开始日期,格式:YYYY-MM-DD",
- "schema": {
- "type": "string",
- "format": "date"
- }
- },
- {
- "name": "end_date",
- "in": "query",
- "required": false,
- "description": "结束日期,格式:YYYY-MM-DD",
- "schema": {
- "type": "string",
- "format": "date"
- }
- },
- {
- "name": "min_stock_count",
- "in": "query",
- "required": false,
- "description": "最少股票数量过滤",
- "schema": {
- "type": "integer",
- "minimum": 1,
- "default": 3
- }
- }
- ],
- "responses": {
- "200": {
- "description": "获取成功",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/ConceptStatisticsResponse"
- }
- }
- }
- },
- "400": {
- "description": "参数错误"
- }
- }
- }
- }
- },
- "components": {
- "schemas": {
- "SearchRequest": {
- "type": "object",
- "required": ["query"],
- "properties": {
- "query": {
- "type": "string",
- "description": "搜索查询文本",
- "example": "新能源汽车"
- },
- "size": {
- "type": "integer",
- "minimum": 1,
- "maximum": 100,
- "default": 10,
- "description": "每页返回结果数量"
- },
- "page": {
- "type": "integer",
- "minimum": 1,
- "default": 1,
- "description": "页码"
- },
- "search_size": {
- "type": "integer",
- "minimum": 10,
- "maximum": 1000,
- "default": 100,
- "description": "搜索数量(从ES获取的结果数),用于排序后分页"
- },
- "semantic_weight": {
- "type": "number",
- "minimum": 0,
- "maximum": 1,
- "nullable": true,
- "description": "语义搜索权重(0-1),null表示自动计算"
- },
- "filter_stocks": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "description": "过滤特定股票代码或名称"
- },
- "trade_date": {
- "type": "string",
- "format": "date",
- "nullable": true,
- "description": "交易日期,格式:YYYY-MM-DD"
- },
- "sort_by": {
- "type": "string",
- "enum": ["change_pct", "_score", "stock_count", "concept_name", "added_date"],
- "default": "change_pct",
- "description": "排序方式"
- },
- "use_knn": {
- "type": "boolean",
- "default": true,
- "description": "是否使用KNN搜索优化语义搜索"
- }
- }
- },
- "SearchResponse": {
- "type": "object",
- "properties": {
- "total": {
- "type": "integer",
- "description": "总结果数"
- },
- "took_ms": {
- "type": "integer",
- "description": "查询耗时(毫秒)"
- },
- "results": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/ConceptResult"
- }
- },
- "search_info": {
- "type": "object",
- "description": "搜索信息",
- "properties": {
- "query": {
- "type": "string"
- },
- "cleaned_query": {
- "type": "string"
- },
- "semantic_weight": {
- "type": "number"
- },
- "keyword_weight": {
- "type": "number"
- },
- "match_type": {
- "type": "string",
- "enum": ["keyword", "semantic", "hybrid", "semantic_knn", "hybrid_knn"]
- },
- "stock_filters": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "has_embedding": {
- "type": "boolean"
- },
- "sort_by": {
- "type": "string"
- },
- "use_knn": {
- "type": "boolean"
- }
- }
- },
- "price_date": {
- "type": "string",
- "format": "date",
- "nullable": true,
- "description": "价格数据日期"
- },
- "page": {
- "type": "integer",
- "description": "当前页码"
- },
- "total_pages": {
- "type": "integer",
- "description": "总页数"
- }
- }
- },
- "ConceptResult": {
- "type": "object",
- "properties": {
- "concept_id": {
- "type": "string",
- "description": "概念ID"
- },
- "concept": {
- "type": "string",
- "description": "概念名称"
- },
- "description": {
- "type": "string",
- "nullable": true,
- "description": "概念描述"
- },
- "stocks": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/StockInfo"
- },
- "description": "关联股票列表(最多20条)"
- },
- "stock_count": {
- "type": "integer",
- "description": "关联股票总数"
- },
- "happened_times": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true,
- "description": "发生时间列表"
- },
- "score": {
- "type": "number",
- "description": "搜索相关性分数"
- },
- "match_type": {
- "type": "string",
- "enum": ["semantic", "keyword", "hybrid", "semantic_knn", "hybrid_knn"],
- "description": "匹配类型"
- },
- "highlights": {
- "type": "object",
- "nullable": true,
- "description": "高亮匹配信息"
- },
- "price_info": {
- "$ref": "#/components/schemas/ConceptPriceInfo"
- }
- }
- },
- "StockInfo": {
- "type": "object",
- "properties": {
- "stock_name": {
- "type": "string",
- "description": "股票名称"
- },
- "stock_code": {
- "type": "string",
- "description": "股票代码"
- },
- "reason": {
- "type": "string",
- "nullable": true,
- "description": "关联原因"
- },
- "industry": {
- "type": "string",
- "nullable": true,
- "description": "所属行业"
- },
- "project": {
- "type": "string",
- "nullable": true,
- "description": "相关项目"
- }
- }
- },
- "ConceptPriceInfo": {
- "type": "object",
- "nullable": true,
- "properties": {
- "trade_date": {
- "type": "string",
- "format": "date",
- "description": "交易日期"
- },
- "avg_change_pct": {
- "type": "number",
- "nullable": true,
- "description": "平均涨跌幅(%)"
- }
- }
- },
- "ConceptDetail": {
- "type": "object",
- "properties": {
- "concept_id": {
- "type": "string"
- },
- "concept": {
- "type": "string"
- },
- "description": {
- "type": "string",
- "nullable": true
- },
- "stocks": {
- "type": "array",
- "items": {
- "type": "object"
- }
- },
- "stocks_reason": {
- "type": "object",
- "description": "股票关联原因详情"
- },
- "happened_times": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true
- },
- "created_at": {
- "type": "string",
- "nullable": true
- },
- "price_info": {
- "$ref": "#/components/schemas/ConceptPriceInfo"
- }
- }
- },
- "StockConceptsResponse": {
- "type": "object",
- "properties": {
- "stock_code": {
- "type": "string",
- "description": "股票代码"
- },
- "stats": {
- "type": "object",
- "properties": {
- "total_concepts": {
- "type": "integer",
- "description": "关联概念总数"
- },
- "returned_concepts": {
- "type": "integer",
- "description": "返回概念数"
- },
- "stock_info": {
- "type": "object",
- "nullable": true,
- "properties": {
- "stock_code": {
- "type": "string"
- },
- "stock_name": {
- "type": "string"
- }
- }
- },
- "concept_categories": {
- "type": "object",
- "description": "概念分类统计"
- }
- }
- },
- "concepts": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/StockConceptInfo"
- }
- },
- "price_date": {
- "type": "string",
- "format": "date",
- "nullable": true
- }
- }
- },
- "StockConceptInfo": {
- "type": "object",
- "properties": {
- "concept_id": {
- "type": "string"
- },
- "concept": {
- "type": "string"
- },
- "stock_count": {
- "type": "integer"
- },
- "happened_times": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "nullable": true
- },
- "description": {
- "type": "string",
- "nullable": true
- },
- "stock_detail": {
- "type": "object",
- "nullable": true,
- "description": "该股票在此概念中的详细信息"
- },
- "price_info": {
- "$ref": "#/components/schemas/ConceptPriceInfo"
- }
- }
- },
- "StockSearchResponse": {
- "type": "object",
- "properties": {
- "total": {
- "type": "integer",
- "description": "搜索结果总数"
- },
- "stocks": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/StockSearchResult"
- }
- }
- }
- },
- "StockSearchResult": {
- "type": "object",
- "properties": {
- "stock_code": {
- "type": "string"
- },
- "stock_name": {
- "type": "string"
- },
- "concept_count": {
- "type": "integer",
- "description": "关联概念数量"
- }
- }
- },
- "PriceTimeSeriesResponse": {
- "type": "object",
- "properties": {
- "concept_id": {
- "type": "string"
- },
- "concept_name": {
- "type": "string"
- },
- "start_date": {
- "type": "string",
- "format": "date"
- },
- "end_date": {
- "type": "string",
- "format": "date"
- },
- "data_points": {
- "type": "integer",
- "description": "数据点数量"
- },
- "timeseries": {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/PriceTimeSeriesItem"
- }
- }
- }
- },
- "PriceTimeSeriesItem": {
- "type": "object",
- "properties": {
- "trade_date": {
- "type": "string",
- "format": "date"
- },
- "avg_change_pct": {
- "type": "number",
- "nullable": true,
- "description": "平均涨跌幅(%)"
- },
- "stock_count": {
- "type": "integer",
- "nullable": true,
- "description": "当日股票数量"
- }
- }
- },
- "ConceptStatisticsResponse": {
- "type": "object",
- "properties": {
- "success": {
- "type": "boolean"
- },
- "data": {
- "$ref": "#/components/schemas/ConceptStatistics"
- },
- "params": {
- "type": "object",
- "properties": {
- "days": {
- "type": "integer"
- },
- "min_stock_count": {
- "type": "integer"
- },
- "start_date": {
- "type": "string"
- },
- "end_date": {
- "type": "string"
- }
- }
- },
- "note": {
- "type": "string",
- "nullable": true
- }
- }
- },
- "ConceptStatistics": {
- "type": "object",
- "properties": {
- "hot_concepts": {
- "type": "array",
- "description": "涨幅榜",
- "items": {
- "$ref": "#/components/schemas/ConceptStatItem"
- }
- },
- "cold_concepts": {
- "type": "array",
- "description": "跌幅榜",
- "items": {
- "$ref": "#/components/schemas/ConceptStatItem"
- }
- },
- "active_concepts": {
- "type": "array",
- "description": "活跃榜",
- "items": {
- "$ref": "#/components/schemas/ConceptStatItem"
- }
- },
- "volatile_concepts": {
- "type": "array",
- "description": "波动榜",
- "items": {
- "$ref": "#/components/schemas/ConceptStatItem"
- }
- },
- "momentum_concepts": {
- "type": "array",
- "description": "连涨榜",
- "items": {
- "$ref": "#/components/schemas/ConceptStatItem"
- }
- },
- "summary": {
- "type": "object",
- "description": "统计摘要",
- "properties": {
- "total_concepts": {
- "type": "integer"
- },
- "positive_count": {
- "type": "integer"
- },
- "negative_count": {
- "type": "integer"
- },
- "avg_change": {
- "type": "number"
- },
- "update_time": {
- "type": "string"
- },
- "date_range": {
- "type": "string"
- },
- "days": {
- "type": "integer"
- },
- "start_date": {
- "type": "string"
- },
- "end_date": {
- "type": "string"
- }
- }
- }
- }
- },
- "ConceptStatItem": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "概念名称"
- },
- "concept_id": {
- "type": "string",
- "nullable": true
- },
- "change_pct": {
- "type": "number",
- "nullable": true,
- "description": "涨跌幅"
- },
- "stock_count": {
- "type": "integer",
- "nullable": true,
- "description": "股票数量"
- },
- "news_count": {
- "type": "integer",
- "nullable": true,
- "description": "新闻数量/交易天数"
- },
- "report_count": {
- "type": "integer",
- "nullable": true
- },
- "total_mentions": {
- "type": "integer",
- "nullable": true
- },
- "volatility": {
- "type": "number",
- "nullable": true,
- "description": "波动率"
- },
- "avg_change": {
- "type": "number",
- "nullable": true
- },
- "max_change": {
- "type": "number",
- "nullable": true
- },
- "consecutive_days": {
- "type": "integer",
- "nullable": true,
- "description": "连涨天数"
- },
- "total_change": {
- "type": "number",
- "nullable": true,
- "description": "累计涨幅"
- },
- "avg_daily": {
- "type": "number",
- "nullable": true,
- "description": "日均涨幅"
- }
- }
- },
- "HTTPError": {
- "type": "object",
- "properties": {
- "detail": {
- "type": "string",
- "description": "错误详情"
- }
- }
- }
- }
- }
-}
diff --git a/concept_hierarchy.json b/concept_hierarchy.json
deleted file mode 100644
index 41951054..00000000
--- a/concept_hierarchy.json
+++ /dev/null
@@ -1,1176 +0,0 @@
-{
- "hierarchy": [
- {
- "lv1": "人工智能",
- "lv1_id": "L1-01",
- "children": [
- {
- "lv2": "AI算力基础设施",
- "lv2_id": "L2-01-01",
- "children": [
- {
- "lv3": "AI芯片与半导体",
- "lv3_id": "L3-01-01-01",
- "concepts": [
- "AI算力芯片",
- "AI芯片",
- "ASIC",
- "GPU概念股",
- "HBM",
- "国产GPU",
- "国产算力芯片",
- "英伟达H20",
- "英伟达概念",
- "算力",
- "阿里AI芯片",
- "中昊芯英概念股",
- "TPU芯片"
- ]
- },
- {
- "lv3": "AI服务器与组件",
- "lv3_id": "L3-01-01-02",
- "concepts": [
- "AI PCB",
- "AI PCB英伟达M9",
- "AI服务器钽电容",
- "PCB",
- "服务器零部件",
- "服务器",
- "电磁屏蔽",
- "钽电容",
- "超聚变"
- ]
- },
- {
- "lv3": "数据中心(供电与散热)",
- "lv3_id": "L3-01-01-03",
- "concepts": [
- "AIDC供配电设备弹性",
- "固态变压器SST",
- "数据中心电力设备",
- "数据中心",
- "数据中心液冷",
- "柴油发电机",
- "微通道水冷板",
- "液态金属散热",
- "液冷数据中心",
- "液冷",
- "电池备份单元(BBU)",
- "秦淮数据",
- "磁悬浮压缩机",
- "英伟达电源方案",
- "铅酸电池",
- "超级电容器",
- "钻石散热",
- "北美缺电AI电力"
- ]
- },
- {
- "lv3": "高速互联(光/铜)",
- "lv3_id": "L3-01-01-04",
- "concepts": [
- "OCS光电路交换机",
- "光通信CPO",
- "光芯片",
- "光纤",
- "光通信",
- "光纤列阵单元FAU",
- "博通交换机",
- "硅光技术",
- "空芯光纤",
- "薄膜铌酸锂",
- "铜连接",
- "铜互连"
- ]
- },
- {
- "lv3": "区域与综合算力",
- "lv3_id": "L3-01-01-05",
- "concepts": [
- "上海算力",
- "中国星际之门芜湖",
- "四川算力",
- "大厂算力订单",
- "星际之门概念",
- "核心城市智算算力",
- "毫秒用算",
- "杭州算力大会",
- "甲骨文概念股",
- "字节算力",
- "云计算各厂商云"
- ]
- }
- ]
- },
- {
- "lv2": "AI模型与应用",
- "lv2_id": "L2-01-02",
- "children": [
- {
- "lv3": "大模型与平台",
- "lv3_id": "L3-01-02-01",
- "concepts": [
- "DeepSeek FP8",
- "DeepSeek、国产算力",
- "DeepSeek",
- "国产大模型",
- "昇腾异构计算架构-CANN",
- "KIMI",
- "Minimax",
- "MOE模型",
- "腾讯大模型",
- "腾讯混元大模型",
- "谷歌",
- "豆包大模型",
- "谷歌概念",
- "通义千问阿里云",
- "阿里云通义千问",
- "阿里云",
- "马斯克Grok3大模型",
- "阶跃星辰"
- ]
- },
- {
- "lv3": "AI原生应用与智能体",
- "lv3_id": "L3-01-02-02",
- "concepts": [
- "AI-细分延伸更新",
- "AI Agent",
- "AI应用智能体",
- "AI智能体AI应用",
- "AI编程",
- "AI营销",
- "AI语音助手",
- "Manus",
- "MCP",
- "低代码",
- "开发智能体",
- "微软Azure云平台",
- "腾讯元宝",
- "秘塔AI",
- "英伟达代理",
- "阿里“千问”项目",
- "阿里AI来听",
- "阿里AI千问、灵光",
- "AI伴侣",
- "AI应用陪伴智能体",
- "AI成人陪伴",
- "AI陪伴",
- "字节AI陪伴",
- "AI教育",
- "AI监考"
- ]
- },
- {
- "lv3": "AI赋能行业",
- "lv3_id": "L3-01-02-03",
- "concepts": [
- "AI合集",
- "AI应用AI语料",
- "AI医药华为"
- ]
- }
- ]
- },
- {
- "lv2": "AI生态与解决方案",
- "lv2_id": "L2-01-03",
- "children": [
- {
- "lv3": "华为AI生态",
- "lv3_id": "L3-01-03-01",
- "concepts": [
- "华为910C",
- "华为AI容器",
- "华为通信大模型",
- "华为昇腾",
- "华为云",
- "华为昇腾超节点",
- "华为AI存储"
- ]
- },
- {
- "lv3": "国产化与信创AI",
- "lv3_id": "L3-01-03-02",
- "concepts": [
- "AI一体机",
- "DeepSeek智算一体机",
- "昇腾推理一体机",
- "一体机核心标的弹性测算",
- "央国企AI一张图"
- ]
- },
- {
- "lv3": "其他AI生态",
- "lv3_id": "L3-01-03-03",
- "concepts": [
- "字节豆包概念股",
- "腾讯云及大模型合作公司"
- ]
- }
- ]
- }
- ]
- },
- {
- "lv1": "机器人与智能制造",
- "lv1_id": "L1-02",
- "children": [
- {
- "lv2": "人形机器人",
- "lv2_id": "L2-02-01",
- "children": [
- {
- "lv3": "核心厂商与整机",
- "lv3_id": "L3-02-01-01",
- "concepts": [
- "Optimus特斯拉机器人",
- "乐聚机器人",
- "人形机器人Figure",
- "人形机器人",
- "优必选",
- "人形机器人核心标的概览",
- "优必选机器人",
- "人形机器人核心标的估值弹性测算",
- "华为人形机器人",
- "各厂商机器人",
- "天太机器人",
- "天工机器人",
- "宇树人形机器人",
- "宇树机器人",
- "小米智元机器人产业链机构版",
- "小米机器人",
- "小鹏机器人",
- "开普勒机器人",
- "智元机器人",
- "松延动力机器人",
- "特斯拉人形机器人弹性测算",
- "特斯拉人形机器人",
- "特斯拉人形机器人价值量",
- "苹果机器人",
- "美的库卡机器人",
- "赛力斯机器人",
- "荣耀华为人形机器人"
- ]
- },
- {
- "lv3": "核心零部件",
- "lv3_id": "L3-02-01-02",
- "concepts": [
- "PCB轴向磁通电机",
- "人形机器人万向节",
- "人形机器人-滚柱丝杆丝杠",
- "摆线减速器",
- "轴向磁通电机"
- ]
- },
- {
- "lv3": "材料与工艺",
- "lv3_id": "L3-02-01-03",
- "concepts": [
- "MIM概念",
- "人形机器人腱绳",
- "人形机器人轻量化-PEEK材料",
- "冷锻产业链",
- "机器人轻量化-PEEK",
- "机器人轻量化-镁铝合金",
- "机器人轻量化—碳纤维",
- "金属粉末注射成形MIM",
- "超高分子量聚乙烯纤维"
- ]
- },
- {
- "lv3": "感知与交互",
- "lv3_id": "L3-02-01-04",
- "concepts": [
- "机器人动作捕捉",
- "机器人皮肤仿生皮肤",
- "机器人电子鼻",
- "电子皮肤"
- ]
- },
- {
- "lv3": "产业链与配套",
- "lv3_id": "L3-02-01-05",
- "concepts": [
- "人形机器产业链",
- "奇瑞机器人潜在产业链",
- "机器人零部件加工设备",
- "机器人充电"
- ]
- }
- ]
- },
- {
- "lv2": "工业及特种机器人",
- "lv2_id": "L2-02-02",
- "concepts": [
- "云深处",
- "工业机器人",
- "外骨骼机器人",
- "机电",
- "机器狗四足机器人",
- "AGV"
- ]
- },
- {
- "lv2": "智能制造与设备",
- "lv2_id": "L2-02-03",
- "concepts": [
- "超跌-机械板块",
- "工业母机"
- ]
- }
- ]
- },
- {
- "lv1": "半导体产业链",
- "lv1_id": "L1-03",
- "children": [
- {
- "lv2": "半导体设备",
- "lv2_id": "L2-03-01",
- "concepts": [
- "PCB设备及耗材",
- "上海微电子",
- "光刻机宇量昇",
- "光刻机",
- "半导体设备",
- "大湾区芯片展览会-新凯莱",
- "新凯来示波器",
- "新凯来概念股",
- "电子束光刻机“羲之”"
- ]
- },
- {
- "lv2": "半导体材料",
- "lv2_id": "L2-03-02",
- "concepts": [
- "PSPI",
- "先进陶瓷",
- "光刻胶",
- "半导体材料",
- "半导体抛光液",
- "国产光刻胶",
- "电子级玻璃纤维布",
- "电子级玻璃纤维纱布",
- "电子特气",
- "电子特气六氟化钨",
- "石英砂",
- "磷化铟"
- ]
- },
- {
- "lv2": "芯片设计与EDA",
- "lv2_id": "L2-03-03",
- "concepts": [
- "EDA",
- "RISC-V",
- "mobileye替代概念",
- "功率半导体",
- "国产芯片参股公司",
- "摩尔线程IPO",
- "摩尔线程",
- "模拟芯片",
- "沐曦集成",
- "模拟厂商弹性测算",
- "汽车芯片",
- "第三代半导体",
- "英诺赛科概念股",
- "紫光展锐IPO",
- "半导体设计"
- ]
- },
- {
- "lv2": "先进封装与封测",
- "lv2_id": "L2-03-04",
- "concepts": [
- "华为910C",
- "改良型半加成工艺mSAP",
- "半导体封测",
- "盛合晶微",
- "玻璃基板",
- "盛合晶微概念股"
- ]
- },
- {
- "lv2": "存储芯片",
- "lv2_id": "L2-03-05",
- "concepts": [
- "SRAM存储",
- "利基型存储DDR4",
- "华为存储OceanStor",
- "存储",
- "存储芯片产业",
- "存储芯片",
- "忆阻器",
- "磁电存储",
- "长鑫存储",
- "长鑫、长江产业链"
- ]
- },
- {
- "lv2": "半导体综合与分销",
- "lv2_id": "L2-03-06",
- "concepts": [
- "半导体产业链",
- "华为麒麟芯片",
- "国产半导体",
- "芯片分销概念",
- "芯片替代",
- "科特估半导体",
- "英特尔概念股"
- ]
- }
- ]
- },
- {
- "lv1": "消费电子",
- "lv1_id": "L1-04",
- "children": [
- {
- "lv2": "AI终端与智能硬件",
- "lv2_id": "L2-04-01",
- "children": [
- {
- "lv3": "AI PC与手机",
- "lv3_id": "L3-04-01-01",
- "concepts": [
- "AI手机",
- "努比亚手机",
- "苹果手机产业链",
- "端侧AI芯片",
- "AI PC",
- "AIPC",
- "高通概念"
- ]
- },
- {
- "lv3": "AR/VR/MR与智能眼镜",
- "lv3_id": "L3-04-01-02",
- "concepts": [
- "AI眼镜",
- "AR眼镜",
- "META智能眼镜",
- "MR",
- "Rokid AR",
- "小米眼镜",
- "智能眼镜",
- "苹果MR产业链",
- "雷鸟创新光波导",
- "阿里夸克AI眼镜"
- ]
- },
- {
- "lv3": "智能穿戴",
- "lv3_id": "L3-04-01-03",
- "concepts": [
- "智能穿戴",
- "华为Mate70手表",
- "消费电子-玄玑感知系统"
- ]
- },
- {
- "lv3": "其他AI终端",
- "lv3_id": "L3-04-01-04",
- "concepts": [
- "微泵液冷",
- "石墨烯散热",
- "云手机"
- ]
- }
- ]
- },
- {
- "lv2": "创新形态与零部件",
- "lv2_id": "L2-04-02",
- "concepts": [
- "AI手势识别",
- "AI隔空投送",
- "AR镀膜",
- "ISP视觉",
- "华为三折叠屏",
- "折叠屏",
- "显影液及硅基OLED",
- "果链OPEN AI复用",
- "苹果供应商核心公司",
- "苹果折叠屏",
- "苹果OLED潜在受益"
- ]
- },
- {
- "lv2": "华为消费电子生态",
- "lv2_id": "L2-04-03",
- "concepts": [
- "华为Pura70",
- "华为P70",
- "华为鸿蒙",
- "华为Mate80",
- "华为MATE70",
- "华为海思星闪",
- "鸿蒙PC"
- ]
- }
- ]
- },
- {
- "lv1": "未来出行与新空间",
- "lv1_id": "L1-05",
- "children": [
- {
- "lv2": "智能驾驶与汽车",
- "lv2_id": "L2-05-01",
- "children": [
- {
- "lv3": "自动驾驶(L4及以上)",
- "lv3_id": "L3-05-01-01",
- "concepts": [
- "Robotaxi",
- "京东物流Robovan",
- "小马智行",
- "无人环卫车",
- "无人物流",
- "文远知行",
- "无人物流车九识智能",
- "无人驾驶",
- "特斯拉无人驾驶出租车Robotaxi",
- "特斯拉RoboTaxi概念",
- "菜鸟无人物流车",
- "无人驾驶公交",
- "矿山智驾",
- "自动驾驶"
- ]
- },
- {
- "lv3": "智能驾驶(L2-L3)",
- "lv3_id": "L3-05-01-02",
- "concepts": [
- "特斯拉FSD",
- "地平线概念",
- "地平线",
- "智能驾驶产业链",
- "比亚迪智驾",
- "禾赛科技概念股"
- ]
- },
- {
- "lv3": "整车与生态",
- "lv3_id": "L3-05-01-03",
- "concepts": [
- "特斯拉产业链",
- "小米汽车产业链",
- "小米汽车产业链弹性",
- "小米概念",
- "小米汽车产业链弹性测算",
- "小米YU7供应链弹性测算",
- "比亚迪产业链",
- "上汽集团华为智行",
- "奇瑞汽车",
- "荣耀股改"
- ]
- },
- {
- "lv3": "汽车电子与零部件",
- "lv3_id": "L3-05-01-04",
- "concepts": [
- "空中成像",
- "汽车无线充电",
- "汽车安全"
- ]
- }
- ]
- },
- {
- "lv2": "低空经济与飞行汽车",
- "lv2_id": "L2-05-02",
- "concepts": [
- "长安飞行汽车机器人概念",
- "eVTOL材料",
- "亿航智能订单量",
- "低空经济亿航智能",
- "低空管控",
- "低空物流",
- "低空经济&飞行汽车",
- "低空设计",
- "低空经济",
- "低空经济产业链汇集",
- "小鹏汇天",
- "小鹏汇天供应商",
- "飞行汽车eVTOL"
- ]
- },
- {
- "lv2": "商业航天与卫星通信",
- "lv2_id": "L2-05-03",
- "concepts": [
- "6G概念",
- "低轨卫星通信华为",
- "凌空天行",
- "北斗信使",
- "北斗导航",
- "卫星互联网",
- "卫星出海",
- "商业航天卫星通信",
- "商业航天",
- "太空旅行",
- "太空行走",
- "太空算力",
- "手机直连卫星",
- "星河动力",
- "星网",
- "蓝箭航天朱雀三号"
- ]
- },
- {
- "lv2": "车路协同",
- "lv2_id": "L2-05-04",
- "concepts": [
- "车路云-车路协同运营建设",
- "车路协同",
- "车路云一体化"
- ]
- }
- ]
- },
- {
- "lv1": "新能源与电力",
- "lv1_id": "L1-06",
- "children": [
- {
- "lv2": "新能源技术",
- "lv2_id": "L2-06-01",
- "children": [
- {
- "lv3": "光伏",
- "lv3_id": "L3-06-01-01",
- "concepts": [
- "N型产业链",
- "光伏",
- "光伏行业兼并重组",
- "光伏产业链",
- "反内卷光伏",
- "叠层钙钛矿",
- "钙钛矿电池"
- ]
- },
- {
- "lv3": "风电",
- "lv3_id": "L3-06-01-02",
- "concepts": [
- "海上风电",
- "风电"
- ]
- },
- {
- "lv3": "储能与电池技术",
- "lv3_id": "L3-06-01-03",
- "concepts": [
- "固态电池设备",
- "固态电池-硅基负极",
- "固态电池-硫化物",
- "固态电池负极集流体材料-铜箔",
- "固态电池",
- "固态电池产业链",
- "复合集流体",
- "富锂锰基材料",
- "有机框架材料",
- "硅基负极材料",
- "钠离子电池",
- "陶瓷隔膜骨架膜",
- "储能",
- "电化学储能系统"
- ]
- },
- {
- "lv3": "核能",
- "lv3_id": "L3-06-01-04",
- "concepts": [
- "可控核聚变",
- "微型核电",
- "核污染防治",
- "核聚变超导",
- "核电钍基熔盐堆",
- "核电产业链",
- "高温概念"
- ]
- }
- ]
- },
- {
- "lv2": "电力设备与电网",
- "lv2_id": "L2-06-02",
- "concepts": [
- "变压器出海",
- "燃气轮机HRSG",
- "电力产业链",
- "电力电网",
- "电力",
- "电力设备",
- "智能电表"
- ]
- },
- {
- "lv2": "充电与换电",
- "lv2_id": "L2-06-03",
- "concepts": [
- "换电重卡",
- "换电",
- "充电桩",
- "华为智能充电",
- "华为智能充电网络",
- "比亚迪兆瓦闪充"
- ]
- }
- ]
- },
- {
- "lv1": "周期性行业与资源品",
- "lv1_id": "L1-07",
- "children": [
- {
- "lv2": "化工",
- "lv2_id": "L2-07-01",
- "concepts": [
- "H酸活性染料",
- "TMA偏苯三酸酐",
- "六氟磷酸锂",
- "光引发剂",
- "农药杀虫剂-氯虫苯甲酰胺",
- "化工品涨价",
- "化工概念",
- "化工",
- "双季戊四醇",
- "己内酰胺",
- "有机染料",
- "有机硅",
- "有机硅产业链",
- "泰乐菌素原料药",
- "正丙醇",
- "涤纶长丝",
- "烧碱",
- "甲苯二异氰酸酯-TDI",
- "电解液产业链",
- "环氧丙烷",
- "电解液添加剂",
- "纯碱",
- "维生素",
- "磷化工六氟磷酸锂",
- "苯酚丙酮",
- "聚酯产业",
- "磷化工",
- "隔膜",
- "除草剂-烯草酮"
- ]
- },
- {
- "lv2": "金属与矿产",
- "lv2_id": "L2-07-02",
- "concepts": [
- "铜",
- "铜产业",
- "电解铝",
- "锂资源",
- "白银",
- "出口管制",
- "化工有色元素周期表",
- "缅甸地震",
- "稀土",
- "钨金属",
- "钴金属",
- "钼金属",
- "锡矿",
- "钴"
- ]
- },
- {
- "lv2": "航运",
- "lv2_id": "L2-07-03",
- "concepts": [
- "以伊冲突-天然气",
- "以伊冲突-航运",
- "以伊冲突-油运仓储",
- "航运",
- "远洋航运"
- ]
- },
- {
- "lv2": "能源",
- "lv2_id": "L2-07-04",
- "concepts": [
- "以伊冲突-资源化工",
- "油气",
- "石油"
- ]
- },
- {
- "lv2": "其他周期",
- "lv2_id": "L2-07-05",
- "concepts": [
- "面板",
- "工程机械",
- "家用电器"
- ]
- }
- ]
- },
- {
- "lv1": "宏观政策与金融",
- "lv1_id": "L1-08",
- "children": [
- {
- "lv2": "国企改革与市值管理",
- "lv2_id": "L2-08-01",
- "concepts": [
- "IPO终止相关企业重组预期",
- "中兵集团并购重组",
- "上海并购重组",
- "安徽国资",
- "宝德计算机",
- "并购重组预期",
- "并购重组",
- "整车央企重组",
- "消费医疗重组预期",
- "河南国资能源集团重组",
- "珠海国资",
- "科创板并购重组",
- "科技重组",
- "超聚变借壳预期",
- "重组-中科院系&海光系",
- "中字头央企",
- "中字头",
- "国资高息股",
- "央国企地产",
- "央企市值管理",
- "央国企重组",
- "央国企",
- "市值管理16条-破净股",
- "破净央国企",
- "破净股合集",
- "高分红预期",
- "高股息合集"
- ]
- },
- {
- "lv2": "地缘政治与贸易",
- "lv2_id": "L2-08-02",
- "concepts": [
- "华为",
- "东盟贸易",
- "中俄贸易",
- "中美关系",
- "中欧贸易",
- "二轮车全地形车",
- "乙烷",
- "关税豁免",
- "关税减免出口链",
- "反制关税涨价预期",
- "后关税战受益",
- "墨西哥汽车零部件",
- "外贸出口",
- "对日反制",
- "海事反制",
- "绒毛浆",
- "芬太尼管制",
- "越南工厂",
- "金霉素",
- "转口贸易出口转内销"
- ]
- },
- {
- "lv2": "区域发展与基建",
- "lv2_id": "L2-08-03",
- "concepts": [
- "三峡水运新通道",
- "新疆概念",
- "新藏铁路",
- "水利工程",
- "水利",
- "混凝土减水剂、砂石设备",
- "节水产业240423",
- "西南水电站-机构测算",
- "西南水电站",
- "西南水电",
- "西部大开发240424",
- "西部大开发",
- "重庆",
- "隧洞设备盾构机",
- "雅下水电站大件物流",
- "雅下水电站",
- "雅下水电对电力设备增量测算-机构",
- "上海自贸区",
- "海南",
- "海南自贸区",
- "海南自贸港"
- ]
- },
- {
- "lv2": "金融改革",
- "lv2_id": "L2-08-04",
- "concepts": [
- "AMC中央汇金",
- "AMC",
- "券商合并预期",
- "国有大行定增一览",
- "大金融",
- "湘财合并大智慧",
- "证券"
- ]
- },
- {
- "lv2": "产业与社会政策",
- "lv2_id": "L2-08-05",
- "concepts": [
- "新质生产力",
- "工业设备更新",
- "设备更新",
- "反内卷",
- "反内卷食用盐",
- "反内卷造纸",
- "反内卷合集",
- "反内卷快递",
- "涨价概念",
- "城市旧改",
- "城市更新",
- "城市更新电梯",
- "房屋检测",
- "房地产产业链"
- ]
- }
- ]
- },
- {
- "lv1": "数字内容与消费",
- "lv1_id": "L1-09",
- "children": [
- {
- "lv2": "AIGC与数字内容",
- "lv2_id": "L2-09-01",
- "concepts": [
- "AI游戏",
- "SORA概念",
- "内容审核概念",
- "国产游戏黑神话",
- "影视",
- "影视IP",
- "幻兽帕鲁",
- "影视传媒",
- "文生视频",
- "游戏",
- "版权",
- "漫剧",
- "短剧",
- "腾讯短剧重点名单",
- "智象未来"
- ]
- },
- {
- "lv2": "IP经济与新消费",
- "lv2_id": "L2-09-02",
- "concepts": [
- "诡秘之主",
- "上市潮玩盲盒公司",
- "卡游文创玩具",
- "布鲁可IP衍生品",
- "布鲁可谷子经济",
- "泡泡玛特概念",
- "潮玩产业",
- "蜜雪冰城",
- "谷子商城",
- "积木玩具10大",
- "谷子经济"
- ]
- },
- {
- "lv2": "平台经济",
- "lv2_id": "L2-09-03",
- "concepts": [
- "华为抖音支付",
- "华为鸿蒙甄选与支付",
- "TikTok",
- "小红书概念",
- "小红书概念股",
- "抖音概念"
- ]
- },
- {
- "lv2": "体育、文旅与服务消费",
- "lv2_id": "L2-09-04",
- "concepts": [
- "体育",
- "体育产业",
- "川超联赛",
- "第十五届全运会",
- "荒野求生",
- "足球",
- "足球-苏超联赛、体彩",
- "冰雪经济",
- "大消费",
- "文旅旅游消费",
- "旅游",
- "旅游消费"
- ]
- },
- {
- "lv2": "生物医药与养老",
- "lv2_id": "L2-09-05",
- "concepts": [
- "创新药双抗",
- "创新药相关",
- "创新药",
- "医疗器械",
- "医药",
- "三胎",
- "多胎",
- "多胎辅助生殖概念240926",
- "学前教育",
- "育儿补贴",
- "辅助生殖",
- "养老机器人",
- "个人养老金",
- "养老概念",
- "银发经济"
- ]
- }
- ]
- },
- {
- "lv1": "国防军工",
- "lv1_id": "L1-10",
- "children": [
- {
- "lv2": "航空航天装备",
- "lv2_id": "L2-10-01",
- "concepts": [
- "军机",
- "国产航母",
- "地面兵装",
- "巴黎航展",
- "海军",
- "珠海航展",
- "电磁弹射概念股",
- "电磁发射设备",
- "航母福建舰240430"
- ]
- },
- {
- "lv2": "信息化与无人作战",
- "lv2_id": "L2-10-02",
- "concepts": [
- "AI军工",
- "九天无人机",
- "信息支援概念整理",
- "军工水面水下作战",
- "军工信息化",
- "军用无人机反无人机",
- "无人机蜂群",
- "水下军工",
- "远程火力"
- ]
- },
- {
- "lv2": "军工综合与军贸",
- "lv2_id": "L2-10-03",
- "concepts": [
- "军工",
- "军工-阅兵",
- "军贸",
- "国防军工",
- "巴印军贸"
- ]
- }
- ]
- },
- {
- "lv1": "信息技术与安全",
- "lv1_id": "L1-11",
- "children": [
- {
- "lv2": "信创与自主可控",
- "lv2_id": "L2-11-01",
- "concepts": [
- "信创概念",
- "关键软件",
- "国产信创概览",
- "工业软件",
- "政务云政务IT",
- "自主可控",
- "软件自主可控"
- ]
- },
- {
- "lv2": "数据要素与数字基建",
- "lv2_id": "L2-11-02",
- "concepts": [
- "地理信息",
- "RDA概念股",
- "RWA上链— IoT设备数据采集",
- "数据可信",
- "数据要素",
- "文化数据资产交易",
- "数据维护",
- "数据交易所",
- "跨境数据数据要素"
- ]
- },
- {
- "lv2": "金融科技与数字货币",
- "lv2_id": "L2-11-03",
- "concepts": [
- "上海浦江数链",
- "复星稳定币",
- "树图链概念",
- "稳定币-蚂蚁国际",
- "稳定币一体机",
- "稳定币RWA概念股",
- "香港金融牌照",
- "CIPS",
- "数字货币",
- "跨境支付",
- "金砖支付概念"
- ]
- },
- {
- "lv2": "通信技术与网络安全",
- "lv2_id": "L2-11-04",
- "concepts": [
- "5.5G",
- "5G-A",
- "5G毫米波",
- "eSIM概念",
- "IPV6",
- "华为5G",
- "通感一体",
- "充电宝",
- "信息安全",
- "安全概念股",
- "通信安全",
- "通信设备"
- ]
- }
- ]
- },
- {
- "lv1": "其他主题",
- "lv1_id": "L1-12",
- "children": [
- {
- "lv2": "事件驱动与Meme",
- "lv2_id": "L2-12-01",
- "concepts": [
- "孙潇雅团队概念股",
- "小鹏产业链",
- "燃气设备",
- "“马”字辈",
- "低价股",
- "周杰伦概念股",
- "章盟主概念股",
- "韦神概念股",
- "高送转概念股"
- ]
- }
- ]
- }
- ],
- "uncategorized": []
-}
\ No newline at end of file
diff --git a/src/views/Concept/index.js b/src/views/Concept/index.js
index bef70f23..14dc571d 100644
--- a/src/views/Concept/index.js
+++ b/src/views/Concept/index.js
@@ -1693,8 +1693,7 @@ const ConceptCenter = () => {
-
-
+
{searchQuery && sortBy === '_score' && (