Spaces:
Running
Running
Update src/utils/visualizations.py
Browse files- src/utils/visualizations.py +134 -78
src/utils/visualizations.py
CHANGED
|
@@ -834,39 +834,61 @@ def summarize_fixed_assets(df: pd.DataFrame) -> dict:
|
|
| 834 |
# 科目分類設定
|
| 835 |
# 格式:(科目代碼前綴或精確碼, 描述, 活動類別, 資產/負債符號)
|
| 836 |
# 符號:資產科目 sign=-1(資產增加→現金減少),負債/淨資產 sign=+1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
_CF_ITEMS = [
|
| 838 |
-
# ── 一、營業活動 ──────────────────────────────
|
| 839 |
-
|
| 840 |
-
("
|
| 841 |
-
("
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
("
|
| 845 |
-
("
|
| 846 |
-
("
|
| 847 |
-
("
|
| 848 |
-
|
| 849 |
-
("
|
| 850 |
-
|
| 851 |
-
("
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
("
|
| 855 |
-
("
|
| 856 |
-
("
|
| 857 |
-
("
|
| 858 |
-
("
|
| 859 |
-
("
|
| 860 |
-
|
| 861 |
-
("
|
| 862 |
-
("
|
| 863 |
-
("
|
| 864 |
-
# ── 三、籌資活動 ──────────────────────────────────────────────
|
| 865 |
-
("
|
| 866 |
-
("
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
|
|
|
| 870 |
]
|
| 871 |
|
| 872 |
_ACTIVITY_LABEL = {
|
|
@@ -900,6 +922,15 @@ def _get_amount(d: dict, code_or_pfx: str) -> float:
|
|
| 900 |
return sum(v for k, v in d.items() if k.startswith(code_or_pfx))
|
| 901 |
|
| 902 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 903 |
def summarize_cashflow(
|
| 904 |
df_start: pd.DataFrame,
|
| 905 |
df_end: pd.DataFrame,
|
|
@@ -908,16 +939,16 @@ def summarize_cashflow(
|
|
| 908 |
"""
|
| 909 |
以間接法推估現金流量。
|
| 910 |
|
| 911 |
-
Parameters
|
| 912 |
-
----------
|
| 913 |
-
df_start : 期初平衡表 DataFrame
|
| 914 |
-
df_end : 期末平衡表 DataFrame
|
| 915 |
-
surplus : 本期賸餘(短絀),單位億元(從收支餘絀表取)
|
| 916 |
-
|
| 917 |
Returns
|
| 918 |
-------
|
| 919 |
list of dict,每筆含:
|
| 920 |
-
activity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
"""
|
| 922 |
start = _bs_to_dict(df_start)
|
| 923 |
end = _bs_to_dict(df_end)
|
|
@@ -926,69 +957,94 @@ def summarize_cashflow(
|
|
| 926 |
|
| 927 |
# 本期賸餘作為營業活動起點
|
| 928 |
items.append({
|
| 929 |
-
"activity": "operating",
|
| 930 |
-
"
|
| 931 |
-
"
|
| 932 |
-
"is_subtotal": False, "is_total": False,
|
| 933 |
})
|
| 934 |
|
| 935 |
# 各調整科目
|
| 936 |
for code, label, activity, sign in _CF_ITEMS:
|
| 937 |
end_val = _get_amount(end, code)
|
| 938 |
start_val = _get_amount(start, code)
|
| 939 |
-
delta = (end_val - start_val) * sign
|
| 940 |
if abs(delta) < 0.001:
|
| 941 |
continue
|
| 942 |
items.append({
|
| 943 |
-
"activity": activity,
|
| 944 |
-
"
|
| 945 |
-
"
|
| 946 |
-
"is_subtotal": False, "is_total": False,
|
| 947 |
})
|
| 948 |
|
| 949 |
-
#
|
| 950 |
result = []
|
| 951 |
for act in ["operating", "investing", "financing"]:
|
| 952 |
act_items = [x for x in items if x["activity"] == act]
|
| 953 |
subtotal = sum(x["amount"] for x in act_items)
|
| 954 |
result.extend(act_items)
|
| 955 |
result.append({
|
| 956 |
-
"activity": act,
|
| 957 |
-
"
|
| 958 |
-
"
|
| 959 |
-
"is_subtotal": True, "is_total": False,
|
| 960 |
})
|
| 961 |
|
| 962 |
-
#
|
| 963 |
cash_start = _get_amount(start, "1101")
|
| 964 |
cash_end = _get_amount(end, "1101")
|
| 965 |
-
net_change = cash_end - cash_start
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
"
|
| 969 |
-
"
|
| 970 |
-
"
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
|
| 976 |
-
|
| 977 |
-
|
| 978 |
-
|
| 979 |
-
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 984 |
|
| 985 |
-
# 算誤差(三活動小計合計 vs 實際現金變動)
|
| 986 |
-
calc_net = sum(x["amount"] for x in result if x.get("is_subtotal"))
|
| 987 |
result.append({
|
| 988 |
-
"activity": "
|
| 989 |
-
"label": "推估誤差(三活動合計
|
| 990 |
-
"amount":
|
| 991 |
"is_subtotal": False, "is_total": False,
|
|
|
|
|
|
|
| 992 |
})
|
| 993 |
|
| 994 |
return result
|
|
|
|
| 834 |
# 科目分類設定
|
| 835 |
# 格式:(科目代碼前綴或精確碼, 描述, 活動類別, 資產/負債符號)
|
| 836 |
# 符號:資產科目 sign=-1(資產增加→現金減少),負債/淨資產 sign=+1
|
| 837 |
+
# 科目分類說明(間接法推估,以平衡表增減替代現金流量):
|
| 838 |
+
#
|
| 839 |
+
# 一、營業活動:
|
| 840 |
+
# - 本期賸餘為起點(在 summarize_cashflow 另行傳入)
|
| 841 |
+
# - 非現金費用加回:折舊/攤銷以資產淨額減少近似
|
| 842 |
+
# - 準備金提存:屬非現金調整(退休金/補償準備提撥),並非對外投資
|
| 843 |
+
# - 遞延負債:遞延收入/補助款等日常營運相關,歸營業活動
|
| 844 |
+
# - 流動資產/負債變動:應收、存貨、預付、應付、預收
|
| 845 |
+
#
|
| 846 |
+
# 二、投資活動:
|
| 847 |
+
# - 不動產廠房及設備:各項購建(淨額變動含折舊,近似資本支出淨效果)
|
| 848 |
+
# - 短期貸墊款:對外放款,非日常營運
|
| 849 |
+
# - 非流動金融資產、長期應收款:長期投資性質
|
| 850 |
+
#
|
| 851 |
+
# 三、籌資活動:
|
| 852 |
+
# - 什項負債(長期借款/融資性負債)
|
| 853 |
+
# - 基金及公積:
|
| 854 |
+
# ‧ 3101 基金:撥入資本,有變動才列籌資,通常穩定
|
| 855 |
+
# ‧ 3201/3202 公積:由賸餘提撥,屬內部分配非現金流 → 排除
|
| 856 |
+
# ‧ 3401 累積其他綜合餘絀:評價調整,非現金 → 排除
|
| 857 |
+
|
| 858 |
_CF_ITEMS = [
|
| 859 |
+
# ── 一、營業活動:非現金費用調整 ──────────────────────────────
|
| 860 |
+
("1701", "無形資產攤銷(淨額減少≈攤銷)", "operating", +1),
|
| 861 |
+
("1801", "遞延資產攤銷(淨額減少≈攤銷)", "operating", +1),
|
| 862 |
+
("1207", "準備金提存(退休金/補償準備等)", "operating", +1), # ★ 改為營業
|
| 863 |
+
# ── 一、營業活動:流動資產變動(增加=現金流出)──────────────────
|
| 864 |
+
("1102", "流動金融資產增減", "operating", -1),
|
| 865 |
+
("1103", "應收款項增減", "operating", -1),
|
| 866 |
+
("1104", "存貨增減", "operating", -1),
|
| 867 |
+
("1107", "預付款項增減", "operating", -1),
|
| 868 |
+
("1807", "什項資產增減(含暫付/存出保證金)", "operating", -1),
|
| 869 |
+
# ── 一、營業活動:流動負債變動(增加=現金流入)──────────────────
|
| 870 |
+
("2102", "應付款項增減", "operating", +1),
|
| 871 |
+
("2103", "預收款項增減", "operating", +1),
|
| 872 |
+
("2801", "遞延負債增減(遞延收入/補助款)", "operating", +1), # ★ 改為營業
|
| 873 |
+
# ── 二、投資活動:不動產廠房及設備 ──────────────────────────────
|
| 874 |
+
("1301", "土地增減", "investing", -1),
|
| 875 |
+
("1302", "土地改良物增減", "investing", -1),
|
| 876 |
+
("1303", "房屋及建築增減", "investing", -1),
|
| 877 |
+
("1304", "機械及設備增減", "investing", -1),
|
| 878 |
+
("1305", "交通及運輸設備增減", "investing", -1),
|
| 879 |
+
("1306", "什項設備增減", "investing", -1),
|
| 880 |
+
("1309", "購建中固定資產增減", "investing", -1), # ★ 13xx 改為投資
|
| 881 |
+
# ── 二、投資活動:其他長期資產 ──────────────────────────────────
|
| 882 |
+
("1108", "短期貸墊款增減", "investing", -1), # ★ 改為投資
|
| 883 |
+
("1202", "非流動金融資產增減", "investing", -1),
|
| 884 |
+
("1204", "長期應收款增減", "investing", -1),
|
| 885 |
+
# ── 三、籌資活動 ─────────────────────────────────────────────────
|
| 886 |
+
("2807", "什項負債增減(長期融資性負債)", "financing", +1),
|
| 887 |
+
("3101", "基金增減(政府撥補)", "financing", +1),
|
| 888 |
+
# 排除項(非現金或重複計算):
|
| 889 |
+
# 3201 資本公積、3202 特別公積:賸餘內部提撥,非現金流
|
| 890 |
+
# 3301 累積賸餘:已以本期賸餘為起點,避免重複
|
| 891 |
+
# 3401 累積其他綜合餘絀:評價調整,非現金
|
| 892 |
]
|
| 893 |
|
| 894 |
_ACTIVITY_LABEL = {
|
|
|
|
| 922 |
return sum(v for k, v in d.items() if k.startswith(code_or_pfx))
|
| 923 |
|
| 924 |
|
| 925 |
+
# 刻意排除的科目(非現金或避免重複計算),但需在誤差說明中揭露其增減
|
| 926 |
+
_CF_EXCLUDED = [
|
| 927 |
+
("3201", "資本公積", "由本期賸餘提撥,非現金流,已含於起點"),
|
| 928 |
+
("3202", "特別公積", "由本期賸餘提撥,非現金流,已含於起點"),
|
| 929 |
+
("3301", "累積賸餘", "與本期賸餘重複,已以本期賸餘為起點"),
|
| 930 |
+
("3401", "累積其他綜合餘絀", "金融資產評價調整,屬非現金項目"),
|
| 931 |
+
]
|
| 932 |
+
|
| 933 |
+
|
| 934 |
def summarize_cashflow(
|
| 935 |
df_start: pd.DataFrame,
|
| 936 |
df_end: pd.DataFrame,
|
|
|
|
| 939 |
"""
|
| 940 |
以間接法推估現金流量。
|
| 941 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 942 |
Returns
|
| 943 |
-------
|
| 944 |
list of dict,每筆含:
|
| 945 |
+
activity : 'operating' | 'investing' | 'financing' | 'total' | 'unexplained'
|
| 946 |
+
label : 顯示名稱
|
| 947 |
+
amount : 金額(億元),正=流入,負=流出
|
| 948 |
+
is_subtotal : 小計列
|
| 949 |
+
is_total : 合計/現金餘額列
|
| 950 |
+
is_excluded : 刻意排除的科目(顯示於誤差說明)
|
| 951 |
+
exclude_reason : 排除原因說明
|
| 952 |
"""
|
| 953 |
start = _bs_to_dict(df_start)
|
| 954 |
end = _bs_to_dict(df_end)
|
|
|
|
| 957 |
|
| 958 |
# 本期賸餘作為營業活動起點
|
| 959 |
items.append({
|
| 960 |
+
"activity": "operating", "label": "本期賸餘(短絀)",
|
| 961 |
+
"amount": surplus, "is_subtotal": False, "is_total": False,
|
| 962 |
+
"is_excluded": False, "exclude_reason": "",
|
|
|
|
| 963 |
})
|
| 964 |
|
| 965 |
# 各調整科目
|
| 966 |
for code, label, activity, sign in _CF_ITEMS:
|
| 967 |
end_val = _get_amount(end, code)
|
| 968 |
start_val = _get_amount(start, code)
|
| 969 |
+
delta = (end_val - start_val) * sign
|
| 970 |
if abs(delta) < 0.001:
|
| 971 |
continue
|
| 972 |
items.append({
|
| 973 |
+
"activity": activity, "label": label,
|
| 974 |
+
"amount": delta, "is_subtotal": False, "is_total": False,
|
| 975 |
+
"is_excluded": False, "exclude_reason": "",
|
|
|
|
| 976 |
})
|
| 977 |
|
| 978 |
+
# 各活動小計
|
| 979 |
result = []
|
| 980 |
for act in ["operating", "investing", "financing"]:
|
| 981 |
act_items = [x for x in items if x["activity"] == act]
|
| 982 |
subtotal = sum(x["amount"] for x in act_items)
|
| 983 |
result.extend(act_items)
|
| 984 |
result.append({
|
| 985 |
+
"activity": act, "label": f"{_ACTIVITY_LABEL[act]} 小計",
|
| 986 |
+
"amount": subtotal, "is_subtotal": True, "is_total": False,
|
| 987 |
+
"is_excluded": False, "exclude_reason": "",
|
|
|
|
| 988 |
})
|
| 989 |
|
| 990 |
+
# 現金期初期末(確定值)
|
| 991 |
cash_start = _get_amount(start, "1101")
|
| 992 |
cash_end = _get_amount(end, "1101")
|
| 993 |
+
net_change = cash_end - cash_start # ← 這是「確定的」現金淨增減
|
| 994 |
+
|
| 995 |
+
for label, amount in [
|
| 996 |
+
("現金及約當現金淨增減(確定值)", net_change),
|
| 997 |
+
("期初現金及約當現金", cash_start),
|
| 998 |
+
("期末現金及約當現金", cash_end),
|
| 999 |
+
]:
|
| 1000 |
+
result.append({
|
| 1001 |
+
"activity": "total", "label": label,
|
| 1002 |
+
"amount": amount, "is_subtotal": False, "is_total": True,
|
| 1003 |
+
"is_excluded": False, "exclude_reason": "",
|
| 1004 |
+
})
|
| 1005 |
+
|
| 1006 |
+
# ── 誤差分析 ──────────────────────────────────────────────────────────────
|
| 1007 |
+
# 三活動合計(推估值)
|
| 1008 |
+
est_net = sum(x["amount"] for x in result if x.get("is_subtotal"))
|
| 1009 |
+
|
| 1010 |
+
# 刻意排除科目的實際增減(揭露用)
|
| 1011 |
+
excluded_items = []
|
| 1012 |
+
excluded_total = 0.0
|
| 1013 |
+
for code, name, reason in _CF_EXCLUDED:
|
| 1014 |
+
end_val = _get_amount(end, code)
|
| 1015 |
+
start_val = _get_amount(start, code)
|
| 1016 |
+
# 排除科目通常是淨資產,增加對現金影響為 0(非現金)
|
| 1017 |
+
# 但若有變動仍需揭露,讓使用者知道這部分無法解釋
|
| 1018 |
+
delta = end_val - start_val # 原始增減,不加 sign(讓使用者自行判斷)
|
| 1019 |
+
if abs(delta) < 0.001:
|
| 1020 |
+
continue
|
| 1021 |
+
excluded_items.append({
|
| 1022 |
+
"activity": "unexplained", "label": f"{name}({code})",
|
| 1023 |
+
"amount": delta, "is_subtotal": False, "is_total": False,
|
| 1024 |
+
"is_excluded": True, "exclude_reason": reason,
|
| 1025 |
+
})
|
| 1026 |
+
excluded_total += delta
|
| 1027 |
+
|
| 1028 |
+
# 純粹不明誤差 = 確定現金增減 - 推估三活動合計 - 排除科目影響
|
| 1029 |
+
# 排除科目因屬非現金,理論上不影響現金,純粹誤差應接近 0
|
| 1030 |
+
pure_error = net_change - est_net
|
| 1031 |
+
|
| 1032 |
+
if excluded_items:
|
| 1033 |
+
result.append({
|
| 1034 |
+
"activity": "unexplained",
|
| 1035 |
+
"label": "── 以下為刻意排除科目(非現金或避免重複) ──",
|
| 1036 |
+
"amount": 0.0, "is_subtotal": False, "is_total": False,
|
| 1037 |
+
"is_excluded": True, "exclude_reason": "區隔標題",
|
| 1038 |
+
})
|
| 1039 |
+
result.extend(excluded_items)
|
| 1040 |
|
|
|
|
|
|
|
| 1041 |
result.append({
|
| 1042 |
+
"activity": "unexplained",
|
| 1043 |
+
"label": "推估誤差(確定現金增減 − 三活動合計)",
|
| 1044 |
+
"amount": pure_error,
|
| 1045 |
"is_subtotal": False, "is_total": False,
|
| 1046 |
+
"is_excluded": False, "exclude_reason":
|
| 1047 |
+
"接近 0 表示推估合理;偏大表示仍有科目未能分類或分類有誤",
|
| 1048 |
})
|
| 1049 |
|
| 1050 |
return result
|