|
|
"""
|
|
|
财务数据服务
|
|
|
"""
|
|
|
from typing import List, Dict
|
|
|
from datetime import date
|
|
|
from sqlalchemy.orm import Session
|
|
|
from sqlalchemy import and_
|
|
|
import pandas as pd
|
|
|
import logging
|
|
|
|
|
|
from app.models.finance import FinanceBalanceSheet, FinanceCashFlow, FinanceIncome
|
|
|
from app.services.sdk_manager import sdk_manager
|
|
|
from app.services.base_data_service import BaseDataService
|
|
|
from app.utils.date_utils import parse_date, format_date
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class FinanceService:
|
|
|
"""财务数据服务"""
|
|
|
|
|
|
def __init__(self, db: Session):
|
|
|
self.db = db
|
|
|
self.base_service = BaseDataService(db)
|
|
|
|
|
|
def _get_adapter(self):
|
|
|
"""获取SDK适配器(使用连接管理器)"""
|
|
|
return sdk_manager.get_default_connection()
|
|
|
|
|
|
def get_balance_sheet(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date: date,
|
|
|
end_date: date
|
|
|
) -> Dict[str, List[dict]]:
|
|
|
"""获取资产负债表"""
|
|
|
result = {}
|
|
|
|
|
|
for code in codes:
|
|
|
try:
|
|
|
# 查询本地缓存
|
|
|
records = self.db.query(FinanceBalanceSheet).filter(
|
|
|
and_(
|
|
|
FinanceBalanceSheet.code == code,
|
|
|
FinanceBalanceSheet.report_date >= start_date,
|
|
|
FinanceBalanceSheet.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceBalanceSheet.report_date.desc()).all()
|
|
|
|
|
|
# 如果本地没有数据,从SDK获取
|
|
|
if not records:
|
|
|
adapter = self._get_adapter()
|
|
|
if adapter:
|
|
|
sdk_data = adapter.get_balance_sheet([code], start_date, end_date)
|
|
|
|
|
|
if code in sdk_data and not sdk_data[code].empty:
|
|
|
self._save_balance_sheet(code, sdk_data[code])
|
|
|
|
|
|
records = self.db.query(FinanceBalanceSheet).filter(
|
|
|
and_(
|
|
|
FinanceBalanceSheet.code == code,
|
|
|
FinanceBalanceSheet.report_date >= start_date,
|
|
|
FinanceBalanceSheet.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceBalanceSheet.report_date.desc()).all()
|
|
|
|
|
|
result[code] = [
|
|
|
{
|
|
|
"report_date": format_date(r.report_date),
|
|
|
"report_type": r.report_type,
|
|
|
"statement_type": r.statement_type,
|
|
|
"total_assets": float(r.total_assets) if r.total_assets else None,
|
|
|
"total_cur_assets": float(r.total_cur_assets) if r.total_cur_assets else None,
|
|
|
"total_noncur_assets": float(r.total_noncur_assets) if r.total_noncur_assets else None,
|
|
|
"currency_cap": float(r.currency_cap) if r.currency_cap else None,
|
|
|
"notes_receivable": float(r.notes_receivable) if r.notes_receivable else None,
|
|
|
"acct_receivable": float(r.acct_receivable) if r.acct_receivable else None,
|
|
|
"inventory": float(r.inventory) if r.inventory else None,
|
|
|
"fix_assets": float(r.fix_assets) if r.fix_assets else None,
|
|
|
"total_liab": float(r.total_liab) if r.total_liab else None,
|
|
|
"total_cur_liab": float(r.total_cur_liab) if r.total_cur_liab else None,
|
|
|
"total_noncur_liab": float(r.total_noncur_liab) if r.total_noncur_liab else None,
|
|
|
"notes_payable": float(r.notes_payable) if r.notes_payable else None,
|
|
|
"acct_payable": float(r.acct_payable) if r.acct_payable else None,
|
|
|
"st_borrowing": float(r.st_borrowing) if r.st_borrowing else None,
|
|
|
"lt_loan": float(r.lt_loan) if r.lt_loan else None,
|
|
|
"tot_share_equity": float(r.tot_share_equity) if r.tot_share_equity else None
|
|
|
}
|
|
|
for r in records
|
|
|
]
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取{code}资产负债表失败: {str(e)}")
|
|
|
result[code] = []
|
|
|
|
|
|
return result
|
|
|
|
|
|
def get_cash_flow(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date: date,
|
|
|
end_date: date
|
|
|
) -> Dict[str, List[dict]]:
|
|
|
"""获取现金流量表"""
|
|
|
result = {}
|
|
|
|
|
|
for code in codes:
|
|
|
try:
|
|
|
records = self.db.query(FinanceCashFlow).filter(
|
|
|
and_(
|
|
|
FinanceCashFlow.code == code,
|
|
|
FinanceCashFlow.report_date >= start_date,
|
|
|
FinanceCashFlow.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceCashFlow.report_date.desc()).all()
|
|
|
|
|
|
if not records:
|
|
|
adapter = self._get_adapter()
|
|
|
if adapter:
|
|
|
sdk_data = adapter.get_cash_flow([code], start_date, end_date)
|
|
|
|
|
|
if code in sdk_data and not sdk_data[code].empty:
|
|
|
self._save_cash_flow(code, sdk_data[code])
|
|
|
|
|
|
records = self.db.query(FinanceCashFlow).filter(
|
|
|
and_(
|
|
|
FinanceCashFlow.code == code,
|
|
|
FinanceCashFlow.report_date >= start_date,
|
|
|
FinanceCashFlow.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceCashFlow.report_date.desc()).all()
|
|
|
|
|
|
result[code] = [
|
|
|
{
|
|
|
"report_date": format_date(r.report_date),
|
|
|
"report_type": r.report_type,
|
|
|
"statement_type": r.statement_type,
|
|
|
"net_cash_flows_opera_act": float(r.net_cash_flows_opera_act) if r.net_cash_flows_opera_act else None,
|
|
|
"net_cash_flows_inv_act": float(r.net_cash_flows_inv_act) if r.net_cash_flows_inv_act else None,
|
|
|
"net_cash_flows_fin_act": float(r.net_cash_flows_fin_act) if r.net_cash_flows_fin_act else None,
|
|
|
"net_incr_cash_and_cash_equ": float(r.net_incr_cash_and_cash_equ) if r.net_incr_cash_and_cash_equ else None,
|
|
|
"cash_recp_sg_and_rs": float(r.cash_recp_sg_and_rs) if r.cash_recp_sg_and_rs else None,
|
|
|
"cash_pay_goods_services": float(r.cash_pay_goods_services) if r.cash_pay_goods_services else None
|
|
|
}
|
|
|
for r in records
|
|
|
]
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取{code}现金流量表失败: {str(e)}")
|
|
|
result[code] = []
|
|
|
|
|
|
return result
|
|
|
|
|
|
def get_income_statement(
|
|
|
self,
|
|
|
codes: List[str],
|
|
|
start_date: date,
|
|
|
end_date: date
|
|
|
) -> Dict[str, List[dict]]:
|
|
|
"""获取利润表"""
|
|
|
result = {}
|
|
|
|
|
|
for code in codes:
|
|
|
try:
|
|
|
records = self.db.query(FinanceIncome).filter(
|
|
|
and_(
|
|
|
FinanceIncome.code == code,
|
|
|
FinanceIncome.report_date >= start_date,
|
|
|
FinanceIncome.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceIncome.report_date.desc()).all()
|
|
|
|
|
|
if not records:
|
|
|
adapter = self._get_adapter()
|
|
|
if adapter:
|
|
|
sdk_data = adapter.get_income_statement([code], start_date, end_date)
|
|
|
|
|
|
if code in sdk_data and not sdk_data[code].empty:
|
|
|
self._save_income(code, sdk_data[code])
|
|
|
|
|
|
records = self.db.query(FinanceIncome).filter(
|
|
|
and_(
|
|
|
FinanceIncome.code == code,
|
|
|
FinanceIncome.report_date >= start_date,
|
|
|
FinanceIncome.report_date <= end_date
|
|
|
)
|
|
|
).order_by(FinanceIncome.report_date.desc()).all()
|
|
|
|
|
|
result[code] = [
|
|
|
{
|
|
|
"report_date": format_date(r.report_date),
|
|
|
"report_type": r.report_type,
|
|
|
"statement_type": r.statement_type,
|
|
|
"tot_opera_rev": float(r.tot_opera_rev) if r.tot_opera_rev else None,
|
|
|
"opera_rev": float(r.opera_rev) if r.opera_rev else None,
|
|
|
"tot_opera_cost": float(r.tot_opera_cost) if r.tot_opera_cost else None,
|
|
|
"opera_profit": float(r.opera_profit) if r.opera_profit else None,
|
|
|
"total_profit": float(r.total_profit) if r.total_profit else None,
|
|
|
"net_pro_incl_min_int_inc": float(r.net_pro_incl_min_int_inc) if r.net_pro_incl_min_int_inc else None,
|
|
|
"basic_eps": float(r.basic_eps) if r.basic_eps else None,
|
|
|
"diluted_eps": float(r.diluted_eps) if r.diluted_eps else None,
|
|
|
"rd_exp": float(r.rd_exp) if r.rd_exp else None,
|
|
|
"selling_exp": float(r.selling_exp) if r.selling_exp else None,
|
|
|
"admin_exp": float(r.admin_exp) if r.admin_exp else None,
|
|
|
"fin_exp": float(r.fin_exp) if r.fin_exp else None
|
|
|
}
|
|
|
for r in records
|
|
|
]
|
|
|
except Exception as e:
|
|
|
logger.error(f"获取{code}利润表失败: {str(e)}")
|
|
|
result[code] = []
|
|
|
|
|
|
return result
|
|
|
|
|
|
def _save_balance_sheet(self, code: str, df: pd.DataFrame):
|
|
|
"""保存资产负债表"""
|
|
|
if df.empty:
|
|
|
return
|
|
|
|
|
|
for idx, row in df.iterrows():
|
|
|
report_date = idx if isinstance(idx, date) else parse_date(str(idx))
|
|
|
statement_type = int(row.get("statement_type", 0))
|
|
|
|
|
|
existing = self.db.query(FinanceBalanceSheet).filter(
|
|
|
and_(
|
|
|
FinanceBalanceSheet.code == code,
|
|
|
FinanceBalanceSheet.report_date == report_date,
|
|
|
FinanceBalanceSheet.statement_type == statement_type
|
|
|
)
|
|
|
).first()
|
|
|
|
|
|
def get_float(val):
|
|
|
if pd.isna(val):
|
|
|
return None
|
|
|
return float(val)
|
|
|
|
|
|
if existing:
|
|
|
existing.total_assets = get_float(row.get("total_assets"))
|
|
|
existing.total_cur_assets = get_float(row.get("total_cur_assets"))
|
|
|
existing.total_noncur_assets = get_float(row.get("total_noncur_assets"))
|
|
|
existing.tot_share_equity = get_float(row.get("tot_share_equity"))
|
|
|
else:
|
|
|
record = FinanceBalanceSheet(
|
|
|
code=code,
|
|
|
report_date=report_date,
|
|
|
report_type=get_float(row.get("report_type")),
|
|
|
statement_type=statement_type,
|
|
|
total_assets=get_float(row.get("total_assets")),
|
|
|
total_cur_assets=get_float(row.get("total_cur_assets")),
|
|
|
tot_share_equity=get_float(row.get("tot_share_equity"))
|
|
|
)
|
|
|
self.db.add(record)
|
|
|
|
|
|
self.db.commit()
|
|
|
|
|
|
def _save_cash_flow(self, code: str, df: pd.DataFrame):
|
|
|
"""保存现金流量表"""
|
|
|
if df.empty:
|
|
|
return
|
|
|
|
|
|
for idx, row in df.iterrows():
|
|
|
report_date = idx if isinstance(idx, date) else parse_date(str(idx))
|
|
|
statement_type = int(row.get("statement_type", 0))
|
|
|
|
|
|
existing = self.db.query(FinanceCashFlow).filter(
|
|
|
and_(
|
|
|
FinanceCashFlow.code == code,
|
|
|
FinanceCashFlow.report_date == report_date,
|
|
|
FinanceCashFlow.statement_type == statement_type
|
|
|
)
|
|
|
).first()
|
|
|
|
|
|
if not existing:
|
|
|
def get_float(val):
|
|
|
if pd.isna(val):
|
|
|
return None
|
|
|
return float(val)
|
|
|
|
|
|
record = FinanceCashFlow(
|
|
|
code=code,
|
|
|
report_date=report_date,
|
|
|
report_type=get_float(row.get("report_type")),
|
|
|
statement_type=statement_type,
|
|
|
net_cash_flows_opera_act=get_float(row.get("net_cash_flows_opera_act"))
|
|
|
)
|
|
|
self.db.add(record)
|
|
|
|
|
|
self.db.commit()
|
|
|
|
|
|
def _save_income(self, code: str, df: pd.DataFrame):
|
|
|
"""保存利润表"""
|
|
|
if df.empty:
|
|
|
return
|
|
|
|
|
|
for idx, row in df.iterrows():
|
|
|
report_date = idx if isinstance(idx, date) else parse_date(str(idx))
|
|
|
statement_type = int(row.get("statement_type", 0))
|
|
|
|
|
|
existing = self.db.query(FinanceIncome).filter(
|
|
|
and_(
|
|
|
FinanceIncome.code == code,
|
|
|
FinanceIncome.report_date == report_date,
|
|
|
FinanceIncome.statement_type == statement_type
|
|
|
)
|
|
|
).first()
|
|
|
|
|
|
if not existing:
|
|
|
def get_float(val):
|
|
|
if pd.isna(val):
|
|
|
return None
|
|
|
return float(val)
|
|
|
|
|
|
record = FinanceIncome(
|
|
|
code=code,
|
|
|
report_date=report_date,
|
|
|
report_type=get_float(row.get("report_type")),
|
|
|
statement_type=statement_type,
|
|
|
tot_opera_rev=get_float(row.get("tot_opera_rev")),
|
|
|
net_pro_incl_min_int_inc=get_float(row.get("net_pro_incl_min_int_inc")),
|
|
|
basic_eps=get_float(row.get("basic_eps"))
|
|
|
)
|
|
|
self.db.add(record)
|
|
|
|
|
|
self.db.commit()
|