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:
@@ -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
|
||||||
|
|||||||
39
realestate-lab/tests/test_db_migration.py
Normal file
39
realestate-lab/tests/test_db_migration.py
Normal 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
|
||||||
Reference in New Issue
Block a user