Spaces:
Runtime error
Runtime error
Update src/app.py
Browse files- src/app.py +63 -69
src/app.py
CHANGED
|
@@ -1,36 +1,39 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
import plotly.graph_objects as go
|
|
|
|
| 4 |
import sqlite3
|
| 5 |
import datetime
|
| 6 |
from database import init_db, load_full_project_example, clear_to_actual, backup_current_project, DB_PATH
|
| 7 |
|
| 8 |
-
# 初始化
|
| 9 |
init_db()
|
| 10 |
-
st.set_page_config(page_title="SPV 財務
|
|
|
|
| 11 |
|
| 12 |
-
# --- 側邊欄 ---
|
| 13 |
with st.sidebar:
|
| 14 |
-
st.header("
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
backup_current_project()
|
| 17 |
clear_to_actual()
|
| 18 |
-
st.success("已重置為空白實際專案")
|
| 19 |
st.rerun()
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
load_full_project_example(100000)
|
| 23 |
-
st.success("範例載入成功")
|
| 24 |
st.rerun()
|
| 25 |
|
| 26 |
st.divider()
|
| 27 |
-
st.subheader("➕
|
| 28 |
-
with st.form("
|
| 29 |
f_month = st.number_input("月份", min_value=1, value=1)
|
| 30 |
-
f_name = st.text_input("項目名稱 (如:
|
| 31 |
-
f_type = st.selectbox("性質", ["支出", "收入"])
|
| 32 |
f_amt = st.number_input("金額 (萬)", min_value=0.0)
|
| 33 |
-
if st.form_submit_button("確認
|
| 34 |
if f_name:
|
| 35 |
conn = sqlite3.connect(DB_PATH)
|
| 36 |
conn.execute("INSERT OR REPLACE INTO finance_transactions VALUES (?, '實際', ?, ?, ?)",
|
|
@@ -39,63 +42,54 @@ with st.sidebar:
|
|
| 39 |
conn.close()
|
| 40 |
st.rerun()
|
| 41 |
|
| 42 |
-
# --- 主介面 ---
|
| 43 |
-
tabs = st.tabs(["📊 損益分析", "💰 現金流
|
| 44 |
|
| 45 |
-
# --- Tab
|
| 46 |
-
with tabs[
|
| 47 |
-
st.subheader("📅 動態現金流流水帳")
|
| 48 |
conn = sqlite3.connect(DB_PATH)
|
| 49 |
-
|
| 50 |
-
|
| 51 |
conn.close()
|
| 52 |
-
|
| 53 |
if not df.empty:
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
# 區分收支色彩
|
| 58 |
-
inc_list = df[df['科目性質']=='收入']['科目名稱'].unique()
|
| 59 |
-
exp_list = df[df['科目性質']=='支出']['科目名稱'].unique()
|
| 60 |
|
| 61 |
-
st.
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
else:
|
| 66 |
-
st.info("
|
| 67 |
|
| 68 |
-
# --- Tab
|
| 69 |
-
with tabs[
|
|
|
|
| 70 |
if not df.empty:
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
col1.metric("總流入金額", f"{total_inc:,.0f} 萬")
|
| 76 |
-
col2.metric("總流出金額", f"{total_exp:,.0f} 萬")
|
| 77 |
-
col3.metric("專案剩餘毛利", f"{(total_inc - total_exp):,.0f} 萬")
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
fig.update_layout(title="自訂項目支出占比分析")
|
| 83 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 84 |
-
|
| 85 |
-
# --- Tab 2: 現金流總表 (色系區分) ---
|
| 86 |
-
with tabs[1]:
|
| 87 |
-
conn = sqlite3.connect(DB_PATH)
|
| 88 |
-
df_act = pd.read_sql_query("SELECT * FROM finance_data WHERE 類型='實際' ORDER BY 月份 ASC", conn)
|
| 89 |
-
conn.close()
|
| 90 |
-
if not df_act.empty:
|
| 91 |
-
num_cols = ['工程款', '貸款撥入', '利息支出', '貸款還本', '預售收入', '代銷費用']
|
| 92 |
-
st.subheader("📅 現金流明細 (🟢 流入 / 🔴 流出)")
|
| 93 |
-
st.dataframe(df_act.style.format(subset=num_cols, formatter="{:,.0f}")
|
| 94 |
-
.applymap(lambda x: 'color: #228B22; font-weight: bold', subset=['貸款撥入', '預售收入'])
|
| 95 |
-
.applymap(lambda x: 'color: #DC143C', subset=['工程款', '利息支出', '貸款還本', '代銷費用']),
|
| 96 |
use_container_width=True)
|
| 97 |
|
| 98 |
-
# --- Tab 3
|
| 99 |
with tabs[2]:
|
| 100 |
conn = sqlite3.connect(DB_PATH)
|
| 101 |
stages = pd.read_sql_query("SELECT * FROM project_stages", conn)
|
|
@@ -105,14 +99,14 @@ with tabs[2]:
|
|
| 105 |
st.plotly_chart(ff.create_gantt(df_g, index_col='Resource', group_tasks=True), use_container_width=True)
|
| 106 |
conn.close()
|
| 107 |
|
|
|
|
| 108 |
with tabs[3]:
|
| 109 |
-
st.subheader("🏦 融資合約清單")
|
| 110 |
-
conn = sqlite3.connect(DB_PATH)
|
| 111 |
-
st.dataframe(pd.read_sql_query("SELECT 貸款名稱, 授信額度, 年利率, 狀態 FROM loan_contracts", conn), use_container_width=True)
|
| 112 |
-
conn.close()
|
| 113 |
-
|
| 114 |
-
with tabs[4]:
|
| 115 |
-
st.subheader("🏠 房屋銷售清單")
|
| 116 |
conn = sqlite3.connect(DB_PATH)
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
conn.close()
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
import plotly.graph_objects as go
|
| 4 |
+
import plotly.figure_factory as ff
|
| 5 |
import sqlite3
|
| 6 |
import datetime
|
| 7 |
from database import init_db, load_full_project_example, clear_to_actual, backup_current_project, DB_PATH
|
| 8 |
|
| 9 |
+
# 系統初始化
|
| 10 |
init_db()
|
| 11 |
+
st.set_page_config(page_title="SPV 都更財務動態系統", layout="wide")
|
| 12 |
+
st.title("🏢 SPV 都更計畫:現金流整合管理系統")
|
| 13 |
|
| 14 |
+
# --- 側邊欄:專案管理與自訂項目 ---
|
| 15 |
with st.sidebar:
|
| 16 |
+
st.header("⚙️ 專案管理面板")
|
| 17 |
+
total_b = st.number_input("總工程預算 (萬)", value=100000, step=1000)
|
| 18 |
+
project_start = st.date_input("計畫啟動日期", datetime.date(2026, 1, 20))
|
| 19 |
+
|
| 20 |
+
col_a, col_b = st.columns(2)
|
| 21 |
+
if col_a.button("🆕 切換至原專案", use_container_width=True):
|
| 22 |
backup_current_project()
|
| 23 |
clear_to_actual()
|
|
|
|
| 24 |
st.rerun()
|
| 25 |
+
if col_b.button("💡 載入範例", type="primary", use_container_width=True):
|
| 26 |
+
load_full_project_example(total_b)
|
|
|
|
|
|
|
| 27 |
st.rerun()
|
| 28 |
|
| 29 |
st.divider()
|
| 30 |
+
st.subheader("➕ 錄入自訂收支項目")
|
| 31 |
+
with st.form("custom_item_form"):
|
| 32 |
f_month = st.number_input("月份", min_value=1, value=1)
|
| 33 |
+
f_name = st.text_input("項目名稱 (如:公關費、印花稅)")
|
| 34 |
+
f_type = st.selectbox("科目性質", ["支出", "收入"])
|
| 35 |
f_amt = st.number_input("金額 (萬)", min_value=0.0)
|
| 36 |
+
if st.form_submit_button("確認提交項目"):
|
| 37 |
if f_name:
|
| 38 |
conn = sqlite3.connect(DB_PATH)
|
| 39 |
conn.execute("INSERT OR REPLACE INTO finance_transactions VALUES (?, '實際', ?, ?, ?)",
|
|
|
|
| 42 |
conn.close()
|
| 43 |
st.rerun()
|
| 44 |
|
| 45 |
+
# --- 主介面 Tabs ---
|
| 46 |
+
tabs = st.tabs(["📊 損益與利潤分析", "💰 現金流動態總表", "🏗️ 工程管理", "🏦 融資與銷售"])
|
| 47 |
|
| 48 |
+
# --- Tab 1: 損益與瀑布圖 (自動整合自訂項目) ---
|
| 49 |
+
with tabs[0]:
|
|
|
|
| 50 |
conn = sqlite3.connect(DB_PATH)
|
| 51 |
+
df = pd.read_sql_query("SELECT * FROM finance_transactions WHERE 類型='實際'", conn)
|
| 52 |
+
params = pd.read_sql_query("SELECT * FROM project_params", conn)
|
| 53 |
conn.close()
|
| 54 |
+
|
| 55 |
if not df.empty:
|
| 56 |
+
total_in = df[df['科目性質']=='收入']['金額'].sum()
|
| 57 |
+
total_out = df[df['科目性質']=='支出']['金額'].sum()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
st.subheader("⚖️ 專案獲利結構分析")
|
| 60 |
+
c1, c2, c3 = st.columns(3)
|
| 61 |
+
c1.metric("總流入金額", f"{total_in:,.0f} 萬")
|
| 62 |
+
c2.metric("總流出支出", f"{total_out:,.0f} 萬")
|
| 63 |
+
c3.metric("預計結餘毛利", f"{(total_in - total_out):,.0f} 萬")
|
| 64 |
+
|
| 65 |
+
# 瀑布圖:自動抓取所有支出科目
|
| 66 |
+
exp_df = df[df['科目性質']=='支出'].groupby('科目名稱')['金額'].sum().reset_index()
|
| 67 |
+
fig_w = go.Figure(go.Waterfall(
|
| 68 |
+
orientation = "v",
|
| 69 |
+
measure = ["absolute"] + ["relative"] * len(exp_df) + ["total"],
|
| 70 |
+
x = ["總收入預估"] + exp_df['科目名稱'].tolist() + ["最終淨利"],
|
| 71 |
+
y = [total_in] + (-exp_df['金額']).tolist() + [0],
|
| 72 |
+
connector = {"line":{"color":"rgb(63, 63, 63)"}},
|
| 73 |
+
))
|
| 74 |
+
st.plotly_chart(fig_w, use_container_width=True)
|
| 75 |
else:
|
| 76 |
+
st.info("請利用左側選單載入數據。")
|
| 77 |
|
| 78 |
+
# --- Tab 2: 現金流總表 (色系與動態科目) ---
|
| 79 |
+
with tabs[1]:
|
| 80 |
+
st.subheader("📅 動態現金流流水帳 (實際發生額)")
|
| 81 |
if not df.empty:
|
| 82 |
+
# 動態轉置表格
|
| 83 |
+
pivot_df = df.pivot_table(index='月份', columns='科目名稱', values='金額', fill_value=0).reset_index()
|
| 84 |
+
inc_cols = df[df['科目性質']=='收入']['科目名稱'].unique()
|
| 85 |
+
exp_cols = df[df['科目性質']=='支出']['科目名稱'].unique()
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
+
st.dataframe(pivot_df.style.format(precision=0)
|
| 88 |
+
.applymap(lambda x: 'color: #228B22; font-weight: bold', subset=[c for c in inc_cols if c in pivot_df.columns])
|
| 89 |
+
.applymap(lambda x: 'color: #DC143C', subset=[c for c in exp_cols if c in pivot_df.columns]),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
use_container_width=True)
|
| 91 |
|
| 92 |
+
# --- Tab 3: 工程排程 ---
|
| 93 |
with tabs[2]:
|
| 94 |
conn = sqlite3.connect(DB_PATH)
|
| 95 |
stages = pd.read_sql_query("SELECT * FROM project_stages", conn)
|
|
|
|
| 99 |
st.plotly_chart(ff.create_gantt(df_g, index_col='Resource', group_tasks=True), use_container_width=True)
|
| 100 |
conn.close()
|
| 101 |
|
| 102 |
+
# --- Tab 4: 融資與銷售 ---
|
| 103 |
with tabs[3]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
conn = sqlite3.connect(DB_PATH)
|
| 105 |
+
col_l, col_r = st.columns(2)
|
| 106 |
+
with col_l:
|
| 107 |
+
st.subheader("🏦 融資合約清單")
|
| 108 |
+
st.dataframe(pd.read_sql_query("SELECT * FROM loan_contracts", conn), use_container_width=True)
|
| 109 |
+
with col_r:
|
| 110 |
+
st.subheader("🏠 房屋銷售計畫")
|
| 111 |
+
st.dataframe(pd.read_sql_query("SELECT * FROM inventory", conn), use_container_width=True)
|
| 112 |
conn.close()
|