-
๊ฑฐ๋์ ํ: {escape(tx_type)}
-
person: {escape(person)}
-
๊ณ ์ ๋น: {escape(fixed_label)}
-
์ํ: {escape(status_label)}
-
ID: {escape(rule_id)}
- {f'
๋ฉ๋ชจ: {escape(memo)}
' if memo else '
'}
+
+
+
IF
+
{escape(match_field)} {escape(match_type)} โ{escape(pattern_text)}โ
+
+
โ
+
+
THEN
+
{escape(result_text)}
+
+ ์ํ: {escape(status_label)}
+ ์ฐ์ ์์: p{escape(str(priority))}
+ ๊ฑฐ๋์ ํ: {escape(tx_type)}
+ ์กฐ๊ฑด: {escape(condition_text)}
+ person: {escape(person)}
+ ๊ณ ์ ๋น: {escape(fixed_label)}
+ ID: {escape(rule_id)}
+
+ {f'
๋ฉ๋ชจ: {escape(memo)}
' if memo else ''}
""",
unsafe_allow_html=True,
)
- if idx < len(rules) - 1:
- st.divider()
+ action_cols = st.columns([1.35, 0.9, 0.8, 1.4, 3.2], vertical_alignment="center")
+ if action_cols[0].button("๐ ์ ์ฉ(๋ฏธ๋ฆฌ๋ณด๊ธฐ)", key=f"rule_expander_preview_{rule_id}_{idx}", use_container_width=True):
+ category_rule_preview_dialog(rule, db_path)
+ if action_cols[1].button("โ
์ ์ฉ", key=f"rule_expander_apply_{rule_id}_{idx}", use_container_width=True):
+ apply_fn = get_service_callable("apply_category_rule")
+ if not callable(apply_fn):
+ st.error("ํ์ฌ ์คํ ์ค์ธ ์๋น์ค์์ ๋จ์ผ ๊ท์น ์ ์ฉ ํจ์๋ฅผ ์ฐพ์ง ๋ชปํ์ต๋๋ค. Streamlit ์๋ฒ๋ฅผ ์์ ํ ์ข
๋ฃํ ๋ค ๋ค์ ์์ํด ์ฃผ์ธ์.")
+ else:
+ res = apply_fn(rule_id, limit=10000, db_path=db_path)
+ clear_cache()
+ if res.get("success"):
+ st.success(f"๋ฏธ๋ถ๋ฅ ๋งค์นญ {res.get('applied_count', 0):,}๊ฑด์ ๊ท์น์ ์ ์ฉํ์ต๋๋ค.")
+ st.rerun()
+ else:
+ st.error(res.get("error", res))
+ if action_cols[2].button("โ๏ธ ์์ ", key=f"rule_expander_edit_{rule_id}_{idx}", use_container_width=True):
+ category_rule_edit_dialog(rule, db_path)
+ with action_cols[3]:
+ st.markdown('
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
', unsafe_allow_html=True)
+ if action_cols[4].button("๐๏ธ ์ญ์ ", key=f"rule_expander_delete_{rule_id}_{idx}"):
+ delete_rule = getattr(service, "delete_category_rule", None)
+ if delete_rule is None:
+ st.error("ํ์ฌ ์คํ ์ค์ธ ์๋น์ค์ delete_category_rule ํจ์๊ฐ ์์ต๋๋ค. ์ฑ์ ์ฌ์์ํด ์ต์ ์ฝ๋๋ฅผ ๋ค์ ๋ถ๋ฌ์ค์ธ์.")
+ else:
+ res = delete_rule(rule_id, db_path=db_path)
+ clear_cache()
+ if res.get("success"):
+ if st.session_state.get("rules_edit_rule_id") == rule_id:
+ st.session_state.pop("rules_edit_rule_id", None)
+ st.success("๊ท์น์ ์ญ์ ํ์ต๋๋ค.")
+ st.rerun()
+ else:
+ st.error(res.get("error", res))
def page_rules(db_path: str) -> None:
- title_cols = st.columns([8, 1], vertical_alignment="center")
- with title_cols[0]:
- st.title("โ๏ธ ๋ถ๋ฅ ๊ท์น")
- with title_cols[1]:
+ render_page_header(
+ "๋ถ๋ฅ ๊ท์น",
+ "๋ฐ๋ณต๋๋ ์ฌ์ฉ์ฒ์ ๊ฑฐ๋ ํจํด์ ๊ท์น์ผ๋ก ๊ด๋ฆฌํด ๋ถ๋ฅ ์๋ํ ํ์ง์ ๋์
๋๋ค.",
+ kicker="โ๏ธ Rule Engine",
+ pills=["์๋ ๋ถ๋ฅ", "Preview", "Seed/Update"],
+ )
+ action_cols = st.columns([5, 1], vertical_alignment="center")
+ with action_cols[1]:
if st.button("์ถ๊ฐ", key="rule_add_dialog_open", use_container_width=True, type="primary"):
category_rule_create_dialog(db_path)
rules = service.list_category_rules(active_only=False, db_path=db_path).get("rules", [])
- st.subheader("ํ์ฌ ๋ถ๋ฅ ๊ท์น")
+ render_section_label("๐", "ํ์ฌ ๋ถ๋ฅ ๊ท์น")
render_rule_list_items(rules, db_path)
st.caption("๊ท์น ์ถ๊ฐ๋ ์ ๋ชฉ ์ค๋ฅธ์ชฝ `์ถ๊ฐ` ๋ฒํผ, ๊ธฐ์กด ๊ท์น ์์ ์ ๊ฐ ํญ๋ชฉ์ `โฎ โ ๋ณ๊ฒฝ` ํ์
์ ์ฌ์ฉํ์ธ์.")
@@ -844,7 +1408,12 @@ def page_rules(db_path: str) -> None:
def page_categories(db_path: str) -> None:
- st.header("๐ท๏ธ ์นดํ
๊ณ ๋ฆฌ ๊ด๋ฆฌ")
+ render_page_header(
+ "์นดํ
๊ณ ๋ฆฌ ๊ด๋ฆฌ",
+ "ํ๊ธํ๋ฆ ์ฑ๊ฒฉ๊น์ง ํจ๊ป ๊ด๋ฆฌํด ์์ฝ ๋ฆฌํฌํธ์ ํฌ์ ๊ฐ๋ฅ ํ๊ธํ๋ฆ ๊ณ์ฐ์ ๊ธฐ์ค์ ์ ๋ํฉ๋๋ค.",
+ kicker="๐ท๏ธ Category Taxonomy",
+ pills=["์์
/์ง์ถ/์ด์ฒด", "๋นํ์ฑํ ์์ ์ฅ์น", "์ฌ์ฉ๋ ํ์ธ"],
+ )
st.caption("์นดํ
๊ณ ๋ฆฌ ์ญ์ ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋นํ์ฑํ ์ฒ๋ฆฌํฉ๋๋ค. ๊ธฐ์กด ๊ฑฐ๋์ ๊ณผ๊ฑฐ category ๊ฐ์ ๋ณด์กด๋ฉ๋๋ค.")
c1, c2 = st.columns(2)
@@ -855,7 +1424,7 @@ def page_categories(db_path: str) -> None:
st.rerun()
show_inactive = c2.checkbox("๋นํ์ฑ ์นดํ
๊ณ ๋ฆฌ๋ ๋ณด๊ธฐ", value=False)
- st.subheader("์นดํ
๊ณ ๋ฆฌ ์ถ๊ฐ")
+ render_section_label("โ", "์นดํ
๊ณ ๋ฆฌ ์ถ๊ฐ")
with st.form("create_ledger_category"):
cols = st.columns([2, 2, 2, 1, 3])
category = cols[0].text_input("๋๋ถ๋ฅ", placeholder="์: ๋ฐ๋ ค๋๋ฌผ")
@@ -876,7 +1445,7 @@ def page_categories(db_path: str) -> None:
result = service.list_ledger_categories(active_only=not show_inactive, include_usage=True, db_path=db_path)
rows = result.get("categories", [])
- st.subheader("์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก")
+ render_section_label("๐๏ธ", "์นดํ
๊ณ ๋ฆฌ ๋ชฉ๋ก")
if rows:
df = pd.DataFrame(rows)
if "cashflow_kind" in df:
@@ -888,7 +1457,7 @@ def page_categories(db_path: str) -> None:
st.info("๋ฑ๋ก๋ ์นดํ
๊ณ ๋ฆฌ๊ฐ ์์ต๋๋ค. ๊ธฐ๋ณธ ์นดํ
๊ณ ๋ฆฌ๋ฅผ seedํ๊ฑฐ๋ ์ ์นดํ
๊ณ ๋ฆฌ๋ฅผ ์ถ๊ฐํ์ธ์.")
return
- st.subheader("์นดํ
๊ณ ๋ฆฌ ์ญ์ /๋นํ์ฑํ")
+ render_section_label("๐ก๏ธ", "์นดํ
๊ณ ๋ฆฌ ์ญ์ /๋นํ์ฑํ")
options = {
f"{r['category']} / {r['subcategory'] or '-'} ยท ์ฌ์ฉ {r.get('transaction_count', 0)}๊ฑด ยท {'ํ์ฑ' if r.get('active') else '๋นํ์ฑ'}": r
for r in rows
@@ -908,17 +1477,106 @@ def page_categories(db_path: str) -> None:
def page_accounts(db_path: str) -> None:
- st.header("๐ฆ ๊ณ์ข")
- accounts = read_sql(db_path, "SELECT * FROM accounts ORDER BY created_at DESC")
+ render_page_header(
+ "๊ณ์ข",
+ "์์งยทimport์ ์ฐ๊ฒฐ๋ ๊ณ์ข์ ์นด๋, ๋ง์ง๋ง ์์ก ์ ๋ณด๋ฅผ ํ์ธํฉ๋๋ค.",
+ kicker="๐ฆ Accounts",
+ pills=["์์ก", "๊ธฐ๊ด", "statement_status", "์๋ ์์ก๋์กฐ"],
+ )
+ accounts_res = service.list_ledger_accounts(active_only=False, db_path=db_path)
+ accounts_data = accounts_res.get("accounts", []) if accounts_res.get("success") else []
+ accounts = pd.DataFrame(accounts_data)
if not accounts.empty:
+ accounts["statement_status_label"] = accounts["statement_status"].map(STATEMENT_STATUS_LABELS).fillna(accounts["statement_status"])
+ accounts["liquidity_bucket_label"] = accounts["liquidity_bucket"].map(LIQUIDITY_BUCKET_LABELS).fillna(accounts["liquidity_bucket"])
accounts["last_balance_display"] = accounts["last_balance"].map(format_krw)
- st.dataframe(accounts, width="stretch")
+ display_cols = [c for c in ["id", "name", "type", "institution", "liquidity_bucket", "liquidity_bucket_label", "statement_status", "statement_status_label", "last_balance_display", "last_synced_at", "active", "memo"] if c in accounts.columns]
+ st.dataframe(accounts[display_cols], width="stretch")
+ else:
+ st.info("๋ฑ๋ก๋ ๊ณ์ข๊ฐ ์์ต๋๋ค.")
+
+ st.divider()
+ render_section_label("๐ ๏ธ", "๊ณ์ข ์ด์ ์์ฑ ์์ ")
+ if accounts_data:
+ account_options = {f"{a['name']} ({a['id']})": a for a in accounts_data}
+ with st.form("account_statement_status_form"):
+ selected_label = st.selectbox("๊ณ์ข", list(account_options.keys()))
+ selected = account_options[selected_label]
+ current_status = selected.get("statement_status", "unknown")
+ status = st.selectbox(
+ "statement_status",
+ STATEMENT_STATUS_OPTIONS,
+ index=STATEMENT_STATUS_OPTIONS.index(current_status) if current_status in STATEMENT_STATUS_OPTIONS else 0,
+ format_func=lambda v: f"{v} โ {STATEMENT_STATUS_LABELS.get(v, v)}",
+ )
+ current_bucket = selected.get("liquidity_bucket", "other") or "other"
+ liquidity_bucket = st.selectbox(
+ "liquidity_bucket",
+ LIQUIDITY_BUCKET_OPTIONS,
+ index=LIQUIDITY_BUCKET_OPTIONS.index(current_bucket) if current_bucket in LIQUIDITY_BUCKET_OPTIONS else LIQUIDITY_BUCKET_OPTIONS.index("other"),
+ format_func=lambda v: f"{v} โ {LIQUIDITY_BUCKET_LABELS.get(v, v)}",
+ )
+ last_balance_raw = st.text_input("๊ธฐ์ค ์์ก(last_balance, ์ ํ)", value="" if selected.get("last_balance") is None else str(selected.get("last_balance")))
+ last_synced_at = st.text_input("๊ธฐ์ค ์๊ฐ(last_synced_at, ์ ํ)", value=selected.get("last_synced_at") or "", help="์: 2026-06 ๋๋ 2026-06-30T23:59:00+09:00")
+ memo = st.text_input("๋ฉ๋ชจ", value=selected.get("memo") or "")
+ submitted = st.form_submit_button("๊ณ์ข ์ํ ์ ์ฅ", type="primary")
+ if submitted:
+ update_fields: dict[str, Any] = {"statement_status": status, "liquidity_bucket": liquidity_bucket, "last_synced_at": last_synced_at, "memo": memo}
+ if last_balance_raw.strip():
+ try:
+ update_fields["last_balance"] = int(last_balance_raw.replace(",", ""))
+ except ValueError:
+ st.error("๊ธฐ์ค ์์ก์ ์ ์๋ก ์
๋ ฅํ์ธ์.")
+ return
+ res = service.update_ledger_account(selected["id"], db_path=db_path, **update_fields)
+ clear_cache()
+ if res.get("success"):
+ st.success("๊ณ์ข ์ํ๋ฅผ ์ ์ฅํ์ต๋๋ค.")
+ st.rerun()
+ else:
+ st.error(res.get("error", res))
+
+ st.divider()
+ render_section_label("โ๏ธ", "์ต์ ์์ก๋์กฐ ๊ฒฐ๊ณผ")
+ st.caption("๋์: statement_status๊ฐ manual_only ๋๋ partial_import์ธ ๊ณ์ข. ๊ฒฐ๊ณผ๋ preview/report์ด๋ฉฐ ์๋ ํ์ ยท์๋ฆผยทrestore๋ฅผ ์ํํ์ง ์์ต๋๋ค.")
+ c1, c2, c3 = st.columns([1, 1, 1])
+ baseline_at = c1.text_input("์ค๋
์ท ๊ธฐ์ค ์๊ฐ", value="", placeholder="2026-06 ๋๋ 2026-06-30T23:59:00+09:00")
+ tolerance = c2.number_input("ํ์ฉ ์ค์ฐจ(์)", min_value=0, value=1000, step=100)
+ source_note = c3.text_input("์ถ์ฒ ๋ฉ๋ชจ", value="", placeholder="์: ์๋ง ์ฑ ์์ก")
+ if st.button("์์ก๋์กฐ ์คํ", type="secondary"):
+ st.session_state["balance_reconciliation_report"] = service.get_balance_reconciliation_report(
+ baseline_at=baseline_at,
+ source_note=source_note,
+ tolerance=int(tolerance),
+ db_path=db_path,
+ )
+ report = st.session_state.get("balance_reconciliation_report")
+ if report:
+ if report.get("success"):
+ items = pd.DataFrame(report.get("items", []))
+ if not items.empty:
+ for col in ["baseline_balance", "calculated_balance", "difference"]:
+ if col in items.columns:
+ items[f"{col}_display"] = items[col].map(format_krw)
+ show_cols = [c for c in ["account_name", "statement_status", "status", "baseline_at", "baseline_balance_display", "calculated_balance_display", "difference_display", "difference_ratio", "calculation_basis", "transaction_count"] if c in items.columns]
+ st.dataframe(items[show_cols], width="stretch")
+ review_count = int((items["status"] == "REVIEW_NEEDED").sum()) if "status" in items else 0
+ st.metric("ํ์ธํ์", f"{review_count}๊ฑด")
+ else:
+ st.info("์์ก๋์กฐ ๋์ ๊ณ์ข๊ฐ ์์ต๋๋ค.")
+ else:
+ st.error(report.get("error", report))
def page_imports(db_path: str) -> None:
- st.header("๐ฅ Import")
+ render_page_header(
+ "Import",
+ "PDF/XLSX ๊ฑฐ๋๋ด์ญ์ ๋จผ์ Preview๋ก ๊ฒ์ฆํ ๋ค ์์ฅ์ ๋ฐ์ํฉ๋๋ค.",
+ kicker="๐ฅ Statement Import",
+ pills=["Preview ์ฐ์ ", "์ค๋ณต skip", "Import ์ด๋ ฅ"],
+ )
- st.subheader("ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ")
+ render_section_label("๐", "ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ")
st.caption("ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ์ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ํฌ์๊ณ์ข ๊ฑฐ๋๋ก ๊ฐ์ ธ์ต๋๋ค. ๋งค์/๋งค๋/ํ์ /์
์ถ๊ธ์ `์ด์ฒด/ํฌ์์ด๋`, ๋ฐฐ๋น/์ด์๋ ์ค์ ์์
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค.")
uploaded = st.file_uploader("ํ ์ค์ฆ๊ถ PDF", type=["pdf"], key="toss_pdf")
if uploaded is not None:
@@ -952,7 +1610,7 @@ def page_imports(db_path: str) -> None:
st.error(res)
st.divider()
- st.subheader("ํ ์ค๋ฑ
ํฌ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ")
+ render_section_label("๐ฆ", "ํ ์ค๋ฑ
ํฌ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ")
st.caption("ํ ์ค๋ฑ
ํฌ ๊ณ์ข ๊ฑฐ๋๋ด์ญ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ์
์ถ๊ธ/์ด์ฒด/์นด๋๊ฒฐ์ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ธ ์ด์ฒด/์ถ๊ธ์ `์ด์ฒด/ํ์ธํ์`๋ก ๋ถ๋ฅ๋ฉ๋๋ค.")
toss_bank_uploaded = st.file_uploader("ํ ์ค๋ฑ
ํฌ PDF", type=["pdf"], key="tossbank_pdf")
if toss_bank_uploaded is not None:
@@ -986,8 +1644,8 @@ def page_imports(db_path: str) -> None:
st.error(res)
st.divider()
- st.subheader("Import ์ด๋ ฅ")
- batches = service.list_ledger_import_batches(limit=100).get("import_batches", [])
+ render_section_label("๐งพ", "Import ์ด๋ ฅ")
+ batches = service.list_ledger_import_batches(limit=100, db_path=db_path).get("import_batches", [])
st.dataframe(pd.DataFrame(batches), width="stretch", height=350)
if batches:
with st.form("rollback"):
@@ -995,7 +1653,7 @@ def page_imports(db_path: str) -> None:
hard = st.checkbox("Hard delete", value=False)
submitted = st.form_submit_button("Rollback ์คํ", type="secondary")
if submitted:
- res = service.rollback_ledger_import_batch(batch_id, hard_delete=hard)
+ res = service.rollback_ledger_import_batch(batch_id, hard_delete=hard, db_path=db_path)
clear_cache()
st.warning(res)
st.rerun()
@@ -1003,7 +1661,7 @@ def page_imports(db_path: str) -> None:
def main() -> None:
db_path = get_db_path()
- inject_sidebar_nav_css()
+ inject_app_css()
pages = ["์์ฝ", "๊ฑฐ๋๋ด์ญ", "ํ์ธํ์ ํ", "๋ฏธ๋ถ๋ฅ", "๋ถ๋ฅ ๊ท์น", "์นดํ
๊ณ ๋ฆฌ", "๊ณ์ข", "Import"]
query_page = st.query_params.get("page", "์์ฝ")
@@ -1011,7 +1669,8 @@ def main() -> None:
st.session_state["page"] = page
with st.sidebar:
- st.markdown("## Jarca Ledger")
+ st.markdown("## ๐ฐ Jarca Ledger")
+ st.caption("๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์")
st.markdown('
๋ฉ๋ด
', unsafe_allow_html=True)
render_sidebar_nav(pages, page)
if st.button("์๋ก๊ณ ์นจ", key="refresh_button", use_container_width=True):
diff --git a/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/implementation.md b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/implementation.md
new file mode 100644
index 0000000..4b30150
--- /dev/null
+++ b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/implementation.md
@@ -0,0 +1,28 @@
+# BAT-003 Implementation
+
+## ์ฝ๋ ๋ณ๊ฒฝ
+
+- `jarca_ledger_mcp/db.py`
+ - schema version 7
+ - `accounts.statement_status TEXT NOT NULL DEFAULT 'unknown'`
+ - ๊ธฐ์กด DB์ฉ additive column migration
+- `jarca_ledger_mcp/service.py`
+ - `VALID_STATEMENT_STATUSES` ๋ฐ reconciliation ๋์ ์์
+ - `create_ledger_account`์ `statement_status` ์
๋ ฅ ์ถ๊ฐ
+ - `update_ledger_account` ์ ๊ท ์ถ๊ฐ
+ - `get_balance_reconciliation_report` ์ ๊ท ์ถ๊ฐ
+- `jarca_ledger_mcp/server.py`
+ - `create_ledger_account` signature ํ์ฅ
+ - `update_ledger_account`, `get_balance_reconciliation_report` MCP tool ์ถ๊ฐ
+- `dashboard/app.py`
+ - ๊ณ์ข ํ๋ฉด์ statement ์ํ ํ์/์์ UI ์ถ๊ฐ
+ - ์ต์ ์์ก๋์กฐ ๊ฒฐ๊ณผ ํ ์ถ๊ฐ
+- `tests/test_service.py`
+ - schema version 7 ๋ฐ์
+ - statement status validation ๋ฐ balance reconciliation ํ
์คํธ ์ถ๊ฐ
+
+## ์ด์ ์์
+
+- ์ด์ DB ๋ณ๊ฒฝ ์ dcy-002 ๋ฐฑ์
์ ์ฐจ๋ฅผ ๋จผ์ ์ํํ๋ค.
+- ๊ฒ์ฆ DB์์ preview/reconcile ํ ์ด์ DB์ ๋ฐ์ํ๋ค.
+- restore๋ ์ฌ์ฉ์์ ๋ช
์ ์น์ธ ์์ด ์คํํ์ง ์๋๋ค.
diff --git a/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/index.md b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/index.md
new file mode 100644
index 0000000..b0541ca
--- /dev/null
+++ b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/index.md
@@ -0,0 +1,25 @@
+# BAT-003 Statement-unavailable ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ
+
+- Profile: batch-lite
+- Status: release-candidate
+- Included REQ: REQ-003
+- Discovery: dcy-003
+- Owner: ๋ ์ ์ฅ์ธ
+- Date (KST): 2026-06-29
+
+## ๋ชฉํ
+
+statement ํ์ผ์ด ์๊ฑฐ๋ ์ผ๋ถ๋ง import ๊ฐ๋ฅํ ๊ณ์ข๋ฅผ `statement_status`๋ก ๊ตฌ๋ถํ๊ณ , ์๋ ์คํ ๊ฐ๋ฅํ ์์ก๋์กฐ report ๋ฐ ๊ณ์ข ํ๋ฉด ์ต์ UI๋ฅผ ์ ๊ณตํ๋ค.
+
+## ์ฐ์ถ๋ฌผ
+
+- `accounts.statement_status` additive migration
+- service ๊ณ์ข update ๋ฐ ์์ก๋์กฐ report
+- MCP tool surface ์ถ๊ฐ
+- dashboard ๊ณ์ข ํ๋ฉด ํ์/์์ /๋์กฐ ๊ฒฐ๊ณผ
+- REQ-003 ๋ฐ ์ด์ ๋ฌธ์ ๊ฐฑ์
+- pytest ๊ธฐ๋ฐ regression ๊ฒ์ฆ
+
+## ๋ฒ์ ์ ํ
+
+์๋ ์ค์ผ์ค๋ง/์๋ฆผ, ์ ์ฉ ์ํฌ๋ฒค์น ์ ์ฒด ์ ์ค, ์๋ ํ์ mutation, ์ฌ์ฉ์ ์น์ธ ์๋ restore๋ ํฌํจํ์ง ์๋๋ค.
diff --git a/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/verification.md b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/verification.md
new file mode 100644
index 0000000..b209330
--- /dev/null
+++ b/docs/batches/bat-003_20260629_statement-unavailable-reconciliation/verification.md
@@ -0,0 +1,35 @@
+# BAT-003 Verification
+
+## ๊ฒ์ฆ ๊ณํ
+
+1. Python ๋ฌธ๋ฒ ๊ฒ์ฆ
+ - `python3 -m py_compile jarca_ledger_mcp/db.py jarca_ledger_mcp/service.py jarca_ledger_mcp/server.py dashboard/app.py tests/test_service.py`
+2. ๋์ ํ
์คํธ
+ - `python3 -m pytest tests/test_service.py -q`
+3. ์ ์ฒด ํ
์คํธ
+ - ๊ฐ๋ฅํ๋ฉด `python3 -m pytest -q`
+
+## Acceptance Mapping
+
+- AC-1/2: `test_health_check_initializes_db`, `test_invalid_statement_status_rejected`
+- AC-3/4: `test_statement_status_account_update_and_balance_reconciliation`
+- AC-5: dashboard py_compile ๋ฐ ๊ณ์ข page ์ฝ๋ ๊ฒ์ฆ
+- AC-6: server py_compile ๋ฐ tool ํจ์ ์ถ๊ฐ ํ์ธ
+- AC-7: REQ/batch/data-model/interface/runtime/runbook ๋ฌธ์ ๊ฐฑ์
+
+## ๊ฒฐ๊ณผ
+
+์ต์ข
๊ฒ์ฆ ๊ฒฐ๊ณผ(2026-06-29 KST):
+
+- `python -m py_compile dashboard/app.py jarca_ledger_mcp/service.py jarca_ledger_mcp/server.py jarca_ledger_mcp/db.py` โ PASS
+- `pytest tests/test_service.py tests/test_categorization.py tests/test_uncategorized_workbench.py -q` โ PASS (`12 passed`)
+- `pytest -q` โ PASS (`28 passed, 4 skipped`)
+- Streamlit `AppTest.from_file('dashboard/app.py')` with synthetic temp DB and `?page=๊ณ์ข` โ PASS; account page rendered without exceptions and exposed `์์ก๋์กฐ ์คํ`.
+
+๊ฒ์ฆ ์ค ๋ฐ๊ฒฌ/์์ :
+
+- `tests/test_ledger_categories.py`์ schema version ๊ธฐ๋๊ฐ์ด ์ด์ ๋ฒ์ `6`์ผ๋ก ๋จ์ ์์ด `7`๋ก ๊ฐฑ์ ํ๋ค.
+
+๋จ์ ๋ฒ์ ์ธ ํญ๋ชฉ:
+
+- dcy-004/dcy-005 ๋ฌธ์๋ ํ์ฌ ์์
๋ฒ์๊ฐ ์๋๋ฏ๋ก ๊ตฌํ์ ์์ํ์ง ์์๋ค.
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.dom.html
new file mode 100644
index 0000000..3c2bb0f
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ฅ Statement Import
+
+
PDF/XLSX ๊ฑฐ๋๋ด์ญ์ ๋จผ์ Preview๋ก ๊ฒ์ฆํ ๋ค ์์ฅ์ ๋ฐ์ํฉ๋๋ค.
+
Preview ์ฐ์ ์ค๋ณต skipImport ์ด๋ ฅ
+
๐ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ
ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ์ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ํฌ์๊ณ์ข ๊ฑฐ๋๋ก ๊ฐ์ ธ์ต๋๋ค. ๋งค์/๋งค๋/ํ์ /์
์ถ๊ธ์ ์ด์ฒด/ํฌ์์ด๋, ๋ฐฐ๋น/์ด์๋ ์ค์ ์์
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค.
๐ฆํ ์ค๋ฑ
ํฌ ๊ฑฐ๋๋ด์ญ PDF ๊ฐ์ ธ์ค๊ธฐ
ํ ์ค๋ฑ
ํฌ ๊ณ์ข ๊ฑฐ๋๋ด์ญ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ์
์ถ๊ธ/์ด์ฒด/์นด๋๊ฒฐ์ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ธ ์ด์ฒด/์ถ๊ธ์ ์ด์ฒด/ํ์ธํ์๋ก ๋ถ๋ฅ๋ฉ๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.png
new file mode 100644
index 0000000..fa6bde5
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/manifest.json b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/manifest.json
new file mode 100644
index 0000000..04427e9
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/manifest.json
@@ -0,0 +1,62 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18501",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835",
+ "fixture": {
+ "account_id": "acct_a4f4de4cdd0f",
+ "transaction_ids": [
+ "tx_cba36b3d2c33",
+ "tx_24fa1050eb2b",
+ "tx_8cd3af3d60b6"
+ ],
+ "db_path": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "๋ถ๋ฅ ๊ท์น",
+ "url": "http://127.0.0.1:18501/?page=%EB%B6%84%EB%A5%98%20%EA%B7%9C%EC%B9%99",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.png",
+ "screenshot_bytes": 364876,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.dom.html",
+ "snapshot_bytes": 174470,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\nStop\nDeploy\nโ๏ธ Rule Engine\n๋ถ๋ฅ ๊ท์น\n\n๋ฐ๋ณต๋๋ ์ฌ์ฉ์ฒ์ ๊ฑฐ๋ ํจํด์ ๊ท์น์ผ๋ก ๊ด๋ฆฌํด ๋ถ๋ฅ ์๋ํ ํ์ง์ ๋์
๋๋ค.\n\n์๋ ๋ถ๋ฅ\nPreview\nSeed/Update\nkeyboard_arrow_right\n\n๐ข ์ด์ ยท merchant/contains โ ์์
/ ์ด์ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข ํ๊ธ ยท merchant/contains โ ์์
/ ํ๊ธ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข ๊ธ์ฌ ยท merchant/contains โ ์์
/ ๊ธ์ฌ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข KB์นด๋์ถ๊ธ ยท merchant/contains โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense\n\nkeyboard_arro"
+ },
+ {
+ "page": "ํ์ธํ์ ํ",
+ "url": "http://127.0.0.1:18501/?page=%ED%99%95%EC%9D%B8%ED%95%84%EC%9A%94%20%ED%81%90",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.png",
+ "screenshot_bytes": 275792,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.dom.html",
+ "snapshot_bytes": 25551,
+ "devtools_port": 19223,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\nStop\nDeploy\n๐ Transfer Review\nKBโํ ์ค๋ฑ
ํฌ ํ์ธํ์ ํ\n\n๋ ์งยท๊ธ์กยท์๋ ๊ธฐ์ค์ผ๋ก ๊ณ์ข ๊ฐ ์ด๋ ํ๋ณด๋ฅผ ๋์กฐํ๊ณ , ๋๋ผ์ด๋ฐ ํ ์์ ํ๊ฒ ๋งค์นญํฉ๋๋ค.\n\n๋๋ผ์ด๋ฐ ์ฐ์ \n๋ณธ์ธ๊ณ์ข์ด๋\n์๋ ๊ฒํ \n๐\nํ์ธํ์ ํ\n\n๊ฑฐ๋ ID: tx_8cd3af3d60b6\n\n2026-06-29 ยท SYNTHETIC_TRANSFER ยท 120,000์"
+ },
+ {
+ "page": "Import",
+ "url": "http://127.0.0.1:18501/?page=Import",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.png",
+ "screenshot_bytes": 297424,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/import.dom.html",
+ "snapshot_bytes": 23649,
+ "devtools_port": 19224,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\nStop\nDeploy\n\nํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ์ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ํฌ์๊ณ์ข ๊ฑฐ๋๋ก ๊ฐ์ ธ์ต๋๋ค. ๋งค์/๋งค๋/ํ์ /์
์ถ๊ธ์ ์ด์ฒด/ํฌ์์ด๋, ๋ฐฐ๋น/์ด์๋ ์ค์ ์์
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค.\n\nํ ์ค๋ฑ
ํฌ ๊ณ์ข ๊ฑฐ๋๋ด์ญ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ์
์ถ๊ธ/์ด์ฒด/์นด๋๊ฒฐ์ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ธ ์ด์ฒด/์ถ๊ธ์ ์ด์ฒด/ํ์ธํ์๋ก ๋ถ๋ฅ๋ฉ๋๋ค."
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18501",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.dom.html
new file mode 100644
index 0000000..02ba3f9
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.dom.html
@@ -0,0 +1,889 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
โ๏ธ Rule Engine
+
+
๋ฐ๋ณต๋๋ ์ฌ์ฉ์ฒ์ ๊ฑฐ๋ ํจํด์ ๊ท์น์ผ๋ก ๊ด๋ฆฌํด ๋ถ๋ฅ ์๋ํ ํ์ง์ ๋์
๋๋ค.
+
์๋ ๋ถ๋ฅPreviewSeed/Update
+
keyboard_arrow_right๐ข ์ด์ ยท merchant/contains โ ์์
/ ์ด์ ยท p10 ยท income
+
+
IF
+
merchant contains โ์ด์โ
+
+
โ
+
+
THEN
+
์์
/ ์ด์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p10
+ ๊ฑฐ๋์ ํ: income
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_1f77940cb31c
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ํ๊ธ ยท merchant/contains โ ์์
/ ํ๊ธ ยท p10 ยท income
+
+
IF
+
merchant contains โํ๊ธโ
+
+
โ
+
+
THEN
+
์์
/ ํ๊ธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p10
+ ๊ฑฐ๋์ ํ: income
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_6e38f120e5c6
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๊ธ์ฌ ยท merchant/contains โ ์์
/ ๊ธ์ฌ ยท p10 ยท income
+
+
IF
+
merchant contains โ๊ธ์ฌโ
+
+
โ
+
+
THEN
+
์์
/ ๊ธ์ฌ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p10
+ ๊ฑฐ๋์ ํ: income
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_8ceb1ae3a132
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข KB์นด๋์ถ๊ธ ยท merchant/contains โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense
+
+
IF
+
merchant contains โKB์นด๋์ถ๊ธโ
+
+
โ
+
+
THEN
+
๊ธ์ต / ์นด๋๋๊ธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p20
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์
+ ID: rule_235b49d33fbc
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๊ตญ๋ฏผ์นด๋ ยท raw_summary/equals โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense
+
+
IF
+
raw_summary equals โ๊ตญ๋ฏผ์นด๋โ
+
+
โ
+
+
THEN
+
๊ธ์ต / ์นด๋๋๊ธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p20
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์
+ ID: rule_4d9d7b455bd0
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข KTํต์ ยท merchant/contains โ ํต์ / ํต์ ์๊ธ ยท p30 ยท expense
+
+
IF
+
merchant contains โKTํต์ โ
+
+
โ
+
+
THEN
+
ํต์ / ํต์ ์๊ธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p30
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์
+ ID: rule_060c204c6d91
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข SKํ
๋ ์ฝค ยท merchant/contains โ ํต์ / ํด๋ํฐ ยท p30 ยท expense
+
+
IF
+
merchant contains โSKํ
๋ ์ฝคโ
+
+
โ
+
+
THEN
+
ํต์ / ํด๋ํฐ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p30
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์
+ ID: rule_be4f08863838
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ฝ๊ตญ ยท merchant/contains โ ์๋ฃ / ์ฝ๊ตญ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์ฝ๊ตญโ
+
+
โ
+
+
THEN
+
์๋ฃ / ์ฝ๊ตญ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_50793f60f7ce
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์์ ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์์โ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_688e87cd4148
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ํ์ ยท merchant/contains โ ๊ต์ก / ํ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โํ์โ
+
+
โ
+
+
THEN
+
๊ต์ก / ํ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_8610f7c05413
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๊ต์ต์ ยท merchant/contains โ ๊ต์ก / ์์ฒด๋ฅ ยท p40 ยท expense
+
+
IF
+
merchant contains โ๊ต์ต์โ
+
+
โ
+
+
THEN
+
๊ต์ก / ์์ฒด๋ฅ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_98527962f446
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋ณ์ ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ๋ณ์โ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_d6883e55b483
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์น๊ณผ ยท merchant/contains โ ์๋ฃ / ์น๊ณผ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์น๊ณผโ
+
+
โ
+
+
THEN
+
์๋ฃ / ์น๊ณผ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_e5572dc22f15
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ฐ์ธ๋ํ๊ต ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์ฐ์ธ๋ํ๊ตโ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_ea1c1fe38158
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ด๋งํธ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ด๋งํธโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_2b13c1f27cbd
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ปคํผ ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ปคํผโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_58d4275ec10b
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค์์์ค๋ง์ผ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ค์์์ค๋ง์ผโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_5ce9ce6dba0c
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ํ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ํโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_63f37fa65134
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋งค๋จธ๋ ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ๋งค๋จธ๋โ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_8c72d1931af7
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋ฉ๊ฐMGC ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ๋ฉ๊ฐMGCโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_c477743d98c3
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์คํ๋ฒ
์ค ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์คํ๋ฒ
์คโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_db17797fb76a
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์๊ตญ์ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์๊ตญ์โ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_102cd412f647
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ง์ฅ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์ง์ฅโ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_335996f7e00e
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข KFC ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โKFCโ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_9d2d65fc6e87
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค์ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์ค์โ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_beaa5f36255c
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค๋งํธ์ถ๊ธ ยท raw_summary/equals โ ์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋ ยท p80 ยท ์ ์ฒด
+
+
IF
+
raw_summary equals โ์ค๋งํธ์ถ๊ธโ
+
+
โ
+
+
THEN
+
์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p80
+ ๊ฑฐ๋์ ํ: ์ ์ฒด
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_6d74cb69a357
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ATM์
๊ธ ยท raw_summary/equals โ ์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋ ยท p80 ยท ์ ์ฒด
+
+
IF
+
raw_summary equals โATM์
๊ธโ
+
+
โ
+
+
THEN
+
์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p80
+ ๊ฑฐ๋์ ํ: ์ ์ฒด
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_afa3a6aaf448
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
๊ท์น ์ถ๊ฐ๋ ์ ๋ชฉ ์ค๋ฅธ์ชฝ ์ถ๊ฐ ๋ฒํผ, ๊ธฐ์กด ๊ท์น ์์ ์ ๊ฐ ํญ๋ชฉ์ โฎ โ ๋ณ๊ฒฝ ํ์
์ ์ฌ์ฉํ์ธ์.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.png
new file mode 100644
index 0000000..8a54d3d
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/rules.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.dom.html
new file mode 100644
index 0000000..6687e91
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ Transfer Review
+
KBโํ ์ค๋ฑ
ํฌ ํ์ธํ์ ํ
+
๋ ์งยท๊ธ์กยท์๋ ๊ธฐ์ค์ผ๋ก ๊ณ์ข ๊ฐ ์ด๋ ํ๋ณด๋ฅผ ๋์กฐํ๊ณ , ๋๋ผ์ด๋ฐ ํ ์์ ํ๊ฒ ๋งค์นญํฉ๋๋ค.
+
๋๋ผ์ด๋ฐ ์ฐ์ ๋ณธ์ธ๊ณ์ข์ด๋์๋ ๊ฒํ
+
๊ฑฐ๋ ID: tx_8cd3af3d60b6
2026-06-29 ยท SYNTHETIC_TRANSFER ยท 120,000์
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.png
new file mode 100644
index 0000000..c6f7d39
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/transfer-review.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.dom.html
new file mode 100644
index 0000000..8f93317
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.dom.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
ํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ์ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ํฌ์๊ณ์ข ๊ฑฐ๋๋ก ๊ฐ์ ธ์ต๋๋ค. ๋งค์/๋งค๋/ํ์ /์
์ถ๊ธ์ ์ด์ฒด/ํฌ์์ด๋, ๋ฐฐ๋น/์ด์๋ ์ค์ ์์
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค.
ํ ์ค๋ฑ
ํฌ ๊ณ์ข ๊ฑฐ๋๋ด์ญ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ์
์ถ๊ธ/์ด์ฒด/์นด๋๊ฒฐ์ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ธ ์ด์ฒด/์ถ๊ธ์ ์ด์ฒด/ํ์ธํ์๋ก ๋ถ๋ฅ๋ฉ๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.png
new file mode 100644
index 0000000..a7389af
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/manifest.json b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/manifest.json
new file mode 100644
index 0000000..3fc23f8
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/manifest.json
@@ -0,0 +1,62 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18501",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632",
+ "fixture": {
+ "account_id": "acct_7579e5091359",
+ "transaction_ids": [
+ "tx_22a85feba578",
+ "tx_04e500d0745c",
+ "tx_3ee6a6e14179"
+ ],
+ "db_path": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "๋ถ๋ฅ ๊ท์น",
+ "url": "http://127.0.0.1:18501/?page=%EB%B6%84%EB%A5%98%20%EA%B7%9C%EC%B9%99",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.png",
+ "screenshot_bytes": 364622,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.dom.html",
+ "snapshot_bytes": 190599,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\nStop\nDeploy\nโ๏ธ Rule Engine\n๋ถ๋ฅ ๊ท์น\n\n๋ฐ๋ณต๋๋ ์ฌ์ฉ์ฒ์ ๊ฑฐ๋ ํจํด์ ๊ท์น์ผ๋ก ๊ด๋ฆฌํด ๋ถ๋ฅ ์๋ํ ํ์ง์ ๋์
๋๋ค.\n\n์๋ ๋ถ๋ฅ\nPreview\nSeed/Update\n๐\nํ์ฌ ๋ถ๋ฅ ๊ท์น\nkeyboard_arrow_right\n\n๐ข ๊ธ์ฌ ยท merchant/contains โ ์์
/ ๊ธ์ฌ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข ์ด์ ยท merchant/contains โ ์์
/ ์ด์ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข ํ๊ธ ยท merchant/contains โ ์์
/ ํ๊ธ ยท p10 ยท income\n\nkeyboard_arrow_right\n\n๐ข KB์นด๋์ถ๊ธ ยท merchant/contains โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense\n\nke"
+ },
+ {
+ "page": "ํ์ธํ์ ํ",
+ "url": "http://127.0.0.1:18501/?page=%ED%99%95%EC%9D%B8%ED%95%84%EC%9A%94%20%ED%81%90",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.png",
+ "screenshot_bytes": 282069,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.dom.html",
+ "snapshot_bytes": 27701,
+ "devtools_port": 19223,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\n\n์๋ก๊ณ ์นจ\n\nStop\nDeploy\n๐ Transfer Review\nKBโํ ์ค๋ฑ
ํฌ ํ์ธํ์ ํ\n\n๋ ์งยท๊ธ์กยท์๋ ๊ธฐ์ค์ผ๋ก ๊ณ์ข ๊ฐ ์ด๋ ํ๋ณด๋ฅผ ๋์กฐํ๊ณ , ๋๋ผ์ด๋ฐ ํ ์์ ํ๊ฒ ๋งค์นญํฉ๋๋ค.\n\n๋๋ผ์ด๋ฐ ์ฐ์ \n๋ณธ์ธ๊ณ์ข์ด๋\n์๋ ๊ฒํ \n\n๋ฏธ๋ฆฌ๋ณด๊ธฐ(๋๋ผ์ด๋ฐ)\n\n์๋ ๋งค์นญ ์คํ\n\n๐\nํ์ธํ์ ํ\n\n๊ฑฐ๋ ID: tx_3ee6a6e14179\n\n2026-06-29 ยท SYNTHETIC_TRANSFER ยท 120,000์"
+ },
+ {
+ "page": "Import",
+ "url": "http://127.0.0.1:18501/?page=Import",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.png",
+ "screenshot_bytes": 298123,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/import.dom.html",
+ "snapshot_bytes": 15721,
+ "devtools_port": 19224,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\nStop\nDeploy\n\nํ ์ค์ฆ๊ถ ๊ฑฐ๋๋ด์ญ์ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ํฌ์๊ณ์ข ๊ฑฐ๋๋ก ๊ฐ์ ธ์ต๋๋ค. ๋งค์/๋งค๋/ํ์ /์
์ถ๊ธ์ ์ด์ฒด/ํฌ์์ด๋, ๋ฐฐ๋น/์ด์๋ ์ค์ ์์
์ผ๋ก ๋ถ๋ฅํฉ๋๋ค.\n\nํ ์ค๋ฑ
ํฌ ๊ณ์ข ๊ฑฐ๋๋ด์ญ PDF๋ฅผ ์
๋ก๋ํ๋ฉด ์
์ถ๊ธ/์ด์ฒด/์นด๋๊ฒฐ์ ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. ํ์ธ ์ด์ฒด/์ถ๊ธ์ ์ด์ฒด/ํ์ธํ์๋ก ๋ถ๋ฅ๋ฉ๋๋ค."
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18501",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.dom.html
new file mode 100644
index 0000000..44e357d
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.dom.html
@@ -0,0 +1,601 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
โ๏ธ Rule Engine
+
+
๋ฐ๋ณต๋๋ ์ฌ์ฉ์ฒ์ ๊ฑฐ๋ ํจํด์ ๊ท์น์ผ๋ก ๊ด๋ฆฌํด ๋ถ๋ฅ ์๋ํ ํ์ง์ ๋์
๋๋ค.
+
์๋ ๋ถ๋ฅPreviewSeed/Update
+
keyboard_arrow_right๐ข ๊ธ์ฌ ยท merchant/contains โ ์์
/ ๊ธ์ฌ ยท p10 ยท income
keyboard_arrow_right๐ข ์ด์ ยท merchant/contains โ ์์
/ ์ด์ ยท p10 ยท income
keyboard_arrow_right๐ข ํ๊ธ ยท merchant/contains โ ์์
/ ํ๊ธ ยท p10 ยท income
keyboard_arrow_right๐ข KB์นด๋์ถ๊ธ ยท merchant/contains โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense
keyboard_arrow_right๐ข ๊ตญ๋ฏผ์นด๋ ยท raw_summary/equals โ ๊ธ์ต / ์นด๋๋๊ธ ยท p20 ยท expense
keyboard_arrow_right๐ข KTํต์ ยท merchant/contains โ ํต์ / ํต์ ์๊ธ ยท p30 ยท expense
keyboard_arrow_right๐ข SKํ
๋ ์ฝค ยท merchant/contains โ ํต์ / ํด๋ํฐ ยท p30 ยท expense
keyboard_arrow_right๐ข ์น๊ณผ ยท merchant/contains โ ์๋ฃ / ์น๊ณผ ยท p40 ยท expense
keyboard_arrow_right๐ข ์ฝ๊ตญ ยท merchant/contains โ ์๋ฃ / ์ฝ๊ตญ ยท p40 ยท expense
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋ณ์ ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ๋ณ์โ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_57235cbaebee
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ฐ์ธ๋ํ๊ต ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์ฐ์ธ๋ํ๊ตโ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_92542a0cfe34
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ํ์ ยท merchant/contains โ ๊ต์ก / ํ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โํ์โ
+
+
โ
+
+
THEN
+
๊ต์ก / ํ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_a0b6f3b76fb8
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์์ ยท merchant/contains โ ์๋ฃ / ๋ณ์ ยท p40 ยท expense
+
+
IF
+
merchant contains โ์์โ
+
+
โ
+
+
THEN
+
์๋ฃ / ๋ณ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_e211ce51f193
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๊ต์ต์ ยท merchant/contains โ ๊ต์ก / ์์ฒด๋ฅ ยท p40 ยท expense
+
+
IF
+
merchant contains โ๊ต์ต์โ
+
+
โ
+
+
THEN
+
๊ต์ก / ์์ฒด๋ฅ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p40
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_f5ac747ee372
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ด๋งํธ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ด๋งํธโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_0d931666810c
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋งค๋จธ๋ ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ๋งค๋จธ๋โ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_28f573731c8f
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ปคํผ ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ปคํผโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_48e3a7933a41
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค์์์ค๋ง์ผ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ค์์์ค๋ง์ผโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_6a91fe915bf9
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ๋ฉ๊ฐMGC ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ๋ฉ๊ฐMGCโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_7190bc920d0b
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์คํ๋ฒ
์ค ยท merchant/contains โ ์๋น / ์นดํ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์คํ๋ฒ
์คโ
+
+
โ
+
+
THEN
+
์๋น / ์นดํ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_a63374ad1371
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ํ ยท merchant/contains โ ์๋น / ๋งํธ ยท p50 ยท expense
+
+
IF
+
merchant contains โ์ํโ
+
+
โ
+
+
THEN
+
์๋น / ๋งํธ
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p50
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_fd23a8b83ac2
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข KFC ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โKFCโ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_104037affb55
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค์ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์ค์โ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_a1c526b50bd3
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ง์ฅ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์ง์ฅโ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_b9066cf22874
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์๊ตญ์ ยท merchant/contains โ ์๋น / ์ธ์ ยท p60 ยท expense
+
+
IF
+
merchant contains โ์๊ตญ์โ
+
+
โ
+
+
THEN
+
์๋น / ์ธ์
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p60
+ ๊ฑฐ๋์ ํ: expense
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_fcf12c6d4fff
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ATM์
๊ธ ยท raw_summary/equals โ ์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋ ยท p80 ยท ์ ์ฒด
+
+
IF
+
raw_summary equals โATM์
๊ธโ
+
+
โ
+
+
THEN
+
์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p80
+ ๊ฑฐ๋์ ํ: ์ ์ฒด
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_0df2c802bf63
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
keyboard_arrow_right๐ข ์ค๋งํธ์ถ๊ธ ยท raw_summary/equals โ ์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋ ยท p80 ยท ์ ์ฒด
+
+
IF
+
raw_summary equals โ์ค๋งํธ์ถ๊ธโ
+
+
โ
+
+
THEN
+
์ด์ฒด / ํ๊ธ/๊ณ์ข์ด๋
+
+
+
+ ์ํ: ํ์ฑ
+ ์ฐ์ ์์: p80
+ ๊ฑฐ๋์ ํ: ์ ์ฒด
+ ์กฐ๊ฑด: ์ถ๊ฐ ์กฐ๊ฑด ์์
+ person: -
+ ๊ณ ์ ๋น: ์๋์ค
+ ID: rule_9644817a4d34
+
+
๋ฉ๋ชจ: default: initial Jarca ledger categorization
์ญ์ ๋ณด๋ค ๋นํ์ฑํ๋ฅผ ๊ถ์ฅํฉ๋๋ค.
๊ท์น ์ถ๊ฐ๋ ์ ๋ชฉ ์ค๋ฅธ์ชฝ ์ถ๊ฐ ๋ฒํผ, ๊ธฐ์กด ๊ท์น ์์ ์ ๊ฐ ํญ๋ชฉ์ โฎ โ ๋ณ๊ฒฝ ํ์
์ ์ฌ์ฉํ์ธ์.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.png
new file mode 100644
index 0000000..3307371
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/rules.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.dom.html
new file mode 100644
index 0000000..e9e6b98
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ Transfer Review
+
KBโํ ์ค๋ฑ
ํฌ ํ์ธํ์ ํ
+
๋ ์งยท๊ธ์กยท์๋ ๊ธฐ์ค์ผ๋ก ๊ณ์ข ๊ฐ ์ด๋ ํ๋ณด๋ฅผ ๋์กฐํ๊ณ , ๋๋ผ์ด๋ฐ ํ ์์ ํ๊ฒ ๋งค์นญํฉ๋๋ค.
+
๋๋ผ์ด๋ฐ ์ฐ์ ๋ณธ์ธ๊ณ์ข์ด๋์๋ ๊ฒํ
+
๊ฑฐ๋ ID: tx_3ee6a6e14179
2026-06-29 ยท SYNTHETIC_TRANSFER ยท 120,000์
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.png
new file mode 100644
index 0000000..e3e8b73
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-195632/transfer-review.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/manifest.json b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/manifest.json
new file mode 100644
index 0000000..02a4ed0
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/manifest.json
@@ -0,0 +1,44 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18501",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603",
+ "fixture": {
+ "account_id": "acct_75c4cbc5a305",
+ "transaction_ids": [
+ "tx_2123d9b9c1f5",
+ "tx_a13db72f4885",
+ "tx_3edaa32c38ae",
+ "tx_fe176567300b",
+ "tx_d674d4ddbf1e"
+ ],
+ "db_path": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "์์ฝ",
+ "url": "http://127.0.0.1:18501/?page=%EC%9A%94%EC%95%BD",
+ "screenshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.png",
+ "screenshot_bytes": 300562,
+ "snapshot": "docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.dom.html",
+ "snapshot_bytes": 33871,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\n\n์๋ก๊ณ ์นจ\n\nStop\nDeploy\n๐ ์์ฝ\n๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋\n\n์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.\n\nschema v8\nKST ์ด๋ฒ ๋ฌ\nlast_balance ๊ธฐ์ค ์์ฐ\n\nDB: /root/projects/jarca-ledger-mcp/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/fixture.sqlite3\n\n๐\n์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ\n\n๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ\n\n๐ฆ\n์์ฐ ์ ๋์ฑ ํํฉ\n\n๊ธฐ์ค: accounts.liquidity_bucket"
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18501",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.dom.html b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.dom.html
new file mode 100644
index 0000000..94c7933
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ ์์ฝ
+
๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋
+
์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.
+
schema v8KST ์ด๋ฒ ๋ฌlast_balance ๊ธฐ์ค ์์ฐ
+
DB: /root/projects/jarca-ledger-mcp/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/fixture.sqlite3
๐์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ
๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ
๐ฆ์์ฐ ์ ๋์ฑ ํํฉ
๊ธฐ์ค: accounts.liquidity_bucket + accounts.last_balance. ๊ฑฐ๋ ํฉ๊ณ๋ก ๋น๊ฑฐ๋ ์์ฐ์ ์ถ์ ํ์ง ์์ต๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.png b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.png
new file mode 100644
index 0000000..a130358
Binary files /dev/null and b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260630-071603/overview.png differ
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/implementation.md b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/implementation.md
new file mode 100644
index 0000000..26d71ae
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/implementation.md
@@ -0,0 +1,33 @@
+# BAT-004 Implementation
+
+## ์ฝ๋ ๋ณ๊ฒฝ
+
+- `scripts/streamlit_browser_evidence.py`
+ - synthetic fixture DB ์์ฑ.
+ - ์ด์ DB ๊ธฐ๋ณธ ๊ฒฝ๋ก ์ฌ์ฉ ๊ฑฐ๋ถ ์์ ์ฅ์น.
+ - Streamlit local server ๊ธฐ๋ ๋ฐ HTTP 200 readiness ํ์ธ.
+ - `/usr/bin/google-chrome-stable` headless Chrome DevTools Protocol ์ฐ๊ฒฐ.
+ - page๋ณ DOM ํ
์คํธ ๋ก๋ ํ์ธ ํ PNG screenshot ๋ฐ DOM snapshot ์ ์ฅ.
+ - manifest JSON์ evidence path/byte size/fixture DB/command ์ ๋ณด๋ฅผ ๊ธฐ๋ก.
+- `dashboard/app.py`
+ - Import page์ import batch ์กฐํ์ rollback ์คํ์ด ํ์ฌ dashboard `db_path`๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
+ - ์ด ์์ ์ browser evidence fixture DB ์์น์ ์งํค๊ธฐ ์ํ ์์ ๋ณด์์ด๋ฉฐ ์ด์ DB ๋์๋ฅผ ๋ง๋๋ค.
+
+## ๋ฌธ์ ๋ณ๊ฒฝ
+
+- `docs/srs/Testing/req-004_browser-streamlit-e2e-evidence-recovery.md`
+- `docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/`
+- `docs/runbooks/streamlit-browser-evidence.md`
+- `docs/runtime-flows.md`
+- `docs/interface-contract.md`
+- `docs/project-structure.md`
+- `docs/srs/index.md`
+- `docs/batches/index.md`
+- source Discovery์ ์์ฑ๋ REQ ์ฐธ์กฐ
+
+## ์ด์ ์์
+
+- evidence script๋ `~/.local/share/jarca/ledger.sqlite3`๋ฅผ fixture DB๋ก ์ฌ์ฉํ๋ ๊ฒ์ ๊ฑฐ๋ถํ๋ค.
+- fixture์๋ `SYNTHETIC_*` ๊ฐ์ง ๊ณ์ข/๊ฑฐ๋/์๋๋ช
๋ง ์ฌ์ฉํ๋ค.
+- ์๋ณธ ๊ธ์ต ํ์ผ, ์ค์ ๊ณ์ข๋ฒํธ, ๋น๋ฐ๋ฒํธ, raw_payload๋ screenshot/log/docs์ ๋จ๊ธฐ์ง ์๋๋ค.
+- CI ํตํฉ์ ์ด๋ฒ batch ๋ฒ์๊ฐ ์๋๋ฉฐ, ๋ก์ปฌ ์ฌํ ์ ์ฐจ๋ง ์ ๊ณตํ๋ค.
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/index.md b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/index.md
new file mode 100644
index 0000000..584ff92
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/index.md
@@ -0,0 +1,24 @@
+# BAT-004 Browser/Streamlit E2E evidence ํ๊ฒฝ ๋ณต๊ตฌ
+
+- Profile: batch-lite
+- Status: release-candidate
+- Included REQ: REQ-004
+- Discovery: dcy-004
+- Owner: ๋ ์ ์ฅ์ธ
+- Date (KST): 2026-06-29
+
+## ๋ชฉํ
+
+Streamlit UI batch ๊ฒ์ฆ์์ AppTest/HTTP 200 ๋์ฒด ์ฆ๊ฑฐ์ ๋จธ๋ฌผ์ง ์๋๋ก, synthetic DB์ `/usr/bin/google-chrome-stable` ๊ธฐ๋ฐ ๋ก์ปฌ browser screenshot/snapshot evidence ์ ์ฐจ๋ฅผ ๋ณต๊ตฌํ๋ค.
+
+## ์ฐ์ถ๋ฌผ
+
+- `scripts/streamlit_browser_evidence.py` ๋ก์ปฌ evidence ์์ง ์คํฌ๋ฆฝํธ
+- `docs/runbooks/streamlit-browser-evidence.md` ์ด์ runbook
+- REQ-004 ๋ฐ BAT-004 planning/implementation/verification ๋ฌธ์
+- runtime/interface/project-structure ๋ฌธ์์ evidence gate ๊ธฐ์ค ๊ฐฑ์
+- synthetic DB ๊ธฐ๋ฐ browser screenshot/snapshot evidence
+
+## ๋ฒ์ ์ ํ
+
+CI ํตํฉ, Playwright/Selenium ๋์
, pixel-diff visual regression, Streamlit ์ ๊ท ํ๋ฉด ๊ธฐ๋ฅ ๊ฐ๋ฐ์ ํฌํจํ์ง ์๋๋ค.
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/planning.md b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/planning.md
new file mode 100644
index 0000000..a6cadbc
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/planning.md
@@ -0,0 +1,31 @@
+# BAT-004 Planning
+
+## ๊ตฌํ ์ ๋ต
+
+1. `REQ-004`๋ฅผ Testing ์๊ตฌ์ฌํญ์ผ๋ก ์ ๋ฆฌํ๋ค.
+2. ์ด์ DB์ ์ค์ ๊ธ์ต ํ์ผ์ ๋ฐฐ์ ํ synthetic fixture DB๋ฅผ evidence script๊ฐ ์ง์ ์์ฑํ๋ค.
+3. Streamlit์ `JARCA_LEDGER_DB_PATH=
`๋ก ๊ธฐ๋ํ๋ค.
+4. `/usr/bin/google-chrome-stable` headless + Chrome DevTools Protocol๋ก ๋์ ํ์ด์ง๋ฅผ ์ด๊ณ , ํ๋ฉด ํ
์คํธ ๋ก๋ ์๋ฃ๋ฅผ ํ์ธํ ๋ค screenshot๊ณผ DOM snapshot์ ์ ์ฅํ๋ค.
+5. UI/dashboard ์ํฅ batch์ QC gate ๊ธฐ์ค์ ๋ฌธ์ํํ๋ค.
+
+## Design Gate
+
+- Profile: `batch-lite`
+- ํ๋จ ๊ทผ๊ฑฐ: ์ ๊ท ๋๋ฉ์ธ ๊ธฐ๋ฅ์ด๋ DB schema ๋ณ๊ฒฝ ์์ด ๊ฒ์ฆ ์คํฌ๋ฆฝํธ์ ๋ฌธ์ ๊ธฐ์ค์ ์ถ๊ฐํ๋ค.
+- ์ค๊ณ ๋ฌธ์ ๋ณ๋ ์์ฑ ์ฌ๋ถ: ๋ณ๋ `design.md`๋ ๋ง๋ค์ง ์๋๋ค. ๊ตฌํ ๊ฒฐ์ ์ ์ด planning ๋ฐ REQ-004์ ์ถฉ๋ถํ ๊ตญ์์ ์ผ๋ก ๊ธฐ๋กํ๋ค.
+
+## Evidence Gate ๊ธฐ์ค
+
+| Batch ์ ํ | ์ต์ evidence | Gate ํ์ |
+| --- | --- | --- |
+| Streamlit UI ๋๋ `dashboard/app.py` ์ํฅ | Browser screenshot + DOM snapshot + manifest | ๋๋ฝ ์ BLOCK ๋๋ ์ฌ๊ฒ์ฆ |
+| ๋น-UI service/MCP/parser ๋ณ๊ฒฝ | pytest/py_compile ์ค์ฌ, browser evidence ์ ํ | browser ๋๋ฝ์ ๋น์ฐจ๋จ |
+| docs-only ๋ณ๊ฒฝ | ๋ฌธ์ diff/review | browser evidence ๋ถํ์ |
+| Chrome ํ๊ฒฝ ์ฅ์ ๊ฐ ์
์ฆ๋ UI ๋ณ๊ฒฝ | AppTest + HTTP 200 + ์คํจ ์์ธ ๋ก๊ทธ | `PASS_WITH_NOTES`๋ง ํ์ฉ |
+
+## ๋ฆฌ์คํฌ์ ์ํ
+
+- Chrome/DevTools ๊ธฐ๋ ์คํจ: ์คํ ์ Chrome ๊ฒฝ๋ก๋ฅผ ๊ฒ์ฆํ๊ณ ๋ช
ํํ ์ค๋ฅ๋ฅผ ์ถ๋ ฅํ๋ค.
+- Streamlit ๋ ๋๋ง ์ง์ฐ: HTTP 200 ๋๊ธฐ ํ CDP์์ page ์ ๋ชฉ ํ
์คํธ๊ฐ ์ค์ DOM์ ๋ํ๋ ๋๊น์ง ๋๊ธฐํ๋ค.
+- ๋ฏผ๊ฐ์ ๋ณด ๋
ธ์ถ: script๊ฐ ๊ธฐ๋ณธ ์ด์ DB ๊ฒฝ๋ก๋ฅผ ๊ฑฐ๋ถํ๊ณ synthetic fixture๋ง ์์ฑํ๋ค.
+- dependency ์ฆ๊ฐ: Playwright/Selenium์ ์ถ๊ฐํ์ง ์๊ณ ํ์ฌ dashboard extra์ ์ด๋ฏธ ์กด์ฌํ๋ `websockets`๋ฅผ ์ฌ์ฉํ๋ค.
diff --git a/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/verification.md b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/verification.md
new file mode 100644
index 0000000..6c53948
--- /dev/null
+++ b/docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/verification.md
@@ -0,0 +1,43 @@
+# BAT-004 Verification
+
+## ๊ฒ์ฆ ๊ณํ
+
+1. Python ๋ฌธ๋ฒ ๊ฒ์ฆ
+ - `.venv/bin/python -m py_compile scripts/streamlit_browser_evidence.py dashboard/app.py`
+2. ๋์ browser smoke
+ - `.venv/bin/python scripts/streamlit_browser_evidence.py`
+ - ํ์ธ ๋์: `๋ถ๋ฅ ๊ท์น`, `ํ์ธํ์ ํ`, `Import`
+3. ์ ์ฒด ํ
์คํธ
+ - `.venv/bin/python -m pytest -q`
+4. evidence ๋ฏผ๊ฐ์ ๋ณด ๊ฒํ
+ - manifest์ DOM snapshot์๋ synthetic fixture ์๋ณ์๋ง ํฌํจ๋์ด์ผ ํ๋ค.
+ - ์ด์ DB ๊ธฐ๋ณธ ๊ฒฝ๋ก, ์๋ณธ ๊ธ์ต ํ์ผ๋ช
, ๋น๋ฐ๋ฒํธ, ์ค์ raw_payload๋ ํฌํจํ์ง ์๋๋ค.
+
+## Acceptance Mapping
+
+- AC-1: `scripts/streamlit_browser_evidence.py`์ `assert_synthetic_db_path` ๋ฐ fixture ์์ฑ ๋ก์ง.
+- AC-2: py_compile ๊ฒฐ๊ณผ.
+- AC-3: full pytest ๊ฒฐ๊ณผ.
+- AC-4/5: browser evidence script manifest ๋ฐ screenshot/snapshot ํ์ผ.
+- AC-6: `dashboard/app.py` Import page์ `db_path` ์ ๋ฌ ์์ .
+- AC-7: REQ/BAT/runtime/interface/project-structure/runbook ๋ฌธ์ ๊ฐฑ์ .
+
+## ๊ฒฐ๊ณผ
+
+์ต์ข
๊ฒ์ฆ ๊ฒฐ๊ณผ(2026-06-29 KST):
+
+- `.venv/bin/python -m py_compile scripts/streamlit_browser_evidence.py dashboard/app.py jarca_ledger_mcp/db.py jarca_ledger_mcp/service.py jarca_ledger_mcp/server.py tests/test_service.py` โ PASS.
+- `.venv/bin/python -m pytest -ra` โ PASS (`27 passed, 4 skipped in 27.37s`).
+- `.venv/bin/python scripts/streamlit_browser_evidence.py` โ PASS.
+ - Evidence directory: `docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-20260629-192835/`
+ - Manifest: `manifest.json` (`success: true`).
+ - Captured pages:
+ - `๋ถ๋ฅ ๊ท์น`: `rules.png` 364,876 bytes, `rules.dom.html` 174,470 bytes.
+ - `ํ์ธํ์ ํ`: `transfer-review.png` 275,792 bytes, `transfer-review.dom.html` 25,551 bytes.
+ - `Import`: `import.png` 297,424 bytes, `import.dom.html` 23,649 bytes.
+
+## Sensitive Evidence Review
+
+- Evidence DB path is the generated fixture path under BAT-004 evidence, not the operating DB default path.
+- Fixture rows use `SYNTHETIC_*` labels and do not include real account numbers, real financial source filenames, passwords, or raw payloads.
+- DOM/screenshot evidence was generated from synthetic fixture data only.
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/manifest.json b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/manifest.json
new file mode 100644
index 0000000..c7f06b1
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/manifest.json
@@ -0,0 +1,44 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18501",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111",
+ "fixture": {
+ "account_id": "acct_6cf9a9db8669",
+ "transaction_ids": [
+ "tx_f60380340545",
+ "tx_8c16f1387fb1",
+ "tx_445884ce8638",
+ "tx_ead36edf0c7e",
+ "tx_dc27408a7e81"
+ ],
+ "db_path": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "์์ฝ",
+ "url": "http://127.0.0.1:18501/?page=%EC%9A%94%EC%95%BD",
+ "screenshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.png",
+ "screenshot_bytes": 303341,
+ "snapshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.dom.html",
+ "snapshot_bytes": 30757,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\nStop\nDeploy\n๐ ์์ฝ\n๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋\n\n์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.\n\nschema v8\nKST ์ด๋ฒ ๋ฌ\nlast_balance ๊ธฐ์ค ์์ฐ\n\nDB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/fixture.sqlite3\n\n๐\n์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ\n\n๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ\n\n๐ฆ\n์์ฐ ์ ๋์ฑ ํํฉ\n\n๊ธฐ์ค: accounts.liqu"
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18501",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.dom.html b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.dom.html
new file mode 100644
index 0000000..dab7e83
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+ Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ ์์ฝ
+
๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋
+
์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.
+
schema v8KST ์ด๋ฒ ๋ฌlast_balance ๊ธฐ์ค ์์ฐ
+
DB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/fixture.sqlite3
๐์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ
๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ
๐ฆ์์ฐ ์ ๋์ฑ ํํฉ
๊ธฐ์ค: accounts.liquidity_bucket + accounts.last_balance. ๊ฑฐ๋ ํฉ๊ณ๋ก ๋น๊ฑฐ๋ ์์ฐ์ ์ถ์ ํ์ง ์์ต๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.png b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.png
new file mode 100644
index 0000000..ba5b7de
Binary files /dev/null and b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/overview.png differ
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/manifest.json b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/manifest.json
new file mode 100644
index 0000000..c6d3cf3
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/manifest.json
@@ -0,0 +1,44 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18503",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738",
+ "fixture": {
+ "account_id": "acct_d3b8cccfccb4",
+ "transaction_ids": [
+ "tx_4b8447f7aa07",
+ "tx_b2ca8a05ac82",
+ "tx_8c989e7c3b15",
+ "tx_f48e63972a9e",
+ "tx_4e20d08f8df3"
+ ],
+ "db_path": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "์์ฝ",
+ "url": "http://127.0.0.1:18503/?page=%EC%9A%94%EC%95%BD",
+ "screenshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.png",
+ "screenshot_bytes": 301342,
+ "snapshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.dom.html",
+ "snapshot_bytes": 29881,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\nStop\nDeploy\n๐ ์์ฝ\n๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋\n\n์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.\n\nschema v8\nKST ์ด๋ฒ ๋ฌ\nlast_balance ๊ธฐ์ค ์์ฐ\n\nDB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/fixture.sqlite3\n\n๐\n์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ\n\n๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ\n\n๐ฆ\n์์ฐ ์ ๋์ฑ ํํฉ\n\n๊ธฐ์ค: accou"
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18503",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.dom.html b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.dom.html
new file mode 100644
index 0000000..bc100c1
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+ Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ ์์ฝ
+
๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋
+
์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.
+
schema v8KST ์ด๋ฒ ๋ฌlast_balance ๊ธฐ์ค ์์ฐ
+
DB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/fixture.sqlite3
๐์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ
๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ
๐ฆ์์ฐ ์ ๋์ฑ ํํฉ
๊ธฐ์ค: accounts.liquidity_bucket + accounts.last_balance. ๊ฑฐ๋ ํฉ๊ณ๋ก ๋น๊ฑฐ๋ ์์ฐ์ ์ถ์ ํ์ง ์์ต๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.png b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.png
new file mode 100644
index 0000000..e8e1627
Binary files /dev/null and b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-check-20260630-073738/overview.png differ
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/manifest.json b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/manifest.json
new file mode 100644
index 0000000..d35e275
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/manifest.json
@@ -0,0 +1,44 @@
+{
+ "success": true,
+ "base_url": "http://127.0.0.1:18501",
+ "output_dir": "/root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958",
+ "fixture": {
+ "account_id": "acct_86182dd8b283",
+ "transaction_ids": [
+ "tx_6e7d47621557",
+ "tx_546ed7a78004",
+ "tx_f40ec3aa2df0",
+ "tx_3207e5cdde0d",
+ "tx_fa79d639eda4"
+ ],
+ "db_path": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/fixture.sqlite3"
+ },
+ "chrome": "/usr/bin/google-chrome-stable",
+ "streamlit": ".venv/bin/streamlit",
+ "pages": [
+ {
+ "page": "์์ฝ",
+ "url": "http://127.0.0.1:18501/?page=%EC%9A%94%EC%95%BD",
+ "screenshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.png",
+ "screenshot_bytes": 303039,
+ "snapshot": "docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.dom.html",
+ "snapshot_bytes": 32797,
+ "devtools_port": 19222,
+ "inner_text_head": "๐ฐ Jarca Ledger\n\n๊ฐ๊ณ๋ถ ยท ๋ถ๋ฅ ยท Import ์ด์ ์ฝ์\n\n๋ฉ๋ด\n์์ฝ\n๊ฑฐ๋๋ด์ญ\nํ์ธํ์ ํ\n๋ฏธ๋ถ๋ฅ\n๋ถ๋ฅ ๊ท์น\n์นดํ
๊ณ ๋ฆฌ\n๊ณ์ข\nImport\n\n์๋ก๊ณ ์นจ\n\nStop\nDeploy\n๐ ์์ฝ\n๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋\n\n์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.\n\nschema v8\nKST ์ด๋ฒ ๋ฌ\nlast_balance ๊ธฐ์ค ์์ฐ\n\nDB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/fixture.sqlite3\n\n๐\n์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ\n\n๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ\n\n๐ฆ\n์์ฐ ์ ๋์ฑ ํํฉ\n\n๊ธฐ"
+ }
+ ],
+ "server_command": [
+ ".venv/bin/streamlit",
+ "run",
+ "dashboard/app.py",
+ "--server.address",
+ "127.0.0.1",
+ "--server.port",
+ "18501",
+ "--server.headless",
+ "true",
+ "--browser.gatherUsageStats",
+ "false"
+ ],
+ "server_log_tail": ""
+}
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.dom.html b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.dom.html
new file mode 100644
index 0000000..272ae9c
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.dom.html
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+ Jarca Ledger
+
+
+
+
+
+
+
+
+
+
๐ ์์ฝ
+
๊ฐ๊ณ๋ถ ์ด์ ๋์๋ณด๋
+
์ด๋ฒ ๋ฌ ์ค์ ํ๊ธํ๋ฆ, ์์ฐ ์ ๋์ฑ, ์ด์ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์๋๋ก ํ์ธํฉ๋๋ค.
+
schema v8KST ์ด๋ฒ ๋ฌlast_balance ๊ธฐ์ค ์์ฐ
+
DB: /root/projects/jarca-ledger-mcp/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/fixture.sqlite3
๐์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ
๊ธฐ์ค: KST ํ์ฌ ์ 2026-06, transactions.transaction_date, ledger_categories.cashflow_kind ์ค์ ํ๊ธํ๋ฆ
๐ฆ์์ฐ ์ ๋์ฑ ํํฉ
๊ธฐ์ค: accounts.liquidity_bucket + accounts.last_balance. ๊ฑฐ๋ ํฉ๊ณ๋ก ๋น๊ฑฐ๋ ์์ฐ์ ์ถ์ ํ์ง ์์ต๋๋ค.
+
+
+
\ No newline at end of file
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.png b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.png
new file mode 100644
index 0000000..f5dfa06
Binary files /dev/null and b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-verify-20260629-215958/overview.png differ
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/implementation.md b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/implementation.md
new file mode 100644
index 0000000..cc1ac26
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/implementation.md
@@ -0,0 +1,37 @@
+# BAT-005 Implementation
+
+## ์ฝ๋ ๋ณ๊ฒฝ
+
+- `jarca_ledger_mcp/db.py`
+ - schema version์ `8`๋ก ์ฌ๋ฆฌ๊ณ `accounts.liquidity_bucket TEXT NOT NULL DEFAULT 'other'`๋ฅผ schema ๋ฐ additive migration ๋ชฉ๋ก์ ์ถ๊ฐํ๋ค.
+- `jarca_ledger_mcp/service.py`
+ - `VALID_LIQUIDITY_BUCKETS`์ `normalize_liquidity_bucket` ๊ฒ์ฆ์ ์ถ๊ฐํ๋ค.
+ - `create_ledger_account`/`update_ledger_account`์์ `liquidity_bucket`์ ์ ์ฅยท์์ ํ๋ค.
+ - `get_asset_liquidity_report`๋ฅผ ์ถ๊ฐํด `accounts.last_balance` ๊ธฐ๋ฐ ๋ฒํท ํฉ๊ณ, ์ด์์ฐ, ์ด๋ถ์ฑ, ์์์ฐ, ๋ฏธ์
๋ ฅ ๊ณ์ข๋ฅผ ๋ฐํํ๋ค.
+- `jarca_ledger_mcp/server.py`
+ - MCP account create/update tool signature์ `liquidity_bucket`์ ์ถ๊ฐํ๊ณ `get_asset_liquidity_report` tool์ ๋
ธ์ถํ๋ค.
+- `dashboard/app.py`
+ - ์์ฝ ํ๋ฉด์ REQ-005 ์ด์ ํ๋ธ ์์๋ก ์ฌ๊ตฌ์ฑํ๋ค.
+ - KST ํ์ฌ ์ ๊ธฐ์ค ์๊ฐ ์ค์ ์์
/์ง์ถ/์ํ๊ธํ๋ฆ/์ ์ถ๋ฅ ์ ์ต์๋จ์ ํ์ํ๋ค.
+ - ์์ฐ ์ ๋์ฑ, ์์ฐ ๊ตฌ์ฑ, ์์ฐ/์ง์ถ, ์ด์ ๊ฒฝ๊ณ ์น์
์ ์ถ๊ฐํ๋ค.
+ - ๊ณ์ข ํ๋ฉด์ `liquidity_bucket` ํ์/์์ control์ ์ถ๊ฐํ๋ค.
+- `scripts/streamlit_browser_evidence.py`
+ - synthetic fixture์ `cash`, `investment`, `loan`, `real_estate` bucket ๊ณ์ข์ ์๊ฐ ์์
/์ง์ถ/์์ฐ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๋ค.
+- `tests/test_service.py`
+ - schema version, liquidity bucket ๊ฒ์ฆ, account flow, asset liquidity report regression์ ์ถ๊ฐํ๋ค.
+
+## ๋ฌธ์ ๋ณ๊ฒฝ
+
+- `docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/`
+- `docs/batches/index.md`
+- `docs/srs/Interface/req-005_dashboard-operations-hub-liquidity-monthly-cashflow.md`
+- `docs/data-model.md`
+- `docs/project-structure.md`
+- `docs/runtime-flows.md`
+- `docs/interface-contract.md`
+
+## ์ด์ ์์
+
+- ์ด์ DB์ ์ค์ ๊ธ์ต ์๋ณธ ํ์ผ์ ์ฌ์ฉํ์ง ์๋๋ค.
+- browser evidence๋ synthetic fixture DB๋ก๋ง ์์ฑํ๋ค.
+- migration์ additive/idempotent์ด๋ฉฐ ๊ธฐ์กด ๊ณ์ข์ bucket์ `other` ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์์ ํ๊ฒ ์ฑ์์ง๋ค.
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/index.md b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/index.md
new file mode 100644
index 0000000..f301cbe
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/index.md
@@ -0,0 +1,25 @@
+# BAT-005 Dashboard operations hub/liquidity/monthly cashflow
+
+- Profile: standard
+- Status: release-candidate
+- Included REQ: REQ-005
+- Discovery: dcy-005
+- Owner: ๋ ์ ์ฅ์ธ
+- Date (KST): 2026-06-29
+
+## ๋ชฉํ
+
+REQ-005์ ๋ฐ๋ผ Streamlit ์์ฝ ํ๋ฉด์ ์์ค ์ด์ ํ๋ธ๋ก ์ฌ๊ตฌ์ฑํ๊ณ , ๊ณ์ข ๋จ์ `liquidity_bucket`๊ณผ `last_balance`๋ฅผ ๊ถ์ ์์ค๋ก ํ๋ ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ/๋ถ์ฑ/์์์ฐ ๋ทฐ๋ฅผ ์ ๊ณตํ๋ค.
+
+## ์ฐ์ถ๋ฌผ
+
+- `accounts.liquidity_bucket` additive/idempotent schema migration
+- ๊ณ์ข create/list/update ๋ฐ MCP tool signature์ `liquidity_bucket` ์ง์
+- `service.get_asset_liquidity_report` ์์ฐ ์ ๋์ฑ/์์์ฐ report
+- `dashboard/app.py` ์์ฝ ์ด์ ํ๋ธ ๋ฐ ๊ณ์ข liquidity bucket ํธ์ง UI
+- BAT-005 planning/implementation/verification ๋ฌธ์์ baseline ๋ฌธ์ ๊ฐฑ์
+- synthetic/validation DB ๊ธฐ๋ฐ Streamlit browser evidence
+
+## ๋ฒ์ ์ ํ
+
+์ค์๊ฐ ๊ธ์ต ์ฐ๋, ์๋ ์์ธ ํ๊ฐ, ๋ณ๋ `asset_snapshots`/`manual_assets` ํ
์ด๋ธ, ๊ฑฐ๋/์นดํ
๊ณ ๋ฆฌ ๋จ์ liquidity override, dcy-006 ๋ฒ์๋ ํฌํจํ์ง ์๋๋ค.
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/planning.md b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/planning.md
new file mode 100644
index 0000000..d8b3f63
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/planning.md
@@ -0,0 +1,30 @@
+# BAT-005 Planning
+
+## ๊ตฌํ ์ ๋ต
+
+1. REQ-005์ ์น์ธ ์ํ๋ฅผ ์
๋ ฅ gate๋ก ํ์ธํ๊ณ BAT-005 batch ๋ฌธ์๋ฅผ ์์ฑํ๋ค.
+2. `accounts.liquidity_bucket`์ additive/idempotent column์ผ๋ก ์ถ๊ฐํ๋ค. ํ์ฉ๊ฐ์ `cash`, `investment`, `deposit`, `real_estate`, `loan`, `other`์ด๋ฉฐ ๊ธฐ์กด/๋ฏธ์ง์ ๊ณ์ข๋ `other`๋ก ํด๋ฐฑํ๋ค.
+3. ์๋น์ค ๊ณ์ธต์์ create/list/update ๊ณ์ข ํ๋ฆ์ `liquidity_bucket`์ ๋
ธ์ถํ๊ณ ํ์ฉ๊ฐ ๊ฒ์ฆ์ ์ถ๊ฐํ๋ค.
+4. ์์ฐ ์ ๋์ฑ report๋ `accounts.last_balance`๋ง ์ฌ์ฉํ๋ค. `last_balance`๊ฐ ์๋ ๋น๊ฑฐ๋ ์์ฐ/๋ถ์ฑ๋ ์์ ์ถ์ ํ์ง ์๊ณ missing warning์ผ๋ก ๋จ๊ธด๋ค.
+5. `page_overview`๋ ๋ค์ ์์๋ก ์ฌ๊ตฌ์ฑํ๋ค: ์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ โ ์์ฐ ์ ๋์ฑ ํํฉ โ ์์ฐ ๊ตฌ์ฑ โ ์ง์ถ ๊ตฌ์กฐ/์์ฐ โ ์ด์ ๊ฒฝ๊ณ .
+6. `scripts/streamlit_browser_evidence.py` synthetic fixture์ overview ๊ฒ์ฆ์ฉ ์์ฐ/๋ถ์ฑ/์์ฐ/์๊ฐ ํ๊ธํ๋ฆ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๊ณ , BAT-005 evidence output directory๋ก ์คํํ๋ค.
+
+## Design Gate
+
+- Profile: `standard`
+- ํ๋จ ๊ทผ๊ฑฐ: DB schema, service/MCP contract, Streamlit UI, tests, baseline docs๊ฐ ํจ๊ป ๋ณํ๋ ๊ธฐ๋ฅ batch๋ค.
+- ๋ณ๋ `design.md`: planning์ ์ค๊ณ ๊ฒฐ์ ์ ํฌํจํ๊ณ , ๊ตฌํ ์์ธ๋ `implementation.md`์์ ์ถ์ ํ๋ค.
+
+## ํต์ฌ ์ค๊ณ ๊ฒฐ์
+
+- ์ ๋์ฑ ๊ถ์ ์์ค๋ ๊ณ์ข ๋จ์ `accounts.liquidity_bucket`์ผ๋ก ๊ณ ์ ํ๋ค.
+- ์์ฐ ํ๊ฐ๋ `accounts.last_balance`๋ฅผ ์ฌ์ฉํ๋ฉฐ, ๊ฑฐ๋ ํฉ๊ณ๋ก ๋ถ๋์ฐ/์์ ๊ธ/๋์ถ ํ์ฌ๊ฐ๋ฅผ ์ถ์ ํ์ง ์๋๋ค.
+- `loan` ๋ฒํท์ ์์ฐ ๊ตฌ์ฑ ๋น์จ ๋ถ๋ชจ์์ ์ ์ธํ๊ณ ์ด๋ถ์ฑ/์์์ฐ ๊ณ์ฐ์๋ง ๋ฐ์ํ๋ค.
+- `last_balance`๊ฐ ์๋ ๊ณ์ข๋ ๊ณ์ฐ์์ ์ ์ธํ๋ ์ด์ ๊ฒฝ๊ณ ์ ํ์ํ๋ค.
+
+## ๊ฒ์ฆ ๊ณํ
+
+- `py_compile` for changed Python files
+- `pytest tests/test_service.py -q`
+- full `pytest -q`
+- `scripts/streamlit_browser_evidence.py --page ์์ฝ --output-dir docs/batches/bat-005_.../evidence/run-...` with synthetic DB
diff --git a/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/verification.md b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/verification.md
new file mode 100644
index 0000000..8f813d7
--- /dev/null
+++ b/docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/verification.md
@@ -0,0 +1,44 @@
+# BAT-005 Verification
+
+## ๊ฒ์ฆ ๊ณํ
+
+1. Python ๋ฌธ๋ฒ ๊ฒ์ฆ
+ - `.venv/bin/python -m py_compile jarca_ledger_mcp/db.py jarca_ledger_mcp/service.py jarca_ledger_mcp/server.py dashboard/app.py scripts/streamlit_browser_evidence.py tests/test_service.py`
+2. ๋์ service regression
+ - `.venv/bin/python -m pytest tests/test_service.py -q`
+3. ์ ์ฒด ํ
์คํธ
+ - `.venv/bin/python -m pytest -q`
+4. Streamlit browser evidence
+ - `.venv/bin/python scripts/streamlit_browser_evidence.py --page ์์ฝ --output-dir docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-`
+5. ๋ฏผ๊ฐ์ ๋ณด ๊ฒํ
+ - evidence fixture๋ synthetic ๋ฐ์ดํฐ๋ง ํฌํจํ๋ค.
+ - ์ด์ DB ๊ธฐ๋ณธ ๊ฒฝ๋ก, ์ค์ ๊ธ์ต ์๋ณธ ํ์ผ๋ช
, ๋น๋ฐ๋ฒํธ, raw_payload๋ ํฌํจํ์ง ์๋๋ค.
+
+## Acceptance Mapping
+
+- AC-1: `db.py` schema/migration ๋ฐ schema version 8.
+- AC-2: `normalize_liquidity_bucket`, create/update validation tests.
+- AC-3: account create/list/update regression tests ๋ฐ MCP signature.
+- AC-4: `dashboard/app.py` top section ์๊ฐ cashflow metrics.
+- AC-5/6/7: `get_asset_liquidity_report` regression ๋ฐ overview asset/liability/missing balance sections.
+- AC-8: overview operational warning metrics/caption.
+- AC-9: pytest ๋ฐ browser evidence result.
+- AC-10: baseline docs updated.
+
+## ๊ฒฐ๊ณผ
+
+์ต์ข
๊ฒ์ฆ ๊ฒฐ๊ณผ(2026-06-29 KST):
+
+- `source .venv/bin/activate && python -m py_compile jarca_ledger_mcp/db.py jarca_ledger_mcp/service.py jarca_ledger_mcp/server.py dashboard/app.py scripts/streamlit_browser_evidence.py tests/test_service.py tests/test_ledger_categories.py` โ PASS.
+- `source .venv/bin/activate && pytest tests/test_service.py -q` โ PASS (`9 passed`).
+- `source .venv/bin/activate && pytest -q` โ PASS (`31 passed, 4 skipped`).
+- `source .venv/bin/activate && python scripts/streamlit_browser_evidence.py --page ์์ฝ --output-dir docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111` โ PASS.
+ - Evidence directory: `docs/batches/bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/evidence/run-20260629-2111/`
+ - Manifest: `manifest.json` (`success: true`).
+ - Captured page: `์์ฝ` / `overview.png` 303,341 bytes / `overview.dom.html` 30,757 bytes.
+ - DOM evidence includes `schema v8`, `KST ์ด๋ฒ ๋ฌ`, `์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ`, and `์์ฐ ์ ๋์ฑ ํํฉ` from a synthetic fixture DB.
+
+## Sensitive Evidence Review
+
+- Evidence DB path is the generated fixture path under BAT-005 evidence, not the operating DB default path.
+- Fixture rows use `Synthetic`/`SYNTHETIC_*` labels and do not include real account numbers, real financial source filenames, passwords, or raw payloads.
diff --git a/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/design.md b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/design.md
new file mode 100644
index 0000000..06a1f2b
--- /dev/null
+++ b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/design.md
@@ -0,0 +1,64 @@
+# BAT-006 Design
+
+## Architecture Summary
+
+BAT-006 introduces a CI safety layer around existing test and smoke assets. GitHub Actions becomes the external gate for PR/push validation, while CI wrapper scripts provide deterministic entrypoints for environment setup, synthetic fixture DB creation, Streamlit readiness checks, DB safety smoke invocation, timeout handling, cleanup, and sensitive-output control.
+
+The required CI gates are pytest, Streamlit AppTest, dashboard HTTP smoke, and DB safety smoke. Browser screenshot E2E remains a local evidence workflow from dcy-004 and is intentionally excluded from the required CI blocker set.
+
+## Changed Areas
+
+- `.github/workflows/`
+ - New workflow for `pull_request`, `push` to `main`, and `workflow_dispatch`.
+ - Required jobs should have stable names so branch protection can reference them later.
+- `scripts/`
+ - New or adapted CI wrapper entrypoint for dashboard HTTP smoke.
+ - New or adapted CI wrapper entrypoint for DB safety smoke using existing `ledger_restore_smoke.py` logic where appropriate.
+ - Wrapper logs must avoid dumping raw transactions, account numbers, `raw_payload`, source file names, passwords, or live DB paths.
+- `tests/`
+ - Existing pytest and Streamlit AppTest assets are reused. Add fixture helpers only if needed to make CI deterministic.
+- `pyproject.toml` / dependency setup
+ - Confirm CI installs the extras required for pytest and dashboard/AppTest execution.
+- Docs
+ - `docs/project-structure.md`: CI workflow and wrapper responsibilities.
+ - `docs/runtime-flows.md`: PR/push -> CI jobs -> gate result flow and synthetic fixture DB boundary.
+ - `docs/runbooks/`: CI failure reproduction runbook if the workflow/wrapper commands need operator guidance.
+
+## Key Decisions
+
+1. CI platform is GitHub Actions, with `pull_request`, `push` to `main`, and optional `workflow_dispatch`.
+2. Required blocker gates are pytest, Streamlit AppTest, dashboard HTTP smoke, and DB safety smoke.
+3. HTTP smoke and DB safety smoke use CI-specific thin wrappers rather than ad-hoc shell snippets, so exit codes, logs, readiness, timeout, and cleanup are deterministic.
+4. Every CI run creates or uses synthetic fixture/temporary validation DBs. The operating DB default path is not used.
+5. Browser screenshot E2E is not a required CI gate. It remains a local/manual evidence path for UI-heavy changes.
+6. Branch protection enforcement is external GitHub repository configuration; the batch stabilizes workflow/check names and documents the required-check expectation.
+
+## CI Job Boundary
+
+- `pytest` gate:
+ - Install project with test dependencies.
+ - Run the repository pytest suite using the standard project command.
+ - Fail the workflow on any test failure.
+- `dashboard-app-test` gate:
+ - Prepare synthetic fixture DB.
+ - Run Streamlit AppTest coverage, especially `tests/test_dashboard_app.py`.
+ - Fail with test-level logs only.
+- `dashboard-http-smoke` gate:
+ - Prepare synthetic fixture DB in runner temp space.
+ - Start Streamlit dashboard with that DB and deterministic port policy.
+ - Poll HTTP readiness until 200 or timeout.
+ - Terminate the server and fail if cleanup fails materially.
+- `db-safety-smoke` gate:
+ - Prepare source/backup/target paths under runner temp space.
+ - Invoke DB restore/safety smoke without live DB paths.
+ - Fail if overwrite approval policy, integrity check, or fixture health check fails.
+
+## Sensitive Evidence Boundary
+
+CI output may include command names, job names, synthetic DB temp paths, test identifiers, exit status, row counts for synthetic fixtures, and high-level error messages. CI output must not include live account numbers, real balances, `raw_payload`, original financial file names, passwords, or full transaction row dumps.
+
+## Reference Doc Update Plan
+
+- Update `docs/project-structure.md` to replace the current "CI workflow ... not yet defined" baseline with the BAT-006 workflow and wrapper structure.
+- Update `docs/runtime-flows.md` with the CI validation flow and the local browser evidence boundary.
+- Add a `docs/runbooks/ci-smoke.md` or equivalent only if local reproduction commands are not already obvious from the workflow/wrappers.
diff --git a/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/implementation.md b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/implementation.md
new file mode 100644
index 0000000..8619d98
--- /dev/null
+++ b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/implementation.md
@@ -0,0 +1,106 @@
+# BAT-006 Implementation
+
+## Implementation Status
+- Status: completed-for-qc
+- Completed At (KST): 2026-06-30 15:23:00 KST
+- Scope: REQ-006 CI automation, synthetic fixture entrypoints, local CI smoke reproduction docs, and StagePilot evidence/state updates.
+- Boundary: REQ-006 is not marked Implemented yet; QC by `jarcaledgerqc` is the next gate.
+
+## Changed Files
+
+### CI workflow
+- `.github/workflows/ci.yml`
+ - Added GitHub Actions workflow triggers for `pull_request`, `push` to `main`, and `workflow_dispatch`.
+ - Added stable gate jobs/check names:
+ - `pytest-regression`
+ - `dashboard-app-test`
+ - `dashboard-http-smoke`
+ - `db-safety-smoke`
+ - Each job installs with `uv` and runs its narrow CI gate command.
+
+### CI fixture and smoke wrappers
+- `scripts/ci_fixture_db.py`
+ - Added deterministic synthetic fixture DB builder for CI/AppTest/smoke checks.
+ - Refuses the operating ledger DB default path and only allows CI fixture filenames.
+ - Seeds synthetic accounts, budget, and transactions, then verifies SQLite integrity.
+- `scripts/ci_dashboard_http_smoke.py`
+ - Added dashboard HTTP smoke wrapper.
+ - Creates a temp synthetic fixture DB, starts Streamlit on loopback, polls for HTTP 200 readiness, and terminates/kills the process during cleanup.
+ - Emits status-only JSON with fixture/readiness/cleanup result.
+- `scripts/ci_db_safety_smoke.py`
+ - Added DB safety wrapper.
+ - Creates temp synthetic source/backup/target DB paths, invokes `scripts/ledger_restore_smoke.py`, and refuses the operating DB path.
+ - Emits status-only JSON with restore/integrity/health result.
+
+### Tests
+- `tests/test_dashboard_app.py`
+ - Added Streamlit AppTest coverage that creates a synthetic fixture DB through `scripts/ci_fixture_db.py`, sets `JARCA_LEDGER_DB_PATH`, and checks core pages render without Streamlit exceptions.
+ - Existing transfer review helper tests remain covered in the dashboard AppTest command.
+
+### Documentation
+- `docs/project-structure.md`
+ - Documented `.github/workflows/ci.yml`, CI fixture builder, HTTP smoke wrapper, DB safety wrapper, stable gate names, and synthetic fixture policy.
+- `docs/runtime-flows.md`
+ - Added `CI validation gates` flow and clarified that browser screenshot/DOM evidence remains local evidence, not a required CI gate.
+- `docs/runbooks/ci-smoke.md`
+ - Added local reproduction commands, wrapper responsibilities, stable GitHub Actions job names, browser evidence boundary, and failure triage guidance.
+- `docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/implementation.md`
+ - Replaced planned placeholder with actual implementation/evidence log.
+- `docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/verification.md`
+ - Added AC-by-AC verification evidence and command results.
+- `docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/index.md`
+ - Moved BAT-006 batch status from `planned` to `release-candidate` for QC.
+- `docs/batches/index.md`
+ - Moved BAT-006 register status from `planned` to `release-candidate`.
+
+## Execution Log
+
+All commands below were run from `/root/projects/jarca-ledger-mcp`.
+
+1. Pytest regression gate
+ - Command: `uv run pytest --ignore=tests/test_dashboard_app.py -q`
+ - Result: PASS, exit code 0.
+ - Output: `............ss...............ss... [100%]`
+
+2. Streamlit AppTest gate
+ - Command: `uv run pytest tests/test_dashboard_app.py -q`
+ - Result: PASS, exit code 0.
+ - Output: `.... [100%]`
+
+3. Dashboard HTTP smoke gate
+ - Command: `uv run python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45`
+ - Result: PASS, exit code 0.
+ - Output summary:
+ - `success: true`
+ - `gate: dashboard-http-smoke`
+ - `fixture: synthetic`
+ - `fixture_integrity: ok`
+ - `http_status: 200`
+ - `cleanup: terminated`
+
+4. DB safety smoke gate
+ - Command: `uv run python scripts/ci_db_safety_smoke.py --timeout 45`
+ - Result: PASS, exit code 0.
+ - Output summary:
+ - `success: true`
+ - `gate: db-safety-smoke`
+ - `fixture: synthetic`
+ - `fixture_integrity: ok`
+ - `restore_smoke_exit_code: 0`
+ - `restored_exists: true`
+ - `restored_integrity: ok`
+ - `health_success: true`
+ - `operating_db_used: false`
+
+## Validation Summary
+
+- GitHub Actions workflow exists with required BAT-006 trigger shape and stable job names.
+- CI/AppTest/HTTP/DB safety commands pass locally against synthetic/temp DBs.
+- Wrapper output is status-only and does not expose real account identifiers, operating DB data, raw payloads, source financial filenames, or passwords.
+- Browser screenshot/DOM evidence is intentionally not a required CI gate and remains covered by the local browser evidence runbook/flow.
+
+## Remaining Risks / QC Notes
+
+- Remote GitHub Actions has not been executed in this local handoff; QC should confirm the workflow on GitHub if remote runner access is available.
+- Branch protection required-check enforcement is repository configuration outside this codebase.
+- REQ-006 must remain not Implemented until `jarcaledgerqc` completes QC and the requirement implementation state is explicitly confirmed.
diff --git a/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/index.md b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/index.md
new file mode 100644
index 0000000..ed8a452
--- /dev/null
+++ b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/index.md
@@ -0,0 +1,32 @@
+# BAT-006 pytest/AppTest/Smoke CI ์๋ํ
+
+- Profile: standard
+- Status: release-candidate
+- Included REQ: REQ-006
+- Discovery: dcy-006
+- Owner: ๋ ์ ์ฅ์ธ
+- Date (KST): 2026-06-30
+
+## ๋ชฉํ
+
+REQ-006์ ๋ฐ๋ผ GitHub Actions ๊ธฐ๋ฐ CI ๊ณ์ฝ์ ์ ์คํ๊ณ , push/PR์์ pytest ํ๊ท ์ค์ํธ, Streamlit AppTest, ๋์๋ณด๋ HTTP smoke, DB safety smoke๋ฅผ synthetic fixture/์์ ๊ฒ์ฆ DB๋ก ์๋ ์คํํ๋ ํ์ ๊ฒ์ดํธ๋ฅผ ๊ตฌ์ฑํ๋ค.
+
+## ์ฐ์ถ๋ฌผ
+
+- `.github/workflows/` ์๋ GitHub Actions workflow
+- CI ์ ์ฉ ์์ smoke wrapper entrypoint: HTTP smoke์ DB safety smoke์ ๊ฒฐ์ ์ ์คํ/์ข
๋ฃ์ฝ๋/๋ก๊ทธ/์ ๋ฆฌ ์ฑ
์
+- synthetic fixture DB ์ค๋น ํ๋ฆ๊ณผ ์ด์ DB ์ ๊ทผ ์ฐจ๋จ ์ ์ฑ
+- pytest/AppTest/HTTP/DB safety smoke CI job ๊ตฌ์ฑ
+- `docs/project-structure.md`, `docs/runtime-flows.md`, ํ์ ์ `docs/runbooks/`์ CI ๊ฒ์ดํธ/๋ก์ปฌ browser evidence ๊ฒฝ๊ณ ๋ฌธ์ํ
+- BAT-006 planning/design/implementation/verification ๋ฌธ์์ CI evidence
+
+## ๋ฒ์ ์ ํ
+
+browser-screenshot/visual regression์ CI ํ์ ๊ฒ์ดํธํ, CD/์๋น์ค ๋ฐฐํฌ ์๋ํ, ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง ํผ์ผํธ ๊ฐ์ , ์ฑ๋ฅ ๋ฒค์น๋งํฌ ๊ฒ์ดํธ, ์ด์ DB ๋๋ ์ค๊ฑฐ๋/์ค๊ณ์ข ๋ฐ์ดํฐ์ CI ๋ฐ์
์ ํฌํจํ์ง ์๋๋ค.
+
+## ๋ฌธ์
+
+- `planning.md`: delivery plan, dependencies, design gate, risks
+- `design.md`: CI architecture, job boundaries, wrapper policy, evidence boundaries
+- `implementation.md`: ์ค์ ๋ณ๊ฒฝ ํ์ผ๊ณผ ๋ก์ปฌ ์คํ ๋ก๊ทธ ๊ธฐ๋ก
+- `verification.md`: REQ-006 AC๋ณ ๊ฒ์ฆ evidence ๋ฐ QC handoff ๊ธฐ๋ก
diff --git a/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/planning.md b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/planning.md
new file mode 100644
index 0000000..1bb5e0b
--- /dev/null
+++ b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/planning.md
@@ -0,0 +1,41 @@
+# BAT-006 Planning
+
+## Included REQ
+- REQ-006: pytest/AppTest/Smoke CI ์๋ํ
+
+## Delivery Plan
+1. REQ-006 ์น์ธ ์ํ์ dcy-006 freeze ๊ฒฐ์ ์ ์
๋ ฅ gate๋ก ํ์ธํ๋ค.
+2. GitHub Actions workflow๋ฅผ `.github/workflows/` ์๋์ ์ ์คํ๊ณ `pull_request`, `main` ๋์ `push`, `workflow_dispatch` ํธ๋ฆฌ๊ฑฐ๋ฅผ ์ ์ํ๋ค.
+3. CI ์คํ ํ๊ฒฝ์ ํ์คํํ๋ค: `uv`/Python ์ค์น, project extras ์ค์น, runner temp ๊ฒฝ๋ก ์ฌ์ฉ, ์ด์ DB ๊ธฐ๋ณธ ๊ฒฝ๋ก ์ ๊ทผ ๊ธ์ง.
+4. pytest ํ๊ท ์ค์ํธ job์ ๊ตฌ์ฑํด `tests/` ์ ์ฒด ๋๋ ๋์ผ ์๋ฏธ์ ํ์ค ๋ช
๋ น์ ํ์ ๊ฒ์ดํธ๋ก ์คํํ๋ค.
+5. Streamlit AppTest job์ synthetic fixture DB ๊ธฐ์ค์ผ๋ก ์คํํ๋ค.
+6. HTTP smoke์ DB safety smoke์ฉ CI wrapper๋ฅผ ์ถ๊ฐํ๋ค. wrapper๋ fixture ์ค๋น, ํ๊ฒฝ ๋ณ์ ์ค์ , readiness polling, timeout, cleanup, ๋ฏผ๊ฐ์ ๋ณด ๋น์ถ๋ ฅ ์ ์ฑ
์ ๋ด๋นํ๋ค.
+7. DB safety smoke๋ ์์ fixture/๊ฒ์ฆ DB์์๋ง ์คํํ๊ณ ์ด์ DB ๋ฐ ์๋ณธ ๊ธ์ต ํ์ผ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ง ์๋์ง ๊ฒ์ฆํ๋ค.
+8. `docs/project-structure.md`์ `docs/runtime-flows.md`์ CI workflow, smoke wrapper, gate result flow, ๋ก์ปฌ browser evidence(dcy-004)์์ ๊ฒฝ๊ณ๋ฅผ ๋ฐ์ํ๋ค. ํ์ํ๋ฉด `docs/runbooks/ci-smoke.md`๋ฅผ ์ถ๊ฐํ๋ค.
+9. ๋ก์ปฌ์์ ๊ฐ๋ฅํ ๋ฒ์์ CI ๋ฑ๊ฐ ๋ช
๋ น์ ์คํํ๊ณ , GitHub Actions ์ค์ ๋ก๊ทธ ๋๋ ๋ก์ปฌ dry-run ํ๊ณ๋ฅผ verification์ ๋ช
ํํ ๊ธฐ๋กํ๋ค.
+
+## Dependencies
+- ๊ธฐ์กด pytest ์ค์ : `pyproject.toml`์ `[tool.pytest.ini_options]`์ dev extra์ `pytest`.
+- ๊ธฐ์กด Streamlit AppTest ์์ฐ: `tests/test_dashboard_app.py`.
+- ๊ธฐ์กด smoke ์์ฐ: `scripts/ledger_restore_smoke.py`, `scripts/streamlit_browser_evidence.py` ๋ด๋ถ ๋ก์ง ์ค CI์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ถ๋ถ.
+- dcy-002 ์ด์ ์์ ์์น: ๊ฒ์ฆ DB/fixture ์ฐ์ , ์ด์ DB restore/mutation ๊ธ์ง.
+- dcy-004 evidence ๊ฒฝ๊ณ: browser screenshot evidence๋ ๋ก์ปฌ ๋ณด๊ฐ ๊ฒฝ๋ก์ด๋ฉฐ CI ํ์ ๊ฒ์ดํธ๊ฐ ์๋๋ค.
+
+## Design Gate
+Design required: yes.
+
+Reason: ์ด batch๋ CI workflow, wrapper entrypoint, fixture DB safety boundary, baseline runtime docs๋ฅผ ํจ๊ป ์ ์ํ๋ค. runner ํ๊ฒฝยท๋ฏผ๊ฐ์ ๋ณดยท์ด์ DB ์ ๊ทผ ์ฐจ๋จ ๊ท์น์ด ์๊ตฌ์ฌํญ์ ํต์ฌ์ด๋ฏ๋ก ๊ตฌํ ์ job ๊ฒฝ๊ณ์ wrapper ์ฑ
์์ ์ค๊ณ ๋ฌธ์์ ๊ณ ์ ํ๋ค.
+
+## Milestones
+- M1: REQ-006 Approved ๋ฐ BAT-006 standard batch ์์ฑ.
+- M2: CI workflow/job ๊ตฌ์กฐ์ wrapper contract ์ค๊ณ ์๋ฃ.
+- M3: Workflow์ wrapper ๊ตฌํ, synthetic fixture ์คํ ๊ฒฝ๋ก ์ฐ๊ฒฐ.
+- M4: pytest/AppTest/HTTP/DB safety smoke ๋ก์ปฌ ๋ฑ๊ฐ ๊ฒ์ฆ ํต๊ณผ.
+- M5: baseline docs/runbook ๊ฐฑ์ ๋ฐ verification evidence ์์ฑ.
+- M6: QC gate PASS/PASS_WITH_NOTES ํ REQ-006 Implemented ์ ํ.
+
+## Risks
+- Streamlit HTTP smoke๊ฐ readiness ๋๊ธฐ/ํฌํธ ์ถฉ๋๋ก flakyํด์ง ์ ์๋ค. ๊ณ ์ ๋๋ ๊ฒฐ์ ์ ํฌํธ ํ ๋น, polling timeout, ํ๋ก์ธ์ค cleanup์ wrapper์์ ํ์คํํ๋ค.
+- CI ๋ก๊ทธ์ ์ค๊ณ์ข๋ฒํธ, ์ค์์ก, `raw_payload`, ์๋ณธ ํ์ผ๋ช
, ๋น๋ฐ๋ฒํธ๊ฐ ๋
ธ์ถ๋ ์ ์๋ค. synthetic fixture๋ง ์ฌ์ฉํ๊ณ wrapper ์ถ๋ ฅ์ ์์ฝ/์ํ ์ค์ฌ์ผ๋ก ์ ํํ๋ค.
+- GitHub Actions runner์๋ ๋ก์ปฌ browser evidence ํ๊ฒฝ์ด ์์ ์ ์๋ค. browser-screenshot E2E๋ CI ํ์ ๊ฒ์ดํธ์์ ์ ์ธํ๊ณ dcy-004 ๋ก์ปฌ evidence ๊ฒฝ๋ก๋ก ์ ์งํ๋ค.
+- ๋ธ๋์น ๋ณดํธ required check ์ค์ ์ ์ ์ฅ์ ์ธ๋ถ GitHub ์ค์ ์ด๋ฏ๋ก, ์ด batch๋ workflow/check ์ด๋ฆ์ ์์ ํํ๊ณ ๋ฌธ์์ ํ์ ์ด์ ์ค์ ์ผ๋ก ๊ธฐ๋กํ๋ค.
diff --git a/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/verification.md b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/verification.md
new file mode 100644
index 0000000..c824480
--- /dev/null
+++ b/docs/batches/bat-006_20260630_pytest-apptest-smoke-ci-automation/verification.md
@@ -0,0 +1,154 @@
+# BAT-006 Verification
+
+## Verification Status
+- Status: PASS_WITH_NOTES
+- Ready for Release: true
+- Ready for QC: false
+- Verified At (KST): 2026-06-30 15:29 KST
+- Verified By: jarcaledgerqc
+- QC Verdict: PASS_WITH_NOTES
+- QC Boundary: satisfied
+
+## Acceptance Mapping
+
+### REQ-006 AC-1
+`.github/workflows/` ์๋์ GitHub Actions workflow๊ฐ ์์ฑ๋๊ณ , `pull_request`, `main` ๋์ `push`, `workflow_dispatch` ํธ๋ฆฌ๊ฑฐ๊ฐ ์ ์๋๋ค.
+
+Evidence:
+- Workflow file: `.github/workflows/ci.yml`.
+- Trigger excerpt verified in file:
+ - `pull_request:`
+ - `push:` with `branches: [main]`
+ - `workflow_dispatch:`
+- Stable job/check names in workflow:
+ - `pytest-regression`
+ - `dashboard-app-test`
+ - `dashboard-http-smoke`
+ - `db-safety-smoke`
+
+Result: PASS.
+
+### REQ-006 AC-2
+CI์์ pytest ํ๊ท ์ค์ํธ๊ฐ ์๋ ์คํ๋๊ณ , ๋จ์ผ ํ
์คํธ ์คํจ ์ workflow๊ฐ ์คํจ ์ํ๋ก ์ข
๋ฃ๋๋ค.
+
+Evidence:
+- Workflow job: `pytest-regression`.
+- Workflow command: `uv run pytest --ignore=tests/test_dashboard_app.py`.
+- Local equivalent command:
+ - `uv run pytest --ignore=tests/test_dashboard_app.py -q`
+ - Result: PASS, exit code 0.
+ - Output: `............ss...............ss... [100%]`
+- GitHub Actions `run:` step uses normal shell exit-code behavior, so a pytest failure fails the job/workflow.
+
+Result: PASS.
+
+### REQ-006 AC-3
+CI์์ Streamlit `AppTest`๊ฐ synthetic fixture DB ๊ธฐ์ค์ผ๋ก ์๋ ์คํ๋๊ณ ํต๊ณผ/์คํจ ๊ฒฐ๊ณผ๊ฐ ๋ก๊ทธ์ ๋จ๋๋ค.
+
+Evidence:
+- Workflow job: `dashboard-app-test`.
+- Workflow command: `uv run pytest tests/test_dashboard_app.py`.
+- Test evidence: `tests/test_dashboard_app.py` calls `create_ci_fixture_db(tmp_path / "fixture.sqlite3")`, sets `JARCA_LEDGER_DB_PATH`, and runs Streamlit AppTest across core pages.
+- Local equivalent command:
+ - `uv run pytest tests/test_dashboard_app.py -q`
+ - Result: PASS, exit code 0.
+ - Output: `.... [100%]`
+- Fixture policy: `scripts/ci_fixture_db.py` refuses `DEFAULT_DB_PATH` and seeds synthetic data only.
+
+Result: PASS.
+
+### REQ-006 AC-4
+CI์์ ๋์๋ณด๋ HTTP smoke๊ฐ fixture DB ๊ธฐ์ค์ผ๋ก Streamlit์ ๊ธฐ๋ํ ๋ค HTTP 200 ์๋ต์ ํ์ธํ๊ณ , ์ข
๋ฃ ํ ํ๋ก์ธ์ค๋ฅผ ์ ๋ฆฌํ๋ค.
+
+Evidence:
+- Workflow job: `dashboard-http-smoke`.
+- Workflow command: `uv run python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45`.
+- Wrapper: `scripts/ci_dashboard_http_smoke.py` creates a temp synthetic fixture DB, starts Streamlit with `JARCA_LEDGER_DB_PATH`, polls a loopback URL for HTTP 200, and performs process cleanup.
+- Local command:
+ - `uv run python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45`
+ - Result: PASS, exit code 0.
+ - Output summary: `success: true`, `fixture: synthetic`, `fixture_integrity: ok`, `http_status: 200`, `attempts: 3`, `cleanup: terminated`.
+
+Result: PASS.
+
+### REQ-006 AC-5
+CI์์ DB safety smoke๊ฐ ์ด์ DB ๊ธฐ๋ณธ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์์ fixture/๊ฒ์ฆ DB์์ ํต๊ณผํ๋ค.
+
+Evidence:
+- Workflow job: `db-safety-smoke`.
+- Workflow command: `uv run python scripts/ci_db_safety_smoke.py --timeout 45`.
+- Wrapper: `scripts/ci_db_safety_smoke.py` creates temp `source`, `backup`, and `target` DB files and refuses `DEFAULT_DB_PATH` before invoking `scripts/ledger_restore_smoke.py`.
+- Local command:
+ - `uv run python scripts/ci_db_safety_smoke.py --timeout 45`
+ - Result: PASS, exit code 0.
+ - Output summary: `success: true`, `fixture: synthetic`, `fixture_integrity: ok`, `restore_smoke_exit_code: 0`, `restored_exists: true`, `restored_integrity: ok`, `health_success: true`, `operating_db_used: false`.
+
+Result: PASS.
+
+### REQ-006 AC-6
+HTTP smoke/DB safety smoke๋ CI ์ ์ฉ wrapper entrypoint๋ฅผ ํตํด ์คํ๋๋ฉฐ, wrapper๊ฐ ์ข
๋ฃ์ฝ๋ยทtimeoutยทreadinessยทcleanup์ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ๋ค.
+
+Evidence:
+- HTTP wrapper path: `scripts/ci_dashboard_http_smoke.py`.
+ - Readiness: polls URL until HTTP 200 or timeout.
+ - Timeout/failure: returns non-zero status with status-only JSON.
+ - Cleanup: terminates/kills Streamlit process and reports cleanup state.
+- DB safety wrapper path: `scripts/ci_db_safety_smoke.py`.
+ - Timeout: catches `subprocess.TimeoutExpired` and returns non-zero status with timeout JSON.
+ - Exit code: reports restore smoke exit code and returns non-zero on failed restore/integrity/health conditions.
+ - Safety: refuses operating DB path and reports `operating_db_used: false` on pass.
+- Local command results for both wrappers exited 0 and reported success JSON.
+
+Result: PASS.
+
+### REQ-006 AC-7
+CI ๋ก๊ทธยท์ํฐํฉํธ์๋ ์ค๊ณ์ข๋ฒํธ, ์ค์์ก, `raw_payload`, ์๋ณธ ๊ธ์ต ํ์ผ๋ช
, ๋น๋ฐ๋ฒํธ๊ฐ ํ๋ฌธ์ผ๋ก ํฌํจ๋์ง ์๋๋ค.
+
+Evidence:
+- Fixture builder uses synthetic labels such as `CI Synthetic Checking`, `CI_SYNTHETIC_*`, and `ci synthetic ...`; it does not load real statement files or passwords.
+- Wrapper outputs are status-only JSON. Observed local outputs include gate name, fixture type, integrity, HTTP status, cleanup, restore smoke status, and `operating_db_used: false`; they do not include account numbers, real balances, raw payloads, source financial filenames, or passwords.
+- `scripts/ci_fixture_db.py` refuses the operating ledger DB default path.
+
+Result: PASS.
+
+### REQ-006 AC-8
+`docs/project-structure.md`์ `docs/runtime-flows.md`๊ฐ CI ๊ฒ์ดํธ์ ๋ก์ปฌ browser evidence ๊ฒฝ๊ณ๋ฅผ ๋ฐ์ํ๋ค.
+
+Evidence:
+- `docs/project-structure.md` documents `.github/workflows/ci.yml`, stable gate names, `scripts/ci_fixture_db.py`, `scripts/ci_dashboard_http_smoke.py`, `scripts/ci_db_safety_smoke.py`, and synthetic fixture policy.
+- `docs/runtime-flows.md` includes `Flow: CI validation gates` and the constraints that CI gates use synthetic/temp DBs only.
+- `docs/runbooks/ci-smoke.md` provides reproduction commands and failure triage.
+
+Result: PASS.
+
+### REQ-006 AC-9
+browser-screenshot E2E๋ CI ํ์ ์ฐจ๋จ ๊ฒ์ดํธ์์ ์ ์ธ๋๊ณ , dcy-004 ๋ก์ปฌ evidence ์ ์ฐจ์์ ์ญํ ๋ถ๋ด์ด ๋ฌธ์์ ๋ช
์๋๋ค.
+
+Evidence:
+- `.github/workflows/ci.yml` contains the four required CI jobs and no browser screenshot/visual regression required job.
+- `docs/runtime-flows.md` states browser screenshot/DOM evidence is not a required GitHub Actions gate in BAT-006 and remains local evidence for visual/layout-sensitive dashboard changes.
+- `docs/runbooks/ci-smoke.md` states `scripts/streamlit_browser_evidence.py` remains local evidence and is not a required GitHub Actions gate.
+
+Result: PASS.
+
+## Command Evidence Summary
+
+All commands were run from `/root/projects/jarca-ledger-mcp`.
+
+| Command | Result | Evidence |
+| --- | --- | --- |
+| `uv run pytest --ignore=tests/test_dashboard_app.py -q` | PASS, exit code 0 | `............ss...............ss... [100%]` |
+| `uv run pytest tests/test_dashboard_app.py -q` | PASS, exit code 0 | `.... [100%]` |
+| `uv run python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45` | PASS, exit code 0 | `success: true`, `http_status: 200`, `cleanup: terminated` |
+| `uv run python scripts/ci_db_safety_smoke.py --timeout 45` | PASS, exit code 0 | `success: true`, `restored_integrity: ok`, `health_success: true`, `operating_db_used: false` |
+
+## Sensitive Evidence Review
+
+- No command output observed in this verification exposed real account numbers, operating DB balances, source financial filenames, passwords, or `raw_payload`.
+- CI fixture/smoke paths are synthetic/temp paths and refuse the default operating ledger DB path.
+- Remote GitHub Actions logs were not available in this local handoff; `jarcaledgerqc` recorded this as a note rather than a blocker because local equivalent commands passed and the workflow wiring is present.
+
+## Blocking Issues
+
+- None for local QC readiness.
+- QC note: Remote GitHub Actions execution and branch protection required-check setup remain repository/remote configuration checks outside this local evidence update.
diff --git a/docs/batches/index.md b/docs/batches/index.md
index eefd844..a590832 100644
--- a/docs/batches/index.md
+++ b/docs/batches/index.md
@@ -10,3 +10,7 @@
| --- | --- | --- | --- | --- | --- |
| BAT-001 | batch-lite | release-candidate | REQ-001 | dcy-001 | [bat-001_20260626_category-rule-ops](bat-001_20260626_category-rule-ops/) |
| BAT-002 | standard | release-candidate | REQ-002 | dcy-002 | [bat-002_20260627_operational-safety-backup-restore-validation-db](bat-002_20260627_operational-safety-backup-restore-validation-db/) |
+| BAT-003 | batch-lite | release-candidate | REQ-003 | dcy-003 | [bat-003_20260629_statement-unavailable-reconciliation](bat-003_20260629_statement-unavailable-reconciliation/) |
+| BAT-004 | batch-lite | release-candidate | REQ-004 | dcy-004 | [bat-004_20260629_browser-streamlit-e2e-evidence](bat-004_20260629_browser-streamlit-e2e-evidence/) |
+| BAT-005 | standard | release-candidate | REQ-005 | dcy-005 | [bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow](bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/) |
+| BAT-006 | standard | release-candidate | REQ-006 | dcy-006 | [bat-006_20260630_pytest-apptest-smoke-ci-automation](bat-006_20260630_pytest-apptest-smoke-ci-automation/) |
diff --git a/docs/data-model.md b/docs/data-model.md
index bea43f8..af66858 100644
--- a/docs/data-model.md
+++ b/docs/data-model.md
@@ -4,7 +4,7 @@
- Baseline Seed: `.stage-pilot/bootstrap/baseline.yaml`
- Status: draft
- Owner: Jarca / project maintainer
-- Last Updated (KST): 2026-06-27 10:17
+- Last Updated (KST): 2026-06-29
- Source Discovery / Batch: bootstrap-baseline
## Project Summary
@@ -31,10 +31,17 @@
- `account_number_masked` :: text :: masked account identifier
- `holder_name` :: text :: owner context for transfer matching
- `last_balance` :: integer :: latest known balance when available
+ - `last_synced_at` :: text :: ๊ธฐ์ค ์์ก ์ค๋
์ท ์๊ฐ. ์ ๊ธฐ ๋์กฐ๋ ์๋ง 23:59 KST, ์์ ๋์กฐ๋ ์ฌ์ฉ์ ์
๋ ฅ ์๊ฐ
+ - `statement_status` :: text :: `unknown`, `import_supported`, `manual_only`, `partial_import`
+ - `liquidity_bucket` :: text :: `cash`, `investment`, `deposit`, `real_estate`, `loan`, `other`; ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ/์์์ฐ ์ง๊ณ์ ๊ณ์ข ๋จ์ ๊ถ์ ์์ค
- Relationships:
- `transactions.account_id` links rows to accounts.
- State Rules:
- `active=false` hides accounts from default lists without deleting history.
+ - `manual_only` is the statement-unavailable class; `partial_import` remains eligible for manual supplementation and balance reconciliation.
+ - Balance reconciliation is report/preview first: compare `baseline_balance`/`baseline_at` against the latest `balance_after` or net account transaction delta, with 1,000 KRW absolute tolerance.
+ - ๋น๊ฑฐ๋ ์์ฐ/๋ถ์ฑ๋ ๋ณ๋ ํ๊ฐ ํ
์ด๋ธ ์์ด `last_balance`๋ฅผ ํ์ฌ ํ๊ฐ์ก/์์ก, `last_synced_at`์ ๊ธฐ์ค์ผ, `memo`๋ฅผ ์ถ์ฒ ๋ฉ๋ชจ๋ก ์ฌ์ฉํ๋ค.
+ - `liquidity_bucket='loan'`์ ์์ฐ ๊ตฌ์ฑ ๋น์จ ๋ถ๋ชจ์์ ์ ์ธํ๊ณ ์ด๋ถ์ฑ/์์์ฐ ์ฐ์์๋ง ๋ฐ์ํ๋ค. `last_balance`๊ฐ ์๋ ๊ณ์ข/์์ฐ์ ์์ ๊ฑฐ๋ ํฉ๊ณ๋ก ์ถ์ ํ์ง ์๋๋ค.
## Entity: Transaction
- Source of Truth: `transactions`
@@ -141,6 +148,7 @@
- External counterparty securities transfers are review items unless matched or explicitly confirmed.
- Import evidence fields are retained for audit and future correction.
- DB corrections should be narrow and memo-tagged.
+- Dashboard asset liquidity/net-worth reports use `accounts.liquidity_bucket` and `accounts.last_balance` only; transactions are not used to infer non-transaction asset values.
## Persistence Notes
- Schema changes should be additive/idempotent unless a destructive migration is explicitly approved and backed up.
@@ -152,8 +160,11 @@
## Current Gaps / Planned Changes
- Formal schema migration documentation should be added before larger DB changes.
- Some UI workflows need final E2E verification against real rows.
-- Statement-unavailable account handling needs a documented manual evidence model.
+- Statement-unavailable account handling is tracked by REQ-003/BAT-003; future work may add a dedicated reconciliation workbench and persistent review-decision queue.
+- REQ-005/BAT-005 adds dashboard operations hub semantics and account-level liquidity buckets; future work may add separate asset snapshots only after a new REQ.
## Change Log
- 2026-06-26 22:24 KST: Created by `bootstrap-baseline` from repository observation.
- 2026-06-27 10:17 KST: BAT-002/REQ-002 ๊ธฐ์ค์ผ๋ก LedgerBackupArtifact์ metadata-only evidence ์ ์ฑ
์ถ๊ฐ.
+- 2026-06-29 KST: REQ-003/BAT-003 ๊ธฐ์ค์ผ๋ก `accounts.statement_status`์ ์๋ ์์ก๋์กฐ preview ๋ชจ๋ธ ์ถ๊ฐ.
+- 2026-06-29 KST: REQ-005/BAT-005 ๊ธฐ์ค์ผ๋ก `accounts.liquidity_bucket`, `last_balance` ๊ธฐ๋ฐ ์์ฐ ์ ๋์ฑ/๋ถ์ฑ/์์์ฐ ๊ท์น ์ถ๊ฐ.
diff --git a/docs/discovery/dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation.md b/docs/discovery/dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation.md
new file mode 100644
index 0000000..73b14ff
--- /dev/null
+++ b/docs/discovery/dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation.md
@@ -0,0 +1,210 @@
+# 0. ๋ฌธ์ ์ํ
+์ด ๋ฌธ์์ ํ์ฌ ์ํ์ ์๋ณ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ค. ๋ฌธ์ ์ถ์ ๊ณผ ์น์ธ ํ๋ฆ์ ํ์ํ ์ต์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ค.
+
+- ์ํ: confirmed
+- ๋ฌธ์ ID: dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation
+- ์ด์๋ช
: Statement-unavailable ๊ณ์ข ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ ์ํฌํ๋ก์ฐ ์ ๋ฆฝ
+- ์์ฑ ์๊ฐ(KST): 2026-06-29 12:33
+- ๋ง์ง๋ง ๊ฐฑ์ ์๊ฐ(KST): 2026-06-29 13:06
+- ๋์ฒด๋จ: ์์
+- ํ์ Discovery ์ฐธ์กฐ: ์์
+- ์์ฑ๋ REQ ์ฐธ์กฐ: REQ-003 (`docs/srs/Interface/req-003_statement-unavailable-manual-evidence-balance-reconciliation.md`)
+
+# 1. ๊ณํ ์ํ ์์ฝ
+ํ์ฌ Discovery๊ฐ ์ด๋ค ๋ฐฐ๊ฒฝ์์ ์์ฑ๋๊ณ ์๋์ง, ์ฐ๊ณ ๋ฌธ์๊ฐ ์๋์ง, ์ง๊ธ ์์ ์ ํด์์ด ๋ฌด์์ธ์ง ์งง๊ฒ ์์ฝํ๋ค.
+
+- ์ฐ๊ณ ๋ฌธ์ ํํฉ:
+ - ๊ด๋ จ GitHub Project ํญ๋ชฉ: https://github.com/users/genichin/projects/4 DraftIssue
+ - ๊ด๋ จ TODO/๋ฉ๋ชจ: Statement-unavailable ๊ณ์ข ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ ์ํฌํ๋ก์ฐ ์ ๋ฆฝ
+- ํด์:
+ - dcy-001(๋ถ๋ฅยทํ๊ธ UX)๊ณผ dcy-002(๋ฐฑ์
ยท๋ณต๊ตฌ ์์ ์ฅ์น)๋ ๋ชจ๋ statement(๊ฑฐ๋๋ด์ญ ํ์ผ)๊ฐ ์กด์ฌํ๋ ๊ณ์ข์ importยท๋ถ๋ฅยท๊ฒ์ฆ ํ๋ฆ์ ์ ์ ๋ก ํ๋ฉฐ, statement๊ฐ ์๊ฑฐ๋ ์๋ import๊ฐ ์ด๋ ค์ด ๊ณ์ข๋ฅผ ๋ค๋ฃจ๋ ํ์ค ์ ์ฐจ๋ ์์ง ์ ์๋์ง ์์๋ค.
+ - ์ด๋ฒ Discovery๋ ๊ฑฐ๋๋ด์ญ ํ์ผ์ด ์๋ ๊ณ์ข์ ๋ํ ์๋ ์ฆ๋น ์
๋ ฅ, ์์ก๋์กฐ(reconciliation), ํ์ธํ์ ํ ์ฒ๋ฆฌ, ๊ณ์ข์ด๋/์ค์ง์ถ ํ์ ๊ธฐ์ค์ ๋
๋ฆฝ ์ฃผ๊ธฐ๋ก ์ ์ํ๋ค.
+
+# 2. ์๊ตฌ์ฌํญ ํ์ ๊ฒฐ๊ณผ
+์ด๋ฒ ์์ฒญ์ด ๊ธฐ์กด ์๊ตฌ์ฌํญ๊ณผ ๋น๊ตํด ์ ๊ท์ธ์ง, ์ ์ฌํ์ง, ์ค๋ณต์ธ์ง ํ๋จํ๊ณ ๊ทธ ๊ทผ๊ฑฐ๋ฅผ ๋จ๊ธด๋ค.
+
+- ํ์ : ์ ๊ท
+- ๊ทผ๊ฑฐ
+ - ๊ธฐ์กด Discovery dcy-001/dcy-002์ REQ-001/REQ-002๋ statement ๊ธฐ๋ฐ importยท๋ถ๋ฅยท๋ฐฑ์
๋ฒ์๋ฅผ ๋ค๋ฃจ๋ฉฐ, statement-unavailable ๊ณ์ข์ ์๋ ์ฆ๋นยท์์ก๋์กฐ๋ฅผ ๋
๋ฆฝ ์ฑ๊ณต ๊ธฐ์ค์ผ๋ก ๋ค๋ฃจ์ง ์๋๋ค.
+ - ์ฝ๋์๋ `source='manual'` ๊ฑฐ๋, `ledger_accounts.last_balance`, `transactions.balance_after`, transfer ๋งค์นญ/ํ์ธํ์ ํ๊ฐ ๋ถ๋ถ์ ์ผ๋ก ์กด์ฌํ๋, ์ด๋ฅผ ์์ก๋์กฐ ์ํฌํ๋ก์ฐ๋ก ๋ฌถ๋ ๋ช
์์ ์ ์ฐจยทacceptance criteria๊ฐ ์์ด ๋ณ๋ ์ฃผ๊ธฐ๋ก ์ถ์ ํ ํ์๊ฐ ์๋ค.
+
+# 3. ๋ฌธ์ ์ ์ ์์ฝ
+๋ฌธ์ ์ ํ์ฌ ์ํ, ๊ธฐ๋ ์ํ, ๊ด๋ จ ์ดํด๊ด๊ณ์, ์ ์ฝ์ฌํญ์ ์ ๋ฆฌํ๋ค. Discovery์ ํต์ฌ ๋งฅ๋ฝ์ ๊ฐ์ฅ ๋จผ์ ์ดํดํ ์ ์์ด์ผ ํ๋ค.
+
+- ํ์ฌ ์ํ
+ - import ๊ฒฝ๋ก๋ statement ํ์ผ์ ์ ์ ๋ก ํ๋ค: `service.py`์ `preview_ledger_csv_import`, `preview_kb_bank_pdf_import`, `preview_seoulpay_xlsx_import`, `preview_toss_bank_pdf_import`, `preview_toss_securities_pdf_import` ๋ฑ์ ๋ชจ๋ ์
๋ ฅ ํ์ผ์ด ์์ด์ผ ๋์ํ๋ค.
+ - ์คํค๋ง์๋ ์๋ ์ฆ๋นยท์์ก๋์กฐ์ ํ ๋๊ฐ ์ผ๋ถ ์กด์ฌํ๋ค: `db.py`์ `ledger_accounts.last_balance`/`last_synced_at`, `transactions.balance_after`, `transactions.source`(๊ธฐ๋ณธ๊ฐ `manual`).
+ - ๊ณ์ข์ด๋/์ค์ง์ถ ํ์ ์์๊ฐ ๋ถ์ฐ๋์ด ์๋ค: `find_self_transfer_candidates`, `link_self_transfer_pair`, `run_cross_account_transfer_match`/`list_cross_transfer_review_queue`(ํ์ธํ์ ์ฑ๊ฒฉ์ review ํ), `infer_cashflow_kind`(categoryโcashflow_kind ์ถ๋ก ). ๊ทธ๋ฌ๋ statement๊ฐ ์๋ ๊ณ์ข์ ๋ํด ์๋ ์
๋ ฅ ๊ฑฐ๋๋ฅผ ์ด๋ค ๊ธฐ์ค์ผ๋ก ๊ณ์ข์ด๋ ๋๋ ์ค์ง์ถ๋ก ํ์ ํ ์ง์ ๋ํ ๋จ์ผ ๊ท์นยท์ฆ๋น ์๊ฑด์ ๋ฌธ์ํ๋์ด ์์ง ์๋ค.
+ - `docs/runbooks/`์๋ `ledger-backup-restore.md`๋ง ์๊ณ , ์๋ ์ฆ๋น/์์ก๋์กฐ ์ด์ ์ ์ฐจ runbook์ด ์๋ค. `docs/runtime-flows.md`๋ statement-unavailable ๊ณ์ข์ ์
๋ ฅยท๋์กฐ ํ๋ฆ์ ๋ค๋ฃจ์ง ์๋๋ค.
+- ๊ธฐ๋ ์ํ
+ - ์ด์์๋ ๊ฑฐ๋๋ด์ญ ํ์ผ์ด ์๊ฑฐ๋ ์๋ import๊ฐ ์ด๋ ค์ด ๊ณ์ข์ ๋ํด, ํ์คํ๋ ์๋ ์ฆ๋น(๊ฑฐ๋ยท์ฆ๋น ์ถ์ฒยท๊ธ์กยท์ผ์) ์
๋ ฅ ์ ์ฐจ๋ก ๋๋ฝ ์์ด ๊ฑฐ๋๋ฅผ ๊ธฐ๋กํ ์ ์๋ค.
+ - ๊ฐ ๊ณ์ข์ ๊ธฐ์ค ์์ก(`last_balance`)๊ณผ ๋์ ๊ฑฐ๋๋ก ์ฐ์ถํ ๊ณ์ฐ ์์ก์ ๋์กฐํ์ฌ ์ฐจ์ด๋ฅผ ์ ๋์ ์ผ๋ก ํ์ธํ๊ณ , ํ์ฉ ์ค์ฐจ๋ฅผ ๋ฒ์ด๋๋ ์ฐจ์ด๋ ํ์ธํ์ ํ๋ก ๋ผ์ฐํ
ํ๋ค.
+ - ์๋ ์
๋ ฅยท๋์กฐ ๊ณผ์ ์์ ๋ฐ์ํ ๋ชจํธ ๊ฑฐ๋๋ฅผ ๊ณ์ข์ด๋/์ค์ง์ถ ํ์ ๊ธฐ์ค์ ๋ฐ๋ผ ์ผ๊ด๋๊ฒ ๋ถ๋ฅํ์ฌ ์๊ฐ ํ๊ธํ๋ฆยท์นดํ
๊ณ ๋ฆฌ ์ง์ถ ๋ณด๊ณ ์์ ์๊ณก์ ์ค์ธ๋ค.
+- ์ดํด๊ด๊ณ์
+ - ๋ ์ ์ฅ์ธ(์ด์์ ๊ฒธ ์์ฑ์) โ ์๋ ์ฆ๋น ์
๋ ฅ๊ณผ ์์ก๋์กฐ ์คํ ์ฃผ์ฒด
+ - ๊ฒ์๊ด/๋ฆฌ๋ทฐ์ด โ ๋์กฐ ๊ฒฐ๊ณผ์ ํ์ ๊ทผ๊ฑฐ์ ๊ฒ์ฆ ์ฃผ์ฒด
+ - StagePilot ํ์ SDLC(REQ/Batch) ์ํ์ โ ๋ณธ Discovery๋ฅผ ํ์ ์๊ตฌ์ฌํญ์ผ๋ก ์ ์
+- ์ ์ฝ์ฌํญ
+ - ๊ฐ์ธ ๊ธ์ต์ ๋ณด๋ฅผ ๋ค๋ฃจ๋ฏ๋ก ์ฆ๋น ์๋ฌธยท๊ณ์ข๋ฒํธ๋ ๋ง์คํน(`account_number_masked`)ยท์ต์ ๋ณด๊ด ์์น์ ๋ฐ๋ฅธ๋ค.
+ - ์๋ณธ ์ด์ DB ํผ์์ ํผํ๊ธฐ ์ํด dcy-002์์ ์ ์ํ ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๋ฅผ ์ ์ ๋ก ํ๋ค.
+ - ์๋ ์
๋ ฅ์ ํด๋จผ ์๋ฌ ๊ฐ๋ฅ์ฑ์ด ๋์ ์
๋ ฅ ๊ฒ์ฆ๊ณผ ์ฌ๋์กฐ ๊ฐ๋ฅ์ฑ์ด ํ์ํ๋ค.
+
+## ์ฃผ์ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
+์ฌ์ฉ์ ๋๋ ์ด์์๊ฐ ์ค์ ๋ก ์ด๋ค ์ํฉ์์ ์ด ๋ณ๊ฒฝ์ ํ์๋ก ํ๋์ง ๋ํ ์๋๋ฆฌ์ค๋ฅผ ์ ๋๋ค.
+
+- ์ด์์๊ฐ ๊ฑฐ๋๋ด์ญ ํ์ผ์ ์ ๊ณตํ์ง ์๋ ๊ณ์ข(์: ์ผ๋ถ ํ๊ธ ๊ณ์ข, ๋ชจ์ ํต์ฅ, ๋ช
์ธ์ ๋น๊ณต๊ฐ ๊ณ์ข)์ ์๋ง ์์ก๋ง ์๊ณ ์์ ๋, ๋๋ฝ ๊ฑฐ๋๋ฅผ ์๋ ์ฆ๋น์ผ๋ก ์
๋ ฅํ๊ณ ๊ณ์ฐ ์์ก์ ๊ธฐ์ค ์์ก๊ณผ ๋ง์ถ๋ค.
+- import๋ ๋์ง๋ง ์ผ๋ถ ๊ธฐ๊ฐ ๋ฐ์ดํฐ๊ฐ ๋น์ด ์๋ ๊ณ์ข์์, ๋น์ด ์๋ ๊ตฌ๊ฐ์ ๊ฑฐ๋๋ฅผ ์๋ ์ฆ๋น์ผ๋ก ๋ณด์ํ ๋ค ์์ก๋์กฐ๋ก ์ ํฉ์ฑ์ ํ์ธํ๋ค.
+- ์์ก๋์กฐ ๊ฒฐ๊ณผ ์ฐจ์ด๊ฐ ํ์ฉ ์ค์ฐจ๋ฅผ ์ด๊ณผํ์ฌ ํ์ธํ์ ํ์ ์ฌ๋ผ์จ ํญ๋ชฉ์, ์ด์์๊ฐ ๊ณ์ข์ด๋(๋ค๋ฅธ ๋ด ๊ณ์ข๋ก์ ์ด๋)์ธ์ง ์ค์ง์ถ์ธ์ง ํ์ ํ์ฌ ์ฒ๋ฆฌํ๋ค.
+
+# 4. ์ด๋ฒ์ ์ ์ํ ๋ณ๊ฒฝ
+์ด๋ฒ Discovery์์ ๋ค๋ฃจ๋ ํต์ฌ ๋ณ๊ฒฝ ํญ๋ชฉ์ ๋ชฉ๋ก์ผ๋ก ์ ๋๋ค. ๊ธฐ๋ฅ, ๊ตฌ์กฐ, ๊ท์น ๋ณ๊ฒฝ์ ํจ๊ป ํฌํจํด๋ ๋๋ค.
+
+- `accounts.statement_status`๋ฅผ ์ถ๊ฐํด `manual_only`๋ฅผ statement-unavailable ๊ณ์ข๋ก ์ ์ํ๊ณ , `partial_import`๋ ์๋ ๋ณด์/์์ก๋์กฐ ๋์์ผ๋ก ํฌํจํ๋ค.
+- statement-unavailable ๊ณ์ข์ ๋ํ ์๋ ์ฆ๋น ์
๋ ฅ ํ์ค(ํ์ ํ๋: ์ผ์, ๊ธ์ก, ์ถ์ฒ/์ฆ๋น ์ ํ, ๋ฉ๋ชจ, source=manual)๊ณผ ์
๋ ฅ ๊ฒ์ฆ ๊ท์น์ ์ ์ํ๋ค.
+- ๊ณ์ข๋ณ ๊ธฐ์ค ์์ก(`last_balance`) ๋๋น ๋์ ๊ฑฐ๋ ๊ณ์ฐ ์์ก์ ๋น๊ตํ๋ ์์ก๋์กฐ ์ ์ฐจ๋ฅผ ์ ์ํ๋ค. ์ ๊ธฐ ๊ธฐ์ค ์์ ์ ๋งค์ ๋ง์ผ 23:59 KST, ์์ ๊ธฐ์ค ์์ ์ ์ฌ์ฉ์ ์
๋ ฅ ์ค๋
์ท ์๊ฐ์ด๋ฉฐ, ์ด๊ธฐ ํ์ฉ ์ค์ฐจ๋ 1,000์์ผ๋ก ํ๋ค.
+- ํ์ธํ์ ํ ํญ๋ชฉ์ ๋ํ ๊ณ์ข์ด๋/์ค์ง์ถ/์ค์์
/ํฌ์์ด๋/๋ถ์์ ์ธ/ํ์ธํ์ ํ์ ๊ธฐ์ค์ ๋ฌธ์ํํ๋ค. ์๋ ํ์ ์ ์ ํํ๊ณ ์ถ์ฒ/ํ๋ณด ์ ์ ํ ์ฌ์ฉ์ ์๋ ํ์ ์ ๊ธฐ๋ณธ์ผ๋ก ํ๋ค.
+- ์๋ ์ฆ๋นยท์์ก๋์กฐ ์ด์ ์ ์ฐจ๋ฅผ `docs/runtime-flows.md`(์ ๊ท ํ๋ฆ)์ ์ ๊ท runbook ํ๋ณด(`docs/runbooks/`)์ ๋ฐ์ํ ํ์๋ฅผ ๋ช
์ํ๋ค.
+
+## ์ํฅ ๋ฒ์
+๋ณ๊ฒฝ์ด ๋ฏธ์น ์ฝ๋/๋ฌธ์/์ด์ ๋ฒ์๋ฅผ ๋ช
์ํด REQ ์ ์ ์ batch planning์์ ์ถ์ ๋๋ฝ์ ์ค์ธ๋ค.
+
+- ์ฝ๋ ์ํฅ:
+ - `jarca_ledger_mcp/service.py` โ ์๋ ์ฆ๋น ์
๋ ฅ/์์ก๋์กฐ/ํ์ธํ์ ๋ผ์ฐํ
์๋น์ค ๋ก์ง ์ ์ค ๋๋ ๊ธฐ์กด transferยทworkbench ๋ก์ง ํ์ฅ
+ - `jarca_ledger_mcp/server.py` โ ๋์กฐยท์ฆ๋น ๊ด๋ จ MCP tool ๋
ธ์ถ (ํ์ ์)
+ - `dashboard/app.py` โ ๊ณ์ข ํ๋ฉด์ `statement_status` ํ์/์์ , ์ต์ ์์ก๋์กฐ ๊ฒฐ๊ณผ ํ์. ์ ์ฉ ์์ก๋์กฐ ์ํฌ๋ฒค์น ์ ์ฒด ์ ์ค์ ํ์ ๋ฒ์๋ก ๋ถ๋ฆฌ
+ - `jarca_ledger_mcp/db.py` โ `accounts.statement_status` ์ถ๊ฐ ๋ฐ ๋์กฐ ๋ฉํ(๊ธฐ์ค ์์ก ์ค๋
์ท, ๋์กฐ ์๊ฐ ๋ฑ) ๋ณด๊ฐ ํ์ ์ฌ๋ถ ๊ฒํ
+- ๋ฌธ์ ์ํฅ:
+ - `docs/runtime-flows.md` โ statement-unavailable ์๋ ์ฆ๋นยท์์ก๋์กฐ ํ๋ฆ ์ถ๊ฐ
+ - `docs/runbooks/` โ ์๋ ์ฆ๋นยท์์ก๋์กฐ ์ด์ runbook ์ ๊ท ํ๋ณด
+ - `docs/data-model.md` โ ๋์กฐ ๊ด๋ จ ํ๋ ์๋ฏธ ๋ณด๊ฐ ํ์ ์ฌ๋ถ ๊ฒํ
+- ์ด์ ์ํฅ:
+ - ์๋ง/์ ๊ธฐ ์์ก๋์กฐ ์ด์ ๋ฃจํด์ด ์ ์ค๋๋ฉฐ, ๋์กฐ ์ dcy-002 ๋ฐฑ์
์ ์ฐจ ์ ํ์ด ์๊ตฌ๋๋ค.
+
+# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก
+์ค์ ๋ก ๊ตฌํ๊ณผ ๊ฒ์ฆ์ผ๋ก ์ด์ด์ง ์๊ตฌ์ฌํญ์ ๊ธฐ๋ฅ/๋น๊ธฐ๋ฅ์ผ๋ก ๋๋์ด ์ ๋ฆฌํ๊ณ , ๋ฒ์ ๊ฒฝ๊ณ๋ฅผ ๋ช
ํํ ์ ๋๋ค.
+
+## ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+- FR-1: `accounts.statement_status` ํ๋๋ฅผ `unknown`, `import_supported`, `manual_only`, `partial_import` ๊ฐ์ผ๋ก ๊ด๋ฆฌํ๊ณ , `manual_only` ๊ณ์ข๋ฅผ statement-unavailable ๊ณ์ข๋ก ์ ์ํด์ผ ํ๋ค. `partial_import` ๊ณ์ข๋ ์๋ ๋ณด์ ๋ฐ ์์ก๋์กฐ ๋์์ผ๋ก ํฌํจํ๋ค.
+- FR-1a: statement-unavailable ๊ณ์ข์ ๋ํด ์ผ์ยท๊ธ์กยท์ฆ๋น์ ํยท๋ฉ๋ชจ๋ฅผ ๊ฐ์ถ ์๋ ์ฆ๋น ๊ฑฐ๋๋ฅผ `source='manual'`๋ก ์
๋ ฅยท์์ ํ ์ ์์ด์ผ ํ๋ฉฐ, ํ์ ํ๋ ๋๋ฝ ์ ์
๋ ฅ์ ๊ฑฐ๋ถํด์ผ ํ๋ค.
+- FR-2: ๊ณ์ข๋ณ ๊ธฐ์ค ์์ก(`last_balance`)๊ณผ ๋์ ๊ฑฐ๋ ๊ธฐ๋ฐ ๊ณ์ฐ ์์ก์ ๋น๊ตํ์ฌ ์ฐจ์ก๊ณผ ๋์กฐ ์์ ์ ์ฐ์ถํ๋ ์์ก๋์กฐ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ผ ํ๋ค. ์ ๊ธฐ ๋์กฐ ๊ธฐ์ค ์์ ์ ๋งค์ ๋ง์ผ 23:59 KST์ด๊ณ , ์์ ๋์กฐ๋ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ค๋
์ท ์๊ฐ์ ๊ธฐ์ค์ผ๋ก ํ๋ค.
+- FR-3: ์์ก๋์กฐ ์ฐจ์ก์ด ์ด๊ธฐ ํ์ฉ ์ค์ฐจ 1,000์์ ์ด๊ณผํ๋ฉด ํด๋น ํญ๋ชฉ์ ํ์ธํ์ ํ๋ก ๋ผ์ฐํ
ํ๊ณ , ์ด์์๊ฐ ๊ณ์ข์ด๋/์ค์ง์ถ/๋ถ์์ ์ธ๋ก ํ์ ํด ์ฒ๋ฆฌํ ์ ์์ด์ผ ํ๋ค. ๊ธฐ์ค ์์ก ๋๋น 0.1% ๋น์จ์ ์ฐธ๊ณ ๊ฐ์ผ๋ก๋ง ํ์ํ๊ณ 1์ฐจ ํ์ ๊ธฐ์ค์๋ ์ฌ์ฉํ์ง ์๋๋ค.
+- FR-4: ํ์ธํ์ ํญ๋ชฉ์ ๊ณ์ข์ด๋, ์ค์ง์ถ, ์ค์์
, ํฌ์์ด๋, ๋ถ์์ ์ธ, ํ์ธํ์ 6๊ฐ ํ์ ๊ฐ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ด์ผ ํ๋ค. ์๋ ํ์ ๋ณด๋ค๋ ํ๋ณด ์ถ์ฒ๊ณผ ์ฌ์ฉ์ ์๋ ํ์ ์ ๊ธฐ๋ณธ์ผ๋ก ํ๋ฉฐ, ํ์ ๊ฒฐ๊ณผ๋ `cashflow_kind`์ ๋ฐ์๋์ด ์๊ฐ ํ๊ธํ๋ฆยท์นดํ
๊ณ ๋ฆฌ ์ง์ถ ๋ณด๊ณ ์์ ์ผ๊ด๋๊ฒ ์ ์ฉ๋์ด์ผ ํ๋ค.
+- FR-5: ์๋ ์ฆ๋นยท์์ก๋์กฐ ์ด์ ํ๋ฆ์ `docs/runtime-flows.md`์ ๋ฐ์ํ๊ณ , ์ด์ ์ ์ฐจ runbook์ `docs/runbooks/` ํ๋ณด๋ก ์ถ๊ฐํด์ผ ํ๋ค.
+
+## ๋น๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+์ฑ๋ฅ, ์ด์์ฑ, ์ผ๊ด์ฑ, ์๋ํ ์์ ์ฑ ๋ฑ ๊ธฐ๋ฅ ์ธ ์๊ตฌ์ฌํญ์ ์ ๋๋ค.
+
+- NFR-1: ๋ชจ๋ ๋์กฐยท์ฆ๋น ์์
์ dcy-002์ ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๋ฅผ ์ ์ ๋ก ์ํ๋์ด ์๋ณธ ์ด์ DB ํผ์ ์ํ์ ๋ฎ์ถฐ์ผ ํ๋ค.
+- NFR-2: ์ฆ๋น ์๋ฌธยท๊ณ์ข ์๋ณ์ ๋ณด๋ ๋ง์คํนยท์ต์ ๋ณด๊ด ์์น์ ๋ฐ๋ฅด๊ณ , ๋ฌธ์ยท๋ก๊ทธ์ ํ๋ฌธ ๊ณ์ข๋ฒํธ/์๊ฒฉ์ฆ๋ช
์ ๋จ๊ธฐ์ง ์์์ผ ํ๋ค.
+- NFR-3: ์์ก๋์กฐ ๊ฒฐ๊ณผ๋ ์ฌํ ๊ฐ๋ฅํด์ผ ํ๋ฉฐ, ๋์ผ ์
๋ ฅ์ ๋ํด ๋์ผํ ์ฐจ์กยทํ์ธํ์ ๋ถ๋ฅ ๊ฒฐ๊ณผ๋ฅผ ์ฐ์ถํด์ผ ํ๋ค.
+
+## ๋ฒ์ ๊ฒฝ๊ณ
+์ด๋ฒ ๋ฐ๋ณต์์ ๋ฐ๋์ ํฌํจํ ๋ฒ์์ ๋ช
์์ ์ผ๋ก ์ ์ธํ ๋ฒ์๋ฅผ ๊ตฌ๋ถํ๋ค.
+
+- In Scope
+ - `accounts.statement_status` ๊ธฐ๋ฐ statement-unavailable ๊ณ์ข ๋ถ๋ฅ์ ์๋ ์ฆ๋น ์
๋ ฅยท๊ฒ์ฆ ๊ท์น ์ ์
+ - ๊ธฐ์ค ์์ก ๋๋น ๊ณ์ฐ ์์ก ๋์กฐ, ์๋ง/์์ ๊ธฐ์ค ์์ , 1,000์ ํ์ฉ ์ค์ฐจ ๋ฐ ํ์ธํ์ ๋ผ์ฐํ
๊ท์น ์ ์
+ - ๊ณ์ข์ด๋/์ค์ง์ถ ํ์ ๊ธฐ์ค์ ๋ฌธ์ํ
+- Out of Scope
+ - statement ํ์ผ ๊ธฐ๋ฐ ์๋ import ํ์ ์ ๊ท ์ถ๊ฐ(๊ธฐ์กด KB/Toss/SeoulPay ๊ฒฝ๋ก ๋ณ๊ฒฝ ์์)
+ - ์์ก๋์กฐ ์๋ ์ค์ผ์ค๋ง/์๋ฆผ. ์ด๋ฒ ๋ฐ๋ณต์ ์๋ ์คํ ๊ฐ๋ฅํ ์์ก๋์กฐ์ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ๊น์ง๋ง ํฌํจํ๋ค.
+ - ์ ์ฉ ์์ก๋์กฐ ์ํฌ๋ฒค์น ์ ์ฒด ์ ์ค. ์ด๋ฒ ๋ฐ๋ณต์ ๊ณ์ข ํ๋ฉด์ `statement_status` ํ์/์์ ๊ณผ ์ต์ ์์ก๋์กฐ ๊ฒฐ๊ณผ ํ์๊น์ง๋ง ํฌํจํ๋ค.
+
+# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก
+๋ถํ์ค์ฑ๊ณผ ์ ์ ์กฐ๊ฑด์ ๋ถ๋ฆฌํด์ ์ ๋๋ค. ๋ฆฌ์คํฌ๋ ์ํฅ๊ณผ ์ํ ๋ฐฉ์์, ๊ฐ์ ์ ํ์ฌ ํ๋จ์ ์ ์ ๋ฅผ ๊ธฐ๋กํ๋ค.
+
+## ๋ฆฌ์คํฌ
+- R-1 (High): ์๋ ์
๋ ฅ ํด๋จผ ์๋ฌ๋ก ๊ฑฐ๋๊ฐ ์ค๋ณตยท๋๋ฝ๋์ด ์์ก๋์กฐ ์ฐจ์ก์ด ์ค์ ์ค๋ฅ๋ฅผ ๊ฐ๋ฆด ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: ํ์ ํ๋ ๊ฒ์ฆ, ์
๋ ฅ ํ ์ฌ๋์กฐ, ์ฐจ์ก ๋ณํ ๋ก๊ทธ๋ก ์ถ์ ๊ฐ๋ฅ์ฑ ํ๋ณด
+- R-2 (Medium): ๊ณ์ข์ด๋/์ค์ง์ถ ํ์ ๊ธฐ์ค์ด ๋ชจํธํ๋ฉด ๋์ผ ๊ฑฐ๋๊ฐ ์์
์๋ง๋ค ๋ค๋ฅด๊ฒ ๋ถ๋ฅ๋์ด ๋ณด๊ณ ์ ์ผ๊ด์ฑ์ด ๊นจ์ง๋ค.
+ - ์ํ ๋ฐฉ์: ์๊ธฐ๊ณ์ข ๊ฐ ์ด๋ยท์ธ๋ถ ์ง์ถยท๋ถ์์ ์ธ์ ๋ํ ๋ช
์์ ํ์ ํ ์ ์ ๋ฐ ๊ธฐ์กด `infer_cashflow_kind`/transfer ๋งค์นญ๊ณผ ์ ํฉ
+- R-3 (Medium): ํ์ฉ ์ค์ฐจ ์๊ณ๊ฐ์ด ๋ถ์ ์ ํ๋ฉด ํ์ธํ์ ํ๊ฐ ๊ณผ๋ํ๊ฒ ์ปค์ง๊ฑฐ๋ ์ค์ ์ฐจ์ด๋ฅผ ๋์น๋ค.
+ - ์ํ ๋ฐฉ์: ์ด๊ธฐ ์๊ณ๊ฐ์ ๋ณด์์ ์ผ๋ก ์ค์ ํ๊ณ ์ด์ ๋ฐ์ดํฐ๋ก ์กฐ์ , ์๊ณ๊ฐ์ ์ค์ ๊ฐ์ผ๋ก ์ธ๋ถํ
+
+## ๊ฐ์
+์์ง ํ์ ๋์ง ์์์ง๋ง ํ์ฌ ๋ฌธ์ ์์ฑ์ ๊ธฐ๋ฐ์ด ๋๋ ์ ์ ๋ฅผ ์ ๋๋ค.
+
+- A-1: `ledger_accounts.last_balance`, `transactions.balance_after`/`source`, ์ ๊ท `accounts.statement_status` ํ๋๋ฅผ ์์ก๋์กฐ์ ๊ธฐ์ค ๋ฐ์ดํฐ๋ก ์ฌ์ฌ์ฉํ ์ ์๋ค.
+- A-2: ๊ธฐ์กด transfer ๋งค์นญ(`link_self_transfer_pair`, cross-account match)๊ณผ ํ์ธํ์ review ํ ๊ตฌ์กฐ๋ฅผ ๊ณ์ข์ด๋ ํ์ ํ๋ฆ์ ์ฐ๊ณํ ์ ์๋ค.
+- A-3: ๋์กฐ ์์
์ dcy-002 ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๊ฐ ์ด๋ฏธ ์ด์ ๊ฐ๋ฅํ ์ํ์์ ์ ์ ๋ก ํ๋ค.
+
+## ์คํ ์ง๋ฌธ
+๋ต์ ์์ง ๋ชจ๋ฅด๋ ํญ๋ชฉ์ ์ ๋๋ค. ์ถ์ ์ผ๋ก ๋ฎ์ง ๋ง๊ณ REQ ์ด์ ์์ฑ ์ด์ ์ ํ์ธํ ์ง๋ฌธ์ผ๋ก ๋จ๊ธด๋ค.
+
+- OQ-1: ์์ก๋์กฐ ํ์ฉ ์ค์ฐจ(์ ๋ ๊ธ์ก/๋น์จ)์ ๊ธฐ์ค ์์ (์๋ง/์์ ์ค๋
์ท)์ ์ด๋ป๊ฒ ์ ์ํ ๊ฒ์ธ๊ฐ?
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: ์ ๊ธฐ ์์ก๋์กฐ๋ ๋งค์ ๋ง์ผ 23:59 KST ๊ธฐ์ค์ผ๋ก ์ํํ๋ค. ์์ ๋์กฐ๋ ์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์ค๋
์ท ์๊ฐ์ ๊ธฐ์ค์ผ๋ก ํ์ฉํ๋ค. ์ด๊ธฐ ํ์ฉ ์ค์ฐจ๋ ์ ๋ ๊ธ์ก 1,000์์ผ๋ก ๋๋ฉฐ, ์ฐจ์ก์ด 1,000์์ ์ด๊ณผํ๋ฉด ํ์ธํ์ ํ๋ก ๋ผ์ฐํ
ํ๋ค. ๊ธฐ์ค ์์ก ๋๋น 0.1% ๋น์จ์ ํ์/์ฐธ๊ณ ๊ฐ์ผ๋ก๋ง ์ ๊ณตํ๊ณ , 1์ฐจ ํ์ ๊ธฐ์ค์ผ๋ก๋ ์ฌ์ฉํ์ง ์๋๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: REQ์ acceptance criteria์ ๊ธฐ์ค ์์ , ํ์ฉ ์ค์ฐจ 1,000์, ํ์ธํ์ ๋ผ์ฐํ
์กฐ๊ฑด์ด ์์น๋ก ๋ฐ์๋๋ฉด Answered
+- OQ-2: ์ด๋ค ๊ณ์ข๋ฅผ "statement-unavailable"๋ก ๋ถ๋ฅยทํ์ํ ๊ฒ์ธ๊ฐ(๊ณ์ข ์์ฑ ์ ์ค ์ฌ๋ถ)?
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: `accounts` ํ
์ด๋ธ์ `statement_status` ํ๋๋ฅผ ์ถ๊ฐํ๋ค. ํ์ฉ๊ฐ์ `unknown`, `import_supported`, `manual_only`, `partial_import`๋ก ์ ์ํ๋ค. ๊ธฐ๋ณธ๊ฐ์ `unknown`์ด๋ค. `manual_only` ๊ณ์ข๋ฅผ dcy-003์ statement-unavailable ๊ณ์ข๋ก ๊ฐ์ฃผํ๊ณ , `partial_import` ๊ณ์ข๋ ์๋ ๋ณด์ ๋ฐ ์์ก๋์กฐ ๋์์ผ๋ก ํฌํจํ๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: REQ์ ๋ฐ์ดํฐ ๋ชจ๋ธ/acceptance criteria์ `statement_status` ํ๋, ํ์ฉ๊ฐ, ๊ธฐ๋ณธ๊ฐ, ์์ก๋์กฐ ๋์ ์กฐ๊ฑด์ด ๋ฐ์๋๋ฉด Answered
+
+# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค
+์ด ๋ณ๊ฒฝ์ด ์ฑ๊ณตํ๋ค๊ณ ํ๋จํ ๊ธฐ์ค์ ์ธก์ ๊ฐ๋ฅํ๊ฒ ์ ์ํ๋ค. ๊ฐ๋ฅํ๋ฉด ๋ฐ์ดํฐ ์ถ์ฒ๊น์ง ์ฐ๊ฒฐํ๋ค.
+
+- S-1: `statement_status=manual_only` ๋๋ `partial_import`์ธ ๋์ ๊ณ์ข์ ๋ํด ์๋ ์ฆ๋น ์
๋ ฅ ํ ๊ณ์ฐ ์์ก๊ณผ ๊ธฐ์ค ์์ก์ ์ฐจ์ก์ด 1,000์ ์ด๋ด๋ก ์๋ ดํ๋ค.
+- S-2: ์์ก๋์กฐ ์ฐจ์ก์ด 1,000์์ ์ด๊ณผํ๋ ๋ชจ๋ ํญ๋ชฉ์ด ํ์ธํ์ ํ์ ๋ผ์ฐํ
๋๊ณ , ๊ณ์ข์ด๋/์ค์ง์ถ/๋ถ์์ ์ธ ์ค ํ๋๋ก ํ์ ยท์ฒ๋ฆฌ๋๋ค.
+- S-3: ์๋ ์ฆ๋นยท์์ก๋์กฐ ์ด์ ํ๋ฆ์ด `docs/runtime-flows.md`์ runbook ํ๋ณด ๋ฌธ์๋ก ์ ๋ฆฌ๋์ด ํ์ REQ/Batch์์ ์ฐธ์กฐ ๊ฐ๋ฅํ๋ค.
+
+## ์ธก์ ๋ฐฉ์ ๋ฐ ๋ฐ์ดํฐ ์ถ์ฒ
+๊ฐ ์ฑ๊ณต ๊ธฐ์ค์ ์ด๋ป๊ฒ ์ธก์ ํ ์ง์ ์ด๋ค ๋ฐ์ดํฐ๋ ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก ์ธ์ง ์ ๋๋ค.
+
+- S-1 ์ธก์ ๋ฐฉ์: ๊ฒ์ฆ DB์์ `statement_status in (manual_only, partial_import)` ๊ณ์ข๋ณ (๊ณ์ฐ ์์ก โ ๊ธฐ์ค ์์ก) ์ฐจ์ก ์ฐ์ถ ๋ฐ 1,000์ ์ด๋ด ์ฌ๋ถ ํ์ธ / ๋ฐ์ดํฐ ์ถ์ฒ: `accounts.statement_status`, `ledger_accounts.last_balance`, `transactions`(`amount`/`balance_after`)
+- S-2 ์ธก์ ๋ฐฉ์: ํ์ธํ์ ํ ์ ์ฌ ๊ฑด์์ ๋ฏธํ์ ์์ฌ ๊ฑด์ ๋น๊ต / ๋ฐ์ดํฐ ์ถ์ฒ: ํ์ธํ์ ํ(์๋น์ค ๋ก์ง ๊ฒฐ๊ณผ), ํ์ ์ฒ๋ฆฌ ๋ก๊ทธ
+- S-3 ์ธก์ ๋ฐฉ์: ๋ฌธ์ ์กด์ฌ ๋ฐ ํ๋ฆ ๋ฐ์ ์ฌ๋ถ ๋ฆฌ๋ทฐ / ๋ฐ์ดํฐ ์ถ์ฒ: `docs/runtime-flows.md`, `docs/runbooks/`
+
+# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ
+REQ ์ด์ ์์ฑ ๋จ๊ณ๋ก ๋๊ธฐ๊ธฐ ์ ์ ๋ฌด์์ด ํ์ ๋์ด์ผ ํ๋์ง ํ์ธํ๋ค. ๋ฏธ์๋ฃ ํญ๋ชฉ์ ํ์ ๊ฒฐ์ ์ด ํ์ํจ์ ๋ปํ๋ค.
+
+- [x] ๋ฒ์(In Scope/Out of Scope) ํ์
+ - ๊ธฐ์ค: ์๋ ์ค์ผ์ค๋ง/์๋ฆผ๊ณผ ์ ์ฉ ์์ก๋์กฐ ์ํฌ๋ฒค์น ์ ์ฒด ์ ์ค์ ์ ์ธํ๊ณ , ์๋ ์คํ ์์ก๋์กฐยท๊ฒฐ๊ณผ ๋ฆฌํฌํธยท๊ณ์ข ํ๋ฉด์ `statement_status` ํ์/์์ ์ ํฌํจํ๋ค.
+- [x] ์ฑ๊ณต ์งํ์ ์ธก์ ๋ฐฉ์/๋ฐ์ดํฐ ์ถ์ฒ ํ์
+ - ๊ธฐ์ค: ํ์ฉ ์ค์ฐจ 1,000์, ์๋ง 23:59 KST ์ ๊ธฐ ๊ธฐ์ค ์์ , ์ฌ์ฉ์ ์
๋ ฅ ์์ ์ค๋
์ท ๊ธฐ์ค ์์ ์ด ์ธก์ ๋ฐฉ์์ ๋ฐ์๋จ
+- [x] High ์ํฅ ๋ฆฌ์คํฌ ์ฐ์ ์์ ๋ฐ ํด์ ์ฑ
์์ ์ง์
+ - ๊ธฐ์ค: R-1(์๋ ์
๋ ฅ ํด๋จผ ์๋ฌ)์ ๋ ์ ์ฅ์ธ์ด ํ์ ํ๋ ๊ฒ์ฆยท์ฌ๋์กฐยท๊ฒ์ฆ DB evidence๋ก ํด์ํ๋ค.
+- [x] ๋ชจ๋ ์คํ ์ง๋ฌธ(OQ) `Open` ์ํ ์์ ํ์ธ
+ - ๊ธฐ์ค: OQ ๋ชฉ๋ก์ `Open` ์ํ ํญ๋ชฉ์ด 0๊ฐ์ด๊ฑฐ๋, ์์ฌ ํญ๋ชฉ ์ ๋ถ `Deferred` ์ฒ๋ฆฌ ์๋ฃ
+- [x] Freeze ํ์ ๋ด๋น์(CONFIRMED_BY) ์ง์
+ - ๊ธฐ์ค: ์ฌ์ฉ์ ์งํ ๊ณ ๊ฐ ๋ณธ Discovery ๊ถ์ฅ์์ ์น์ธํ๊ณ ๊ตฌํ ์์์ ์ง์ํ๋ค.
+
+# 9. ํ์ผ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ
+์ด Discovery ๋ฌธ์ ์์ฑ/๊ฐฑ์ ๊ฒฐ๊ณผ์ ์ฐธ์กฐ ๋ฌธ์๋ฅผ ๊ธฐ๋กํ๋ค. ์ดํ ์ถ์ ๊ณผ ๋ฆฌ๋ทฐ ์ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉํ๋ค.
+
+- ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: ์์ฑ
+- ์์ฑ ๊ฒฝ๋ก: docs/discovery/dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation.md
+- ์ฐธ์กฐ ๋ฌธ์:
+ - docs/discovery/dcy-002_20260627_operational-safety-backup-restore-validation-db.md
+ - docs/runtime-flows.md
+ - docs/data-model.md
+
+# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ
+์ฌ์ฉ์๋ ์น์ธ์๊ฐ ๊ฒฐ์ ํด์ผ ํ๋ ํญ๋ชฉ๋ง ๋ชจ์ ์ ๋ฆฌํ๋ค. ๊ฒฐ์ , ํ์ธ, ๋ฐ์ดํฐ ์์ฒญ์ ๊ตฌ๋ถํด์ ์ ๋๋ค.
+
+## DECIDE
+- ์์ก๋์กฐ ์๋ ์ค์ผ์ค๋ง/์๋ฆผ์ ์ด๋ฒ ๋ฐ๋ณต ๋ฒ์์์ ์ ์ธํ๊ณ , ์๋ ์คํ ๊ฐ๋ฅํ ์์ก๋์กฐ์ ๊ฒฐ๊ณผ ๋ฆฌํฌํธ๊น์ง๋ง ํฌํจํ๋ค.
+- ์ ์ฉ ์๋ ์ฆ๋น/์์ก๋์กฐ ์ํฌ๋ฒค์น ์ ์ฒด ์ ์ค์ ์ ์ธํ๊ณ , ๊ณ์ข ํ๋ฉด์ `statement_status` ํ์/์์ ๊ณผ ์ต์ ์์ก๋์กฐ ๊ฒฐ๊ณผ ํ์๊น์ง๋ง ํฌํจํ๋ค.
+- ๊ณ์ข์ด๋, ์ค์ง์ถ, ์ค์์
, ํฌ์์ด๋, ๋ถ์์ ์ธ, ํ์ธํ์ 6๊ฐ ํ์ ๊ฐ์ ์ฌ์ฉํ๋ค. ์๋ ํ์ ๋ณด๋ค๋ ์ถ์ฒ/ํ๋ณด ์ ์ ํ ์ฌ์ฉ์ ์๋ ํ์ ์ ๊ธฐ๋ณธ์ผ๋ก ํ๋ค.
+
+## CONFIRM
+- ๋์กฐ ์์
์ dcy-002 ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ ์ ํ์ ์ด์ ํ์ค์ผ๋ก ํ์ ํ๋ค. ์ด์ DB ๋ณ๊ฒฝ ์ ๋ฐฑ์
์ ํ์์ด๋ฉฐ, ๊ฒ์ฆ DB์์ preview/reconcile์ ๋จผ์ ์ํํ๋ค. Restore๋ ์ฌ์ฉ์ ๋ช
์ ์น์ธ ์์ด๋ ์คํํ์ง ์๋๋ค.
+
+## DATA
+- ์ค์ ๊ณ์ข๋ณ ๊ธฐ์ค ์์ก ๊ฐ์ ์ด์ ์์ ์ ์
๋ ฅํ๋ค. REQ์๋ `account_id`, `statement_status`, `baseline_balance`, `baseline_at`, `source_note`, `memo` ์
๋ ฅ ํฌ๋งท๊ณผ ๊ฒ์ฆ ๊ท์น์ ์ ์ํ๋ค.
+
+# 11. Discovery Freeze
+REQ ์ด์ ์์ฑ ์ธ๊ณ ์์ ์ ํ์ ํ๋๊ทธ์ ์น์
์ฐธ์กฐ๋ง ๊ธฐ๋กํ๋ค. ๋ด์ฉ ์ฌ์์ฑ ์์ด ์๋ณธ ์น์
์ ๋งํฌ๋ก ๋์ ํ๋ค.
+
+## ์น์
์ฐธ์กฐ
+- ๋ฒ์: [# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก > ๋ฒ์ ๊ฒฝ๊ณ](#5-์๊ตฌ์ฌํญ-๋ชฉ๋ก)
+- ๋ฆฌ์คํฌ/์คํ ์ง๋ฌธ: [# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก](#6-๋ฆฌ์คํฌ๊ฐ์ -๋ชฉ๋ก)
+- ์ฑ๊ณต ๊ธฐ์ค: [# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค](#7-์ด๊ธฐ-์ฑ๊ณต-๊ธฐ์ค)
+- ์ธ๊ณ ์ ํ์ธ: [# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ](#8-req๋ก-๋๊ธฐ๊ธฐ-์ -ํ์ธ-์ฒดํฌ)
+- ์ฌ์ฉ์ ๊ฒฐ์ ํ์: [# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ](#10-์ฌ์ฉ์-๊ฒฐ์ -ํ์-ํญ๋ชฉ-์์ฝ)
+
+## Freeze ํ๋๊ทธ
+- Handoff Decision: ์น์ธ
+- Handoff Rationale: ์ฌ์ฉ์ ์น์ธ์ผ๋ก ์์ก๋์กฐ ํ์ฉ ์ค์ฐจ, ๊ธฐ์ค ์์ , statement-unavailable ๊ณ์ข ๋ถ๋ฅ ๋ฐฉ์, ์๋ํ/UI ๋ฒ์, ํ์ ๊ธฐ์ค, ๋ฐฑ์
์ ํ ์กฐ๊ฑด, ๊ธฐ์ค ์์ก ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐฉ์์ด ํ์ ๋์์ผ๋ฏ๋ก REQ ์ด์ ์์ฑ๊ณผ ๊ตฌํ ์์์ผ๋ก ๋๊ธด๋ค.
+- Ready for REQ Drafting: true
+- Confirmed By: ์งํ ๊ณ
+- Confirmed At (KST): 2026-06-29 13:06
diff --git a/docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md b/docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md
new file mode 100644
index 0000000..e07ae56
--- /dev/null
+++ b/docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md
@@ -0,0 +1,210 @@
+# 0. ๋ฌธ์ ์ํ
+์ด ๋ฌธ์์ ํ์ฌ ์ํ์ ์๋ณ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ค. ๋ฌธ์ ์ถ์ ๊ณผ ์น์ธ ํ๋ฆ์ ํ์ํ ์ต์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ค.
+
+- ์ํ: confirmed
+- ๋ฌธ์ ID: dcy-004_20260629_browser-streamlit-e2e-evidence-recovery
+- ์ด์๋ช
: Browser/Streamlit E2E evidence ํ๊ฒฝ ๋ณต๊ตฌ ๋ฐ QC evidence ๊ธฐ์ค ๊ฐํ
+- ์์ฑ ์๊ฐ(KST): 2026-06-29 13:29
+- ๋ง์ง๋ง ๊ฐฑ์ ์๊ฐ(KST): 2026-06-30 13:32
+- ๋์ฒด๋จ: ์์
+- ํ์ Discovery ์ฐธ์กฐ: docs/discovery/dcy-006_20260630_pytest-apptest-smoke-ci-automation.md
+- ์์ฑ๋ REQ ์ฐธ์กฐ: REQ-004 (`docs/srs/Testing/req-004_browser-streamlit-e2e-evidence-recovery.md`)
+
+# 1. ๊ณํ ์ํ ์์ฝ
+ํ์ฌ Discovery๊ฐ ์ด๋ค ๋ฐฐ๊ฒฝ์์ ์์ฑ๋๊ณ ์๋์ง, ์ฐ๊ณ ๋ฌธ์๊ฐ ์๋์ง, ์ง๊ธ ์์ ์ ํด์์ด ๋ฌด์์ธ์ง ์งง๊ฒ ์์ฝํ๋ค.
+
+- ์ฐ๊ณ ๋ฌธ์ ํํฉ:
+ - ๊ด๋ จ GitHub Project ํญ๋ชฉ: https://github.com/users/genichin/projects/4 DraftIssue (Browser/Streamlit E2E evidence ํ๊ฒฝ ๋ณต๊ตฌ)
+ - ๊ด๋ จ TODO/๋ฉ๋ชจ: `docs/batches/bat-001_20260626_category-rule-ops/verification.md`์ "Future Streamlit UX/E2E batches should restore real browser/screenshot evidence" ๋ฉ๋ชจ
+- ํด์:
+ - dcy-001(Streamlit UX/E2E ์์ ํ)๋ถํฐ bat-001 ๊ฒ์ฆ๊น์ง, browser-level screenshot ์ฆ๊ฑฐ๋ ๋ก์ปฌ Chromium snap ๋ถ์ฌ๋ก ํ ๋ฒ๋ ํ๋ณด๋์ง ๋ชปํ๊ณ AppTest + HTTP 200 smoke๊ฐ ๋์ฒด ์ฆ๊ฑฐ๋ก ๋์ ์ฌ์ฉ๋์ด ์๋ค.
+ - ์ด๋ฒ Discovery๋ ํน์ ๊ธฐ๋ฅ ์ถ๊ฐ๊ฐ ์๋๋ผ ๊ฒ์ฆ ์ธํ๋ผ ๋ณต๊ตฌ๋ฅผ ๋
๋ฆฝ ์ฃผ๊ธฐ๋ก ๋ค๋ฃฌ๋ค. ์ฆ Chromium/screenshot ๊ธฐ๋ฐ browser-level Streamlit E2E ์ฆ๊ฑฐ ์์ง ํ๊ฒฝ์ ๋ณต๊ตฌํ๊ณ , ๊ฒ์๊ด QC gate๊ฐ ์๊ตฌํ๋ evidence ๊ธฐ์ค์ ๊ฐํํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ๋ค.
+
+# 2. ์๊ตฌ์ฌํญ ํ์ ๊ฒฐ๊ณผ
+์ด๋ฒ ์์ฒญ์ด ๊ธฐ์กด ์๊ตฌ์ฌํญ๊ณผ ๋น๊ตํด ์ ๊ท์ธ์ง, ์ ์ฌํ์ง, ์ค๋ณต์ธ์ง ํ๋จํ๊ณ ๊ทธ ๊ทผ๊ฑฐ๋ฅผ ๋จ๊ธด๋ค.
+
+- ํ์ : ์ ๊ท
+- ๊ทผ๊ฑฐ
+ - dcy-001์ Streamlit UX/E2E ์ํฌํ๋ก์ฐ ์์ฒด์ acceptance criteria ์ ์๊ฐ ์ค์ฌ์ด๋ฉฐ, "browser ์ฆ๊ฑฐ๋ฅผ ์ด๋ป๊ฒ ํ๋ณดยทํ์คํํ ์ง"๋ผ๋ ๊ฒ์ฆ ํ๊ฒฝ/QC ๊ธฐ์ค ๋ฌธ์ ๋ฅผ ๋
๋ฆฝ ์ฑ๊ณต ๊ธฐ์ค์ผ๋ก ๋ค๋ฃจ์ง ์์๋ค. ํด๋น Discovery์์ OQ-1(์๋ ์ฒดํฌ๋ฆฌ์คํธ vs Playwright/Selenium ์๋ํ)๋ ๋ฏธํด๊ฒฐ๋ก ๋จ์ ์์๋ค.
+ - bat-001 verification๊ณผ req-001 QC note๋ browser screenshot ๋ถ์ฌ๋ฅผ ๋ฐ๋ณต์ ์ผ๋ก `PASS_WITH_NOTES`์ ์์ฌ ํญ๋ชฉ์ผ๋ก ๊ธฐ๋กํ์ ๋ฟ, ํ๊ฒฝ ๋ณต๊ตฌ๋ฅผ ํ์ ์์
์ผ๋ก ์ ์ ์ถ์ ํ์ง ์์๋ค. ์ด๋ฅผ ๋
๋ฆฝ ์ฃผ๊ธฐ๋ก ๋์ด์ฌ๋ ค ์ถ์ ํ๋ ๊ฒ์ด ์ฑ
์ ๊ฒฝ๊ณ์ ๋ช
ํํ๋ค.
+
+# 3. ๋ฌธ์ ์ ์ ์์ฝ
+๋ฌธ์ ์ ํ์ฌ ์ํ, ๊ธฐ๋ ์ํ, ๊ด๋ จ ์ดํด๊ด๊ณ์, ์ ์ฝ์ฌํญ์ ์ ๋ฆฌํ๋ค. Discovery์ ํต์ฌ ๋งฅ๋ฝ์ ๊ฐ์ฅ ๋จผ์ ์ดํดํ ์ ์์ด์ผ ํ๋ค.
+
+- ํ์ฌ ์ํ
+ - browser ์ฆ๊ฑฐ ์์ง์ด ์ฐจ๋จ๋์ด ์๋ค: `bat-001` ๊ตฌํ/๊ฒ์ฆ ๊ธฐ๋ก์ ๋ฐ๋ฅด๋ฉด `/usr/bin/chromium-browser`๊ฐ ๋ฏธ์ค์น chromium snap์ ์๊ตฌํด `Chrome exited early ... requires the chromium snap to be installed`๋ก ์คํจํ๊ณ , browser tool์ ํ์ด์ง ๋ก๋ ์ ์ ์ข
๋ฃ๋์๋ค.
+ - ๋์ฒด ์ฆ๊ฑฐ๊ฐ ํ์ค์ฒ๋ผ ๊ตณ์ด์ก๋ค: `streamlit.testing.v1.AppTest`์ ํฌํธ 8502 HTTP 200 smoke(synthetic fixture DB `/tmp/jarca-ledger-bat-001-smoke.sqlite3`)๊ฐ browser screenshot ๋์ ๋ฐ๋ณต ์ฌ์ฉ๋์๋ค(`bat-001/implementation.md`, `bat-001/verification.md`).
+ - QC gate๊ฐ ์ด๋ฅผ ํ์ฉํด ์๋ค: `req-001`์ QC note๋ `PASS_WITH_NOTES`๋ก ํต๊ณผํ๋ "browser screenshot unavailable due Chromium snap absence; future batches should restore real browser/screenshot evidence"๋ฅผ ์์ฌ ํญ๋ชฉ์ผ๋ก ๋จ๊ฒผ๋ค. ์ฆ evidence ๋ฑ๊ธ์ ๋ํ ๋ช
์์ ๊ธฐ์คยท๊ฒ์ดํธ ๊ท์น์ด ๋ฌธ์ํ๋์ด ์์ง ์๋ค.
+ - baseline ๋ฌธ์๋ค๋ ์ด gap์ ์ธ์งํ๊ณ ์๋ค: `docs/project-structure.md`("Final UX verification is still needed ... import E2E flows"), `docs/runtime-flows.md`("Need final browser-level UX verification ..."), `docs/interface-contract.md`("Browser-level UI acceptance criteria for Streamlit flows are not yet captured as REQ documents")๊ฐ ๋ชจ๋ browser-level ๊ฒ์ฆ ๋ฏธ๋น๋ฅผ ๊ธฐ๋กํ๋ค.
+- ๊ธฐ๋ ์ํ
+ - ์ด์์/๊ฒ์๊ด์ด ๋ก์ปฌ ๋๋ CI ํ๊ฒฝ์์ Chromium์ ์ฌํ ๊ฐ๋ฅํ๊ฒ ๊ธฐ๋ํ์ฌ Streamlit ์ฃผ์ ํ๋ฉด(๋ถ๋ฅ ๊ท์น, ํ์ธํ์ ํ, import preview, ์์ก๋์กฐ ๊ฒฐ๊ณผ ๋ฑ)์ browser-level screenshot ์ฆ๊ฑฐ๋ฅผ ์์ ์ ์ผ๋ก ์์งํ ์ ์๋ค.
+ - evidence ๋ฑ๊ธ(์: browser-screenshot E2E > AppTest semi-auto > HTTP 200 smoke)์ด ๋ช
์์ ์ผ๋ก ์ ์๋๊ณ , ์ด๋ค ์ข
๋ฅ์ batch์ ์ด๋ค ์ต์ ์ฆ๊ฑฐ๊ฐ ํ์ํ์ง QC gate ๊ท์น์ผ๋ก ๋ฌธ์ํ๋๋ค.
+ - "Chromium ๋ถ์ฌ โ ๋์ฒด ์ฆ๊ฑฐ ํ์ฉ"์ด ๋ฌด๊ธฐํ ๊ธฐ๋ณธ๊ฐ์ด ์๋๋ผ, ํ๊ฒฝ ๋ณต๊ตฌ ํ์๋ browser ์ฆ๊ฑฐ๊ฐ 1์์ ๊ธฐ์ค์ด ๋๋๋ก QC evidence ๊ธฐ์ค์ด ๊ฐํ๋๋ค.
+- ์ดํด๊ด๊ณ์
+ - ๋ ์ ์ฅ์ธ(์ด์์ ๊ฒธ ์์ฑ์) โ Streamlit E2E ๊ฒ์ฆ ์คํ ๋ฐ ์ฆ๊ฑฐ ์์ง ์ฃผ์ฒด
+ - ๊ฒ์๊ด/๋ฆฌ๋ทฐ์ด โ QC gate์์ evidence ๋ฑ๊ธ๊ณผ ์ถฉ์กฑ ์ฌ๋ถ๋ฅผ ํ์ ํ๋ ์ฃผ์ฒด
+ - StagePilot ํ์ SDLC(REQ/Batch) ์ํ์ โ ๋ณธ Discovery๋ฅผ ๊ฒ์ฆ ํ๊ฒฝ ์๊ตฌ์ฌํญ์ผ๋ก ์ ์
+- ์ ์ฝ์ฌํญ
+ - ์คํ ํ๊ฒฝ(snap ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ, ์ปจํ
์ด๋/CI ๊ถํ, ํค๋๋ฆฌ์ค ๊ฐ๋ฅ ์ฌ๋ถ)์ด ๊ณ ์ ์ ์ด์ง ์์ ๋จ์ผ ์ค์น ๋ฐฉ์์ ๊ฐ์ ํ๊ธฐ ์ด๋ ต๋ค.
+ - ๊ฐ์ธ ๊ธ์ต์ ๋ณด๋ฅผ ๋ค๋ฃจ๋ฏ๋ก screenshot ์ฆ๊ฑฐ์๋ ์ค๊ณ์ข๋ฒํธยท์์ก ๋ฑ ๋ฏผ๊ฐ์ ๋ณด๊ฐ ๋
ธ์ถ๋์ง ์๋๋ก synthetic fixture ๋๋ ๋ง์คํน์ ์ ์ ๋ก ํ๋ค.
+ - ์ด์ DB ํผ์์ ํผํ๊ธฐ ์ํด E2E๋ fixture/๋ณต์ฌ๋ณธ DB์์ ์ํํ๋ฉฐ, dcy-002 ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๋ฅผ ์ ์ ๋ก ํ๋ค.
+
+## ์ฃผ์ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
+์ฌ์ฉ์ ๋๋ ์ด์์๊ฐ ์ค์ ๋ก ์ด๋ค ์ํฉ์์ ์ด ๋ณ๊ฒฝ์ ํ์๋ก ํ๋์ง ๋ํ ์๋๋ฆฌ์ค๋ฅผ ์ ๋๋ค.
+
+- ๊ฒ์๊ด์ด ์ Streamlit UX batch๋ฅผ QC ๊ฒ์ํ ๋, AppTest ๋์ฒด ์ฆ๊ฑฐ๊ฐ ์๋๋ผ ์ค์ browser screenshot์ผ๋ก ํ๋ฉด ๋ ๋๋งยท๋ฒํผ ๋์์ ํ์ธํ๊ณ evidence๋ฅผ ์ฒจ๋ถํ๋ค.
+- ์ด์์๊ฐ ๋ถ๋ฅ ๊ท์น/ํ์ธํ์ ํ ํ๋ฉด ๋ณ๊ฒฝ์ ๊ฒ์ฆํ ๋, fixture DB๋ก Streamlit์ ๊ธฐ๋ํ๊ณ headless Chromium์ผ๋ก ์ฃผ์ ํ๋ก์ฐ๋ฅผ ์คํฌ๋ฆฐ์ท ์บก์ฒํ์ฌ ์ฌํ ๊ฐ๋ฅํ ์ฆ๊ฑฐ ๋๋ ํฐ๋ฆฌ์ ์ ์ฅํ๋ค.
+- ๊ฒ์๊ด์ด browser ์ฆ๊ฑฐ๋ฅผ ํ๋ณดํ ์ ์๋ ํ๊ฒฝ์ ํํด, ์ ์๋ evidence ๋ฑ๊ธ ๊ท์น์ ๋ฐ๋ผ ๋ช
์์ ์ผ๋ก ๋์ฒด ์ฆ๊ฑฐ(AppTest+HTTP 200)๋ฅผ `PASS_WITH_NOTES`๋ก๋ง ํ์ฉํ๊ณ ์์ฌ ํญ๋ชฉ์ ์ถ์ ํ๋ค.
+
+# 4. ์ด๋ฒ์ ์ ์ํ ๋ณ๊ฒฝ
+์ด๋ฒ Discovery์์ ๋ค๋ฃจ๋ ํต์ฌ ๋ณ๊ฒฝ ํญ๋ชฉ์ ๋ชฉ๋ก์ผ๋ก ์ ๋๋ค. ๊ธฐ๋ฅ, ๊ตฌ์กฐ, ๊ท์น ๋ณ๊ฒฝ์ ํจ๊ป ํฌํจํด๋ ๋๋ค.
+
+- Chromium/screenshot ๊ธฐ๋ฐ browser-level Streamlit E2E ์ฆ๊ฑฐ ์์ง ํ๊ฒฝ์ ๋ณต๊ตฌํ๋ค. ์ฆ ๋ฏธ์ค์น chromium snap์ ์์กดํ์ง ์๊ณ ์ฌํ ๊ฐ๋ฅํ ๋ฐฉ์์ผ๋ก headless Chromium์ ๊ธฐ๋ํ๋ ํ์ค ์ ์ฐจ๋ฅผ ์ ์ํ๋ค.
+- browser E2E ์ฆ๊ฑฐ ์์ง์ ์๋ํ ๊ฐ๋ฅํ smoke/E2E ์ ์ฐจ(์: fixture DB๋ก Streamlit ๊ธฐ๋ โ ์ฃผ์ ํ๋ฉด ์คํฌ๋ฆฐ์ท ์บก์ฒ โ ์ฆ๊ฑฐ ๋๋ ํฐ๋ฆฌ ์ ์ฅ)๋ก ํ์คํํ๋ค.
+- QC evidence ๋ฑ๊ธ๊ณผ ๊ฒ์ดํธ ๊ท์น์ ๊ฐํํ๋ค: browser-screenshot E2E๋ฅผ 1์์ ์ฆ๊ฑฐ๋ก ๋๊ณ , AppTest/HTTP 200 ๋์ฒด ์ฆ๊ฑฐ๋ ํ๊ฒฝ ์ ์ฝ์ด ์
์ฆ๋ ๊ฒฝ์ฐ์๋ง `PASS_WITH_NOTES`๋ก ํ์ฉํ๋ ๊ธฐ์ค์ ๋ฌธ์ํํ๋ค.
+- ๊ฒ์ฆ ํ๊ฒฝยท์ฆ๊ฑฐ ์ ์ฐจ๋ฅผ `docs/runtime-flows.md`(๊ฒ์ฆ ํ๋ฆ)์ `docs/project-structure.md`(ํ
์คํธ/์ฆ๊ฑฐ ๋๊ตฌ ๊ตฌ์กฐ), ๊ทธ๋ฆฌ๊ณ ๊ฒ์ฆ runbook ํ๋ณด์ ๋ฐ์ํ ํ์๋ฅผ ๋ช
์ํ๋ค.
+
+## ์ํฅ ๋ฒ์
+๋ณ๊ฒฝ์ด ๋ฏธ์น ์ฝ๋/๋ฌธ์/์ด์ ๋ฒ์๋ฅผ ๋ช
์ํด REQ ์ ์ ์ batch planning์์ ์ถ์ ๋๋ฝ์ ์ค์ธ๋ค.
+
+- ์ฝ๋ ์ํฅ:
+ - `tests/` โ Streamlit E2E/browser smoke ์คํฌ๋ฆฝํธ(ํค๋๋ฆฌ์ค Chromium ๊ธฐ๋, ์คํฌ๋ฆฐ์ท ์บก์ฒ)์ fixture DB ์ค๋น ์ ํธ๋ฆฌํฐ ์ ์ค ๊ฐ๋ฅ์ฑ. ๊ธฐ์กด `streamlit.testing.v1.AppTest` smoke๋ ๋ณด์กดํ๋ ๋ณด์กฐ ์ฆ๊ฑฐ๋ก ์ฌ๋ฐฐ์น.
+ - ๊ฒ์ฆ ๋ณด์กฐ ์คํฌ๋ฆฝํธ/์คํ ๋ํผ โ Chromium ๊ธฐ๋ยทscreenshot ์ ์ฅ ๊ฒฝ๋กยท์ฆ๊ฑฐ ์์ง์ ์ฌํ ๊ฐ๋ฅํ๊ฒ ๋ฌถ๋ ์ง์
์ (์ฝ๋ ์์น๋ REQ/Batch์์ ํ์ ).
+ - `dashboard/app.py` โ ์์น์ ์ผ๋ก ๊ธฐ๋ฅ ๋ณ๊ฒฝ ์์. E2E ์์ ํ๋ฅผ ์ํ ์ต์ ํ
์คํธ ํ
(์: ๊ฒฐ์ ์ ๋ ๋๋ง)์ด ํ์ํ ๊ฒฝ์ฐ์ ํํด ํ์ ๋ฒ์๋ก ๊ฒํ .
+- ๋ฌธ์ ์ํฅ:
+ - `docs/runtime-flows.md` โ browser-level Streamlit E2E ๊ฒ์ฆ ํ๋ฆ๊ณผ ์ฆ๊ฑฐ ์์ง ๋จ๊ณ ์ถ๊ฐ.
+ - `docs/project-structure.md` โ ํ
์คํธ/์ฆ๊ฑฐ ๋๊ตฌ(ํค๋๋ฆฌ์ค Chromium, E2E ์คํฌ๋ฆฝํธ, ์ฆ๊ฑฐ ๋๋ ํฐ๋ฆฌ) ๊ตฌ์กฐ ๋ฐ์ ๋ฐ ๊ธฐ์กด "browser-level verification still needed" ํญ๋ชฉ ๊ฐฑ์ .
+ - `docs/interface-contract.md` โ browser-level UI acceptance/evidence ๊ธฐ์ค์ REQํ ํ์์ฑ ๋ฐ์ ๊ฒํ .
+ - `docs/runbooks/` โ Streamlit E2E ์ฆ๊ฑฐ ์์ง ์ด์ runbook ์ ๊ท ํ๋ณด.
+- ์ด์ ์ํฅ:
+ - QC gate ์ด์ ๊ธฐ์ค์ด ๊ฐํ๋์ด, browser ์ฆ๊ฑฐ ๊ฐ๋ฅ ํ๊ฒฝ์์๋ ๋์ฒด ์ฆ๊ฑฐ๋ง์ผ๋ก PASS๋ฅผ ํ์ฉํ์ง ์๋ ์ด์ ๊ท์น์ด ์ ์ค๋๋ค.
+ - E2E๋ synthetic fixture ๋๋ dcy-002 ๊ฒ์ฆ DB ๋ณต์ฌ๋ณธ์์ ์ํํ๋ ์ด์ ๋ฃจํด์ด ์ถ๊ฐ๋๋ค.
+
+# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก
+์ค์ ๋ก ๊ตฌํ๊ณผ ๊ฒ์ฆ์ผ๋ก ์ด์ด์ง ์๊ตฌ์ฌํญ์ ๊ธฐ๋ฅ/๋น๊ธฐ๋ฅ์ผ๋ก ๋๋์ด ์ ๋ฆฌํ๊ณ , ๋ฒ์ ๊ฒฝ๊ณ๋ฅผ ๋ช
ํํ ์ ๋๋ค.
+
+## ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+- FR-1: ๋ฏธ์ค์น chromium snap์ ์์กดํ์ง ์๊ณ ์ฌํ ๊ฐ๋ฅํ๊ฒ headless Chromium์ ๊ธฐ๋ํ ์ ์๋ ํ์ค ์คํ ์ ์ฐจ๋ฅผ ์ ๊ณตํด์ผ ํ๋ค. ์คํ ํ๊ฒฝ์ด Chromium์ ์ ๊ณตํ์ง ๋ชปํ๋ ๊ฒฝ์ฐ ๋ช
ํํ ์ง๋จ ๋ฉ์์ง์ ํจ๊ป ๋์ฒด ์ฆ๊ฑฐ ๊ฒฝ๋ก๋ก ์๋ดํด์ผ ํ๋ค.
+- FR-2: fixture DB๋ก Streamlit์ ๊ธฐ๋ํ๊ณ ์ฃผ์ ํ๋ฉด(์ต์: ๋ถ๋ฅ ๊ท์น, ํ์ธํ์ ํ, import preview)์ browser-level๋ก ๋ ๋๋งํด ์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ๋ฅผ ์ ํด์ง ์ฆ๊ฑฐ ๋๋ ํฐ๋ฆฌ์ ์ ์ฅํ๋ E2E/smoke ์ ์ฐจ๋ฅผ ์ ๊ณตํด์ผ ํ๋ค.
+- FR-3: QC evidence ๋ฑ๊ธ์ ์ ์ํ๊ณ (์: browser-screenshot E2E > AppTest semi-auto > HTTP 200/service smoke), ๊ฐ batch ์ ํ์ ํ์ํ ์ต์ ์ฆ๊ฑฐ์ ๊ฒ์ดํธ ๊ฒฐ๊ณผ(`PASS`/`PASS_WITH_NOTES`) ๊ท์น์ ๋ฌธ์ํํด์ผ ํ๋ค.
+- FR-4: browser ์ฆ๊ฑฐ ์์ง์ด ํ๊ฒฝ์ ์ผ๋ก ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ์๋ง ๋์ฒด ์ฆ๊ฑฐ๋ฅผ ํ์ฉํ๋ฉฐ, ๊ทธ ์ฌ์ ์ ์์ฌ ํญ๋ชฉ์ ๊ฒ์ฆ ๋ฌธ์์ ๋ช
์์ ์ผ๋ก ๊ธฐ๋กํ๋๋ก ๊ฐ์ ํด์ผ ํ๋ค.
+- FR-5: ๊ฒ์ฆ ํ๊ฒฝยท์ฆ๊ฑฐ ์ ์ฐจ๋ฅผ `docs/runtime-flows.md`์ `docs/project-structure.md`์ ๋ฐ์ํ๊ณ , Streamlit E2E ์ฆ๊ฑฐ ์์ง runbook์ `docs/runbooks/` ํ๋ณด๋ก ์ถ๊ฐํด์ผ ํ๋ค.
+
+## ๋น๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+์ฑ๋ฅ, ์ด์์ฑ, ์ผ๊ด์ฑ, ์๋ํ ์์ ์ฑ ๋ฑ ๊ธฐ๋ฅ ์ธ ์๊ตฌ์ฌํญ์ ์ ๋๋ค.
+
+- NFR-1: E2E ์ฆ๊ฑฐ ์์ง์ ์ฌํ ๊ฐ๋ฅํด์ผ ํ๋ฉฐ, ๋์ผ fixtureยท๋์ผ ์ ์ฐจ์ ๋ํด ๋์ผํ ํ๋ฉด ์ฆ๊ฑฐ๋ฅผ ์ฐ์ถํ ์ ์์ด์ผ ํ๋ค(๊ฒฐ์ ์ fixture, ๊ณ ์ ํฌํธ/๊ฒฝ๋ก ๊ท์น).
+- NFR-2: ๋ชจ๋ E2E ๊ฒ์ฆ์ synthetic fixture ๋๋ dcy-002 ๊ฒ์ฆ DB ๋ณต์ฌ๋ณธ์์ ์ํํ์ฌ ์๋ณธ ์ด์ DB๋ฅผ ํผ์ํ์ง ์์์ผ ํ๋ฉฐ, ์คํฌ๋ฆฐ์ท์ ์ค๊ณ์ข๋ฒํธยท์์ก ๋ฑ ๋ฏผ๊ฐ์ ๋ณด๊ฐ ํ๋ฌธ์ผ๋ก ๋
ธ์ถ๋์ง ์์์ผ ํ๋ค.
+- NFR-3: ํ๊ฒฝ ์์กด์ฑ(Chromium ์ค์น ๋ฐฉ์)์ ๋จ์ผ ๋ฌผ๋ฆฌ ๋ฐฉ์์ ๊ณ ์ ํ์ง ์๊ณ ์คํ ํ๊ฒฝ๋ณ๋ก ํด์ ๊ฐ๋ฅํ ์ ์ฐจ๋ก ๋ฌธ์ํํ์ฌ, snap ๋ถ์ฌ ๊ฐ์ ๋จ์ผ ์คํจ ์ง์ ์ ๋ค์ ๊ฐํ์ง ์์์ผ ํ๋ค.
+
+## ๋ฒ์ ๊ฒฝ๊ณ
+์ด๋ฒ ๋ฐ๋ณต์์ ๋ฐ๋์ ํฌํจํ ๋ฒ์์ ๋ช
์์ ์ผ๋ก ์ ์ธํ ๋ฒ์๋ฅผ ๊ตฌ๋ถํ๋ค.
+
+- In Scope
+ - headless Chromium ๊ธฐ๋ ํ์ค ์ ์ฐจ ๋ณต๊ตฌ์ browser-level Streamlit ์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ ์์ง ์ ์ฐจ ์ ์
+ - QC evidence ๋ฑ๊ธยท๊ฒ์ดํธ ๊ท์น ์ ์ ๋ฐ ๋ฌธ์ํ(๊ฐํ๋ evidence ๊ธฐ์ค)
+ - ๊ฒ์ฆ ํ๋ฆ/๋๊ตฌ ๊ตฌ์กฐ์ `docs/runtime-flows.md`ยท`docs/project-structure.md` ๋ฐ์๊ณผ ๊ฒ์ฆ runbook ํ๋ณด ์ถ๊ฐ
+- Out of Scope
+ - Streamlit ๊ธฐ๋ฅ/ํ๋ฉด ์์ฒด์ ์ ๊ท ๊ธฐ๋ฅ ์ถ๊ฐ๋ UX ์ฌ์ค๊ณ(๊ธฐ์กด ํ๋ฉด ๊ฒ์ฆ์ด ๋ชฉ์ ์ด๋ฉฐ ๊ธฐ๋ฅ ๋ณ๊ฒฝ ์๋)
+ - full CI/CD ํ์ดํ๋ผ์ธ ์ ์ค ๋ฐ ์ธ๋ถ ํธ์คํ
๋์
. CI ํตํฉ์ Project #4์ `pytest/AppTest/Smoke CI ์๋ํ` ํญ๋ชฉ์ผ๋ก ๋ถ๋ฆฌํ๊ณ , ์ด๋ฒ ๋ฐ๋ณต์ ๋ก์ปฌ ์ฌํ ์ ์ฐจ๊น์ง๋ง ํฌํจ
+ - ์๊ฐ ํ๊ท(pixel-diff) ์๋ ๋น๊ต ๋ฑ ๊ณ ๋ํ๋ visual regression. ์ด๋ฒ ๋ฐ๋ณต์ ์ฆ๊ฑฐ ์บก์ฒยท์ ์ฅยท๋ฑ๊ธํ๊น์ง๋ก ํ์
+
+# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก
+๋ถํ์ค์ฑ๊ณผ ์ ์ ์กฐ๊ฑด์ ๋ถ๋ฆฌํด์ ์ ๋๋ค. ๋ฆฌ์คํฌ๋ ์ํฅ๊ณผ ์ํ ๋ฐฉ์์, ๊ฐ์ ์ ํ์ฌ ํ๋จ์ ์ ์ ๋ฅผ ๊ธฐ๋กํ๋ค.
+
+## ๋ฆฌ์คํฌ
+- R-1 (High): ์คํ ํ๊ฒฝ์ด ์ฌ์ ํ Chromium ์ค์น๋ฅผ ์ ์ฝํ๋ฉด(snap ์ ์ฉ, ๊ถํ ๋ถ์กฑ ๋ฑ) ํ๊ฒฝ ๋ณต๊ตฌ ์์ฒด๊ฐ ์ฐจ๋จ๋์ด ์ด๋ฒ ๋ฐ๋ณต ๋ชฉํ๊ฐ ๋ฏธ๋ฌ์ฑ๋ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: ํ์ฌ ์ค์น๋ `/usr/bin/google-chrome-stable` 149.0.7827.200์ 1์ฐจ runtime์ผ๋ก ์ฌ์ฉํ๊ณ , snap ๊ธฐ๋ฐ `chromium-browser`๋ ํ์ค ๊ฒฝ๋ก์์ ์ ์ธํ๋ค. ์คํจ ์ ์ง๋จยท๋์ฒด ์ฆ๊ฑฐ ๊ฒฝ๋ก๋ฅผ ๋ช
ํํ ๋จ๊ธด๋ค.
+- R-2 (Medium): Streamlit ํ๋ฉด์ด ๋น๊ฒฐ์ ์ ์ผ๋ก ๋ ๋๋ง๋๋ฉด(๋น๋๊ธฐ ๋ก๋ฉ, ๋์ ๋ฐ์ดํฐ) ์คํฌ๋ฆฐ์ท ์ฆ๊ฑฐ๊ฐ ๋ถ์์ ํด์ ธ ์ฌํ์ฑ์ด ๊นจ์ง๋ค.
+ - ์ํ ๋ฐฉ์: ๊ฒฐ์ ์ fixture DB์ ๋ก๋ ์๋ฃ ๋๊ธฐ ์กฐ๊ฑด์ ํ์คํํ๊ณ , ์บก์ฒ ์ ์์ ํ ๋จ๊ณ๋ฅผ ์ ์ฐจ์ ํฌํจํ๋ค.
+- R-3 (Medium): evidence ๊ธฐ์ค์ ๊ณผ๋ํ๊ฒ ๊ฐํํ๋ฉด Chromium ๋ถ๊ฐ ํ๊ฒฝ์์ ์ ๋นํ batch๊น์ง ๊ฒ์ดํธ์ ๋งํ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: "ํ๊ฒฝ ์ ์ฝ์ด ์
์ฆ๋ ๊ฒฝ์ฐ `PASS_WITH_NOTES` ํ์ฉ + ์์ฌ ํญ๋ชฉ ์ถ์ "์ ์์ธ ๊ฒฝ๋ก๋ฅผ ๊ท์น์ ๋ช
์ํด ๊ฐํ์ ์ด์ ํ์ค์ฑ์ ์๋ฆฝ์ํจ๋ค.
+
+## ๊ฐ์
+์์ง ํ์ ๋์ง ์์์ง๋ง ํ์ฌ ๋ฌธ์ ์์ฑ์ ๊ธฐ๋ฐ์ด ๋๋ ์ ์ ๋ฅผ ์ ๋๋ค.
+
+- A-1: ์คํจ ์์ธ์ Streamlit/๋์๋ณด๋ ์ฝ๋ ๊ฒฐํจ์ด ์๋๋ผ Chromium ์คํ ํ๊ฒฝ ๋ถ์ฌ์ด๋ฉฐ, ํ๊ฒฝ ํ๋ณด ์ ๊ธฐ์กด AppTest๋ก ํต๊ณผํ๋ ํ๋ฉด์ browser-level๋ก๋ ๊ฒ์ฆ ๊ฐ๋ฅํ๋ค.
+- A-2: ๊ธฐ์กด `streamlit.testing.v1.AppTest` smoke์ ํฌํธ 8502 HTTP 200 ํจํด, synthetic fixture DB ๋ฐฉ์์ browser E2E์ ๋ณด์กฐ ์ฆ๊ฑฐ ๋ฐ ๊ธฐ๋ ํ ๋๋ก ์ฌ์ฌ์ฉํ ์ ์๋ค.
+- A-3: E2E ๊ฒ์ฆ์ dcy-002์ ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๊ฐ ์ด์ ๊ฐ๋ฅํ ์ํ์์ ์ ์ ๋ก fixture/๋ณต์ฌ๋ณธ DB์์ ์ํํ๋ค.
+
+## ์คํ ์ง๋ฌธ
+๋ต์ ์์ง ๋ชจ๋ฅด๋ ํญ๋ชฉ์ ์ ๋๋ค. ์ถ์ ์ผ๋ก ๋ฎ์ง ๋ง๊ณ REQ ์ด์ ์์ฑ ์ด์ ์ ํ์ธํ ์ง๋ฌธ์ผ๋ก ๋จ๊ธด๋ค.
+
+- OQ-1: browser ์๋ํ ๋๊ตฌ๋ก ๋ฌด์์ ํ์คํํ ๊ฒ์ธ๊ฐ(Playwright / Selenium / ๋ฒ๋ค Chromium + CDP ๋ฑ)? dcy-001 OQ-1์ ๋ฏธํด๊ฒฐ ๊ฒฐ์ ์ ์ด๋ฒ ์ฃผ๊ธฐ๋ก ์ธ๊ณํ๋ค.
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: 1์ฐจ ํ์ค์ Hermes browser tool๊ณผ ํ์ฌ ์ค์น๋ `/usr/bin/google-chrome-stable`์ ์ฌ์ฉํ ๋ก์ปฌ browser evidence ์ ์ฐจ๋ก ํ๋ค. Playwright Python์ ํ์ CI ์๋ํ ํ๋ณด๋ก ๋๊ณ , Streamlit AppTest + HTTP 200 smoke๋ fallback evidence๋ก ์ฌ์ฉํ๋ค. snap ๊ธฐ๋ฐ `chromium-browser`๋ ํ์ค ๊ฒฝ๋ก์์ ์ ์ธํ๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: REQ์ Hermes browser/Google Chrome ๊ธฐ๋ฐ 1์ฐจ ์ ์ฐจ, Playwright ํ์ ํ๋ณด, AppTest/HTTP fallback, snap chromium ์ ์ธ ์์น์ด ๋ฐ์๋๋ฉด Answered
+- OQ-2: ๊ฐํ๋ QC evidence ๊ธฐ์ค์์ browser-screenshot ์ฆ๊ฑฐ๋ฅผ ์ด๋ค batch ์ ํ์ "ํ์(blocker)"๋ก ๋์ง, ์ด๋๊น์ง `PASS_WITH_NOTES` ์์ธ๋ฅผ ํ์ฉํ ์ง?
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: Streamlit UI ๋๋ `dashboard/app.py`์ ์ํฅ์ ์ฃผ๋ batch๋ browser screenshot/snapshot evidence๋ฅผ ํ์๋ก ํ๋ค. ๋น-UI batch๋ browser evidence๋ฅผ ์ ํ์ผ๋ก ๋๊ณ , docs-only batch๋ ๋ถํ์๋ก ํ๋ค. ํ๊ฒฝ ์ฅ์ ๊ฐ ๋ช
ํํ๋ฉด AppTest + HTTP 200 fallback evidence์ ์คํจ ์ฌ์ ๋ฅผ ๋จ๊ธฐ๋ ์กฐ๊ฑด์ผ๋ก `PASS_WITH_NOTES`๋ฅผ ํ์ฉํ๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: REQ์ QC gate ๊ท์น์ UI/non-UI/docs-only ์ ํ๋ณ ์ต์ ์ฆ๊ฑฐ์ `PASS_WITH_NOTES` ์์ธ ์กฐ๊ฑด์ด ๋ฐ์๋๋ฉด Answered
+
+# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค
+์ด ๋ณ๊ฒฝ์ด ์ฑ๊ณตํ๋ค๊ณ ํ๋จํ ๊ธฐ์ค์ ์ธก์ ๊ฐ๋ฅํ๊ฒ ์ ์ํ๋ค. ๊ฐ๋ฅํ๋ฉด ๋ฐ์ดํฐ ์ถ์ฒ๊น์ง ์ฐ๊ฒฐํ๋ค.
+
+- S-1: ์ฌํ ๊ฐ๋ฅํ ํ์ค ์ ์ฐจ๋ก headless Chromium์ ๊ธฐ๋ํด Streamlit ์ฃผ์ ํ๋ฉด(์ต์ ๋ถ๋ฅ ๊ท์น, ํ์ธํ์ ํ, import preview) ์ค 1๊ฐ ์ด์์์ ์ค์ browser-level screenshot ์ฆ๊ฑฐ๋ฅผ ํ๋ณดํ๋ค.
+- S-2: QC evidence ๋ฑ๊ธ๊ณผ batch ์ ํ๋ณ ์ต์ ์ฆ๊ฑฐยท๊ฒ์ดํธ ๊ท์น์ด ๋ฌธ์๋ก ์ ์๋์ด, ๊ฒ์๊ด์ด ๋์ผ ๊ธฐ์ค์ผ๋ก `PASS`/`PASS_WITH_NOTES`๋ฅผ ํ์ ํ ์ ์๋ค.
+- S-3: ๊ฒ์ฆ ํ๊ฒฝยท์ฆ๊ฑฐ ์ ์ฐจ๊ฐ `docs/runtime-flows.md`์ `docs/project-structure.md`์ ๋ฐ์๋๊ณ , Streamlit E2E ์ฆ๊ฑฐ ์์ง runbook ํ๋ณด๊ฐ ์ถ๊ฐ๋์ด ํ์ REQ/Batch์์ ์ฐธ์กฐ ๊ฐ๋ฅํ๋ค.
+
+## ์ธก์ ๋ฐฉ์ ๋ฐ ๋ฐ์ดํฐ ์ถ์ฒ
+๊ฐ ์ฑ๊ณต ๊ธฐ์ค์ ์ด๋ป๊ฒ ์ธก์ ํ ์ง์ ์ด๋ค ๋ฐ์ดํฐ๋ ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก ์ธ์ง ์ ๋๋ค.
+
+- S-1 ์ธก์ ๋ฐฉ์: fixture DB๋ก ๊ธฐ๋ํ Streamlit ํ๋ฉด์ ์คํฌ๋ฆฐ์ท ํ์ผ์ด ์ฆ๊ฑฐ ๋๋ ํฐ๋ฆฌ์ ์์ฑ๋์๋์ง์ ์ ์ฐจ ์ฌ์คํ ์ ๋์ผ ์ฆ๊ฑฐ๊ฐ ์ฌํ๋๋์ง ํ์ธ / ๋ฐ์ดํฐ ์ถ์ฒ: E2E ์คํฌ๋ฆฝํธ ์คํ ๋ก๊ทธ, ์ ์ฅ๋ screenshot ์ฆ๊ฑฐ ํ์ผ, fixture DB
+- S-2 ์ธก์ ๋ฐฉ์: evidence ๋ฑ๊ธยท๊ฒ์ดํธ ๊ท์น ๋ฌธ์์ ์กด์ฌ์, ๋์ผ batch์ ๋ํด ๊ฒ์๊ด ํ์ ์ด ๊ท์น๊ณผ ์ผ์นํ๋์ง ๋ฆฌ๋ทฐ / ๋ฐ์ดํฐ ์ถ์ฒ: ์ ๊ท QC evidence ๊ธฐ์ค ๋ฌธ์, batch verification ๋ฌธ์์ evidence ๋ฑ๊ธ ๊ธฐ๋ก
+- S-3 ์ธก์ ๋ฐฉ์: ๋ฌธ์ ์กด์ฌ ๋ฐ ๊ฒ์ฆ ํ๋ฆยท๋๊ตฌ ๊ตฌ์กฐ ๋ฐ์ ์ฌ๋ถ ๋ฆฌ๋ทฐ / ๋ฐ์ดํฐ ์ถ์ฒ: `docs/runtime-flows.md`, `docs/project-structure.md`, `docs/runbooks/`
+
+# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ
+REQ ์ด์ ์์ฑ ๋จ๊ณ๋ก ๋๊ธฐ๊ธฐ ์ ์ ๋ฌด์์ด ํ์ ๋์ด์ผ ํ๋์ง ํ์ธํ๋ค. ๋ฏธ์๋ฃ ํญ๋ชฉ์ ํ์ ๊ฒฐ์ ์ด ํ์ํจ์ ๋ปํ๋ค.
+
+- [x] ๋ฒ์(In Scope/Out of Scope) ํ์
+ - ๊ธฐ์ค: ์ด๋ฒ ๋ฐ๋ณต์ ๋ก์ปฌ ์ฌํ ๊ฐ๋ฅํ browser evidence ๋ณต๊ตฌ์ ์ฆ๊ฑฐ ๋ฑ๊ธํ๊น์ง๋ก ํ์ ํ๋ค. CI ํตํฉ์ Project #4์ `pytest/AppTest/Smoke CI ์๋ํ` ํญ๋ชฉ์ผ๋ก ๋ถ๋ฆฌํ๊ณ , visual regression์ ์ ์ธํ๋ค.
+- [x] ์ฑ๊ณต ์งํ์ ์ธก์ ๋ฐฉ์/๋ฐ์ดํฐ ์ถ์ฒ ํ์
+ - ๊ธฐ์ค: Hermes browser/Google Chrome ๊ธฐ๋ฐ screenshot/snapshot evidence, console ํ์ธ, Streamlit ์๋ฒ ๋ก๊ทธ, AppTest+HTTP fallback์ด REQ acceptance criteria์ ๋ฐ์๋๋ค.
+- [x] High ์ํฅ ๋ฆฌ์คํฌ ์ฐ์ ์์ ๋ฐ ํด์ ์ฑ
์์ ์ง์
+ - ๊ธฐ์ค: R-1(Chromium ํ๋ณด ๋ถ๊ฐ)์ ํ์ฌ ์ค์น๋ `/usr/bin/google-chrome-stable`์ 1์ฐจ runtime์ผ๋ก ์ฌ์ฉํ๊ณ , ๋ ์ ์ฅ์ธ์ด ๋ก์ปฌ evidence ์ ์ฐจ๋ฅผ ๊ตฌํยท๊ฒ์ฆํ๋ค.
+- [x] ๋ชจ๋ ์คํ ์ง๋ฌธ(OQ) `Open` ์ํ ์์ ํ์ธ
+ - ๊ธฐ์ค: OQ-1(๋๊ตฌ ํ์คํ), OQ-2(๊ฒ์ดํธ ์ ์ฑ
)๊ฐ Answered ์ฒ๋ฆฌ๋จ.
+- [x] Freeze ํ์ ๋ด๋น์(CONFIRMED_BY) ์ง์
+ - ๊ธฐ์ค: ์ฌ์ฉ์ ์งํ ๊ณ ๊ฐ ๊ถ์ฅ์์ ์น์ธํ๊ณ ๋ค์ ๊ณผ์ ์งํ์ ์ง์ํ๋ค.
+
+# 9. ํ์ผ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ
+์ด Discovery ๋ฌธ์ ์์ฑ/๊ฐฑ์ ๊ฒฐ๊ณผ์ ์ฐธ์กฐ ๋ฌธ์๋ฅผ ๊ธฐ๋กํ๋ค. ์ดํ ์ถ์ ๊ณผ ๋ฆฌ๋ทฐ ์ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉํ๋ค.
+
+- ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: ์์ฑ
+- ์์ฑ ๊ฒฝ๋ก: docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md
+- ์ฐธ์กฐ ๋ฌธ์:
+ - docs/discovery/dcy-001_20260626_streamlit-ux-e2e-categorization-refund-workflows.md
+ - docs/batches/bat-001_20260626_category-rule-ops/verification.md
+ - docs/runtime-flows.md
+ - docs/project-structure.md
+
+# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ
+์ฌ์ฉ์๋ ์น์ธ์๊ฐ ๊ฒฐ์ ํด์ผ ํ๋ ํญ๋ชฉ๋ง ๋ชจ์ ์ ๋ฆฌํ๋ค. ๊ฒฐ์ , ํ์ธ, ๋ฐ์ดํฐ ์์ฒญ์ ๊ตฌ๋ถํด์ ์ ๋๋ค.
+
+## DECIDE
+- E2E 1์ฐจ ํ์ค์ Hermes browser tool๊ณผ ์ค์น๋ `/usr/bin/google-chrome-stable` ๊ธฐ๋ฐ ๋ก์ปฌ browser evidence ์ ์ฐจ๋ก ํ๋ค. Playwright Python์ ํ์ CI ์๋ํ ํ๋ณด๋ก ๋๊ณ , Streamlit AppTest + HTTP 200 smoke๋ fallback evidence๋ก ์ฌ์ฉํ๋ค.
+- Streamlit UI ๋๋ `dashboard/app.py` ์ํฅ batch๋ browser screenshot/snapshot evidence๋ฅผ ํ์๋ก ํ๋ค. ๋น-UI batch๋ ์ ํ, docs-only batch๋ ๋ถํ์๋ก ํ๋ค. ํ๊ฒฝ ์ฅ์ ๊ฐ ๋ช
ํํ๋ฉด AppTest + HTTP 200 fallback๊ณผ ์คํจ ์ฌ์ ๋ฅผ ๋จ๊ธฐ๋ ์กฐ๊ฑด์ผ๋ก `PASS_WITH_NOTES`๋ฅผ ํ์ฉํ๋ค.
+- ์ด๋ฒ dcy-004๋ ๋ก์ปฌ ์ฌํ ๊ฐ๋ฅํ browser evidence ๋ณต๊ตฌ๊น์ง๋ก ํ์ ํ๋ค. CI ํ์ดํ๋ผ์ธ ํตํฉ์ Project #4์ `pytest/AppTest/Smoke CI ์๋ํ` ํญ๋ชฉ์์ ๋ณ๋ ์ฒ๋ฆฌํ๋ค.
+
+## CONFIRM
+- Chromium/Chrome runtime์ ํ์ฌ ์ค์น๋ `/usr/bin/google-chrome-stable` 149.0.7827.200์ 1์ฐจ runtime์ผ๋ก ์ฌ์ฉํ๋ค. snap ๊ธฐ๋ฐ `chromium-browser`๋ ํ์ค ๊ฒฝ๋ก์์ ์ ์ธํ๋ค.
+
+## DATA
+- browser E2E๋ ์ด์ DB๊ฐ ์๋๋ผ synthetic/validation DB๋ฅผ ์ฌ์ฉํ๋ค. ๊ณ์ข๋ฒํธ, ์ค์ ๊ฑฐ๋์ฒ, raw_payload, ์๋ณธ ํ์ผ๋ช
, ๋น๋ฐ๋ฒํธ๋ screenshot/log์ ๋
ธ์ถํ์ง ์๊ณ ๊ฐ์ง ๊ฐ ๋๋ ๊ณต๋์ผ๋ก ๋๋ค.
+
+# 11. Discovery Freeze
+REQ ์ด์ ์์ฑ ์ธ๊ณ ์์ ์ ํ์ ํ๋๊ทธ์ ์น์
์ฐธ์กฐ๋ง ๊ธฐ๋กํ๋ค. ๋ด์ฉ ์ฌ์์ฑ ์์ด ์๋ณธ ์น์
์ ๋งํฌ๋ก ๋์ ํ๋ค.
+
+## ์น์
์ฐธ์กฐ
+- ๋ฒ์: [# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก > ๋ฒ์ ๊ฒฝ๊ณ](#5-์๊ตฌ์ฌํญ-๋ชฉ๋ก)
+- ๋ฆฌ์คํฌ/์คํ ์ง๋ฌธ: [# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก](#6-๋ฆฌ์คํฌ๊ฐ์ -๋ชฉ๋ก)
+- ์ฑ๊ณต ๊ธฐ์ค: [# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค](#7-์ด๊ธฐ-์ฑ๊ณต-๊ธฐ์ค)
+- ์ธ๊ณ ์ ํ์ธ: [# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ](#8-req๋ก-๋๊ธฐ๊ธฐ-์ -ํ์ธ-์ฒดํฌ)
+- ์ฌ์ฉ์ ๊ฒฐ์ ํ์: [# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ](#10-์ฌ์ฉ์-๊ฒฐ์ -ํ์-ํญ๋ชฉ-์์ฝ)
+
+## Freeze ํ๋๊ทธ
+- Handoff Decision: ์น์ธ
+- Handoff Rationale: ์ฌ์ฉ์ ์น์ธ์ผ๋ก E2E ๋๊ตฌ ํ์ค, evidence gate ์ ์ฑ
, CI ๋ฒ์, Chrome runtime, fixture/๋ง์คํน ์์น์ด ํ์ ๋์์ผ๋ฏ๋ก REQ ์ด์ ์์ฑ์ผ๋ก ๋๊ธด๋ค.
+- Ready for REQ Drafting: true
+- Confirmed By: ์งํ ๊ณ
+- Confirmed At (KST): 2026-06-29 13:49
diff --git a/docs/discovery/dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow.md b/docs/discovery/dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow.md
new file mode 100644
index 0000000..c28dd81
--- /dev/null
+++ b/docs/discovery/dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow.md
@@ -0,0 +1,209 @@
+# 0. ๋ฌธ์ ์ํ
+์ด ๋ฌธ์์ ํ์ฌ ์ํ์ ์๋ณ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ค. ๋ฌธ์ ์ถ์ ๊ณผ ์น์ธ ํ๋ฆ์ ํ์ํ ์ต์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ค.
+
+- ์ํ: confirmed
+- ๋ฌธ์ ID: dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow
+- ์ด์๋ช
: Jarca Ledger ๋์๋ณด๋ ์ด์ ํ๋ธ ์ฌ์ ์ ๋ฆฝ (์ด๋ฒ ๋ฌ ํ๊ธํ๋ฆ + ์์ฐ ์ ๋์ฑ + ์ด์ ๋ฏธ์ฒ๋ฆฌ)
+- ์์ฑ ์๊ฐ(KST): 2026-06-29 14:02 KST
+- ๋ง์ง๋ง ๊ฐฑ์ ์๊ฐ(KST): 2026-06-29 20:03 KST
+- ๋์ฒด๋จ: ์์
+- ํ์ Discovery ์ฐธ์กฐ: ์์
+- ์์ฑ๋ REQ ์ฐธ์กฐ: REQ-005 (`docs/srs/Interface/req-005_dashboard-operations-hub-liquidity-monthly-cashflow.md`)
+
+# 1. ๊ณํ ์ํ ์์ฝ
+ํ์ฌ Discovery๊ฐ ์ด๋ค ๋ฐฐ๊ฒฝ์์ ์์ฑ๋๊ณ ์๋์ง, ์ฐ๊ณ ๋ฌธ์๊ฐ ์๋์ง, ์ง๊ธ ์์ ์ ํด์์ด ๋ฌด์์ธ์ง ์งง๊ฒ ์์ฝํ๋ค.
+
+- ์ฐ๊ณ ๋ฌธ์ ํํฉ:
+ - ๊ด๋ จ ์ ์ ํ์ผ: ๋ฏธ์์ฑ
+ - ๊ด๋ จ TODO/๋ฉ๋ชจ: ์์
+- ํด์:
+ - ๋์๋ณด๋ ์ฒซ ํ๋ฉด(`page_overview`)์ด ํ์ฌ๋ ์ ์ฒด ๊ธฐ๊ฐ ๋์ ์์ตยท๊ณ์ข ์
์ถ๊ธ ์์ฃผ๋ผ, "์ด๋ฒ ๋ฌ ์ด์ ์ํ"์ "ํ์ฌ ์์ฐ ์ ๋์ฑ/๋ถ์ฑ ๊ตฌ์กฐ"๋ฅผ ํ ํ๋ฉด์์ ๋ณด๋ ค๋ ์ด์์ ์๊ตฌ๋ฅผ ์ถฉ์กฑํ์ง ๋ชปํ๋ค.
+ - ์ด๋ฒ Discovery๋ ๋์๋ณด๋๋ฅผ ๋จ์ผ ์ด์ ํ๋ธ๋ก ์ฌ์ ์ํ๊ณ , ์ด๋ฒ ๋ฌ ํ๊ธํ๋ฆยท์์ฐ ์ ๋์ฑยท์ด์ ๋ฏธ์ฒ๋ฆฌ ๊ฒฝ๊ณ ๋ฅผ ์ฐ์ ์์ ๊ตฌ์กฐ๋ก ๋ฌถ๋ ๋ณ๊ฒฝ ๋ฒ์์ ๋ฐ์ดํฐ ๋ชจ๋ธ ๋ณด๊ฐ(`liquidity_bucket`) ํ์์ฑ์ ํ๋จํ๋ค.
+
+# 2. ์๊ตฌ์ฌํญ ํ์ ๊ฒฐ๊ณผ
+์ด๋ฒ ์์ฒญ์ด ๊ธฐ์กด ์๊ตฌ์ฌํญ๊ณผ ๋น๊ตํด ์ ๊ท์ธ์ง, ์ ์ฌํ์ง, ์ค๋ณต์ธ์ง ํ๋จํ๊ณ ๊ทธ ๊ทผ๊ฑฐ๋ฅผ ๋จ๊ธด๋ค.
+
+- ํ์ : ์ ๊ท
+- ๊ทผ๊ฑฐ
+ - dcy-001์ Streamlit UX/E2Eยท๋ถ๋ฅยทํ๊ธ ์ํฌํ๋ก์ฐ ์์ ํ, dcy-002~004๋ ๋ฐฑ์
ยท๋ณต๊ตฌ, statement-unavailable ์๋ ์ฆ๋น/์์ก๋์กฐ, E2E evidence ํ๊ฒฝ ๋ณต๊ตฌ๋ก ๋ฌธ์ ์ ์๊ฐ ๋ค๋ฅด๋ฉฐ "๋์๋ณด๋ ํ๋ฉด ๊ตฌ์ฑยท์์ฐ ์ ๋์ฑ ๋ทฐ"๋ฅผ ๋ค๋ฃจ์ง ์๋๋ค.
+ - ๋ณธ ์์ฒญ์ ๋์๋ณด๋ ์ ๋ณด๊ตฌ์กฐ ์ฌ์ ์์ ์ ๊ท ์์ฐ ๋ถ๋ฅ ์ถ(`liquidity_bucket`) ๋์
์ ํต์ฌ์ผ๋ก ํ๋ฏ๋ก ๊ธฐ์กด ํ๋ณด์ ๋ฏธ์ธ ์กฐ์ ์ด ์๋๋ผ ๋
๋ฆฝ ์ฃผ๊ธฐ๋ก ์ถ์ ํ๋ ๊ฒ์ด ๋ช
ํํ๋ค.
+
+# 3. ๋ฌธ์ ์ ์ ์์ฝ
+๋ฌธ์ ์ ํ์ฌ ์ํ, ๊ธฐ๋ ์ํ, ๊ด๋ จ ์ดํด๊ด๊ณ์, ์ ์ฝ์ฌํญ์ ์ ๋ฆฌํ๋ค. Discovery์ ํต์ฌ ๋งฅ๋ฝ์ ๊ฐ์ฅ ๋จผ์ ์ดํดํ ์ ์์ด์ผ ํ๋ค.
+
+- ํ์ฌ ์ํ
+ - `dashboard/app.py`์ `page_overview`๋ `status='active'` ์ ์ฒด ๊ฑฐ๋๋ฅผ ๋์ ์ง๊ณํด "์ค์ ์์
/์ง์ถ/์ํ๊ธํ๋ฆ/์ ์ถ๋ฅ /ํ์ธํ์"์ "๊ณ์ข ์
์ถ๊ธ ํ๋ฆ", ์๋ณ/์ธ์๋ณ/์นดํ
๊ณ ๋ฆฌ Top20 ์ฐจํธ๋ฅผ ๋ณด์ฌ์ค๋ค. ์๋จ ์งํ๊ฐ ๋น์์ด ์๋๋ผ ์ ์ฒด ๊ธฐ๊ฐ ํฉ๊ณ ๊ธฐ์ค์ด๋ผ "์ด๋ฒ ๋ฌ ํํฉ"์ ์ฆ์ ์ฝ๊ธฐ ์ด๋ ต๋ค.
+ - ์์ฐ ์ธก๋ฉด์์ `ledger_accounts.type`์ bank/card/cash/investment/loan ๊ตฌ๋ถ๋ง ์๊ณ (`docs/data-model.md` Entity: LedgerAccount), ํ๊ธยทํฌ์ยท์์ ๊ธยท๋ถ๋์ฐยท๋์ถ์ ์ ๋์ฑ ๊ด์ ์ผ๋ก ๋ฌถ๋ `liquidity_bucket` ์ถ์ ์๋ค. ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ/์์์ฐ ๋ทฐ๊ฐ ๋์๋ณด๋์ ์กด์ฌํ์ง ์๋๋ค.
+ - ์ด์ ๋ฏธ์ฒ๋ฆฌ ์ ํธ(๋ฏธ๋ถ๋ฅ ๊ฑฐ๋, ํ์ธํ์ ํ, ์์ก๋์กฐ ํ์, ๋ฐฑ์
/import ์ํ)๋ `page_uncategorized`, `page_cross_transfer_review`, `docs/runbooks/manual-balance-reconciliation.md`, ๋ฐฑ์
์ํฌํ๋ก์ฐ ๋ฑ ๋ณ๋ ํ๋ฉด/๋ฌธ์์ ํฉ์ด์ ธ ์์ด ํ ํ๋ฉด์์ ์ด์ ์ํ๋ฅผ ์ ๊ฒํ์ง ๋ชปํ๋ค.
+ - `docs/project-structure.md`, `docs/runtime-flows.md` baseline ๋ฌธ์๋ ์กด์ฌํ๋ ๋์๋ณด๋ ์ด์ ํ๋ธ ์ ๋ณด๊ตฌ์กฐ์ `liquidity_bucket` ๋ฐ์ดํฐ ํ๋ฆ์ ์์ง ๋ฐ์ํ์ง ์๋๋ค.
+- ๊ธฐ๋ ์ํ
+ - ๋์๋ณด๋ ์ฒซ ํ๋ฉด์ด "์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ โ ์์ฐ ์ ๋์ฑ ํํฉ โ ์์ฐ ๊ตฌ์ฑ โ ์ง์ถ ๊ตฌ์กฐ/์์ฐ โ ์ด์ ๊ฒฝ๊ณ " ์์์ ์ด์ ํ๋ธ๋ก ๋์ํ๋ค.
+ - ์์ฐ์ ์ ๋์ฑ ๋ฒํท(ํ๊ธ/ํฌ์/์์ ๊ธ/๋ถ๋์ฐ/๋์ถ)์ผ๋ก ๋ฌถ์ด ๊ธ์กยท๋น์จ์ ํ์ธํ๊ณ , ๋์ถ์ ์์ฐ ๊ตฌ์ฑ ๋น์จ์์ ์ ์ธํ๋ ๋ถ์ฑ/์์์ฐ ๊ณ์ฐ์์ ๋ณ๋ ์ฐจ๊ฐํ๋ค.
+- ์ดํด๊ด๊ณ์
+ - ๊ฐ๊ณ ์ฌ๋ฌด๋ฅผ ์ด์ํ๋ ์ฌ์ฉ์(๋์๋ณด๋ 1์ฐจ ์ฌ์ฉ์)
+ - ๋ฌธ์/์๊ตฌ์ฌํญ ๋ฆฌ๋ทฐ์ด ๋ฐ ์น์ธ์(Hermes/์ฌ์ฉ์)
+ - ๋์๋ณด๋ยท์๋น์คยทDB๋ฅผ ๊ตฌํ/์ด์ํ๋ ๊ฐ๋ฐ ๋ด๋น์
+- ์ ์ฝ์ฌํญ
+ - DB ์คํค๋ง ๋ณ๊ฒฝ์ additive/idempotent ์์น์ ๋ฐ๋ฅด๋ฉฐ ํ๊ดด์ ๋ง์ด๊ทธ๋ ์ด์
์ ๋ช
์ ์น์ธยท๋ฐฑ์
ํ์๋ง ํ๋ค(`docs/data-model.md` Persistence Notes).
+ - ๋ถ๋์ฐยท๋ฌด๊ธฐํ ์์ฐ์ฒ๋ผ ๊ฑฐ๋ ๊ธฐ๋ฐ์ด ์๋ ์์ฐ์ ์
๋ ฅ ์์ค๊ฐ ๋ถ๋ช
ํํด ๋ฐ์ดํฐ ํ๋ณด ๋ฐฉ์ ๊ฒฐ์ ์ด ์ ํ๋์ด์ผ ํ๋ค.
+ - ๋ณธ Discovery ๋จ๊ณ์์๋ ์์ค ์ฝ๋/ํ
์คํธ/์คํค๋ง๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ์ฌ์๋ง ์ ์ํ๋ค.
+
+## ์ฃผ์ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
+์ฌ์ฉ์ ๋๋ ์ด์์๊ฐ ์ค์ ๋ก ์ด๋ค ์ํฉ์์ ์ด ๋ณ๊ฒฝ์ ํ์๋ก ํ๋์ง ๋ํ ์๋๋ฆฌ์ค๋ฅผ ์ ๋๋ค.
+
+- ์ฌ์ฉ์๊ฐ ์์ค์ ๋์๋ณด๋๋ฅผ ์ด์ด "์ด๋ฒ ๋ฌ ์์
ยท์ง์ถยท์ํ๊ธํ๋ฆยท์ ์ถ๋ฅ "์ ์ต์๋จ์์ ์ฆ์ ํ์ธํ๊ณ ์ด๋ฒ ๋ฌ ์ง์ถ ์๋๋ฅผ ์ ๊ฒํ๋ค.
+- ์ฌ์ฉ์๊ฐ "์ง๊ธ ๋น์ฅ ์ธ ์ ์๋ ํ๊ธ์ด ์ผ๋ง์ด๊ณ , 1~2์ฃผ ๋ด ๊ฐ์ฉ ํฌ์/์์ ๊ธ์ด ์ผ๋ง์ด๋ฉฐ, ๋ถ๋์ฐยท๋์ถ๊น์ง ํฌํจํ ์์์ฐ์ด ์ผ๋ง์ธ์ง" ์์ฐ ์ ๋์ฑยท๊ตฌ์ฑ ์น์
์์ ํ๋์ ํ์
ํ๋ค.
+- ์ฌ์ฉ์๊ฐ ๊ฐ์ ํ๋ฉด ํ๋จ์ ์ด์ ๊ฒฝ๊ณ ์์ ๋ฏธ๋ถ๋ฅ ๊ฑด์, ํ์ธํ์ ํ, ์์ก๋์กฐ ํ์ ๊ณ์ข, ์ต๊ทผ ๋ฐฑ์
/import ์ํ๋ฅผ ๋ณด๊ณ ํ์ ์ ๋ฆฌ ์์
์ผ๋ก ์ด๋ํ๋ค.
+
+# 4. ์ด๋ฒ์ ์ ์ํ ๋ณ๊ฒฝ
+์ด๋ฒ Discovery์์ ๋ค๋ฃจ๋ ํต์ฌ ๋ณ๊ฒฝ ํญ๋ชฉ์ ๋ชฉ๋ก์ผ๋ก ์ ๋๋ค. ๊ธฐ๋ฅ, ๊ตฌ์กฐ, ๊ท์น ๋ณ๊ฒฝ์ ํจ๊ป ํฌํจํด๋ ๋๋ค.
+
+- ๋์๋ณด๋ ์ด์ ํ๋ธ ์ ๋ณด๊ตฌ์กฐ ํ์ : โ ์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ โก ์์ฐ ์ ๋์ฑ ํํฉ โข ์์ฐ ๊ตฌ์ฑ โฃ ์ง์ถ ๊ตฌ์กฐ/์์ฐ โค ์ด์ ๊ฒฝ๊ณ ์ 5๊ฐ ์น์
์ฐ์ ์์์ ๊ฐ ์น์
ํ์ ์งํ๋ฅผ ์ฌ์์ผ๋ก ์ ์ํ๋ค.
+- ์์ฐ ์ ๋์ฑ ๋ถ๋ฅ ์ถ ๋์
๊ฒํ : `liquidity_bucket`(cash, investment, deposit, real_estate, loan, other)์ ๊ถ์ฅ ํ๋๋ก ์ ์ํ๊ณ , ๋์ถ์ ์์ฐ ๊ตฌ์ฑ ๋น์จ์์ ์ ์ธยท๋ถ์ฑ/์์์ฐ์์ ๋ณ๋ ์ฐจ๊ฐํ๋ ์ง๊ณ ๊ท์น์ ์ ํ๋ค.
+- baseline ๋ฌธ์ ๋ฐ์: `liquidity_bucket` ๋ฐ์ดํฐ ๋ชจ๋ธ๊ณผ ๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ์ `docs/data-model.md`, `docs/project-structure.md`, `docs/runtime-flows.md`์ ๊ฐฑ์ ์๊ตฌ๋ก ๋ฑ๋กํ๋ค.
+
+## ์ํฅ ๋ฒ์
+๋ณ๊ฒฝ์ด ๋ฏธ์น ์ฝ๋/๋ฌธ์/์ด์ ๋ฒ์๋ฅผ ๋ช
์ํด REQ ์ ์ ์ batch planning์์ ์ถ์ ๋๋ฝ์ ์ค์ธ๋ค.
+
+- ์ฝ๋ ์ํฅ:
+ - `dashboard/app.py`์ `page_overview` ์ฌ๊ตฌ์ฑ(์ด๋ฒ ๋ฌ ์ค์ฝํ ์ง๊ณ, ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ/์์์ฐ ์น์
, ์ด์ ๊ฒฝ๊ณ ํตํฉ ํจ๋) ๋ฐ ๊ด๋ จ ์ง๊ณ ํฌํผ.
+ - `liquidity_bucket` ๋์
์ `jarca_ledger_mcp/db.py` ์คํค๋ง(additive)์ `service.py` ์์ฐ/์ ๋์ฑ ์ง๊ณ ํจ์, ๋ถ๋ฅ ๋งคํ ๋ก์ง.
+- ๋ฌธ์ ์ํฅ:
+ - `docs/data-model.md`(LedgerAccount/LedgerCategory์ `liquidity_bucket` ์ถ, ์์์ฐ/๋ถ์ฑ ์ง๊ณ ๊ท์น), `docs/project-structure.md`, `docs/runtime-flows.md`(๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ) ๊ฐฑ์ .
+- ์ด์ ์ํฅ:
+ - ๋ถ๋์ฐยท๋์ถ ๋ฑ ๋น๊ฑฐ๋ ์์ฐ ์
๋ ฅ/๊ฐฑ์ ์ ์ฐจ๊ฐ ์ ์ค๋ ์ ์์ด ์ด์ ์
๋ ฅ ๋ฃจํด๊ณผ ์์ก๋์กฐ ์ด์ ํ๋ฆ์ ์ํฅ์ ์ค๋ค.
+
+# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก
+์ค์ ๋ก ๊ตฌํ๊ณผ ๊ฒ์ฆ์ผ๋ก ์ด์ด์ง ์๊ตฌ์ฌํญ์ ๊ธฐ๋ฅ/๋น๊ธฐ๋ฅ์ผ๋ก ๋๋์ด ์ ๋ฆฌํ๊ณ , ๋ฒ์ ๊ฒฝ๊ณ๋ฅผ ๋ช
ํํ ์ ๋๋ค.
+
+## ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+- FR-1: ๋์๋ณด๋ ์ต์๋จ 1์์ ์น์
์ "์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ"์ผ๋ก, ๋น์ ๊ธฐ์ค ์ค์ ์์
ยท์ค์ ์ง์ถยท์ํ๊ธํ๋ฆยท์ ์ถ๋ฅ ์ ํ์ํ๋ค. ๋น์ ๋ฒ์๋ ๊ฑฐ๋์ผ(`transaction_date`) ๊ธฐ์ค ํ์ฌ ์์ ๊ธฐ๋ณธ์ผ๋ก ํ๋ค.
+- FR-2: "์์ฐ ์ ๋์ฑ ํํฉ" ์น์
์ ํ๊ธ(๋น์ฅ ์ฌ์ฉ ๊ฐ๋ฅ), ํฌ์(1~2์ฃผ ๋ด ์ฌ์ฉ ๊ฐ๋ฅ), ์์ ๊ธ(๋ฌ~๋
), ๋ถ๋์ฐ(๋ฌด๊ธฐํ), ๋์ถ(๋ถ์ฑ)์ ๋ฒํท์ผ๋ก ๋ฌถ์ด ๊ฐ ๊ธ์ก๊ณผ ๋น์จ์ ํ์ํ๋ค. ๋น์จ ๋ถ๋ชจ๋ ๋์ถ(๋ถ์ฑ)์ ์ ์ธํ ์์ฐ ํฉ๊ณ๋ก ํ๊ณ , ๋์ถ์ ๋ถ์ฑ/์์์ฐ ๊ณ์ฐ์์ ๋ณ๋ ์ฐจ๊ฐํ๋ค.
+- FR-3: ๋์๋ณด๋๋ โ ์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ โก ์์ฐ ์ ๋์ฑ ํํฉ โข ์์ฐ ๊ตฌ์ฑ โฃ ์ง์ถ ๊ตฌ์กฐ/์์ฐ โค ์ด์ ๊ฒฝ๊ณ (๋ฏธ๋ถ๋ฅ, ํ์ธํ์, ์์ก๋์กฐ ํ์, ๋ฐฑ์
/import ์ํ)์ ์์๋ก ์น์
์ ๋ฐฐ์นํ๋ค.
+- FR-4: ์์ฐ์ ์ ๋์ฑ ๊ด์ ์ผ๋ก ๋ฌถ๊ธฐ ์ํด `accounts.liquidity_bucket`(cash, investment, deposit, real_estate, loan, other) ๋ถ๋ฅ ์ถ์ ๋ฐ์ดํฐ ๋ชจ๋ธ์ ๋์
ํ๋ค. `liquidity_bucket`์ ๊ถ์ ์์ค๋ ๊ณ์ข ๋จ์์ด๋ฉฐ, ๋ฏธ์ง์ ๊ณ์ข๋ `other`๋ก ํด๋ฐฑํ๋ค. ๊ฑฐ๋/์นดํ
๊ณ ๋ฆฌ ๋จ์ override๋ ์ด๋ฒ ๋ฒ์์์ ์ ์ธํ๋ค.
+- FR-5: `docs/data-model.md`, `docs/project-structure.md`, `docs/runtime-flows.md` baseline ๋ฌธ์์ `liquidity_bucket` ๋ชจ๋ธ๊ณผ ๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ ๊ฐฑ์ ์ ํฌํจํ๋ค.
+
+## ๋น๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+์ฑ๋ฅ, ์ด์์ฑ, ์ผ๊ด์ฑ, ์๋ํ ์์ ์ฑ ๋ฑ ๊ธฐ๋ฅ ์ธ ์๊ตฌ์ฌํญ์ ์ ๋๋ค.
+
+- NFR-1 (์ผ๊ด์ฑ): ์์ฐ ๊ตฌ์ฑ ๋น์จ๊ณผ ์์์ฐ/๋ถ์ฑ ๊ณ์ฐ์ ๋จ์ผ ์ ์(๋์ถ์ ๊ตฌ์ฑ ๋น์จ ์ ์ธ, ์์์ฐ์์ ์ฐจ๊ฐ)๋ฅผ ๋ชจ๋ ์น์
ยท์ง๊ณ์์ ๋์ผํ๊ฒ ์ ์ฉํ๋ค.
+- NFR-2 (์ด์์ฑ): ์ด์ ๊ฒฝ๊ณ ์น์
์ ๊ฐ ํญ๋ชฉ(๋ฏธ๋ถ๋ฅยทํ์ธํ์ยท์์ก๋์กฐยท๋ฐฑ์
/import)์ ๊ฑด์/์ํ์ ํจ๊ป ํด๋น ์์ธ ํ๋ฉด์ผ๋ก ์ด๋ํ ์ ์๋ ๋จ์๋ฅผ ์ ๊ณตํ๋ค.
+- NFR-3 (์์ ์ฑ): `accounts.liquidity_bucket` ์คํค๋ง ๋ณ๊ฒฝ์ additive/idempotent๋ก ์ ์ฉํ๊ณ , ๋ฏธ์ง์ ๊ณ์ข๋ `other`๋ก ์์ ํ๊ฒ ํด๋ฐฑํด ๊ธฐ์กด ๋์๋ณด๋ ๋์์ ๊นจ์ง ์๋๋ค.
+
+## ๋ฒ์ ๊ฒฝ๊ณ
+์ด๋ฒ ๋ฐ๋ณต์์ ๋ฐ๋์ ํฌํจํ ๋ฒ์์ ๋ช
์์ ์ผ๋ก ์ ์ธํ ๋ฒ์๋ฅผ ๊ตฌ๋ถํ๋ค.
+
+- In Scope
+ - ๋์๋ณด๋ ์ด์ ํ๋ธ 5๊ฐ ์น์
์ ๋ณด๊ตฌ์กฐ์ ๊ฐ ์น์
ํต์ฌ ์งํ ์ฌ์ ์ ์.
+ - `accounts.liquidity_bucket` ์ถ๊ณผ ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ/์์์ฐยท๋ถ์ฑ ์ง๊ณ ๊ท์น ์ ์ ๋ฐ baseline ๋ฌธ์ ๊ฐฑ์ ์๊ตฌ.
+- Out of Scope
+ - ๋ค๊ณ์ข ์ค์๊ฐ ์์ก ์ฐ๋, ์ธ๋ถ ์์ธ/ํฌ์ ํ๊ฐ์ก ์๋ ์ฐ๋.
+ - ์นด๋ ๋ฏธ๊ฒฐ์ ๊ธ, ๋ณดํ ํด์งํ๊ธ๊ธ, ์๋ ์์ธ ์ฐ๋ ์์ฐ ๋ฐ์.
+ - ๋ณ๋ ์์ฐ ํ๊ฐ ํ
์ด๋ธ(`asset_snapshots`, `manual_assets` ๋ฑ) ์ ์ค. ์ด๊ธฐ ๊ตฌํ์ `accounts` ํญ๋ชฉ์ ์๋ ์
๋ ฅ์ผ๋ก ์ฒ๋ฆฌํ๋ค.
+
+# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก
+๋ถํ์ค์ฑ๊ณผ ์ ์ ์กฐ๊ฑด์ ๋ถ๋ฆฌํด์ ์ ๋๋ค. ๋ฆฌ์คํฌ๋ ์ํฅ๊ณผ ์ํ ๋ฐฉ์์, ๊ฐ์ ์ ํ์ฌ ํ๋จ์ ์ ์ ๋ฅผ ๊ธฐ๋กํ๋ค.
+
+## ๋ฆฌ์คํฌ
+- R-1 (Medium): `liquidity_bucket` ๊ถ์ ์์ค๊ฐ ๊ตฌํ ์ค ๊ณ์ข ์ธ ์นดํ
๊ณ ๋ฆฌ/๊ฑฐ๋ override๋ก ํ์ฅ๋๋ฉด ์์ฐ ์ง๊ณ ์์ค๊ฐ ์ด์คํ๋ผ ๋น์จ์ด ์ด๊ธ๋ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: ์ด๋ฒ ๋ฐ๋ณต์ ๋จ์ผ ๊ถ์ ์์ค๋ `accounts.liquidity_bucket`์ผ๋ก ๊ณ ์ ํ๊ณ , ๊ฑฐ๋/์นดํ
๊ณ ๋ฆฌ override๋ ํ์ ๋ฒ์๋ก ์ ์ธํ๋ค. ๋ฏธ๋งคํ์ `other`๋ก ํด๋ฐฑํ ๋ค ์ด์ ๊ฒฝ๊ณ ๋ก ๋
ธ์ถํ๋ค.
+- R-2 (Medium): ๋ถ๋์ฐยท๋์ถ ๋ฑ ๋น๊ฑฐ๋ ์์ฐ์ ๊ฑฐ๋ ๋ฐ์ดํฐ๋ก ์ฐ์ถ๋์ง ์์ ์
๋ ฅ ๋๋ฝ ์ ์์ฐ ์ ๋์ฑ/์์์ฐ ์์น๊ฐ ์๊ณก๋๋ค.
+ - ์ํ ๋ฐฉ์: ์ด๊ธฐ์๋ `accounts` ํญ๋ชฉ์ ์๋ ์
๋ ฅํ๊ณ , `last_balance`๋ฅผ ํ์ฌ ํ๊ฐ์ก/์์ก, `last_synced_at`์ ๊ธฐ์ค์ผ, `memo`๋ฅผ ์ถ์ฒ ๋ฉ๋ชจ๋ก ์ฌ์ฉํ๋ค. ๋ฏธ์
๋ ฅ ์์ฐ์ ์์น์์ ์ ์ธํ์์ ํ๋ฉด์ ํ๊ธฐํ๋ค.
+- R-3 (Low): ์๋จ "์ด๋ฒ ๋ฌ" ์ค์ฝํ์ ๊ธฐ์กด ์ ์ฒด ๊ธฐ๊ฐ ์ฐจํธ๊ฐ ํ ํ๋ฉด์ ๊ณต์กดํ๋ฉด ์ฌ์ฉ์๊ฐ ๊ธฐ์ค ๊ธฐ๊ฐ์ ํผ๋ํ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: ๊ฐ ์น์
์ ๊ธฐ์ค ๊ธฐ๊ฐ ๋ผ๋ฒจ์ ๋ช
์ํ๊ณ , ๋น์/์ ์ฒด ๊ธฐ์ค์ ์๊ฐ์ ์ผ๋ก ๊ตฌ๋ถํ๋ค.
+
+## ๊ฐ์
+์์ง ํ์ ๋์ง ์์์ง๋ง ํ์ฌ ๋ฌธ์ ์์ฑ์ ๊ธฐ๋ฐ์ด ๋๋ ์ ์ ๋ฅผ ์ ๋๋ค.
+
+- A-1: ์ ์ถ๋ฅ ์ ๊ธฐ์กด `page_overview` ์ ์(์ํ๊ธํ๋ฆ รท ์ค์ ์์
)๋ฅผ ๋น์ ๋ฒ์๋ก ์ ์ฉํ๋ค.
+- A-2: ์ค์ ์์
/์ง์ถ ํ์ ์ ๊ธฐ์กด `ledger_categories.cashflow_kind`(income/expense ๋ฑ) ๊ธฐ์ค์ ๊ทธ๋๋ก ์ฌ์ฌ์ฉํ๋ค.
+- A-3: ๋์ถ์ ์์ฐ์ด ์๋๋ผ ๋ถ์ฑ๋ก ์ทจ๊ธํ๋ฉฐ ์์ฐ ๊ตฌ์ฑ ๋น์จ์ ๋ถ๋ชจ์์ ์ ์ธํ๊ณ , ์ด๋ถ์ฑ/์์์ฐ ๊ณ์ฐ์๋ง ์ฌ์ฉํ๋ค.
+
+## ์คํ ์ง๋ฌธ
+๋ต์ ์์ง ๋ชจ๋ฅด๋ ํญ๋ชฉ์ ์ ๋๋ค. ์ถ์ ์ผ๋ก ๋ฎ์ง ๋ง๊ณ REQ ์ด์ ์์ฑ ์ด์ ์ ํ์ธํ ์ง๋ฌธ์ผ๋ก ๋จ๊ธด๋ค.
+
+- OQ-1: `liquidity_bucket`์ ๊ถ์ ์์ค๋ฅผ ๊ณ์ข ๋จ์๋ก ๋์ง, ์นดํ
๊ณ ๋ฆฌ/๊ฑฐ๋ ๋จ์๋ก ๋์ง ๊ฒฐ์ ํ๋ค.
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: `liquidity_bucket`์ ๊ถ์ ์์ค๋ `accounts.liquidity_bucket`์ผ๋ก ๋๋ค. ํ์ฉ๊ฐ์ `cash`, `investment`, `deposit`, `real_estate`, `loan`, `other`์ด๋ฉฐ, ๋ฏธ์ง์ ๊ณ์ข๋ `other`๋ก ํด๋ฐฑํ๋ค. ๊ฑฐ๋/์นดํ
๊ณ ๋ฆฌ override๋ ์ด๋ฒ ๋ฒ์์์ ์ ์ธํ๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: ์์ฐ ์ง๊ณ ๊ถ์ ์์ค์ ํด๋ฐฑ ๊ท์น์ด REQ ์ด์์ ๋ช
์๋๋ฉด Answered ์ฒ๋ฆฌ.
+- OQ-2: ๋ถ๋์ฐยท๋ฌด๊ธฐํ ์์ฐ๊ณผ ๋์ถ ์์ก์ ์
๋ ฅ/๊ฐฑ์ ๋ฐฉ์(์๋ ์
๋ ฅ vs ์ ์ฉ ๊ณ์ข vs ์ธ๋ถ ์ฐ๋)์ ์ด๋ป๊ฒ ๋์ง ๊ฒฐ์ ํ๋ค.
+ - ์ํ: Answered
+ - ์ฒ๋ฆฌ ๋ฐฉ์: ๋ถ๋์ฐ, ์์ ๊ธ, ๋์ถ ์์ก ๋ฑ ๊ฑฐ๋๋ด์ญ๋ง์ผ๋ก ์ฐ์ถ๋์ง ์๋ ์์ฐ/๋ถ์ฑ๋ ์ด๊ธฐ์๋ `accounts` ํญ๋ชฉ์ผ๋ก ์๋ ์
๋ ฅํ๋ค. `last_balance`๋ ํ์ฌ ํ๊ฐ์ก/์์ก, `last_synced_at`์ ๊ธฐ์ค์ผ, `memo`๋ ์ถ์ฒ ๋ฉ๋ชจ๋ก ์ฌ์ฉํ๋ค. ๋ณ๋ ์์ฐ ํ๊ฐ ํ
์ด๋ธ๊ณผ ์๋ ์์ธ ์ฐ๋์ ํ์ ๋ฒ์๋ก ๋๋ค.
+ - ์ข
๋ฃ ์กฐ๊ฑด: ๋น๊ฑฐ๋ ์์ฐ ์
๋ ฅ/๊ฐฑ์ ๋ฐฉ์์ด REQ ์ด์๊ณผ ์ด์ ์ ์ฐจ์ ๋ฐ์๋๋ฉด Answered ์ฒ๋ฆฌ.
+
+### ์คํ ์ง๋ฌธ ์ฒ๋ฆฌ ๊ธฐ์ค
+- `Open`: ์์ง ๋ต์ด ์๊ณ ๋ค์ ๋จ๊ณ ์งํ์ ์ํฅ์ด ์๋ ์ํ๋ค. ๋ฌธ์์ ์ ์งํ๋ค.
+- `Answered`: ๋ต์ด ํ์ ๋ ์ํ๋ค. ๊ด๋ จ ๋ณธ๋ฌธ/์๊ตฌ์ฌํญ์ ๋ฐ์ํ ๋ค ์คํ ์ง๋ฌธ ๋ชฉ๋ก์์๋ ์ ๊ฑฐํ๊ฑฐ๋ resolved ๊ธฐ๋ก์ผ๋ก ์ด๋ํ๋ค.
+- `Deferred`: ์ง๊ธ ๋จ๊ณ์์ ๊ฒฐ์ ํ์ง ์๊ณ ๋ค์ ๋จ๊ณ ๋๋ ํ์ ์ด์๋ก ๋๊ธฐ๋ ์ํ๋ค. ๋๊ธธ ๋์๊ณผ ์ด์ ๋ฅผ ๋ฐ๋์ ์ ๋๋ค.
+- `Dropped`: ๋ ์ด์ ์ ํจํ์ง ์์ ์ง๋ฌธ์ด๋ค. ์ญ์ ์ฌ์ ๋ฅผ ์งง๊ฒ ๋จ๊ธฐ๊ณ ๋ชฉ๋ก์์ ์ ๊ฑฐํ๋ค.
+- ์ฒ๋ฆฌ ์์น 1: ์ง๋ฌธ๋ง ๋จ๊ธฐ์ง ๋ง๊ณ ๋ฐ๋์ `์ํ`, `์ฒ๋ฆฌ ๋ฐฉ์`, `์ข
๋ฃ ์กฐ๊ฑด`์ ํจ๊ป ์ ๋๋ค.
+- ์ฒ๋ฆฌ ์์น 2: ๋ต์ด ๋์จ ์ง๋ฌธ์ ๋ณธ๋ฌธ ๋ฐ์ ์์ด ๋จ์ ์ญ์ ํ์ง ์๋๋ค. ๋ฐ์ ์์น๋ฅผ ๋จผ์ ๊ฐฑ์ ํ๋ค.
+- ์ฒ๋ฆฌ ์์น 3: High ์ํฅ ์ง๋ฌธ์ `Deferred` ์ฒ๋ฆฌ ํ ์ฒ๋ฆฌ ๋ฐฉ์์ REQ ์ธ๊ณ ์ถ์ ๋ฐฉ์์ ๋ช
์ํ๋ค.
+
+# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค
+์ด ๋ณ๊ฒฝ์ด ์ฑ๊ณตํ๋ค๊ณ ํ๋จํ ๊ธฐ์ค์ ์ธก์ ๊ฐ๋ฅํ๊ฒ ์ ์ํ๋ค. ๊ฐ๋ฅํ๋ฉด ๋ฐ์ดํฐ ์ถ์ฒ๊น์ง ์ฐ๊ฒฐํ๋ค.
+
+- S-1: ๋์๋ณด๋ ์ฒซ ํ๋ฉด์์ ์ด๋ฒ ๋ฌ ์์
/์ง์ถ ํํฉ, ์์ฐ ์ ๋์ฑ ํํฉ, ์ด์ ๊ฒฝ๊ณ ๊ฐ ์ ์๋ ์ฐ์ ์์ ์์๋ก ๋ชจ๋ ๋
ธ์ถ๋๋ค.
+- S-2: ์์ฐ ์ ๋์ฑ/๊ตฌ์ฑ ์น์
์์ `accounts.liquidity_bucket` ๊ธฐ์ค ๋ฒํท๋ณ ๊ธ์กยท๋น์จ, ๋์ถ ์ ์ธ ์์ฐ ํฉ๊ณ, ์ด๋ถ์ฑ, ์์์ฐ์ด ๋จ์ผ ์ ์์ ๋ฐ๋ผ ์ผ๊ด๋๊ฒ ๊ณ์ฐ๋๋ค.
+- S-3: `liquidity_bucket` ๋ฐ์ดํฐ ๋ชจ๋ธ๊ณผ ๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ์ด `docs/data-model.md`, `docs/project-structure.md`, `docs/runtime-flows.md`์ ๋ฐ์๋๋ค.
+
+## ์ธก์ ๋ฐฉ์ ๋ฐ ๋ฐ์ดํฐ ์ถ์ฒ
+๊ฐ ์ฑ๊ณต ๊ธฐ์ค์ ์ด๋ป๊ฒ ์ธก์ ํ ์ง์ ์ด๋ค ๋ฐ์ดํฐ๋ ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก ์ธ์ง ์ ๋๋ค.
+
+- S-1 ์ธก์ ๋ฐฉ์: ๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ๊ฒ์(์คํฌ๋ฆฐ์ท/E2E)๋ก ์น์
์์ยทํ์ ์งํ ํ์ธ / ๋ฐ์ดํฐ ์ถ์ฒ: `dashboard/app.py` ๋ ๋ ๊ฒฐ๊ณผ, REQ ํ๋ฉด ์ฌ์
+- S-2 ์ธก์ ๋ฐฉ์: ํ๋ณธ ๋ฐ์ดํฐ๋ก ๋ฒํท ํฉ๊ณยท๋น์จยท์์์ฐ ๊ณ์ฐ์ ์๊ฒ์ฆ๊ณผ ๋น๊ต / ๋ฐ์ดํฐ ์ถ์ฒ: `transactions`, `ledger_accounts`, `accounts.liquidity_bucket` ๋งคํ ๊ท์น
+- S-3 ์ธก์ ๋ฐฉ์: baseline ๋ฌธ์ diff์์ `liquidity_bucket`ยท์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ ๋ฐ์ ์ฌ๋ถ ํ์ธ / ๋ฐ์ดํฐ ์ถ์ฒ: `docs/data-model.md`, `docs/project-structure.md`, `docs/runtime-flows.md`
+
+# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ
+REQ ์ด์ ์์ฑ ๋จ๊ณ๋ก ๋๊ธฐ๊ธฐ ์ ์ ๋ฌด์์ด ํ์ ๋์ด์ผ ํ๋์ง ํ์ธํ๋ค. ๋ฏธ์๋ฃ ํญ๋ชฉ์ ํ์ ๊ฒฐ์ ์ด ํ์ํจ์ ๋ปํ๋ค.
+
+- [x] ๋ฒ์(In Scope/Out of Scope) ํ์
+ - ๊ธฐ์ค: ์์์ฐ ์ฐ์์ `accounts.liquidity_bucket` ๊ธฐ์ค ๊ธ์ต์์ฐยท์๋ ์
๋ ฅ ๋น๊ฑฐ๋ ์์ฐ์์ `loan`์ ์ฐจ๊ฐํ๋ ๋ฐฉ์์ผ๋ก ํ์ ํ๊ณ , ๋ถ๋์ฐ ํ๊ฐ ์๋ํยท์๋ ์์ธ ์ฐ๋ยท๋ณ๋ ์์ฐ ํ
์ด๋ธ์ ์ ์ธํ๋ค.
+- [x] ์ฑ๊ณต ์งํ์ ์ธก์ ๋ฐฉ์/๋ฐ์ดํฐ ์ถ์ฒ ํ์
+ - ๊ธฐ์ค: S-1~S-3์ ๊ฒ์ ๋ฐฉ์๊ณผ ๋ฐ์ดํฐ ์ถ์ฒ๊ฐ REQ์์ ์คํ ๊ฐ๋ฅํ๊ฒ ๊ตฌ์ฒดํ๋จ
+- [x] High ์ํฅ ๋ฆฌ์คํฌ ์ฐ์ ์์ ๋ฐ ํด์ ์ฑ
์์ ์ง์
+ - ๊ธฐ์ค: `liquidity_bucket` ๊ถ์ ์์ค๋ ๊ณ์ข ๋จ์๋ก ๊ณ ์ ํ๊ณ , ๋น๊ฑฐ๋ ์์ฐ ์
๋ ฅ ๋ฆฌ์คํฌ๋ ์๋ ์
๋ ฅ ๊ณ์ข/์์ฐ ํญ๋ชฉ๊ณผ ๊ธฐ์ค์ผ/์ถ์ฒ ๋ฉ๋ชจ๋ก ํด์ํ๋ค.
+- [x] ๋ชจ๋ ์คํ ์ง๋ฌธ(OQ) `Open` ์ํ ์์ ํ์ธ
+ - ๊ธฐ์ค: OQ ๋ชฉ๋ก์ `Open` ์ํ ํญ๋ชฉ์ด 0๊ฐ์ด๊ฑฐ๋, ์์ฌ ํญ๋ชฉ ์ ๋ถ `Deferred` ์ฒ๋ฆฌ ์๋ฃ
+- [x] Freeze ํ์ ๋ด๋น์(CONFIRMED_BY) ์ง์
+ - ๊ธฐ์ค: ์ฌ์ฉ์ ์งํ ๊ณ ๊ฐ ๊ถ์ฅ์์ ์น์ธํ๊ณ confirm-discovery ์งํ์ ์ง์ํ๋ค.
+
+# 9. ํ์ผ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ
+์ด Discovery ๋ฌธ์ ์์ฑ/๊ฐฑ์ ๊ฒฐ๊ณผ์ ์ฐธ์กฐ ๋ฌธ์๋ฅผ ๊ธฐ๋กํ๋ค. ์ดํ ์ถ์ ๊ณผ ๋ฆฌ๋ทฐ ์ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉํ๋ค.
+
+- ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: ์์ฑ
+- ์์ฑ ๊ฒฝ๋ก: docs/discovery/dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow.md
+- ์ฐธ์กฐ ๋ฌธ์:
+ - docs/data-model.md (LedgerAccount/LedgerCategory, `liquidity_bucket` ๋์
๋์)
+ - docs/runtime-flows.md, docs/project-structure.md (๋์๋ณด๋ ์ด์ ํ๋ธ ํ๋ฉด ํ๋ฆ ๊ฐฑ์ ๋์; stage-pilot/templates/runtime-flows.md, stage-pilot/templates/project-structure.md๋ฅผ ๋
ผ๋ฆฌ ํ
ํ๋ฆฟ ์ฐธ์กฐ๋ก ์ฌ์ฉ)
+ - dashboard/app.py `page_overview` (ํ์ฌ ๋์๋ณด๋ ์ฒซ ํ๋ฉด ๊ตฌํ ๊ทผ๊ฑฐ)
+
+# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ
+์ฌ์ฉ์๋ ์น์ธ์๊ฐ ๊ฒฐ์ ํด์ผ ํ๋ ํญ๋ชฉ๋ง ๋ชจ์ ์ ๋ฆฌํ๋ค. ๊ฒฐ์ , ํ์ธ, ๋ฐ์ดํฐ ์์ฒญ์ ๊ตฌ๋ถํด์ ์ ๋๋ค.
+
+## DECIDE
+- `liquidity_bucket`์ ๊ถ์ ์์ค๋ `accounts.liquidity_bucket`์ผ๋ก ๋๋ค. ํ์ฉ๊ฐ์ `cash`, `investment`, `deposit`, `real_estate`, `loan`, `other`์ด๋ฉฐ, ๋ฏธ์ง์ ๊ณ์ข๋ `other`๋ก ํด๋ฐฑํ๋ค. ๊ฑฐ๋/์นดํ
๊ณ ๋ฆฌ override๋ ์ด๋ฒ ๋ฒ์์์ ์ ์ธํ๋ค.
+- ์์์ฐ์ `cash`, `investment`, `deposit`, `real_estate`, `other` ์ค ์์ฐ์ฑ ํญ๋ชฉ์ ํฉ๊ณ์์ `loan` bucket ํฉ๊ณ๋ฅผ ์ฐจ๊ฐํด ๊ณ์ฐํ๋ค. ๋์ถ์ ์์ฐ ๊ตฌ์ฑ ๋น์จ์๋ ํฌํจํ์ง ์๊ณ ์ด๋ถ์ฑ/์์์ฐ ๊ณ์ฐ์๋ง ์ฌ์ฉํ๋ค. ์นด๋ ๋ฏธ๊ฒฐ์ ๊ธ, ๋ณดํ ํด์งํ๊ธ๊ธ, ์๋ ์์ธ ์ฐ๋์ ์ด๋ฒ ๋ฒ์์์ ์ ์ธํ๋ค.
+
+## CONFIRM
+- "์ด๋ฒ ๋ฌ"์ `transaction_date` ๊ธฐ์ค ํ์ฌ ์(KST)๋ก ์ ์ํ๋ค. `posted_date` ๊ธฐ์ค ๋๋ ์ฌ์ฉ์ ์ ์ ํ๊ณ ์ ๋ง๊ฐ์ ํ์ ์ต์
์ผ๋ก ๋๋ค.
+
+## DATA
+- ๋ถ๋์ฐ, ์์ ๊ธ, ๋์ถ ์์ก ๋ฑ ๊ฑฐ๋๋ด์ญ๋ง์ผ๋ก ์ฐ์ถ๋์ง ์๋ ์์ฐ/๋ถ์ฑ๋ ์ด๊ธฐ์๋ `accounts` ํญ๋ชฉ์ผ๋ก ์๋ ์
๋ ฅํ๋ค. `last_balance`๋ ํ์ฌ ํ๊ฐ์ก/์์ก, `last_synced_at`์ ๊ธฐ์ค์ผ, `memo`๋ ์ถ์ฒ ๋ฉ๋ชจ๋ก ์ฌ์ฉํ๋ค. ๋ณ๋ ์์ฐ ํ
์ด๋ธ๊ณผ ์๋ ์์ธ ์ฐ๋์ ํ์ ๋ฒ์๋ก ๋๋ค.
+
+# 11. Discovery Freeze
+REQ ์ด์ ์์ฑ ์ธ๊ณ ์์ ์ ํ์ ํ๋๊ทธ์ ์น์
์ฐธ์กฐ๋ง ๊ธฐ๋กํ๋ค. ๋ด์ฉ ์ฌ์์ฑ ์์ด ์๋ณธ ์น์
์ ๋งํฌ๋ก ๋์ ํ๋ค.
+
+## ์น์
์ฐธ์กฐ
+- ๋ฒ์: [# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก > ๋ฒ์ ๊ฒฝ๊ณ](#5-์๊ตฌ์ฌํญ-๋ชฉ๋ก)
+- ๋ฆฌ์คํฌ/์คํ ์ง๋ฌธ: [# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก](#6-๋ฆฌ์คํฌ๊ฐ์ -๋ชฉ๋ก)
+- ์ฑ๊ณต ๊ธฐ์ค: [# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค](#7-์ด๊ธฐ-์ฑ๊ณต-๊ธฐ์ค)
+- ์ธ๊ณ ์ ํ์ธ: [# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ](#8-req๋ก-๋๊ธฐ๊ธฐ-์ -ํ์ธ-์ฒดํฌ)
+- ์ฌ์ฉ์ ๊ฒฐ์ ํ์: [# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ](#10-์ฌ์ฉ์-๊ฒฐ์ -ํ์-ํญ๋ชฉ-์์ฝ)
+
+## Freeze ํ๋๊ทธ
+- Handoff Decision: ์น์ธ
+- Handoff Rationale: ์ฌ์ฉ์ ์น์ธ์ผ๋ก ๋์๋ณด๋ ์ด์ ํ๋ธ ์ ๋ณด๊ตฌ์กฐ, `accounts.liquidity_bucket` ๊ถ์ ์์ค, ์์์ฐ/๋ถ์ฑ ๊ณ์ฐ ๊ฒฝ๊ณ, ์ด๋ฒ ๋ฌ ๊ธฐ์ค, ๋น๊ฑฐ๋ ์์ฐ ์๋ ์
๋ ฅ ๋ฐฉ์์ด ํ์ ๋์์ผ๋ฏ๋ก REQ ์ด์ ์์ฑ์ผ๋ก ๋๊ธด๋ค.
+- Ready for REQ Drafting: true
+- Confirmed By: ์งํ ๊ณ
+- Confirmed At (KST): 2026-06-29 14:08 KST
diff --git a/docs/discovery/dcy-006_20260630_pytest-apptest-smoke-ci-automation.md b/docs/discovery/dcy-006_20260630_pytest-apptest-smoke-ci-automation.md
new file mode 100644
index 0000000..13bfb05
--- /dev/null
+++ b/docs/discovery/dcy-006_20260630_pytest-apptest-smoke-ci-automation.md
@@ -0,0 +1,216 @@
+# 0. ๋ฌธ์ ์ํ
+์ด ๋ฌธ์์ ํ์ฌ ์ํ์ ์๋ณ ์ ๋ณด๋ฅผ ๊ธฐ๋กํ๋ค. ๋ฌธ์ ์ถ์ ๊ณผ ์น์ธ ํ๋ฆ์ ํ์ํ ์ต์ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋๋ค.
+
+- ์ํ: confirmed
+- ๋ฌธ์ ID: dcy-006_20260630_pytest-apptest-smoke-ci-automation
+- ์ด์๋ช
: pytest/AppTest/Smoke CI ์๋ํ
+- ์์ฑ ์๊ฐ(KST): 2026-06-30 13:32
+- ๋ง์ง๋ง ๊ฐฑ์ ์๊ฐ(KST): 2026-06-30 14:42 KST
+- ๋์ฒด๋จ: ์์
+- ํ์ Discovery ์ฐธ์กฐ: ์์
+- ์์ฑ๋ REQ ์ฐธ์กฐ: REQ-006 (`docs/srs/Testing/req-006_pytest-apptest-smoke-ci-automation.md`)
+
+# 1. ๊ณํ ์ํ ์์ฝ
+ํ์ฌ Discovery๊ฐ ์ด๋ค ๋ฐฐ๊ฒฝ์์ ์์ฑ๋๊ณ ์๋์ง, ์ฐ๊ณ ๋ฌธ์๊ฐ ์๋์ง, ์ง๊ธ ์์ ์ ํด์์ด ๋ฌด์์ธ์ง ์งง๊ฒ ์์ฝํ๋ค.
+
+- ์ฐ๊ณ ๋ฌธ์ ํํฉ:
+ - ๊ด๋ จ GitHub Project ํญ๋ชฉ: https://github.com/users/genichin/projects/4 ์ `pytest/AppTest/Smoke CI ์๋ํ` ํญ๋ชฉ (Project #4 ์์ฌ ์์
)
+ - ์ ํ Discovery: `docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md` (Out of Scope/์ฒดํฌ๋ฆฌ์คํธ์์ CI ํตํฉ์ ๋ณธ ํญ๋ชฉ์ผ๋ก ๋ถ๋ฆฌ)
+- ํด์:
+ - dcy-004๋ ๋ก์ปฌ ์ฌํ ๊ฐ๋ฅํ browser evidence ๋ณต๊ตฌ๊น์ง๋ก ๋ฒ์๋ฅผ ํ์ ํ๊ณ , `pytest/AppTest/Smoke CI ์๋ํ`๋ฅผ Project #4์ ๋ณ๋ ํญ๋ชฉ์ผ๋ก ๋ช
์์ ์ผ๋ก ๋ถ๋ฆฌํ๋ค. ๋ณธ Discovery๋ ๊ทธ ๋ถ๋ฆฌ๋ ํ์ ์์
์ ๋
๋ฆฝ ์ฃผ๊ธฐ๋ก ๋์ด์ฌ๋ ค ์ถ์ ํ๋ค.
+ - ํ์ฌ ์ ์ฅ์์๋ pytest ํ๊ท ์ค์ํธ, Streamlit AppTest, HTTP/DB safety smoke ์์ฐ์ด ์ด๋ฏธ ์กด์ฌํ์ง๋ง ์ด๋ฅผ ๊ฐ์ ์คํยท๊ฒ์ดํธํํ๋ CI ๊ณ์ฝ(workflow)์ด ์ ํ ์๋ค. ๋ณธ Discovery๋ "์ด๋ฏธ ์๋ ๊ฒ์ฆ ์์ฐ์ CI๋ก ์๋ ์คํยท๊ฒ์ดํธํํ๋ ๊ณ์ฝ"์ ์ ์ํ๋ ๊ฒ์ ๋ชฉํ๋ก ํ๋ค.
+
+# 2. ์๊ตฌ์ฌํญ ํ์ ๊ฒฐ๊ณผ
+์ด๋ฒ ์์ฒญ์ด ๊ธฐ์กด ์๊ตฌ์ฌํญ๊ณผ ๋น๊ตํด ์ ๊ท์ธ์ง, ์ ์ฌํ์ง, ์ค๋ณต์ธ์ง ํ๋จํ๊ณ ๊ทธ ๊ทผ๊ฑฐ๋ฅผ ๋จ๊ธด๋ค.
+
+- ํ์ : ์ ๊ท
+- ๊ทผ๊ฑฐ
+ - dcy-004๋ browser-level evidence ๋ณต๊ตฌ์ QC evidence ๋ฑ๊ธ ์ ์๊ฐ ์ค์ฌ์ด๋ฉฐ, "๊ฒ์ฆ ์์ฐ์ CI์์ ์๋ ์คํยท๊ฒ์ดํธํ"ํ๋ ์๋ํ ๊ณ์ฝ์ ๋
๋ฆฝ ์ฑ๊ณต ๊ธฐ์ค์ผ๋ก ๋ค๋ฃจ์ง ์์๋ค. dcy-004 ๋ณธ๋ฌธ(`# 5 ๋ฒ์ ๊ฒฝ๊ณ`, `# 8 ํ์ธ ์ฒดํฌ`)์ด CI ํตํฉ์ ๋ณธ ํญ๋ชฉ์ผ๋ก ๋ช
์ ๋ถ๋ฆฌํ๋ค.
+ - ์ ์ฅ์์ `.github/workflows/`๊ฐ ์กด์ฌํ์ง ์๊ณ `docs/project-structure.md`๋ "CI workflow and deployment/service management are not yet defined."๋ก ๊ธฐ๋กํ๋ค. ๋ฐ๋ผ์ CI ์๋ํ๋ ๊ธฐ์กด ์๊ตฌ์ฌํญ์ ๋ฏธ์ธ ์กฐ์ ์ด ์๋๋ผ ์ ๊ท governance unit์ด๋ค.
+
+# 3. ๋ฌธ์ ์ ์ ์์ฝ
+๋ฌธ์ ์ ํ์ฌ ์ํ, ๊ธฐ๋ ์ํ, ๊ด๋ จ ์ดํด๊ด๊ณ์, ์ ์ฝ์ฌํญ์ ์ ๋ฆฌํ๋ค. Discovery์ ํต์ฌ ๋งฅ๋ฝ์ ๊ฐ์ฅ ๋จผ์ ์ดํดํ ์ ์์ด์ผ ํ๋ค.
+
+- ํ์ฌ ์ํ
+ - CI ๊ณ์ฝ์ด ๋ถ์ฌํ๋ค: ์ ์ฅ์์ `.github/workflows/` ๋๋ ํฐ๋ฆฌ ์์ฒด๊ฐ ์์ด, push/PR ์ ์๋์ผ๋ก ํ
์คํธ๋ฅผ ์คํยท์ฐจ๋จํ๋ ๊ฒ์ดํธ๊ฐ ์กด์ฌํ์ง ์๋๋ค. ๊ฒ์ฆ์ ์ ์ ์ผ๋ก ์์ฑ์/๊ฒ์๊ด์ ์๋ ๋ก์ปฌ ์คํ์ ์์กดํ๋ค. `docs/project-structure.md`๋ "CI workflow and deployment/service management are not yet defined."๋ก ๋ช
์ํ๋ค.
+ - ๊ฒ์ฆ ์์ฐ์ ์ด๋ฏธ ํ๋ถํ๋ค: pytest ํ๊ท ์ค์ํธ(`tests/test_categorization.py`, `tests/test_service.py`, `tests/test_backup_workflow.py`, `tests/test_dashboard_app.py` ๋ฑ 11๊ฐ ํ
์คํธ ๋ชจ๋), `pyproject.toml`์ `[tool.pytest.ini_options]`(testpaths=["tests"], dev extra์ `pytest>=8.0.0`)๊ฐ ๊ฐ์ถฐ์ ธ ์๋ค. ๊ทธ๋ฌ๋ ์ด๋ค์ ์๋ ์คํํ๋ ์ธ๋ถ ๊ณ์ฝ์ด ์๋ค.
+ - AppTest/smoke ์์ฐ์ด ๋ก์ปฌ์ ๋ฌถ์ฌ ์๋ค: Streamlit `AppTest` ๊ธฐ๋ฐ `tests/test_dashboard_app.py`, browser evidence ์คํฌ๋ฆฝํธ `scripts/streamlit_browser_evidence.py`, DB restore safety smoke `scripts/ledger_restore_smoke.py`๊ฐ ์กด์ฌํ์ง๋ง ๋ชจ๋ ๋ก์ปฌ ์๋ ์คํ์ด ์ ์ ๋ค. `docs/project-structure.md`๋ "Local browser evidence capture exists ... broader flow coverage and CI automation remain planned."๋ก ์ด ํ๊ณ๋ฅผ ๊ธฐ๋กํ๋ค.
+ - browser evidence๊ฐ ๋ก์ปฌ ํ์ ์ด๋ค: dcy-004 ๊ฒฐ๊ณผ๋ก `/usr/bin/google-chrome-stable` ๊ธฐ๋ฐ ๋ก์ปฌ browser evidence ์ ์ฐจ๋ ๋ณต๊ตฌ๋์ผ๋, ์ด๋ ํค๋๋ฆฌ์ค Chrome/snap ๊ฐ์ฉ์ฑ์ ์์กดํด CI runner๋ก ๊ทธ๋๋ก ์ด์ํ๊ธฐ ์ด๋ ต๋ค. ๋ฐ๋ผ์ CI 1์ฐจ ๊ฒ์ดํธ๋ browser-screenshot์ด ์๋๋ผ ๊ฒฐ์ ์ ์ผ๋ก ์คํ ๊ฐ๋ฅํ pytest/AppTest/HTTP/DB safety smoke๋ก ๊ตฌ์ฑํด์ผ ํ๋ค.
+- ๊ธฐ๋ ์ํ
+ - push/PR ์ pytest ํ๊ท ์ค์ํธ, Streamlit AppTest, HTTP smoke, DB safety smoke๊ฐ CI์์ ์๋ ์คํ๋์ด ํฉ๊ฒฉ/๋ถํฉ๊ฒฉ์ด ๊ฒ์ดํธ๋ก ๊ฐ์ ๋๋ค.
+ - CI ๊ณ์ฝ์ด ๋ฌธ์ํ๋ ๋จ์ผ ์ง์
์ (workflow ์ ์)์ผ๋ก ์กด์ฌํ๊ณ , ์ด๋ค ๊ฒ์ฆ์ด blocker์ด๊ณ ์ด๋ค ๊ฒ์ด best-effort์ธ์ง ๋ช
ํํ ๊ตฌ๋ถ๋๋ค.
+ - browser-level evidence์ฒ๋ผ ํ๊ฒฝ ์์กด์ด ํฐ ๊ฒ์ฆ์ CI ํ์ ๊ฒ์ดํธ์์ ๋ถ๋ฆฌํ๊ณ , ๋ก์ปฌ/์๋ evidence ์ ์ฐจ(dcy-004)์์ ๊ฒฝ๊ณ๊ฐ ๋ช
ํํ ์ ์๋๋ค.
+- ์ดํด๊ด๊ณ์
+ - ๋ ์ ์ฅ์ธ(์ด์์ ๊ฒธ ์์ฑ์) โ CI workflow๋ฅผ ๊ตฌ์ฑยท์ ์งํ๊ณ ๋ก์ปฌ ๊ฒ์ฆ๊ณผ์ ๊ฒฝ๊ณ๋ฅผ ์ด์ํ๋ ์ฃผ์ฒด
+ - ๊ฒ์๊ด/๋ฆฌ๋ทฐ์ด โ CI ๊ฒ์ดํธ ๊ฒฐ๊ณผ๋ฅผ QC gate ํ์ ์ 1์ฐจ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉํ๋ ์ฃผ์ฒด
+ - StagePilot ํ์ SDLC(REQ/Batch) ์ํ์ โ ๋ณธ Discovery๋ฅผ CI ์๋ํ ์๊ตฌ์ฌํญ์ผ๋ก ์ ์
+- ์ ์ฝ์ฌํญ
+ - ๊ฐ์ธ ๊ธ์ต์ ๋ณด๋ฅผ ๋ค๋ฃจ๋ฏ๋ก CI ์คํ ๋ก๊ทธยท์ํฐํฉํธ์ ์ค๊ณ์ข๋ฒํธยท์์กยทraw_payloadยท์๋ณธ ํ์ผ๋ช
๋ฑ ๋ฏผ๊ฐ์ ๋ณด๊ฐ ๋
ธ์ถ๋์ด์๋ ์ ๋๋ค. CI ๊ฒ์ฆ์ synthetic fixture๋ฅผ ์ฐ์ ์ฌ์ฉํ๋ค.
+ - CI runner๋ ํค๋๋ฆฌ์ค browser/snap ๊ฐ์ฉ์ฑ์ด ๋ณด์ฅ๋์ง ์์ผ๋ฏ๋ก, browser-screenshot E2E๋ฅผ CI ํ์ ๊ฒ์ดํธ๋ก ๊ฐ์ ํ์ง ์๋๋ค(dcy-004 evidence ๋ฑ๊ธ์ fallback ๊ฒฝ๋ก๋ฅผ CI ๋งฅ๋ฝ์์ ์ฌํด์).
+ - DB safety smoke๋ ์ด์ DB๊ฐ ์๋๋ผ ์์ fixture/๊ฒ์ฆ DB(dcy-002 ์ ์ฐจ)์์๋ง ์ํํ์ฌ ์๋ณธ ๋ฐ์ดํฐ๋ฅผ ํผ์ํ์ง ์์์ผ ํ๋ค.
+
+## ์ฃผ์ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
+์ฌ์ฉ์ ๋๋ ์ด์์๊ฐ ์ค์ ๋ก ์ด๋ค ์ํฉ์์ ์ด ๋ณ๊ฒฝ์ ํ์๋ก ํ๋์ง ๋ํ ์๋๋ฆฌ์ค๋ฅผ ์ ๋๋ค.
+
+- ์์ฑ์๊ฐ ๋ถ๋ฅ/์๋น์ค/๋ฐฑ์
๋ก์ง์ ๋ณ๊ฒฝํด PR์ ์ฌ๋ฆฌ๋ฉด, CI๊ฐ pytest ํ๊ท ์ค์ํธ๋ฅผ ์๋ ์คํํ๊ณ ์คํจ ์ ๋จธ์ง๋ฅผ ์ฐจ๋จํด ํ๊ท๋ฅผ ์กฐ๊ธฐ์ ์ก๋๋ค.
+- ์์ฑ์๊ฐ `dashboard/app.py`๋ฅผ ๋ณ๊ฒฝํ๋ฉด, CI๊ฐ Streamlit AppTest์ HTTP smoke๋ฅผ fixture DB๋ก ์คํํด ๋์๋ณด๋๊ฐ ๊ธฐ๋ยท๋ ๋๋ง๋๋์ง ์๋ ๊ฒ์ฆํ๋ค.
+- ๊ฒ์๊ด์ด batch verification์ ์์ฑํ ๋, CI ๊ฒ์ดํธ ํต๊ณผ ๋ก๊ทธ๋ฅผ 1์ฐจ evidence๋ก ์ธ์ฉํ๊ณ , browser-screenshot evidence๋ ํ๊ฒฝ์ด ํ์ฉํ๋ ๋ก์ปฌ ์ ์ฐจ(dcy-004)์์ ๋ณด๊ฐํ๋ค.
+- ๋ฐฑ์
/๋ณต๊ตฌ ๋ก์ง์ ๋ณ๊ฒฝํ๋ฉด, CI๊ฐ DB safety smoke(`scripts/ledger_restore_smoke.py` ๊ณ์ด)๋ฅผ ์์ fixture DB๋ก ์คํํด ๋ณต๊ตฌ ์ ์ฐจ๊ฐ ์ด์ DB๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ณ ํต๊ณผํ๋์ง ํ์ธํ๋ค.
+
+# 4. ์ด๋ฒ์ ์ ์ํ ๋ณ๊ฒฝ
+์ด๋ฒ Discovery์์ ๋ค๋ฃจ๋ ํต์ฌ ๋ณ๊ฒฝ ํญ๋ชฉ์ ๋ชฉ๋ก์ผ๋ก ์ ๋๋ค. ๊ธฐ๋ฅ, ๊ตฌ์กฐ, ๊ท์น ๋ณ๊ฒฝ์ ํจ๊ป ํฌํจํด๋ ๋๋ค.
+
+- pytest ํ๊ท ์ค์ํธ๋ฅผ push/PR์์ ์๋ ์คํํ๋ CI ๊ณ์ฝ(workflow)์ ์ ์คํ๋ค. ์ด๋ค ์ด๋ฒคํธ์์ ์ด๋ค job์ด ์คํ๋๊ณ ๋ฌด์์ด ๋จธ์ง blocker์ธ์ง ์ ์ํ๋ค.
+- Streamlit AppTest์ HTTP smoke, DB safety smoke๋ฅผ CI job์ผ๋ก ์๋ํํ๋ค. ๋ชจ๋ synthetic fixture/์์ ๊ฒ์ฆ DB์์ ๊ฒฐ์ ์ ์ผ๋ก ์คํ๋๋๋ก ํ์คํํ๋ค.
+- CI ํ์ ๊ฒ์ดํธ์ ํ๊ฒฝ ์์กด ๊ฒ์ฆ(browser-screenshot E2E)์ ๊ฒฝ๊ณ๋ฅผ ๊ท์น์ผ๋ก ์ ์ํ๋ค. browser evidence๋ CI ํ์ ๊ฒ์ดํธ์์ ๋ถ๋ฆฌํ๊ณ ๋ก์ปฌ ์ ์ฐจ(dcy-004)์์ ์ญํ ์ ๋ฌธ์ํํ๋ค.
+- CI ๊ณ์ฝ๊ณผ ๊ฒ์ฆ job ๊ตฌ์กฐ๋ฅผ baseline ๋ฌธ์์ ๋ฐ์ํ๋ค: `docs/project-structure.md`์ "CI workflow ... not yet defined" ํญ๋ชฉ์ ๊ฐฑ์ ํ๊ณ , `docs/runtime-flows.md`์ CI ๊ฒ์ฆ ํ๋ฆ์ ์ถ๊ฐํ ํ์๋ฅผ ๋ช
์ํ๋ค.
+
+## ์ํฅ ๋ฒ์
+๋ณ๊ฒฝ์ด ๋ฏธ์น ์ฝ๋/๋ฌธ์/์ด์ ๋ฒ์๋ฅผ ๋ช
์ํด REQ ์ ์ ์ batch planning์์ ์ถ์ ๋๋ฝ์ ์ค์ธ๋ค.
+
+- ์ฝ๋ ์ํฅ:
+ - `.github/workflows/` โ CI workflow ์ ์ ์ ์ค(ํ์ผ ๊ฒฝ๋ก/์ด๋ฆ์ REQ/Batch์์ ํ์ ). pytest, AppTest, HTTP smoke, DB safety smoke job ๊ตฌ์ฑ.
+ - `scripts/` โ ๊ธฐ์กด `ledger_restore_smoke.py`, `streamlit_browser_evidence.py`๋ฅผ CI์์ ํธ์ถ ๊ฐ๋ฅํ ๊ฒฐ์ ์ ์ง์
์ ์ผ๋ก ์ฌ์ฌ์ฉ/๋ํํ ๊ฐ๋ฅ์ฑ. HTTP smoke ์ ์ฉ ํฌํผ ์ ์ค ์ฌ๋ถ๋ ํ์ ๋ฒ์.
+ - `tests/` โ ๊ธฐ์กด pytest/AppTest ์์ฐ์ ๋ณด์กด. CI์์ fixture DB๋ฅผ ๊ฒฐ์ ์ ์ผ๋ก ์ค๋นํ๋ ๊ณต์ฉ ์ ํธ ์ถ๊ฐ ๊ฐ๋ฅ์ฑ.
+ - `pyproject.toml` โ CI ์คํ์ ํ์ํ dev/dashboard extra ์ค์น ๊ฒฝ๊ณ ํ์ธ(์ด๋ฏธ `dev`=pytest, `dashboard`=streamlit/pandas/plotly ์กด์ฌ).
+- ๋ฌธ์ ์ํฅ:
+ - `docs/project-structure.md` โ "CI workflow and deployment/service management are not yet defined." ๋ฐ "CI automation remain planned." ํญ๋ชฉ ๊ฐฑ์ .
+ - `docs/runtime-flows.md` โ CI ๊ฒ์ฆ ํ๋ฆ(์ด๋ฒคํธ โ job โ ๊ฒ์ดํธ ๊ฒฐ๊ณผ) ์ถ๊ฐ.
+ - `docs/interface-contract.md` โ CI ๊ฒ์ดํธ ๊ฒฐ๊ณผ๋ฅผ QC evidence๋ก ์ธ์ฉํ๋ ๊ธฐ์ค ๋ฐ์ ๊ฒํ .
+ - `docs/runbooks/` โ CI ์คํจ ๋์/๋ก์ปฌ ์ฌํ runbook ํ๋ณด.
+- ์ด์ ์ํฅ:
+ - push/PR๋ง๋ค ์๋ ๊ฒ์ฆ์ด ์คํ๋์ด ์๋ ๋ก์ปฌ ์คํ ์์กด๋๊ฐ ๋ฎ์์ง๋ค.
+ - QC gate ์ด์์์ CI ํต๊ณผ ๋ก๊ทธ๊ฐ 1์ฐจ evidence๋ก ํ์คํ๋๊ณ , browser evidence๋ ํ๊ฒฝ ํ์ฉ ์ ๋ณด๊ฐ ์ฆ๊ฑฐ๋ก ์ด์๋๋ค.
+
+# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก
+์ค์ ๋ก ๊ตฌํ๊ณผ ๊ฒ์ฆ์ผ๋ก ์ด์ด์ง ์๊ตฌ์ฌํญ์ ๊ธฐ๋ฅ/๋น๊ธฐ๋ฅ์ผ๋ก ๋๋์ด ์ ๋ฆฌํ๊ณ , ๋ฒ์ ๊ฒฝ๊ณ๋ฅผ ๋ช
ํํ ์ ๋๋ค.
+
+## ๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+- FR-1: push/PR ์ด๋ฒคํธ์์ pytest ํ๊ท ์ค์ํธ(`testpaths=["tests"]`)๋ฅผ ์๋ ์คํํ๊ณ , ์คํจ ์ ๋จธ์ง blocker๋ก ๋์ํ๋ CI workflow๋ฅผ ์ ๊ณตํด์ผ ํ๋ค.
+- FR-2: Streamlit `AppTest`(`tests/test_dashboard_app.py` ๊ณ์ด)์ ๋์๋ณด๋ HTTP smoke(๊ณ ์ ํฌํธ๋ก ๊ธฐ๋ ํ HTTP 200 ํ์ธ)๋ฅผ synthetic fixture DB๋ก CI์์ ์๋ ์คํํด์ผ ํ๋ค.
+- FR-3: DB safety smoke(`scripts/ledger_restore_smoke.py` ๊ณ์ด)๋ฅผ ์์ fixture/๊ฒ์ฆ DB์์ CI job์ผ๋ก ์คํํ์ฌ, ๋ณต๊ตฌ ์ ์ฐจ๊ฐ ์๋ณธ ์ด์ DB๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ณ ํต๊ณผํ๋์ง ์๋ ๊ฒ์ฆํด์ผ ํ๋ค.
+- FR-4: CI ํ์ ๊ฒ์ดํธ(pytest/AppTest/HTTP/DB safety smoke)์ ํ๊ฒฝ ์์กด ๊ฒ์ฆ(browser-screenshot E2E)์ ๋ช
ํํ ๊ตฌ๋ถํ๊ณ , browser evidence๋ CI ํ์ ๊ฒ์ดํธ์์ ์ ์ธํ๋ ๋ก์ปฌ ์ ์ฐจ(dcy-004)์์ ์ญํ ์ ๋ฌธ์ํํด์ผ ํ๋ค.
+- FR-5: CI ๊ณ์ฝ๊ณผ ๊ฒ์ฆ job ๊ตฌ์กฐ๋ฅผ `docs/project-structure.md`์ `docs/runtime-flows.md`์ ๋ฐ์ํ๊ณ ("CI workflow ... not yet defined" ํญ๋ชฉ ๊ฐฑ์ ), ํ์ ์ CI ์คํจ ๋์ runbook ํ๋ณด๋ฅผ `docs/runbooks/`์ ์ถ๊ฐํด์ผ ํ๋ค.
+
+## ๋น๊ธฐ๋ฅ ์๊ตฌ์ฌํญ
+์ฑ๋ฅ, ์ด์์ฑ, ์ผ๊ด์ฑ, ์๋ํ ์์ ์ฑ ๋ฑ ๊ธฐ๋ฅ ์ธ ์๊ตฌ์ฌํญ์ ์ ๋๋ค.
+
+- NFR-1: CI ๊ฒ์ฆ์ ๊ฒฐ์ ์ ์ด์ด์ผ ํ๋ค. ๋์ผ ์ปค๋ฐ์ ๋ํด ๋์ผ ๊ฒฐ๊ณผ๋ฅผ ์ฐ์ถํ๋๋ก synthetic fixtureยท๊ณ ์ ํฌํธ/๊ฒฝ๋กยท๋ก๋ ์๋ฃ ๋๊ธฐ ์กฐ๊ฑด์ ํ์คํํ๋ค(flaky job ์ต์ํ).
+- NFR-2: CI ๋ก๊ทธยท์ํฐํฉํธ์ ์ค๊ณ์ข๋ฒํธยท์์กยทraw_payloadยท์๋ณธ ํ์ผ๋ช
ยท๋น๋ฐ๋ฒํธ ๋ฑ ๋ฏผ๊ฐ์ ๋ณด๊ฐ ํ๋ฌธ์ผ๋ก ๋
ธ์ถ๋์ด์๋ ์ ๋๋ฉฐ, ๋ชจ๋ CI ๊ฒ์ฆ์ synthetic fixture๋ฅผ ์ฐ์ ์ฌ์ฉํ๋ค.
+- NFR-3: CI๋ ์ด์ DB๋ฅผ ์ ๋ ์ฌ์ฉํ์ง ์๊ณ ์์ fixture/๊ฒ์ฆ DB(dcy-002 ์ ์ฐจ)์์๋ง ๋์ํ์ฌ ์๋ณธ ๋ฐ์ดํฐ ํผ์ ์ํ์ด ์์ด์ผ ํ๋ค.
+- NFR-4: CI runner์ browser/snap ๊ฐ์ฉ์ฑ์ ์์กดํ์ง ์๊ณ ๋ ํ์ ๊ฒ์ดํธ๊ฐ ํต๊ณผ ๊ฐ๋ฅํด์ผ ํ๋ค(headless browser๋ best-effort, ํ์ ๊ฒ์ดํธ๋ pytest/AppTest/HTTP/DB safety smoke).
+
+## ๋ฒ์ ๊ฒฝ๊ณ
+์ด๋ฒ ๋ฐ๋ณต์์ ๋ฐ๋์ ํฌํจํ ๋ฒ์์ ๋ช
์์ ์ผ๋ก ์ ์ธํ ๋ฒ์๋ฅผ ๊ตฌ๋ถํ๋ค.
+
+- In Scope
+ - pytest ํ๊ท ์ค์ํธ, Streamlit AppTest, ๋์๋ณด๋ HTTP smoke, DB safety smoke๋ฅผ push/PR์์ ์๋ ์คํํ๋ CI ๊ณ์ฝ ์ ์
+ - CI ํ์ ๊ฒ์ดํธ์ ํ๊ฒฝ ์์กด(browser) ๊ฒ์ฆ์ ๊ฒฝ๊ณ ๊ท์น ์ ์ ๋ฐ baseline ๋ฌธ์ ๋ฐ์
+ - synthetic fixture ์ฐ์ ยท๋ฏผ๊ฐ์ ๋ณด ๊ธ์งยท์ด์ DB ๋ฏธ์ฌ์ฉ ์์น์ CI ์ ์ฉ
+- Out of Scope
+ - browser-screenshot/visual regression์ CI ํ์ ๊ฒ์ดํธํ(ํ๊ฒฝ ์์กด์ด ์ปค best-effort๋ก๋ง ๊ฒํ ). ๋ก์ปฌ browser evidence๋ dcy-004 ๋ฒ์๋ก ์ ์ง.
+ - ๋ฐฐํฌ/๋ฆด๋ฆฌ์ค ์๋ํ(CD), ์ธ๋ถ ํธ์คํ
/์๋น์ค ์ด์ ๋์
. ๋ณธ ๋ฐ๋ณต์ ๊ฒ์ฆ CI ๊ฒ์ดํธ๊น์ง๋ก ํ์ .
+ - ํ
์คํธ ์ปค๋ฒ๋ฆฌ์ง ๋ชฉํ์น ๊ฐ์ , ์ฑ๋ฅ ๋ฒค์น๋งํฌ ๊ฒ์ดํธ ๊ฐ์ ๊ณ ๋ํ ํ์ง ๊ฒ์ดํธ๋ ํ์ ๋ฐ๋ณต์ผ๋ก ๋ถ๋ฆฌ.
+ - CI ํ๋ซํผ์ GitHub Actions๋ก ๊ณ ์ ํ๊ณ , ๊ธฐ๋ณธ ํธ๋ฆฌ๊ฑฐ๋ `pull_request`์ `main` ๋์ `push`, ์ ํ ๊ฒฝ๋ก๋ `workflow_dispatch`๋ก ๋๋ค.
+
+# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก
+๋ถํ์ค์ฑ๊ณผ ์ ์ ์กฐ๊ฑด์ ๋ถ๋ฆฌํด์ ์ ๋๋ค. ๋ฆฌ์คํฌ๋ ์ํฅ๊ณผ ์ํ ๋ฐฉ์์, ๊ฐ์ ์ ํ์ฌ ํ๋จ์ ์ ์ ๋ฅผ ๊ธฐ๋กํ๋ค.
+
+## ๋ฆฌ์คํฌ
+- R-1 (Medium): Streamlit AppTest/HTTP smoke๊ฐ ๋์ ๋ ๋๋งยท๋น๋๊ธฐ ๋ก๋ฉ์ผ๋ก CI์์ ๋น๊ฒฐ์ ์ ์ผ๋ก ์คํจ(flaky)ํ๋ฉด ๊ฒ์ดํธ ์ ๋ขฐ๋๊ฐ ๋จ์ด์ง๋ค.
+ - ์ํ ๋ฐฉ์: ๊ฒฐ์ ์ synthetic fixture DB, ๊ณ ์ ํฌํธ, ๊ธฐ๋ ํ readiness ๋๊ธฐ(HTTP 200 ํด๋ง) ์กฐ๊ฑด์ ํ์คํํ๊ณ , ์ฌ์๋/ํ์์์ ์ ์ฑ
์ ๋ช
์ํ๋ค.
+- R-2 (Medium): CI ์คํ ์ค fixture ์ค๋น๋ ๋ก๊ทธ ์ถ๋ ฅ์ ์ค๋ฐ์ดํฐ/๋ฏผ๊ฐ์ ๋ณด๊ฐ ์์ด๋ฉด ์ธ๋ถ CI ๋ก๊ทธ์ ํ๋ฌธ ๋
ธ์ถ๋ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: ๋ชจ๋ CI ๊ฒ์ฆ์ synthetic fixture๋ก ๊ณ ์ ํ๊ณ ์ด์ DB ๊ฒฝ๋ก ์ ๊ทผ์ ์ฐจ๋จํ๋ค. ๋ก๊ทธ/์ํฐํฉํธ ๋ง์คํน ๋ฐ ๋ฏผ๊ฐ ํ๋ ๊ณต๋ ์์น์ ๊ฒ์ฆ ๋จ๊ณ์ ํฌํจํ๋ค.
+- R-3 (Low): browser evidence๋ฅผ CI ํ์ ๊ฒ์ดํธ๋ก ๋ฌด๋ฆฌํ๊ฒ ๋์ด๋ค์ด๋ฉด runner ํ๊ฒฝ ์ ์ฝ์ผ๋ก ๊ฒ์ดํธ๊ฐ ์์ ์คํจํ ์ ์๋ค.
+ - ์ํ ๋ฐฉ์: browser-screenshot E2E๋ฅผ ํ์ ๊ฒ์ดํธ์์ ์ ์ธํ๊ณ best-effort/๋ก์ปฌ ์ ์ฐจ๋ก ๋ถ๋ฆฌํ๋ ๊ฒฝ๊ณ ๊ท์น์ ๋ช
์ํ๋ค.
+
+## ๊ฐ์
+์์ง ํ์ ๋์ง ์์์ง๋ง ํ์ฌ ๋ฌธ์ ์์ฑ์ ๊ธฐ๋ฐ์ด ๋๋ ์ ์ ๋ฅผ ์ ๋๋ค.
+
+- A-1: ๊ธฐ์กด pytest ์ค์ํธ, `tests/test_dashboard_app.py`์ AppTest, `scripts/ledger_restore_smoke.py`, `scripts/streamlit_browser_evidence.py` ์์ฐ์ CI์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ํํ๋ก ํธ์ถํ ์ ์๋ค.
+- A-2: CI ํ์ ๊ฒ์ดํธ๋ headless browser ์์ด๋ pytest/AppTest/HTTP smoke/DB safety smoke๋ง์ผ๋ก ์๋ฏธ ์๋ ํ๊ท ๋ณดํธ๋ฅผ ์ ๊ณตํ๋ค.
+- A-3: DB safety smoke์ fixture ์ค๋น๋ dcy-002์ ๋ฐฑ์
ยท๊ฒ์ฆ DB ์ ์ฐจ๊ฐ ์ด์ ๊ฐ๋ฅํ ์ํ์์ ์ ์ ๋ก ์์/๋ณต์ฌ๋ณธ DB์์ ์ํํ๋ค.
+- A-4: synthetic fixture๋ง์ผ๋ก ํต์ฌ import/๋ถ๋ฅ/๋์๋ณด๋ ๊ฒฝ๋ก์ CI ๊ฒ์ฆ์ด ๊ฐ๋ฅํ๋ฉฐ, ์ค๋ฐ์ดํฐ ์์ด๋ ํ๊ท๋ฅผ ์ถฉ๋ถํ ํฌ์ฐฉํ ์ ์๋ค.
+
+## ์คํ ์ง๋ฌธ
+๋ต์ ์์ง ๋ชจ๋ฅด๋ ํญ๋ชฉ์ ์ ๋๋ค. ์ถ์ ์ผ๋ก ๋ฎ์ง ๋ง๊ณ REQ ์ด์ ์์ฑ ์ด์ ์ ํ์ธํ ์ง๋ฌธ์ผ๋ก ๋จ๊ธด๋ค.
+
+- OQ-1: CI ์คํ ํ๋ซํผ๊ณผ ํธ๋ฆฌ๊ฑฐ ๋ฒ์๋ฅผ ๋ฌด์์ผ๋ก ํ์ ํ ๊ฒ์ธ๊ฐ(GitHub Actions push/PR vs ์๋ dispatch vs ๋ก์ปฌ pre-commit ๋ณํ)?
+ - ์ํ: Answered
+ - ๋ต๋ณ: GitHub Actions๋ฅผ ๊ธฐ๋ณธ CI ํ๋ซํผ์ผ๋ก ์ฌ์ฉํ๊ณ , `pull_request`์ `main` ๋์ `push`๋ฅผ ๊ธฐ๋ณธ ํธ๋ฆฌ๊ฑฐ๋ก ํ๋ฉฐ `workflow_dispatch`๋ฅผ ์ ํ์ ์๋ ์ฌ์คํ ๊ฒฝ๋ก๋ก ํ์ฉํ๋ค.
+ - ๋ฐ์ ๋ฉ๋ชจ: REQ acceptance criteria์๋ PR ๊ธฐ์ค ํ์ ๊ฒ์ดํธ์ `main` push ์ฌ์คํ ๊ธฐ์ค, ์ ํ์ ์๋ ์คํ ํ์ฉ์ ํฌํจํ๋ค.
+- OQ-2: HTTP smoke์ DB safety smoke๋ฅผ ๊ธฐ์กด `scripts/` ์์ฐ์ ๊ทธ๋๋ก CI์์ ํธ์ถํ ์ง, CI ์ ์ฉ ๊ฒฐ์ ์ ์ง์
์ ์ผ๋ก ๋ณ๋ ๋ํํ ์ง?
+ - ์ํ: Answered
+ - ๋ต๋ณ: ๊ธฐ์กด `scripts/`์ ํ
์คํธ ์์ฐ์ ๋ด๋ถ ๋ก์ง์ ์ฌ์ฌ์ฉํ๋, CI์์๋ ์ข
๋ฃ์ฝ๋ยท์ธ์ยท๋ก๊ทธ๊ฐ ๊ฒฐ์ ์ ์ธ ์์ wrapper entrypoint๋ฅผ ๋ณ๋๋ก ๋๋ค.
+ - ๋ฐ์ ๋ฉ๋ชจ: REQ/Batch ์ค๊ณ ์ smoke job์ CI ์ ์ฉ wrapper๋ฅผ ํ์ค ์ง์
์ ์ผ๋ก ์ฌ์ฉํ๊ณ , ๊ธฐ์กด ์์ฐ์ ๋ด๋ถ ๊ตฌํ์ผ๋ก ์ฌ์ฌ์ฉํ๋ค.
+
+# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค
+์ด ๋ณ๊ฒฝ์ด ์ฑ๊ณตํ๋ค๊ณ ํ๋จํ ๊ธฐ์ค์ ์ธก์ ๊ฐ๋ฅํ๊ฒ ์ ์ํ๋ค. ๊ฐ๋ฅํ๋ฉด ๋ฐ์ดํฐ ์ถ์ฒ๊น์ง ์ฐ๊ฒฐํ๋ค.
+
+- S-1: push/PR ์ด๋ฒคํธ์์ pytest ํ๊ท ์ค์ํธ๊ฐ CI๋ก ์๋ ์คํ๋๊ณ , ์๋์ ์ผ๋ก ๊นจ์ง ๋ณ๊ฒฝ์ ๋ํด ๊ฒ์ดํธ๊ฐ ๋จธ์ง๋ฅผ ์ฐจ๋จํจ์ ์ฌํ ๊ฐ๋ฅํ๊ฒ ํ์ธํ๋ค.
+- S-2: Streamlit AppTest, HTTP smoke, DB safety smoke๊ฐ synthetic fixture/์์ ๊ฒ์ฆ DB๋ก CI์์ ํต๊ณผํ๊ณ , ์คํ ๋ก๊ทธยท์ํฐํฉํธ์ ๋ฏผ๊ฐ์ ๋ณด๊ฐ ๋
ธ์ถ๋์ง ์์์ ํ์ธํ๋ค.
+- S-3: CI ํ์ ๊ฒ์ดํธ์ browser evidence ๊ฒฝ๊ณ, CI ๊ฒ์ฆ ํ๋ฆ์ด `docs/project-structure.md`("CI workflow ... not yet defined" ๊ฐฑ์ )์ `docs/runtime-flows.md`์ ๋ฐ์๋์ด ํ์ REQ/Batch์์ ์ฐธ์กฐ ๊ฐ๋ฅํ๋ค.
+
+## ์ธก์ ๋ฐฉ์ ๋ฐ ๋ฐ์ดํฐ ์ถ์ฒ
+๊ฐ ์ฑ๊ณต ๊ธฐ์ค์ ์ด๋ป๊ฒ ์ธก์ ํ ์ง์ ์ด๋ค ๋ฐ์ดํฐ๋ ๋ฌธ์๋ฅผ ๊ทผ๊ฑฐ๋ก ์ธ์ง ์ ๋๋ค.
+
+- S-1 ์ธก์ ๋ฐฉ์: CI ์คํ ๊ฒฐ๊ณผ(์ฑ๊ณต/์คํจ) ๋ก๊ทธ์, ์๋์ ํ๊ท ์ฃผ์
์ ๊ฒ์ดํธ๊ฐ failํ๋์ง ์ฌํ ํ์ธ / ๋ฐ์ดํฐ ์ถ์ฒ: CI workflow ์คํ ๋ก๊ทธ, pytest ๊ฒฐ๊ณผ
+- S-2 ์ธก์ ๋ฐฉ์: AppTest/HTTP/DB safety smoke job์ ํต๊ณผ ๋ก๊ทธ ํ์ธ ๋ฐ ๋ก๊ทธยท์ํฐํฉํธ์ ๋ฏผ๊ฐ์ ๋ณด ๋ฏธ๋
ธ์ถ ๊ฒ์ / ๋ฐ์ดํฐ ์ถ์ฒ: CI job ๋ก๊ทธยท์ํฐํฉํธ, synthetic fixture DB
+- S-3 ์ธก์ ๋ฐฉ์: baseline ๋ฌธ์ ๊ฐฑ์ ๋ฐ CI ๊ฒ์ฆ ํ๋ฆ ๋ฐ์ ์ฌ๋ถ ๋ฆฌ๋ทฐ / ๋ฐ์ดํฐ ์ถ์ฒ: `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/runbooks/`
+
+# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ
+REQ ์ด์ ์์ฑ ๋จ๊ณ๋ก ๋๊ธฐ๊ธฐ ์ ์ ๋ฌด์์ด ํ์ ๋์ด์ผ ํ๋์ง ํ์ธํ๋ค. ๋ฏธ์๋ฃ ํญ๋ชฉ์ ํ์ ๊ฒฐ์ ์ด ํ์ํจ์ ๋ปํ๋ค.
+
+- [x] ๋ฒ์(In Scope/Out of Scope) ํ์
+ - ๊ธฐ์ค: ๋ณธ ๋ฐ๋ณต์ ๊ฒ์ฆ CI ๊ฒ์ดํธ(pytest/AppTest/HTTP/DB safety smoke)๊น์ง๋ก ํ์ ํ๊ณ CDยทvisual regressionยท์ปค๋ฒ๋ฆฌ์ง ๊ฒ์ดํธ๋ ์ ์ธํ๋ค๋ ์ ์ ์ฌ์ฉ์๊ฐ ํ์ธํ๋ค.
+- [x] ์ฑ๊ณต ์งํ์ ์ธก์ ๋ฐฉ์/๋ฐ์ดํฐ ์ถ์ฒ ํ์
+ - ๊ธฐ์ค: CI ์คํ ๋ก๊ทธยท์ํฐํฉํธยทsynthetic fixture๊ฐ acceptance criteria์ ์ธก์ ๊ทผ๊ฑฐ๋ก ํ์ ๋๋ค.
+- [ ] High ์ํฅ ๋ฆฌ์คํฌ ์ฐ์ ์์ ๋ฐ ํด์ ์ฑ
์์ ์ง์
+ - ๊ธฐ์ค: flaky ์ํ(R-1)๊ณผ ๋ฏผ๊ฐ์ ๋ณด ๋
ธ์ถ(R-2) ์ํ ์ฑ
์์๋ฅผ ์ง์ ํ๋ค. ๋ณธ Discovery์ ํ์ฌ High ๋ฆฌ์คํฌ๋ ์์ผ๋ R-1/R-2๋ฅผ ์ฐ์ ๊ด๋ฆฌํ๋ค.
+- [x] ๋ชจ๋ ์คํ ์ง๋ฌธ(OQ) `Open` ์ํ ์์ ํ์ธ
+ - ๊ธฐ์ค: OQ-1(CI ํ๋ซํผ/ํธ๋ฆฌ๊ฑฐ), OQ-2(smoke ์ง์
์ ์ฌ์ฌ์ฉ vs ๋ํ)๊ฐ ๋ชจ๋ Answered๋ก ์ ๋ฆฌ๋์๋ค.
+- [x] Freeze ํ์ ๋ด๋น์(CONFIRMED_BY) ์ง์
+ - ๊ธฐ์ค: ์ฌ์ฉ์(์งํ ๊ณ )๊ฐ ๊ถ์ฅ์์ ์น์ธํ๋ค.
+
+# 9. ํ์ผ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ
+์ด Discovery ๋ฌธ์ ์์ฑ/๊ฐฑ์ ๊ฒฐ๊ณผ์ ์ฐธ์กฐ ๋ฌธ์๋ฅผ ๊ธฐ๋กํ๋ค. ์ดํ ์ถ์ ๊ณผ ๋ฆฌ๋ทฐ ์ ๊ทผ๊ฑฐ๋ก ์ฌ์ฉํ๋ค.
+
+- ์ฒ๋ฆฌ ๊ฒฐ๊ณผ: ์์ฑ
+- ์์ฑ ๊ฒฝ๋ก: docs/discovery/dcy-006_20260630_pytest-apptest-smoke-ci-automation.md
+- ์ฐธ์กฐ ๋ฌธ์:
+ - docs/discovery/dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md
+ - docs/project-structure.md
+ - docs/runtime-flows.md
+ - stage-pilot/templates/project-structure.md
+ - stage-pilot/templates/runtime-flows.md
+
+# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ
+์ฌ์ฉ์๋ ์น์ธ์๊ฐ ๊ฒฐ์ ํด์ผ ํ๋ ํญ๋ชฉ๋ง ๋ชจ์ ์ ๋ฆฌํ๋ค. ๊ฒฐ์ , ํ์ธ, ๋ฐ์ดํฐ ์์ฒญ์ ๊ตฌ๋ถํด์ ์ ๋๋ค.
+
+## DECIDE
+- CI ํ๋ซํผ์ GitHub Actions๋ฅผ ์ฌ์ฉํ๋ค. ๊ธฐ๋ณธ ํธ๋ฆฌ๊ฑฐ๋ `pull_request`์ `main` ๋์ `push`์ด๋ฉฐ, `workflow_dispatch`๋ ์ ํ์ ์๋ ์ฌ์คํ ๊ฒฝ๋ก๋ก ํ์ฉํ๋ค.
+- HTTP smoke/DB safety smoke๋ ๊ธฐ์กด `scripts/`์ ํ
์คํธ ์์ฐ์ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฌ์ฉํ๋, CI์์๋ ์ข
๋ฃ์ฝ๋ยท์ธ์ยท๋ก๊ทธ๊ฐ ๊ฒฐ์ ์ ์ธ ์์ wrapper entrypoint๋ฅผ ํ์ค์ผ๋ก ์ฌ์ฉํ๋ค.
+
+## CONFIRM
+- CI ํ์ ๊ฒ์ดํธ๋ `pytest`, `Streamlit AppTest`, `HTTP smoke`, `DB safety smoke`๋ก ํ์ ํ๋ค. browser-screenshot E2E๋ ํ์ ์ฐจ๋จ ๊ฒ์ดํธ์ ๋ฃ์ง ์๊ณ ๋ก์ปฌ ๋ณด๊ฐ evidence ์ ์ฐจ(dcy-004)๋ก ์ ์งํ๋ค.
+
+## DATA
+- CI๋ ๋งค ์คํ ์ ์์ฑ๋๋ synthetic fixture DB๋ง ์ฌ์ฉํ๋ค. ์ด์ DB๋ ์ฌ์ฉํ์ง ์์ผ๋ฉฐ ์ค๊ณ์ข๋ฒํธยท์ค์์กยท`raw_payload`ยท์๋ณธ ํ์ผ๋ช
ยท๋น๋ฐ๋ฒํธ๋ ํฌํจํ์ง ์๋๋ค.
+- synthetic fixture DB๋ ๊ณ์ข/๊ฑฐ๋/ํ์ธํ์ ํ/์๊ฐ ํ๊ธํ๋ฆ/๋ณต๊ตฌ smoke๊ฐ ๋ชจ๋ ์ฌํ ๊ฐ๋ฅํ ์ต์ ๊ฒฐ์ ์ ์ํ ์ธํธ๋ฅผ ํฌํจํ๊ณ , runner temp ๊ฒฝ๋ก ๋๋ ๋๋ฑํ ์์ ๊ฒฝ๋ก์ ์์ฑํ๋ค.
+
+# 11. Discovery Freeze
+REQ ์ด์ ์์ฑ ์ธ๊ณ ์์ ์ ํ์ ํ๋๊ทธ์ ์น์
์ฐธ์กฐ๋ง ๊ธฐ๋กํ๋ค. ๋ด์ฉ ์ฌ์์ฑ ์์ด ์๋ณธ ์น์
์ ๋งํฌ๋ก ๋์ ํ๋ค.
+
+## ์น์
์ฐธ์กฐ
+- ๋ฒ์: [# 5. ์๊ตฌ์ฌํญ ๋ชฉ๋ก > ๋ฒ์ ๊ฒฝ๊ณ](#5-์๊ตฌ์ฌํญ-๋ชฉ๋ก)
+- ๋ฆฌ์คํฌ/์คํ ์ง๋ฌธ: [# 6. ๋ฆฌ์คํฌ/๊ฐ์ ๋ชฉ๋ก](#6-๋ฆฌ์คํฌ๊ฐ์ -๋ชฉ๋ก)
+- ์ฑ๊ณต ๊ธฐ์ค: [# 7. ์ด๊ธฐ ์ฑ๊ณต ๊ธฐ์ค](#7-์ด๊ธฐ-์ฑ๊ณต-๊ธฐ์ค)
+- ์ธ๊ณ ์ ํ์ธ: [# 8. REQ๋ก ๋๊ธฐ๊ธฐ ์ ํ์ธ ์ฒดํฌ](#8-req๋ก-๋๊ธฐ๊ธฐ-์ -ํ์ธ-์ฒดํฌ)
+- ์ฌ์ฉ์ ๊ฒฐ์ ํ์: [# 10. ์ฌ์ฉ์ ๊ฒฐ์ ํ์ ํญ๋ชฉ ์์ฝ](#10-์ฌ์ฉ์-๊ฒฐ์ -ํ์-ํญ๋ชฉ-์์ฝ)
+
+## Freeze ํ๋๊ทธ
+- Handoff Decision: ์น์ธ
+- Handoff Rationale: ์ฌ์ฉ์๊ฐ ๊ถ์ฅ ๊ธฐ๋ณธ์(GitHub Actions + CI ์ ์ฉ ์์ smoke wrapper + pytest/AppTest/HTTP/DB safety smoke ํ์ ๊ฒ์ดํธ + synthetic fixture DB ์ ์ฑ
)์ ์น์ธํด ์ฌ๋ ๊ฒฐ์ ํญ๋ชฉ์ด ํด์๋์๋ค. ๋จ์ ์ฑ
์์ ์ง์ ์ REQ/Batch planning ๋จ๊ณ์์ ํ ๋น ๊ฐ๋ฅํ ์ด์ ํญ๋ชฉ์ด๋ฉฐ Discovery confirm์ ๋ง์ง ์๋๋ค.
+- Ready for REQ Drafting: true
+- Confirmed By: ์งํ ๊ณ
+- Confirmed At (KST): 2026-06-30 13:54 KST
diff --git a/docs/discovery/index.md b/docs/discovery/index.md
index afc3ee1..0c66911 100644
--- a/docs/discovery/index.md
+++ b/docs/discovery/index.md
@@ -8,3 +8,7 @@
|---|---|---|---|---|
| dcy-001_20260626_streamlit-ux-e2e-categorization-refund-workflows | 20260626 | Streamlit ์ด์ UX/E2E ๊ฒ์ฆ ๋ฐ ๋ถ๋ฅยทํ๊ธ ์ํฌํ๋ก์ฐ ์์ ํ | [Discovery](./dcy-001_20260626_streamlit-ux-e2e-categorization-refund-workflows.md) | approved |
| dcy-002_20260627_operational-safety-backup-restore-validation-db | 20260627 | Jarca Ledger ์ด์ ์์ ์ฅ์น ๋ฐ ๋ฐฑ์
ยท๋ณต๊ตฌยท๊ฒ์ฆ DB ์ํฌํ๋ก์ฐ ์ ๋ฆฝ | [Discovery](./dcy-002_20260627_operational-safety-backup-restore-validation-db.md) | confirmed |
+| dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation | 20260629 | Statement-unavailable ๊ณ์ข ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ ์ํฌํ๋ก์ฐ ์ ๋ฆฝ | [Discovery](./dcy-003_20260629_statement-unavailable-manual-evidence-balance-reconciliation.md) | confirmed |
+| dcy-004_20260629_browser-streamlit-e2e-evidence-recovery | 20260629 | Browser/Streamlit E2E evidence ํ๊ฒฝ ๋ณต๊ตฌ ๋ฐ QC evidence ๊ธฐ์ค ๊ฐํ | [Discovery](./dcy-004_20260629_browser-streamlit-e2e-evidence-recovery.md) | confirmed |
+| dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow | 20260629 | Jarca Ledger ๋์๋ณด๋ ์ด์ ํ๋ธ ์ฌ์ ์ ๋ฆฝ (์ด๋ฒ ๋ฌ ํ๊ธํ๋ฆ + ์์ฐ ์ ๋์ฑ + ์ด์ ๋ฏธ์ฒ๋ฆฌ) | [Discovery](./dcy-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow.md) | confirmed |
+| dcy-006_20260630_pytest-apptest-smoke-ci-automation | 20260630 | pytest/AppTest/Smoke CI ์๋ํ | [Discovery](./dcy-006_20260630_pytest-apptest-smoke-ci-automation.md) | confirmed |
diff --git a/docs/interface-contract.md b/docs/interface-contract.md
index 9809ee6..208856b 100644
--- a/docs/interface-contract.md
+++ b/docs/interface-contract.md
@@ -4,7 +4,7 @@
- Baseline Seed: `.stage-pilot/bootstrap/baseline.yaml`
- Status: draft
- Owner: Jarca / project maintainer
-- Last Updated (KST): 2026-06-27 10:17
+- Last Updated (KST): 2026-06-29
- Source Discovery / Batch: bootstrap-baseline
## Project Summary
@@ -23,12 +23,13 @@
### Representative Tool Groups
- Health: `ledger_health_check`, `get_ledger_db_status`
- Operational backup: `create_ledger_backup`, `list_ledger_backups`, `get_ledger_backup_status` (restore is intentionally not exposed as MCP)
-- Accounts: `create_ledger_account`, `list_ledger_accounts`
+- Accounts: `create_ledger_account`, `list_ledger_accounts`, `update_ledger_account`; account payloads include additive `liquidity_bucket` for asset-liquidity reporting.
- Transactions: `create_ledger_transaction`, `list_ledger_transactions`, `update_ledger_transaction`, `delete_ledger_transaction`
-- Budgets/reports: `create_ledger_budget`, `list_ledger_budgets`, monthly/category/fixed/budget/investable reports
+- Budgets/reports: `create_ledger_budget`, `list_ledger_budgets`, monthly/category/fixed/budget/investable reports, `get_asset_liquidity_report`
- Imports: KB Bank PDF, Toss Bank PDF, Toss Securities PDF, SeoulPay XLSX, CSV preview/import where implemented
- Categories/rules: category CRUD, rule CRUD, categorization preview/apply, uncategorized summaries
- Reconciliation: cross-account preview/run/review queue, self-transfer candidates/linking, cancel-original candidates/linking
+- Manual balance reconciliation: `get_balance_reconciliation_report` returns preview-only account reconciliation for `manual_only`/`partial_import` accounts.
### Inputs
- Structured MCP tool arguments as declared in `server.py` signatures.
@@ -75,12 +76,17 @@
### Outputs
- Tables, charts, dialogs, previews, import results, and transaction/category updates.
+- Browser evidence runs produce local PNG screenshots, DOM snapshot HTML files, and a manifest JSON for QC review.
### UI Contract Rules
- Surface `cashflow_kind` where classification affects actual cashflow interpretation.
- Keep actual income/expense separated from account movement summaries.
- Rule page should support create/edit/delete and condition fields `amount_min`, `amount_max`, `day_of_month`.
- Dialog-based triage actions should avoid losing context where possible.
+- Account page should display/edit `statement_status` and show a minimal manual balance reconciliation report without adding an automatic schedule or dedicated workbench.
+- Account page should display/edit `liquidity_bucket`; overview uses it with `last_balance` for asset liquidity/composition/debt/net-worth sections.
+- Overview top priority is KST current-month actual income/expense/net cashflow/savings rate based on `transaction_date` and `cashflow_kind`.
+- UI/dashboard-impacting batches require browser screenshot/snapshot evidence from synthetic/validation DBs. Non-UI batches may omit browser evidence, and docs-only batches do not require it.
## Interface: File Import Contracts
- KB Bank PDF: encrypted/password-protected PDF account history; preserve transaction datetime, counterparty, balance, branch, raw summary, and raw payload.
@@ -95,13 +101,19 @@
- Import batch metadata should support rollback and audit.
- Broad mutation workflows should capture metadata-only DB status and backup evidence before/after risky operations.
- Restore execution is limited to script/runbook workflows and explicit approval, not MCP tools.
+- Manual balance reconciliation is preview/report-first. `REVIEW_NEEDED` suggests manual decision values (`๊ณ์ข์ด๋`, `์ค์ง์ถ`, `์ค์์
`, `ํฌ์์ด๋`, `๋ถ์์ ์ธ`, `ํ์ธํ์`) but does not auto-confirm mutations.
+- Asset liquidity reporting is account-based: `liquidity_bucket='loan'` is debt/net-worth only, and missing `last_balance` rows must not be inferred from transaction totals.
+- Browser evidence screenshots/snapshots must use synthetic or validation DBs only and must not include real account identifiers, source financial filenames, passwords, or raw payloads.
- Financial data and passwords are sensitive runtime inputs and should not be committed.
## Current Gaps / Planned Changes
- Formal versioning for MCP response contracts is not yet defined.
-- Browser-level UI acceptance criteria for Streamlit flows are not yet captured as REQ documents.
+- Browser-level UI evidence criteria are captured in REQ-004; broader CI automation is still not present.
- CI contract for tests/lint is not yet present.
## Change Log
- 2026-06-26 22:24 KST: Created by `bootstrap-baseline` from repository observation.
- 2026-06-27 10:17 KST: BAT-002/REQ-002 ๊ธฐ์ค์ผ๋ก backup/status/list/hash MCP contract์ restore ๋น๋
ธ์ถ ์ ์ฑ
์ถ๊ฐ.
+- 2026-06-29 KST: REQ-003/BAT-003 ๊ธฐ์ค์ผ๋ก account statement status update์ balance reconciliation report contract ์ถ๊ฐ.
+- 2026-06-29 KST: REQ-004/BAT-004 ๊ธฐ์ค์ผ๋ก Streamlit browser evidence ์ฐ์ถ๋ฌผ๊ณผ UI evidence gate ๊ท์น ์ถ๊ฐ.
+- 2026-06-29 KST: REQ-005/BAT-005 ๊ธฐ์ค์ผ๋ก account `liquidity_bucket`, `get_asset_liquidity_report`, dashboard operations hub contract ์ถ๊ฐ.
diff --git a/docs/project-structure.md b/docs/project-structure.md
index 0b17f48..bc94f0c 100644
--- a/docs/project-structure.md
+++ b/docs/project-structure.md
@@ -4,8 +4,8 @@
- Baseline Seed: `.stage-pilot/bootstrap/baseline.yaml`
- Status: draft
- Owner: Jarca / project maintainer
-- Last Updated (KST): 2026-06-26 22:24
-- Source Discovery / Batch: bootstrap-baseline
+- Last Updated (KST): 2026-06-30
+- Source Discovery / Batch: bootstrap-baseline, BAT-006
## Project Summary
- Jarca ๊ฐ๊ณ๋ถ ์ ์ฉ MCP ์๋ฒ์ Streamlit ๋์๋ณด๋๋ก ๊ณ์ข/์นด๋ ๊ฑฐ๋, ์์ฐ, ๋ถ๋ฅ ๊ท์น, import, ํ๊ธํ๋ฆ ๋ถ์์ SQLite์ ์ ์ฅํ๊ณ ์ด์ํ๋ค.
@@ -45,6 +45,9 @@ jarca-ledger-mcp/
โโโ pyproject.toml
โโโ uv.lock
โโโ .env.example
+โโโ .github/
+โ โโโ workflows/
+โ โโโ ci.yml
โโโ dashboard/
โ โโโ app.py
โโโ jarca_ledger_mcp/
@@ -65,10 +68,17 @@ jarca-ledger-mcp/
โ โโโ test_toss_securities_pdf.py
โ โโโ test_seoulpay_xlsx.py
โ โโโ test_ledger_categories.py
+โโโ scripts/
+โ โโโ ci_fixture_db.py
+โ โโโ ci_dashboard_http_smoke.py
+โ โโโ ci_db_safety_smoke.py
+โ โโโ ledger_restore_smoke.py
+โ โโโ streamlit_browser_evidence.py
โโโ docs/
โ โโโ discovery/
โ โโโ srs/
โ โโโ batches/
+โ โโโ runbooks/
โ โโโ releases/
โ โโโ project-structure.md
โ โโโ runtime-flows.md
@@ -82,7 +92,7 @@ jarca-ledger-mcp/
- `jarca_ledger_mcp/db.py`
- SQLite connection, schema initialization, migrations, row conversion, and database path resolution.
- `jarca_ledger_mcp/service.py`
- - Domain service layer for accounts, transactions, categories, category rules, imports, reports, transfer matching, and manual link operations.
+ - Domain service layer for accounts, transactions, categories, category rules, imports, reports, asset liquidity/net-worth aggregation, transfer matching, and manual link operations.
- `jarca_ledger_mcp/server.py`
- FastMCP tool registration layer that exposes service functions to Hermes/MCP clients.
- `jarca_ledger_mcp/kb_bank_pdf.py`
@@ -94,9 +104,21 @@ jarca-ledger-mcp/
- `jarca_ledger_mcp/seoulpay_xlsx.py`
- SeoulPay encrypted XLSX parsing and preview conversion.
- `dashboard/app.py`
- - Streamlit operator UI for summaries, transaction filtering, uncategorized triage, rule management, review queues, categories, accounts, and import operations.
+ - Streamlit operator UI for the operations hub summary, transaction filtering, uncategorized triage, rule management, review queues, categories, accounts/liquidity bucket editing, and import operations.
- `tests/`
- - Regression tests for service behavior, parser behavior, categorization, category rules, cross-account transfer matching, and ledger category semantics.
+ - Regression tests for service behavior, parser behavior, categorization, category rules, cross-account transfer matching, ledger category semantics, and Streamlit AppTest dashboard smoke.
+- `.github/workflows/ci.yml`
+ - GitHub Actions workflow for `pull_request`, `push` to `main`, and `workflow_dispatch`; stable required gate jobs are `pytest-regression`, `dashboard-app-test`, `dashboard-http-smoke`, and `db-safety-smoke`.
+- `scripts/ci_fixture_db.py`
+ - Synthetic fixture DB builder for CI/AppTest/smoke checks. Refuses the default operating DB path and seeds only synthetic ledger data.
+- `scripts/ci_dashboard_http_smoke.py`
+ - CI HTTP smoke wrapper that creates a synthetic fixture DB, starts Streamlit on loopback, polls HTTP 200 readiness, and cleans up the process with status-only JSON output.
+- `scripts/ci_db_safety_smoke.py`
+ - CI DB safety wrapper that runs restore smoke through temp synthetic source/backup/target DB paths only and emits status-only JSON.
+- `scripts/streamlit_browser_evidence.py`
+ - Local browser evidence helper that creates a synthetic fixture DB, starts Streamlit with that DB, and captures Chrome screenshot/DOM snapshot artifacts for QC.
+- `scripts/ledger_restore_smoke.py`
+ - Script-only restore smoke helper for validation DBs; restore remains outside the MCP tool surface.
## Dependency Rules
- `server.py` should remain a thin tool adapter and delegate business logic to `service.py`.
@@ -106,13 +128,17 @@ jarca-ledger-mcp/
## Shared Boundaries
- `cashflow_kind` is the boundary between actual household income/expense and account movement/exclusion semantics.
+- `liquidity_bucket` is the account-level boundary for asset liquidity/composition/debt/net-worth views; `dashboard/app.py` displays it and `service.py` computes it from `accounts.last_balance`.
- Import evidence fields (`source`, `external_id`, `raw_summary`, `raw_payload`, `counterparty`, `import_batch_id`) are audit boundaries and should be preserved.
- Manual reconciliation is represented through memo tags and narrow transaction updates, not broad rewrites.
+- Browser evidence artifacts are generated from synthetic/validation DBs and should not embed real financial identifiers or raw source payloads.
+- CI gate fixtures are generated at runtime under temp space by `scripts/ci_fixture_db.py`; CI and AppTest must not read the default operating DB path.
## Current Gaps / Planned Changes
-- Final UX verification is still needed for ๋ถ๋ฅ ๊ท์น ์ญ์ , ์ทจ์/ํ๊ธ ์๊ฑฐ๋ ์ฐ๊ฒฐ, and import E2E flows.
-- CI workflow and deployment/service management are not yet defined.
+- Local browser evidence capture exists for core Streamlit pages; browser screenshot/visual regression remains local evidence rather than a required CI gate.
+- CI workflow now covers pytest/AppTest/HTTP smoke/DB safety smoke; branch protection required-check enforcement is a repository setting outside this codebase.
- Long-term policy for accounts without downloadable statements remains partially operational/documentation work.
+- Separate automatic asset snapshots/manual asset tables are not present; BAT-005 uses account rows as the manual asset/liability source.
## Update Triggers
- Update this document when package boundaries change.
@@ -121,3 +147,6 @@ jarca-ledger-mcp/
## Change Log
- 2026-06-26 22:24 KST: Created by `bootstrap-baseline` from repository observation.
+- 2026-06-29 KST: REQ-004/BAT-004 ๊ธฐ์ค์ผ๋ก `scripts/streamlit_browser_evidence.py` ๋ฐ browser evidence artifact ๊ตฌ์กฐ ์ถ๊ฐ.
+- 2026-06-29 KST: REQ-005/BAT-005 ๊ธฐ์ค์ผ๋ก dashboard operations hub์ service asset liquidity aggregation ์ฑ
์ ์ถ๊ฐ.
+- 2026-06-30 KST: REQ-006/BAT-006 ๊ธฐ์ค์ผ๋ก GitHub Actions CI workflow, synthetic fixture DB builder, AppTest/HTTP/DB safety smoke wrappers, CI-vs-local-browser evidence ๊ฒฝ๊ณ ์ถ๊ฐ.
diff --git a/docs/runbooks/ci-smoke.md b/docs/runbooks/ci-smoke.md
new file mode 100644
index 0000000..6291739
--- /dev/null
+++ b/docs/runbooks/ci-smoke.md
@@ -0,0 +1,69 @@
+# CI Smoke Gates Runbook
+
+- Scope: BAT-006 / REQ-006 pytest, Streamlit AppTest, dashboard HTTP smoke, and DB safety smoke gates.
+- Safety rule: every command below creates or uses a synthetic fixture/temporary validation DB only. Do not set `JARCA_LEDGER_DB_PATH` to the operating ledger DB for CI reproduction.
+
+## GitHub Actions gates
+
+The workflow is `.github/workflows/ci.yml` and runs on:
+
+- `pull_request`
+- `push` to `main`
+- `workflow_dispatch`
+
+Stable job/check names:
+
+- `pytest-regression`
+- `dashboard-app-test`
+- `dashboard-http-smoke`
+- `db-safety-smoke`
+
+## Local reproduction
+
+From repository root:
+
+```bash
+source .venv/bin/activate
+pytest --ignore=tests/test_dashboard_app.py
+pytest tests/test_dashboard_app.py
+python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45
+python scripts/ci_db_safety_smoke.py --timeout 45
+```
+
+With uv, equivalent CI-style commands are:
+
+```bash
+uv sync --extra dev --extra dashboard
+uv run pytest --ignore=tests/test_dashboard_app.py
+uv run pytest tests/test_dashboard_app.py
+uv run python scripts/ci_dashboard_http_smoke.py --host 127.0.0.1 --port 18501 --timeout 45
+uv run python scripts/ci_db_safety_smoke.py --timeout 45
+```
+
+## Wrapper responsibilities
+
+- `scripts/ci_fixture_db.py`
+ - Creates a deterministic synthetic fixture DB named `fixture.sqlite3`, `ci-ledger-fixture.sqlite3`, or `ledger-fixture.sqlite3`.
+ - Refuses the default operating DB path.
+ - Seeds only synthetic accounts, categories, budgets, and transactions.
+- `scripts/ci_dashboard_http_smoke.py`
+ - Creates a temp synthetic fixture DB.
+ - Starts Streamlit with `JARCA_LEDGER_DB_PATH` pointed at that fixture.
+ - Polls a loopback URL until HTTP 200 or timeout.
+ - Terminates/kills the Streamlit process during cleanup.
+ - Emits status-only JSON.
+- `scripts/ci_db_safety_smoke.py`
+ - Creates a temp synthetic fixture DB and backup copy.
+ - Runs `scripts/ledger_restore_smoke.py` against temp source/backup/target paths only.
+ - Emits status-only JSON and never requires restore approval because the target is newly created under temp space.
+
+## Browser evidence boundary
+
+Browser screenshot/DOM evidence remains local evidence from `scripts/streamlit_browser_evidence.py`. It is not a required GitHub Actions gate. Use it in addition to the CI gates when a change materially affects visual layout or browser-only behavior.
+
+## Failure triage
+
+- `pytest-regression` failure: reproduce with `pytest --ignore=tests/test_dashboard_app.py` and fix the failing unit/service/parser test.
+- `dashboard-app-test` failure: reproduce with `pytest tests/test_dashboard_app.py`; inspect Streamlit AppTest exceptions first.
+- `dashboard-http-smoke` failure: check whether port 18501 is already in use locally, then rerun with a different port if needed. In CI the job uses a fresh runner.
+- `db-safety-smoke` failure: inspect the wrapper JSON fields `restore_smoke_exit_code`, `restored_integrity`, and `health_success`. Do not retry against the operating DB.
diff --git a/docs/runbooks/manual-balance-reconciliation.md b/docs/runbooks/manual-balance-reconciliation.md
new file mode 100644
index 0000000..1a1a3a1
--- /dev/null
+++ b/docs/runbooks/manual-balance-reconciliation.md
@@ -0,0 +1,57 @@
+# ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ Runbook
+
+- Source: REQ-003 / BAT-003 / dcy-003
+- Last Updated (KST): 2026-06-29
+- Owner: ๋ ์ ์ฅ์ธ
+
+## ๋ชฉ์
+
+๊ฑฐ๋๋ด์ญ ํ์ผ์ด ์๊ฑฐ๋ ์ผ๋ถ๋ง import ๊ฐ๋ฅํ ๊ณ์ข(`manual_only`, `partial_import`)๋ฅผ ๋์์ผ๋ก ์๋ ์ฆ๋น ๊ฑฐ๋๋ฅผ ์
๋ ฅํ๊ณ ๊ธฐ์ค ์์ก๊ณผ ๊ณ์ฐ ์์ก์ ๋์กฐํ๋ค.
+
+## ์์ ์์น
+
+1. ์ด์ DB ๋ณ๊ฒฝ ์ dcy-002 ์ ์ฐจ์ ๋ฐ๋ผ DB ์ํ evidence๋ฅผ ํ์ธํ๊ณ ๋ฐฑ์
์ ์์ฑํ๋ค.
+2. ๊ฒ์ฆ DB์์ ๋จผ์ preview/reconcile์ ์ํํ๋ค.
+3. restore๋ ์ฌ์ฉ์ ๋ช
์ ์น์ธ ์์ด๋ ์คํํ์ง ์๋๋ค.
+4. ์๋ฌธ ์ฆ๋น, ํ๋ฌธ ๊ณ์ข๋ฒํธ, ๋น๋ฐ๋ฒํธ, ๋ฏผ๊ฐ counterparty ์๋ฌธ์ ๋ฌธ์/๋ก๊ทธ/์ปค๋ฐ์ ๋จ๊ธฐ์ง ์๋๋ค.
+
+## ๊ณ์ข ์ํ ์ ์
+
+- `unknown`: ์์ง statement ์ง์ ์ฌ๋ถ๊ฐ ์ ๋ฆฌ๋์ง ์์.
+- `import_supported`: ๊ธฐ์กด statement import ํ๋ฆ์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ.
+- `manual_only`: statement-unavailable ๊ณ์ข. ์๋ ์ฆ๋น ๋ฐ ์์ก๋์กฐ ๋์.
+- `partial_import`: ์ผ๋ถ statement import ๊ฐ๋ฅํ์ง๋ง ๋๋ฝ ๊ตฌ๊ฐ ์๋ ๋ณด์ ๋ฐ ์์ก๋์กฐ ๋์.
+
+## ์
๋ ฅ ํฌ๋งท
+
+์์ก๋์กฐ report ์
๋ ฅ:
+
+- `account_id`: ํน์ ๊ณ์ข๋ง ๋์กฐํ ๋ ์ฌ์ฉ. ๋น์ฐ๋ฉด ํ์ฑ `manual_only`, `partial_import` ๊ณ์ข ์ ์ฒด.
+- `baseline_balance`: ๊ธฐ์ค ์์ก. ์์ผ๋ฉด ๊ณ์ข `last_balance` ์ฌ์ฉ.
+- `baseline_at`: ๊ธฐ์ค ์๊ฐ. `YYYY-MM`์ ํด๋น ์ ๋ง์ผ `23:59:00+09:00`, `YYYY-MM-DD`๋ ํด๋น์ผ `23:59:00+09:00`, ISO datetime์ ์์ ์ค๋
์ท ์๊ฐ.
+- `source_note`: ์: ์๋ง ์ฑ ์์ก, ํต์ฅ ํ๋ฉด ๋ฉ๋ชจ ๋ฑ ๋น๋ฏผ๊ฐ ์ถ์ฒ ์ค๋ช
.
+- `memo`: ์ด์์ ๋ฉ๋ชจ.
+
+## ์ ์ฐจ
+
+1. ๊ณ์ข ํ๋ฉด ๋๋ MCP/service๋ก `statement_status`๋ฅผ ์ค์ ํ๋ค.
+2. ์๋ ์ฆ๋น ๊ฑฐ๋๋ `source='manual'`, ์์ ๊ธ์ก, ๊ฑฐ๋์ผ, ๊ณ์ข, ๋น๋ฏผ๊ฐ ๋ฉ๋ชจ๋ฅผ ๊ฐ์ถ์ด ์
๋ ฅํ๋ค.
+3. `get_balance_reconciliation_report`๋ฅผ ์คํํ๋ค.
+4. ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ค.
+ - `OK`: ์ฐจ์ก ์ ๋๊ฐ์ด 1,000์ ์ดํ.
+ - `REVIEW_NEEDED`: ์ฐจ์ก ์ ๋๊ฐ์ด 1,000์ ์ด๊ณผ. ํ๋ณด ํ์ ๊ฐ ์ค ํ๋๋ก ์๋ ๊ฒํ ํ๋ค.
+ - `BASELINE_REQUIRED`: ๊ธฐ์ค ์์ก์ด ์์ด ํ์ ๋ถ๊ฐ.
+5. `REVIEW_NEEDED`๋ `๊ณ์ข์ด๋`, `์ค์ง์ถ`, `์ค์์
`, `ํฌ์์ด๋`, `๋ถ์์ ์ธ`, `ํ์ธํ์` ์ค ํ๋๋ก ์๋ ํ์ ํ ๊ทผ๊ฑฐ๋ฅผ ๋ชจ์๋ค.
+
+## ํ์ ๊ธฐ์ค
+
+- ๊ณ์ข์ด๋: ๊ฐ์ ์์ ์์ ๋ค๋ฅธ ๊ณ์ข ์
์ถ๊ธ๊ณผ ๊ธ์ก/์ผ์/์๋๋ฐฉ ๊ทผ๊ฑฐ๊ฐ ๋ง์.
+- ์ค์ง์ถ: ์ธ๋ถ๋ก ์๊ธ์ด ๋๊ฐ๊ณ household expense๋ก ๋ณด์์ผ ํจ.
+- ์ค์์
: ์ธ๋ถ์์ ๋ค์ด์จ ์ค์ ์์
.
+- ํฌ์์ด๋: ์ฆ๊ถ/ํฌ์ ๊ณ์ข ๋งค์ยท๋งค๋ยท์
์ถ๊ธ ๋ฑ ํฌ์ ํฌ์ง์
์ด๋.
+- ๋ถ์์ ์ธ: ๊ธ์ต ๋ด๋ถ ์กฐ์ , ์ค๋ฅ ์ ์ , ๋ถ์์ ๋ฃ์ผ๋ฉด ์๊ณก๋๋ ํญ๋ชฉ.
+- ํ์ธํ์: ๊ทผ๊ฑฐ ๋ถ์กฑ์ผ๋ก ํ์ ๋ณด๋ฅ.
+
+## ์ ์ธ
+
+์๋ ์ค์ผ์ค๋ง/์๋ฆผ, ์ ์ฉ ์ํฌ๋ฒค์น, ์๋ ํ์ mutation์ ์ด๋ฒ runbook ๋ฒ์๊ฐ ์๋๋ค.
diff --git a/docs/runbooks/streamlit-browser-evidence.md b/docs/runbooks/streamlit-browser-evidence.md
new file mode 100644
index 0000000..1ee5e7a
--- /dev/null
+++ b/docs/runbooks/streamlit-browser-evidence.md
@@ -0,0 +1,90 @@
+# Streamlit Browser Evidence Runbook
+
+## ๋ชฉ์
+
+Streamlit dashboard ๋ณ๊ฒฝ์ QCํ ๋ synthetic DB์ local Chrome์ผ๋ก browser-level screenshot/snapshot evidence๋ฅผ ์์งํ๋ค. ์ด์ DB, ์ค์ ๊ธ์ต ํ์ผ, ๋น๋ฐ๋ฒํธ, raw_payload๋ ์ฌ์ฉํ์ง ์๋๋ค.
+
+## ํ์ค ๋ช
๋ น
+
+```bash
+cd /root/projects/jarca-ledger-mcp
+.venv/bin/python scripts/streamlit_browser_evidence.py
+```
+
+๊ธฐ๋ณธ ์ฐ์ถ๋ฌผ ์์น:
+
+```text
+docs/batches/bat-004_20260629_browser-streamlit-e2e-evidence/evidence/run-YYYYMMDD-HHMMSS/
+โโโ fixture.sqlite3
+โโโ manifest.json
+โโโ rules.png
+โโโ rules.dom.html
+โโโ transfer-review.png
+โโโ transfer-review.dom.html
+โโโ import.png
+โโโ import.dom.html
+```
+
+## ์ ์ ์กฐ๊ฑด
+
+- Chrome: `/usr/bin/google-chrome-stable`
+- Streamlit: `.venv/bin/streamlit`
+- Dashboard dependencies installed in `.venv`
+- ์คํ DB๋ script๊ฐ ์์ฑํ๋ `fixture.sqlite3`๋ง ์ฌ์ฉ
+
+## ์คํ ํ๋ฆ
+
+1. script๊ฐ output directory๋ฅผ ๋ง๋ค๊ณ synthetic fixture DB๋ฅผ ์์ฑํ๋ค.
+2. `JARCA_LEDGER_DB_PATH=