feat(realestate-db): add district / notify / 5tier columns with migration

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 08:13:44 +09:00
parent 2477342272
commit 5749d4d35d
2 changed files with 69 additions and 3 deletions

View File

@@ -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_status ON announcements(status);")
conn.execute("CREATE INDEX IF NOT EXISTS idx_ann_region ON announcements(region_name);") 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 컬럼 추가 ── # ── 마이그레이션: is_bookmarked 컬럼 추가 ──
try: try:
conn.execute("SELECT is_bookmarked FROM announcements LIMIT 1") 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 ──────────────────────────────────────────────── # ── match_results ────────────────────────────────────────────────
conn.execute(""" conn.execute("""
CREATE TABLE IF NOT EXISTS match_results ( 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 ────────────────────────────────────────────────── # ── collect_log ──────────────────────────────────────────────────
conn.execute(""" conn.execute("""
CREATE TABLE IF NOT EXISTS collect_log ( 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(): for c in r.keys():
val = r[c] val = r[c]
if c in ("is_homeless", "is_householder", "has_dependents", "is_newlywed", 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 d[c] = bool(val) if val is not None else None
elif c in ("preferred_regions", "preferred_types"): elif c in ("preferred_regions", "preferred_types"):
d[c] = json.loads(val) if val else [] d[c] = json.loads(val) if val else []
elif c == "preferred_districts":
d[c] = json.loads(val) if val else {}
else: else:
d[c] = val d[c] = val
return d return d
@@ -563,8 +589,9 @@ PROFILE_COLUMNS = {
"subscription_months", "subscription_amount", "family_members", "subscription_months", "subscription_amount", "family_members",
"has_dependents", "children_count", "is_newlywed", "marriage_months", "has_dependents", "children_count", "is_newlywed", "marriage_months",
"has_newborn", "is_first_home", "income_level", "has_newborn", "is_first_home", "income_level",
"preferred_regions", "preferred_types", "preferred_regions", "preferred_types", "preferred_districts",
"min_area", "max_area", "max_price", "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 continue
if isinstance(v, bool): if isinstance(v, bool):
updates[k] = 1 if v else 0 updates[k] = 1 if v else 0
elif isinstance(v, list): elif isinstance(v, (list, dict)):
updates[k] = json.dumps(v) updates[k] = json.dumps(v)
else: else:
updates[k] = v updates[k] = v

View File

@@ -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