ci/woodpecker/push/main Pipeline failed
依据:wecom-finance/reports/CMA_口径裁定_20260917.md(任总 2026-09-17 23:14「按建议执行」) 裁定1(现金流「投资活动净额=0」参与判定) - classify_enterprise():0 为合法输入;投资恒为 0 → 经营(±)×筹资(±) 二维映射出 2 个 候选类型(candidate_type_nos/candidates),不再整体判「未判定」;投资栏 investing_note 标注「本期无投资活动」;经营/筹资亦为 0 → 回退「未判定(无投资活动)」,不报错不静默 - /analysis 增 classification_dimension / investing_note / nets.investing.note;前端出 二维横幅 + 候选行高亮 + 投资栏「本期无投资活动」(不给单一类型名) - import_cashflow_statements.py:打印层修 2D(type_no=None)TypeError 崩溃;NA 措辞改为 「按 0 参与判定」(机读短语 NA_PHRASE_TMPL/parse_na_activities 未动) 裁定2a(比率类单位统一「百分比(%)」口径,先做) - 新增 app/utils/caliber_units.py:逐 KPI 对照表 RATIO_CALIBER_TABLE(唯一事实源)+ 判定侧 唯一入口 resolve_caliber();3 项需归一 ×100(流动/速动/毛利率),12 项取数已百分数不归一; 表外且命中「疑似错配」窄形态(阈值子句量级≥100 且 0<|实测|<10 且单位属比率类)→ dimension_suspect:不落预警、不推送(唯一规则,防 586/587 假红灯复发) - alert_generator:判定前归一,文案写「口径归一 ×100(原值 1.29 → 129%)」留审计线索 - 新增 scripts/kpi_unit_crosscheck.py(只读对照表产出器) 裁定2b(期间由「当月硬编码」→「最近可用期间」,后做) - resolve_latest_period():取该 KPI 自己的最新 YYYY-MM(禁全局 MAX,防演示账套劫持); 伪期间(2026-H1/H2、2025-FY)在 Python 侧过滤(保「单测=生产」语义) - 过渡期覆盖度自检扩展:记录回落后的期间分布 + 当月 0 条归因提示 - --dry-run(零写入零推送)作放行前抽查证据;generate_and_push/run_alert_check 支持 dry_run 测试:backend/tests/test_caliber_rulings_20260917.py 17 项;全量 1158 passed/1 xfailed
451 lines
20 KiB
Python
451 lines
20 KiB
Python
"""
|
||
现金流量表结构分析 — 管理会计OS (CMA P2 · 财务报表分析)
|
||
规范命名:现金流量表结构分析(本模块不采用民间俗称,术语以任务单为准)
|
||
|
||
核心方法:经营(O)/投资(I)/筹资(F) 三大活动净额正负组合 → 8类企业定性分析
|
||
O 造血能力 / I 成长代价 / F 输血管道
|
||
长期黄金法则:经营现金流必须为正,且应大于净利润(利润质量)。
|
||
|
||
数据模型:cashflow_statements — 官方现金流量表三大活动净额按期间注入
|
||
- 未注入期间:无判定(不拿演示/示例数据冒充真实类型)
|
||
- 官方净额入库后类型判定自动生效(博海2026官方表注入路径)
|
||
"""
|
||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||
from pydantic import BaseModel
|
||
from sqlalchemy.orm import Session
|
||
from sqlalchemy import func
|
||
from typing import Optional, List
|
||
from datetime import datetime
|
||
|
||
from app.database import get_db
|
||
from app.auth_middleware import require_role
|
||
from app.deps import get_entity_id
|
||
from app.models import CashFlowStructureValue
|
||
|
||
router = APIRouter(
|
||
prefix="/api/cma/cashflow-structure",
|
||
tags=["现金流量表结构分析"],
|
||
dependencies=[Depends(require_role("ceo", "finance"))],
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# 八类企业定性分析规则表(O/I/F 符号组合 → 类型)
|
||
# ============================================================
|
||
CASHFLOW_TYPE_RULES = [
|
||
{
|
||
"type_no": 1, "o_sign": "+", "i_sign": "-", "f_sign": "-",
|
||
"name": "成熟健康型", "health_emoji": "🟢", "health_label": "最优",
|
||
"lifecycle": "成熟期",
|
||
"profile": "主业盈利,自给自足;仍在投入扩张且有余力还债/分红。最健康形态(如成熟消费龙头)。",
|
||
"risk": "注意扩张效率与分红可持续性;经营现金流应大于净利润,否则利润质量存疑。",
|
||
},
|
||
{
|
||
"type_no": 2, "o_sign": "+", "i_sign": "+", "f_sign": "-",
|
||
"name": "成熟收割型", "health_emoji": "🟢", "health_label": "良好",
|
||
"lifecycle": "成熟期后期/集团收割期",
|
||
"profile": "主业赚钱+过去投资进入回报期(处置/收益回笼)+还在还债分红。",
|
||
"risk": "若投资净流入来自大量处置核心资产,需警惕「卖资产过日子」。",
|
||
},
|
||
{
|
||
"type_no": 3, "o_sign": "+", "i_sign": "-", "f_sign": "+",
|
||
"name": "高速扩张型", "health_emoji": "🟡", "health_label": "看增速",
|
||
"lifecycle": "成长期",
|
||
"profile": "主业能赚钱,但扩张更快、现金不够花,需要融资支撑资本开支。成长质量取决于经营造血增速能否追上扩张。",
|
||
"risk": "扩张节奏失控 → 现金消耗加速 → 滑向类型6/7。",
|
||
},
|
||
{
|
||
"type_no": 4, "o_sign": "+", "i_sign": "+", "f_sign": "+",
|
||
"name": "全面回笼型", "health_emoji": "🟡", "health_label": "需辨析",
|
||
"lifecycle": "重组/收缩盘点期",
|
||
"profile": "三类活动都进钱,少见。多为经营好转+资产处置+新增融资并举(可能正在重组)。",
|
||
"risk": "需具体辨析筹资来源(股权/债权)与投资流入是否靠卖资产。",
|
||
},
|
||
{
|
||
"type_no": 5, "o_sign": "-", "i_sign": "+", "f_sign": "-",
|
||
"name": "衰退变卖型", "health_emoji": "🔴", "health_label": "高危",
|
||
"lifecycle": "衰退期/清盘前奏",
|
||
"profile": "主业亏损,靠卖资产/处置投资回笼现金,同时还在还债。濒危信号。",
|
||
"risk": "资产卖完即无以为继;除非经营反转否则难持续。",
|
||
},
|
||
{
|
||
"type_no": 6, "o_sign": "-", "i_sign": "-", "f_sign": "+",
|
||
"name": "初创烧钱型", "health_emoji": "🟠", "health_label": "高风险",
|
||
"lifecycle": "初创期/高增长投入期",
|
||
"profile": "主业烧钱+持续投入,全靠融资吊命。常见于初创/高增长科技企业;也是庞氏结构的表面特征。",
|
||
"risk": "融资链条断裂即死;须区分「真成长」(收入/用户高速增长)与「假烧钱」。",
|
||
},
|
||
{
|
||
"type_no": 7, "o_sign": "-", "i_sign": "+", "f_sign": "+",
|
||
"name": "输血生存型", "health_emoji": "🔴", "health_label": "高危",
|
||
"lifecycle": "挣扎期",
|
||
"profile": "经营亏损,靠卖资产+借款输血双管齐下维持。压力极大。",
|
||
"risk": "短期可活,长期必须扭转经营现金流,否则资金链断裂。",
|
||
},
|
||
{
|
||
"type_no": 8, "o_sign": "-", "i_sign": "-", "f_sign": "-",
|
||
"name": "全面失血型", "health_emoji": "🔴", "health_label": "极危",
|
||
"lifecycle": "深度危机期",
|
||
"profile": "经营/投资/筹资三个引擎全熄火,现金只出不进。最危险状态。",
|
||
"risk": "极可能需要紧急债务重组/股东注资/断臂求生。",
|
||
},
|
||
]
|
||
|
||
# 类型 → 规则 快速索引
|
||
TYPE_RULE_MAP = {r["type_no"]: r for r in CASHFLOW_TYPE_RULES}
|
||
|
||
# ============================================================
|
||
# 裁定1(2026-09-17 财务口径裁定):投资活动净额 = 0 的合法输入与二维映射
|
||
# 「投资活动净额=0」视为**本期无投资活动**,按 0 如实参与八类判定;
|
||
# 三项中投资恒为 0 → 按 经营(±) × 筹资(±) **二维映射**到对应类型(候选 2 个,
|
||
# 因投资符号未知);投资栏位标注「本期无投资活动」。
|
||
# 依据:wecom-finance/reports/CMA_口径裁定_20260917.md 裁定1
|
||
# ============================================================
|
||
INVESTING_ZERO_NOTE = "本期无投资活动"
|
||
|
||
#: (经营符号, 筹资符号) → 投资符号未知时的候选类型号(对应类型由 8 类规则表反查)
|
||
TWO_DIM_CANDIDATE_MAP = {
|
||
("+", "-"): (1, 2),
|
||
("+", "+"): (3, 4),
|
||
("-", "-"): (5, 8),
|
||
("-", "+"): (6, 7),
|
||
}
|
||
|
||
|
||
def _sign(value: Optional[float]) -> Optional[str]:
|
||
"""净额符号:>0 → +,<0 → -,==0 → '0',None → None"""
|
||
if value is None:
|
||
return None
|
||
if value > 0:
|
||
return "+"
|
||
if value < 0:
|
||
return "-"
|
||
return "0"
|
||
|
||
|
||
def _rule_brief(type_no: int) -> dict:
|
||
"""类型号 → 规则摘要(二维判定用的候选项,字段与三维判定保持一致)"""
|
||
r = TYPE_RULE_MAP[type_no]
|
||
return {
|
||
"type_no": r["type_no"], "name": r["name"], "health_emoji": r["health_emoji"],
|
||
"health_label": r["health_label"], "lifecycle": r["lifecycle"],
|
||
"o_sign": r["o_sign"], "i_sign": r["i_sign"], "f_sign": r["f_sign"],
|
||
"profile": r["profile"], "risk": r["risk"],
|
||
}
|
||
|
||
|
||
def classify_enterprise(operating_net: Optional[float],
|
||
investing_net: Optional[float],
|
||
financing_net: Optional[float]) -> dict:
|
||
"""O/I/F 三净额符号 → 8类企业定性判定
|
||
|
||
返回:
|
||
{classified, type_no, name, health_emoji, health_label, risk, profile, lifecycle,
|
||
dimension, o_sign, i_sign, f_sign, reason, investing_note?, candidate_type_nos?, candidates?}
|
||
classified=False 时 reason 说明未判定的原因。
|
||
|
||
2026-09-17 裁定1(财务口径):
|
||
· **投资活动净额 = 0 是合法输入**(视为「本期无投资活动」),不再整体判「未判定」;
|
||
· 投资为 0 且经营/筹资可判 → **二维映射**(dimension="2D",候选类型 2 个,
|
||
投资栏位 investing_note="本期无投资活动");
|
||
· 经营/筹资任一亦为 0 或缺失 → 回退「未判定(无投资活动)」,**不报错、不静默**。
|
||
"""
|
||
o, i, f = _sign(operating_net), _sign(investing_net), _sign(financing_net)
|
||
names = {"o": "经营", "i": "投资", "f": "筹资"}
|
||
|
||
missing = [names[k] for k, s in (("o", o), ("i", i), ("f", f)) if s is None]
|
||
if missing:
|
||
return {"classified": False, "dimension": "0D",
|
||
"reason": "以下活动净额缺失:" + "、".join(missing) + "(需官方现金流量表数据)"}
|
||
|
||
zeros = [k for k, s in (("o", o), ("i", i), ("f", f)) if s == "0"]
|
||
inv_zero = i == "0"
|
||
|
||
if zeros:
|
||
# 恰好为 0 的维度:投资为 0 → 「本期无投资活动」;其余维度为 0 → 仍是有效输入但不可单独归类
|
||
zero_names = "、".join(names[k] for k in zeros)
|
||
if len(zeros) >= 2 or (o == "0" or f == "0"):
|
||
note = "(无投资活动)" if inv_zero else ""
|
||
return {
|
||
"classified": False, "dimension": "0D",
|
||
"o_sign": o, "i_sign": i, "f_sign": f,
|
||
"investing_note": INVESTING_ZERO_NOTE if inv_zero else None,
|
||
"reason": ("未判定%s:%s活动净额为 0(%s)且不足以二维归类,"
|
||
"请按业务实质人工判定" % (
|
||
note, zero_names,
|
||
"投资活动为 0 = 本期无投资活动" if inv_zero else "该维度无正负号")),
|
||
}
|
||
# 单维为 0 且该维为投资 → 二维(经营 × 筹资)映射
|
||
pair = TWO_DIM_CANDIDATE_MAP.get((o, f)) if (o in ("+", "-") and f in ("+", "-")) else None
|
||
if pair:
|
||
cands = [_rule_brief(t) for t in pair]
|
||
return {
|
||
"classified": True,
|
||
"dimension": "2D",
|
||
"dimension_label": "二维判定(投资活动=0,本期无投资活动)",
|
||
"type_no": None,
|
||
"name": "/".join("%s %s" % (c["type_no"], c["name"]) for c in cands),
|
||
"candidate_type_nos": list(pair),
|
||
"candidates": cands,
|
||
"health_emoji": "", "health_label": None, "lifecycle": None,
|
||
"profile": ";".join("类型%d %s:%s" % (c["type_no"], c["name"], c["profile"]) for c in cands),
|
||
"risk": ";".join("类型%d:%s" % (c["type_no"], c["risk"]) for c in cands),
|
||
"o_sign": o, "i_sign": i, "f_sign": f,
|
||
"investing_note": INVESTING_ZERO_NOTE,
|
||
"reason": ("经营(%s) 投资(0=%s) 筹资(%s) → 二维映射候选:%s"
|
||
"(投资活动为 0 无法定符号,需结合业务实质在候选中确认)" % (
|
||
o, INVESTING_ZERO_NOTE, f,
|
||
" / ".join("类型%d %s" % (c["type_no"], c["name"]) for c in cands))),
|
||
}
|
||
|
||
for rule in CASHFLOW_TYPE_RULES:
|
||
if rule["o_sign"] == o and rule["i_sign"] == i and rule["f_sign"] == f:
|
||
return {
|
||
"classified": True,
|
||
"dimension": "3D",
|
||
"type_no": rule["type_no"],
|
||
"name": rule["name"],
|
||
"health_emoji": rule["health_emoji"],
|
||
"health_label": rule["health_label"],
|
||
"lifecycle": rule["lifecycle"],
|
||
"profile": rule["profile"],
|
||
"risk": rule["risk"],
|
||
"o_sign": o, "i_sign": i, "f_sign": f,
|
||
"investing_note": None,
|
||
"reason": "经营(%s) 投资(%s) 筹资(%s) → 类型%d %s" % (o, i, f, rule["type_no"], rule["name"]),
|
||
}
|
||
return {"classified": False, "dimension": "0D", "reason": "未知组合,请检查数据"}
|
||
|
||
|
||
class CashFlowValueIn(BaseModel):
|
||
"""三大活动净额录入(官方现金流量表 / 人工)"""
|
||
period: str
|
||
operating_net: float
|
||
investing_net: float
|
||
financing_net: float
|
||
begin_cash: Optional[float] = None
|
||
end_cash: Optional[float] = None
|
||
source: str = "official"
|
||
remark: Optional[str] = None
|
||
|
||
|
||
def _validate_period(period: str) -> str:
|
||
"""期间格式校验 YYYY-MM"""
|
||
period = (period or "").strip()
|
||
try:
|
||
datetime.strptime(period, "%Y-%m")
|
||
except Exception:
|
||
raise HTTPException(status_code=422, detail="期间格式须为 YYYY-MM,如 2026-08")
|
||
return period
|
||
|
||
|
||
# ============================================================
|
||
# 接口
|
||
# ============================================================
|
||
|
||
@router.get("/rules")
|
||
def get_rules():
|
||
"""八类企业定性判定规则表(O/I/F 符号组合速查)"""
|
||
return {"rules": CASHFLOW_TYPE_RULES}
|
||
|
||
|
||
@router.get("/periods")
|
||
def list_periods(
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""已注入净额的期间列表(倒序,供按期间查询)"""
|
||
rows = db.query(CashFlowStructureValue.period).filter(
|
||
CashFlowStructureValue.entity_id == entity_id
|
||
).order_by(CashFlowStructureValue.period.desc()).all()
|
||
return {"periods": [r[0] for r in rows]}
|
||
|
||
|
||
@router.get("/values")
|
||
def list_values(
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""已注入的三大活动净额记录(按期间倒序)"""
|
||
rows = db.query(CashFlowStructureValue).filter(
|
||
CashFlowStructureValue.entity_id == entity_id
|
||
).order_by(CashFlowStructureValue.period.desc()).all()
|
||
return {"items": [
|
||
{
|
||
"id": r.id, "period": r.period,
|
||
"operating_net": r.operating_net,
|
||
"investing_net": r.investing_net,
|
||
"financing_net": r.financing_net,
|
||
"begin_cash": r.begin_cash, "end_cash": r.end_cash,
|
||
"source": r.source, "remark": r.remark,
|
||
"updated_at": r.updated_at.strftime("%Y-%m-%d %H:%M") if r.updated_at else None,
|
||
} for r in rows
|
||
]}
|
||
|
||
|
||
@router.get("/analysis")
|
||
def get_analysis(
|
||
period: str = Query(None, description="格式 YYYY-MM,缺省取最近已注入期间"),
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""现金流量表结构分析 — 三净额 → 八类企业判定 + 瀑布图数据"""
|
||
if period:
|
||
period = _validate_period(period)
|
||
|
||
# 1. 取官方/人工注入的净额(唯一权威判定数据)
|
||
q = db.query(CashFlowStructureValue).filter(
|
||
CashFlowStructureValue.entity_id == entity_id
|
||
)
|
||
if period:
|
||
rec = q.filter(CashFlowStructureValue.period == period).first()
|
||
else:
|
||
rec = q.order_by(CashFlowStructureValue.period.desc()).first()
|
||
if rec:
|
||
period = rec.period
|
||
|
||
if rec is None:
|
||
return {
|
||
"period": period or datetime.now().strftime("%Y-%m"),
|
||
"data_available": False,
|
||
"message": "该期间暂无官方现金流量表净额数据,请先录入三大活动净额(官方现金流量表注入后自动生效)。",
|
||
"nets": None,
|
||
"classification": None,
|
||
"waterfall": None,
|
||
}
|
||
|
||
classification = classify_enterprise(rec.operating_net, rec.investing_net, rec.financing_net)
|
||
i_sign = _sign(rec.investing_net)
|
||
# 裁定1:投资活动为 0 → 栏位显式标注「本期无投资活动」,避免读者误判为「收付相抵的 0」
|
||
investing_note = classification.get("investing_note") or (
|
||
INVESTING_ZERO_NOTE if i_sign == "0" else None)
|
||
|
||
nets = {
|
||
"operating": {"key": "operating", "name": "经营活动", "value": rec.operating_net,
|
||
"sign": _sign(rec.operating_net), "unit": "万元", "note": None},
|
||
"investing": {"key": "investing", "name": "投资活动", "value": rec.investing_net,
|
||
"sign": i_sign, "unit": "万元", "note": investing_note},
|
||
"financing": {"key": "financing", "name": "筹资活动", "value": rec.financing_net,
|
||
"sign": _sign(rec.financing_net), "unit": "万元", "note": None},
|
||
}
|
||
|
||
# 2. 瀑布图:期初现金 → +经营 → +投资 → +筹资 → 期末现金
|
||
waterfall = None
|
||
if rec.begin_cash is not None or rec.end_cash is not None:
|
||
begin = rec.begin_cash
|
||
end = rec.end_cash
|
||
steps = []
|
||
cum = begin if begin is not None else 0.0
|
||
if begin is not None:
|
||
steps.append({"step": "期初现金", "value": begin, "type": "begin"})
|
||
for net_key, net_name in [("operating", "经营活动"), ("investing", "投资活动"), ("financing", "筹资活动")]:
|
||
val = getattr(rec, net_key + "_net") or 0.0
|
||
cum += val
|
||
steps.append({"step": net_name, "value": round(val, 2), "type": "flow"})
|
||
if end is not None:
|
||
steps.append({"step": "期末现金", "value": end, "type": "end"})
|
||
elif begin is not None:
|
||
end = round(cum, 2)
|
||
steps.append({"step": "期末现金(推算)", "value": end, "type": "end"})
|
||
waterfall = {
|
||
"begin_cash": begin, "end_cash": end,
|
||
"net_increase": round((end - begin), 2) if (begin is not None and end is not None) else None,
|
||
"steps": steps,
|
||
}
|
||
|
||
return {
|
||
"period": rec.period,
|
||
"data_available": True,
|
||
"source": rec.source,
|
||
"remark": rec.remark,
|
||
"nets": nets,
|
||
"classification": classification,
|
||
"classification_dimension": classification.get("dimension"),
|
||
"investing_note": investing_note,
|
||
"waterfall": waterfall,
|
||
}
|
||
|
||
|
||
@router.post("/values", dependencies=[Depends(require_role("ceo", "finance"))])
|
||
def upsert_value(
|
||
payload: CashFlowValueIn,
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""录入/更新某期间三大活动净额(官方现金流量表注入;同期间重复提交=覆盖更新)"""
|
||
period = _validate_period(payload.period)
|
||
if payload.source not in ("official", "manual"):
|
||
raise HTTPException(status_code=422, detail="source 仅支持 official(官方报表)/manual(人工录入)")
|
||
|
||
rec = db.query(CashFlowStructureValue).filter(
|
||
CashFlowStructureValue.entity_id == entity_id,
|
||
CashFlowStructureValue.period == period,
|
||
).first()
|
||
created = rec is None
|
||
if rec is None:
|
||
rec = CashFlowStructureValue(entity_id=entity_id, period=period)
|
||
db.add(rec)
|
||
|
||
rec.operating_net = round(payload.operating_net, 2)
|
||
rec.investing_net = round(payload.investing_net, 2)
|
||
rec.financing_net = round(payload.financing_net, 2)
|
||
rec.begin_cash = round(payload.begin_cash, 2) if payload.begin_cash is not None else rec.begin_cash
|
||
rec.end_cash = round(payload.end_cash, 2) if payload.end_cash is not None else rec.end_cash
|
||
rec.source = payload.source
|
||
rec.remark = payload.remark
|
||
db.commit()
|
||
|
||
classification = classify_enterprise(rec.operating_net, rec.investing_net, rec.financing_net)
|
||
return {
|
||
"ok": True,
|
||
"id": rec.id,
|
||
"period": rec.period,
|
||
"classification": classification,
|
||
"message": "三大活动净额已%s" % ("入库" if created else "更新"),
|
||
}
|
||
|
||
|
||
# ============================================================
|
||
# 兼容别名端点 — 任务派发单 DoD 验收路径 /api/cma/cashflow/*
|
||
# (规范实现主路径为 /api/cma/cashflow-structure/*;两路径功能一致)
|
||
# ============================================================
|
||
compat_router = APIRouter(
|
||
prefix="/api/cma/cashflow",
|
||
tags=["现金流量表结构分析(兼容路径)"],
|
||
dependencies=[Depends(require_role("ceo", "finance"))],
|
||
)
|
||
|
||
|
||
@compat_router.get("/statements")
|
||
def compat_list_statements(
|
||
period: Optional[str] = Query(None, description="期间 YYYY-MM,可选"),
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""[兼容] 查询已录入三大活动净额(同 /api/cma/cashflow-structure/values)"""
|
||
items = list_values(db=db, entity_id=entity_id)["items"]
|
||
if period:
|
||
period = _validate_period(period)
|
||
items = [x for x in items if x["period"] == period]
|
||
return {"items": items}
|
||
|
||
|
||
@compat_router.post("/statements", dependencies=[Depends(require_role("ceo", "finance"))])
|
||
def compat_save_statement(
|
||
payload: CashFlowValueIn,
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""[兼容] 保存/更新某期间三大活动净额(同 POST /api/cma/cashflow-structure/values)"""
|
||
return upsert_value(payload=payload, db=db, entity_id=entity_id)
|
||
|
||
|
||
@compat_router.get("/analysis")
|
||
def compat_get_analysis(
|
||
period: str = Query(None, description="格式 YYYY-MM,缺省取最近已注入期间"),
|
||
db: Session = Depends(get_db),
|
||
entity_id: int = Depends(get_entity_id),
|
||
):
|
||
"""[兼容] 结构分析+八类判定+瀑布数据(同 /api/cma/cashflow-structure/analysis)"""
|
||
return get_analysis(period=period, db=db, entity_id=entity_id)
|