From 5749d4d35d9637ef6b3411d9484cb55f8fe962be Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 28 Apr 2026 08:13:44 +0900 Subject: [PATCH] feat(realestate-db): add district / notify / 5tier columns with migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - announcements.district + idx_ann_district 인덱스 - user_profile: preferred_districts(JSON obj), min_match_score(int 70), notify_enabled(bool) - match_results: notified_at(TEXT) - _profile_row_to_dict: notify_enabled bool화, preferred_districts dict 역직렬화 - PROFILE_COLUMNS 확장 (3 신규 필드) - upsert_profile: list|dict 모두 JSON 직렬화 Co-Authored-By: Claude Sonnet 4.6 --- realestate-lab/app/db.py | 33 +++++++++++++++++-- realestate-lab/tests/test_db_migration.py | 39 +++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 realestate-lab/tests/test_db_migration.py diff --git a/realestate-lab/app/db.py b/realestate-lab/app/db.py index 255ebe3..737818a 100644 --- a/realestate-lab/app/db.py +++ b/realestate-lab/app/db.py @@ -64,6 +64,13 @@ def init_db(): conn.execute("CREATE INDEX IF NOT EXISTS idx_ann_status ON announcements(status);") conn.execute("CREATE INDEX IF NOT EXISTS idx_ann_region ON announcements(region_name);") + # ── 마이그레이션: district 컬럼 + 인덱스 추가 ── + try: + conn.execute("SELECT district FROM announcements LIMIT 1") + except Exception: + conn.execute("ALTER TABLE announcements ADD COLUMN district TEXT") + conn.execute("CREATE INDEX IF NOT EXISTS idx_ann_district ON announcements(district);") + # ── 마이그레이션: is_bookmarked 컬럼 추가 ── try: conn.execute("SELECT is_bookmarked FROM announcements LIMIT 1") @@ -120,6 +127,17 @@ def init_db(): ); """) + # ── 마이그레이션: user_profile 신규 3컬럼 ── + for col, ddl in ( + ("preferred_districts", "ALTER TABLE user_profile ADD COLUMN preferred_districts TEXT NOT NULL DEFAULT '{}'"), + ("min_match_score", "ALTER TABLE user_profile ADD COLUMN min_match_score INTEGER NOT NULL DEFAULT 70"), + ("notify_enabled", "ALTER TABLE user_profile ADD COLUMN notify_enabled INTEGER NOT NULL DEFAULT 1"), + ): + try: + conn.execute(f"SELECT {col} FROM user_profile LIMIT 1") + except Exception: + conn.execute(ddl) + # ── match_results ──────────────────────────────────────────────── conn.execute(""" CREATE TABLE IF NOT EXISTS match_results ( @@ -135,6 +153,12 @@ def init_db(): ); """) + # ── 마이그레이션: notified_at 컬럼 추가 ── + try: + conn.execute("SELECT notified_at FROM match_results LIMIT 1") + except Exception: + conn.execute("ALTER TABLE match_results ADD COLUMN notified_at TEXT") + # ── collect_log ────────────────────────────────────────────────── conn.execute(""" CREATE TABLE IF NOT EXISTS collect_log ( @@ -539,10 +563,12 @@ def _profile_row_to_dict(r) -> Dict[str, Any]: for c in r.keys(): val = r[c] if c in ("is_homeless", "is_householder", "has_dependents", "is_newlywed", - "has_newborn", "is_first_home"): + "has_newborn", "is_first_home", "notify_enabled"): d[c] = bool(val) if val is not None else None elif c in ("preferred_regions", "preferred_types"): d[c] = json.loads(val) if val else [] + elif c == "preferred_districts": + d[c] = json.loads(val) if val else {} else: d[c] = val return d @@ -563,8 +589,9 @@ PROFILE_COLUMNS = { "subscription_months", "subscription_amount", "family_members", "has_dependents", "children_count", "is_newlywed", "marriage_months", "has_newborn", "is_first_home", "income_level", - "preferred_regions", "preferred_types", + "preferred_regions", "preferred_types", "preferred_districts", "min_area", "max_area", "max_price", + "min_match_score", "notify_enabled", } @@ -575,7 +602,7 @@ def upsert_profile(data: Dict[str, Any]) -> Dict[str, Any]: continue if isinstance(v, bool): updates[k] = 1 if v else 0 - elif isinstance(v, list): + elif isinstance(v, (list, dict)): updates[k] = json.dumps(v) else: updates[k] = v diff --git a/realestate-lab/tests/test_db_migration.py b/realestate-lab/tests/test_db_migration.py new file mode 100644 index 0000000..83fadb7 --- /dev/null +++ b/realestate-lab/tests/test_db_migration.py @@ -0,0 +1,39 @@ +def test_user_profile_has_new_columns(): + from app.db import _conn + with _conn() as conn: + cols = {row["name"] for row in conn.execute("PRAGMA table_info(user_profile)")} + assert "preferred_districts" in cols + assert "min_match_score" in cols + assert "notify_enabled" in cols + + +def test_announcements_has_district(): + from app.db import _conn + with _conn() as conn: + cols = {row["name"] for row in conn.execute("PRAGMA table_info(announcements)")} + assert "district" in cols + + +def test_match_results_has_notified_at(): + from app.db import _conn + with _conn() as conn: + cols = {row["name"] for row in conn.execute("PRAGMA table_info(match_results)")} + assert "notified_at" in cols + + +def test_district_index_exists(): + from app.db import _conn + with _conn() as conn: + idx = {row["name"] for row in conn.execute( + "SELECT name FROM sqlite_master WHERE type='index'" + )} + assert "idx_ann_district" in idx + + +def test_profile_defaults(): + from app.db import upsert_profile, get_profile + upsert_profile({"name": "테스트"}) + profile = get_profile() + assert profile["preferred_districts"] == {} + assert profile["min_match_score"] == 70 + assert profile["notify_enabled"] is True