Files
embrace_life/embrace_life/app.py
2025-11-05 09:32:06 +08:00

197 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app.py
#
# author: deng
# date: 20251104
from datetime import datetime
import pandas as pd
import streamlit as st
import twstock
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from utils import is_valid_taiwan_id, parse_config, parse_txt
class EmbraceLifeApp:
def __init__(self, config_path: str = 'assets/config.yaml', will_template_path: str = 'assets/will_template.txt') -> None:
self._config = parse_config(config_path)
self._will_template_path = will_template_path
self._will_content = None
self._chain = None
def _prepare_chain(self) -> None:
"""Prepare the chain for translation"""
if self._chain is not None:
return
system_template = (
'你是臺灣民法專家,請判斷接下來自書遺囑內容(尤其是財產分配部分)是否合法(例如股票需描述股票代號、房子需包含建號以及地號),'
'是的話則回傳「沒問題」,否的話請一百字內只要告訴我哪些遺內容需要修改。'
)
user_template = '{input_text}'
llm = ChatOpenAI(
model=self._config['app']['openai']['model_name'],
temperature=self._config['app']['openai']['temperature'],
max_tokens=self._config['app']['openai']['max_tokens'],
top_p=self._config['app']['openai']['top_p'],
)
prompt = ChatPromptTemplate.from_messages([('system', system_template), ('human', user_template)])
self._chain = prompt | llm
def run(self) -> None:
"""Descript the web interface and action of this app"""
# Interface
# 1) Page Info
st.set_page_config(
page_title=self._config['app']['page_title'],
page_icon=self._config['app']['page_favicon_path'],
)
# 2) Title and description
st.title(body=self._config['app']['page_title'])
st.text(body=self._config['app']['page_description'])
with st.expander('流程說明'):
st.write("""
1. 請填入以下所有資料
2. 點擊「製作」產生遺囑草稿,建議由專業人員(律師)協助審閱
3. 準備紙筆,由立囑人親筆謄寫所有遺囑內容
4. 請妥善保存紙本遺囑,並將保存地點告訴遺囑執行人
""")
# 3) Testator Info
testator_name = st.text_input(label='立遺囑人姓名')
testator_id = st.text_input(label='立遺囑人身分證字號')
executor_name = st.text_input(
label='遺囑執行人姓名',
help='遺囑執行人即遺囑生效後,負責實行遺囑內容各種事項的人(不得為未成年、受監護宣告或輔助宣告之人),也就是您信任的人',
)
# 4) Property distribution
prop_dist_type = st.radio(
label='財產分配',
options=['給一人', '給多人'],
horizontal=True,
help='將您名下的財產,例如存款、汽車、房子、股票等,分配給您想給的對象',
)
prop_dist_content = ''
if prop_dist_type == '給一人':
sole_heir = st.text_input('繼承者', help='繼承者可以是家人、朋友或機構(機構請填寫全名,例如:`財團法人伊甸社會福利基金會`')
if sole_heir:
prop_dist_content = f'本人名下所有之全部財產,由 {sole_heir} 單獨繼承。\n'
else:
props = st.data_editor(
data=pd.DataFrame([{'財產名稱': None, '繼承者': None, '財產說明': None}]),
column_config={
'財產名稱': st.column_config.SelectboxColumn(
'財產名稱',
options=[
'存款',
'汽車',
'機車',
'房子',
'土地',
'股票',
'其他',
],
required=True,
),
'繼承者': st.column_config.TextColumn(
'繼承者',
help='可以是家人、朋友或機構(機構請填寫全名,例如:`財團法人伊甸社會福利基金會`',
required=True,
),
'財產說明': st.column_config.TextColumn(
'財產說明',
help=(
'- 存款請輸入銀行全名,例如:`國泰世華銀行萬華分行`\n'
'- 汽/機車請輸入車號,例如:`ABC-1234`\n'
'- 房子請輸入建號與地號(請確認您的權狀),例如:`建號19998-000地號0427-0013`\n'
'- 土地請輸入地號(請確認您的權狀),例如:`0427-0013`\n'
'- 股票請輸入股票代號,例如:`0050`\n'
'- 其他請輸入財產名稱,例如:`金飾`'
),
required=True,
),
},
num_rows='dynamic',
use_container_width=True,
hide_index=True,
)
if len(props) > 0:
prop_dist_content = '本人將名下財產分配如下:\n'
for i, row in props.iterrows():
prop_name = row['財產名稱']
heir = row['繼承者']
prop_desc = row['財產說明']
if prop_name == '存款':
prop_dist_content += f' {i + 1}. 本人於{prop_desc}之所有存款,由 {heir} 單獨繼承。\n'
elif prop_name in ['汽車', '機車']:
prop_dist_content += f' {i + 1}. 本人所有之車號{prop_desc}{prop_name}乙輛,由 {heir} 單獨繼承。\n'
elif prop_name == '房子':
prop_dist_content += f' {i + 1}. 本人所有之房地({prop_desc}),由 {heir} 單獨繼承。\n'
elif prop_name == '土地':
prop_dist_content += f' {i + 1}. 本人所有之土地({prop_desc}),由 {heir} 單獨繼承。\n'
elif prop_name == '股票':
stock = twstock.codes.get(prop_desc)
stock_name = stock.name if stock is not None else '未知'
prop_dist_content += f' {i + 1}. 本人所有之{stock_name}(股票代號{prop_desc})的全部股票,由 {heir} 單獨繼承\n'
elif prop_name == '其他':
prop_dist_content += f' {i + 1}. 本人所有之{prop_desc},由 {heir} 單獨繼承。\n'
submit_botton = st.button('製作')
# Prepare chain
self._prepare_chain()
# Action
if testator_id:
if not is_valid_taiwan_id(testator_id):
st.warning('請輸入有效的身分證字號')
if submit_botton or (testator_name and testator_id and executor_name and prop_dist_content):
self._will_content = parse_txt(self._will_template_path).format(
testator_name=testator_name,
testator_id=testator_id,
executor_name=executor_name,
prop_dist_content=prop_dist_content,
year=datetime.now().year - 1911,
month=datetime.now().month,
day=datetime.now().day,
)
st.code(self._will_content, language=None)
col1, col2, _, _, _ = st.columns(5)
with col1:
st.download_button(
label='下載',
data=self._will_content,
file_name='遺囑.txt',
mime='text/plain',
icon=':material/download:',
)
with col2:
ai_check_button = ai_check_button = st.button('AI審閱', icon=':material/search:')
if ai_check_button:
with st.spinner('校稿中...'):
result = self._chain.invoke({'input_text': self._will_content}).content
if result == '沒問題':
st.success(result)
else:
st.info(result)
else:
st.warning('請填入所有資料')
# Page Footer
st.divider()
footer_text = f'<div style="text-align:center"><p>{self._config["app"]["page_footer_text"]}</p></div>'
st.markdown(footer_text, unsafe_allow_html=True)
if __name__ == '__main__':
app = EmbraceLifeApp()
app.run()