197 lines
8.8 KiB
Python
197 lines
8.8 KiB
Python
# 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()
|