cormort commited on
Commit
e1cbba5
·
verified ·
1 Parent(s): 8cfaa77

Update src/utils/visualizations.py

Browse files
Files changed (1) hide show
  1. 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
- ("13", "不動廠房及設備(淨額變動)", "operating", +1), # 減少=折舊
841
- ("1701", "無形資產攤銷", "operating", +1),
842
- ("1801", "遞延資產攤銷", "operating", +1),
843
- # 流動資產(不含現金)—加=現金流出
844
- ("1102", "流動金融資產增減", "operating", -1),
845
- ("1103", "應收款項增減", "operating", -1),
846
- ("1104", "存貨增減", "operating", -1),
847
- ("1107", "預付款項增減", "operating", -1),
848
- ("1108", "短期貸墊款減", "operating", -1),
849
- ("1807", "資產增減", "operating", -1),
850
- # 流動負債—加=現金流入
851
- ("2102", "應付款項增減", "operating", +1),
852
- ("2103", "預收款項增減", "operating", +1),
853
- # ── 二、投資活動 ──────────────────────────────────────────────
854
- ("1202", "非流動金融資產增減", "investing", -1),
855
- ("1204", "長期應收款增減", "investing", -1),
856
- ("1207", "增減", "investing", -1),
857
- ("1301", "土地增減", "investing", -1),
858
- ("1302", "土地改良物增減", "investing", -1),
859
- ("1303", "房屋及增減", "investing", -1),
860
- ("1304", "機械及設備增減", "investing", -1),
861
- ("1305", "交通運輸設備增減", "investing", -1),
862
- ("1306", "什項設備增減", "investing", -1),
863
- ("1309", "購建中固定資產增減", "investing", -1),
864
- # ── 三、籌資活動 ──────────────────────────────────────────────
865
- ("2801", "遞延負債增減", "financing", +1),
866
- ("2807", "什項負債增減", "financing", +1),
867
- ("3101", "基增減", "financing", +1),
868
- ("3201", "資本公積增減", "financing", +1),
869
- ("3202", "特別公增減", "financing", +1),
 
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, label, amount(億元), is_subtotal, is_total
 
 
 
 
 
 
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
- "label": "本期賸餘(短絀)",
931
- "amount": surplus,
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
- "label": label,
945
- "amount": delta,
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
- "label": f"{_ACTIVITY_LABEL[act]} 小計",
958
- "amount": subtotal,
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
- result.append({
967
- "activity": "total",
968
- "label": "現金及約當現金淨增減",
969
- "amount": net_change,
970
- "is_subtotal": False, "is_total": True,
971
- })
972
- result.append({
973
- "activity": "total",
974
- "label": "期初現金及約當現金",
975
- "amount": cash_start,
976
- "is_subtotal": False, "is_total": True,
977
- })
978
- result.append({
979
- "activity": "total",
980
- "label": "期末現金及約當現金",
981
- "amount": cash_end,
982
- "is_subtotal": False, "is_total": True,
983
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
- # 算誤差(三活動小計合計 vs 實際現金變動)
986
- calc_net = sum(x["amount"] for x in result if x.get("is_subtotal"))
987
  result.append({
988
- "activity": "check",
989
- "label": "推估誤差(三活動合計 − 實際現金增減)",
990
- "amount": calc_net - net_change,
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