미니 해커톤. 잔뜩 겁먹고 한이틀 공부했는데 다행히도 수료각.
구글 클라우드 서버에서 작동이 되는 알흠다움..도메인은 안만들었음. 급하게 쓰고 지울예정이라.



으하하...간신히...수료하는구나..
덧, 한글폰트는 잡아보려고 했으나..여러차례 시도 했으나 불가함.
app.py
from flask import Flask, render_template, request
from model_qaoa import qaoa_recommend_main
app = Flask(__name__)
@app.route('/')
def index():
# 입력 폼 페이지
return render_template('index.html')
@app.route('/recommend', methods=['POST'])
def recommend():
# 사용자가 입력한 값을 받아옴
price = float(request.form['price'])
loan = float(request.form['loan'])
commute = float(request.form['commute'])
school = float(request.form['school'])
maintenance = float(request.form['maintenance'])
amenity = float(request.form['amenity'])
safety = float(request.form['safety'])
future = float(request.form['future'])
# 추가적으로 age 정보가 필요하면 아래처럼 처리
# 실제 index.html에 해당 input이 있으면: age = float(request.form['age'])
# 아니면 입력값 구조에 맞게 수정
# QAOA 기반 추천 실행 (model_qaoa.py에서 처리)
chosen_name, chosen_score, chosen_features = qaoa_recommend_main()
# 선택된 아파트 특성값을 템플릿에 넘김 (result.html 출력)
return render_template(
'result.html',
price=chosen_features.get('price', ''),
loan=chosen_features.get('loan', ''),
commute=chosen_features.get('commute', ''),
school=chosen_features.get('school', ''),
age=chosen_features.get('age', ''),
maintenance=chosen_features.get('maintenance', ''),
amenity=chosen_features.get('amenity', ''),
safety=chosen_features.get('safety', ''),
future=chosen_features.get('future', ''),
chosen_name=chosen_name,
chosen_score=f"{chosen_score:.3f}"
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5003, debug=True)
model_qaoa
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
import os
from math import pi
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
# 데이터 및 특성 로드
df = pd.read_csv('apt_data.csv')
feature_cols = ['price', 'loan', 'commute', 'school', 'age', 'maintenance', 'amenity', 'safety', 'future']
loc_names = df['name'].tolist()
loc_features = df[feature_cols].values.astype(np.float32)
# 딥러닝 모델 정의
class SiteUtilityNet(nn.Module):
def __init__(self, d_in):
super().__init__()
self.net = nn.Sequential(
nn.Linear(d_in, 16), nn.ReLU(),
nn.Linear(16, 8), nn.ReLU(),
nn.Linear(8, 1), nn.Sigmoid()
)
def forward(self, x):
return self.net(x)
# 모델 학습 및 저장
def train_and_save_model():
X = torch.tensor(loc_features, dtype=torch.float32)
y = torch.tensor(loc_features.sum(axis=1), dtype=torch.float32).unsqueeze(1)
y = (y - y.min())/(y.max() - y.min())
model = SiteUtilityNet(X.shape[1])
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
for _ in range(200):
optimizer.zero_grad()
loss = criterion(model(X), y)
loss.backward()
optimizer.step()
torch.save(model.state_dict(), 'model.pt')
print("모델 학습/저장 완료: model.pt")
return model
# 예측 함수 (model.pt를 로드해 유틸리티 값 반환)
def predict_utilities():
if not os.path.exists("model.pt"):
train_and_save_model()
X = torch.tensor(loc_features, dtype=torch.float32)
loaded_model = SiteUtilityNet(X.shape[1])
loaded_model.load_state_dict(torch.load('model.pt', weights_only=True))
loaded_model.eval()
with torch.no_grad():
utility_pred = loaded_model(X).numpy().squeeze()
return utility_pred
# QAOA/최적 아파트 선택 + 차트 시각화
def select_apt_qaoa_and_plot(utility_pred):
n = len(loc_names)
u = utility_pred
lambda_pen = 2.0
h = 0.5 * u
J = np.zeros((n, n), float)
for i in range(n):
for j in range(i + 1, n):
J[i, j] = 0.5 * lambda_pen
J[j, i] = J[i, j]
gamma_list = np.linspace(0, pi, 21)
beta_list = np.linspace(0, pi, 21)
best_E = float("inf")
best_gamma, best_beta, best_sv = None, None, None
for g in gamma_list:
for b in beta_list:
qc = build_qaoa(g, b, h, J)
sv = Statevector.from_instruction(qc)
E, _ = qaoa_energy(g, b, h, J)
if E < best_E:
best_E, best_gamma, best_beta, best_sv = E, g, b, sv
probs = np.abs(best_sv.data)**2
best_idx = int(np.argmax(probs))
bits = [(best_idx >> (n-1-k)) & 1 for k in range(n)]
chosen_idx = bits.index(1) if 1 in bits else int(np.argmax(utility_pred))
chosen_name = loc_names[chosen_idx]
chosen_score = float(utility_pred[chosen_idx])
# Bar 차트 저장
plt.figure(figsize=(7, 4))
plt.bar(loc_names, utility_pred, color='#71A6FF')
plt.axhline(y=chosen_score, color='r', linestyle='--', label='선택 아파트 만족도')
plt.xlabel("아파트명")
plt.ylabel("예측 만족도")
plt.title("아파트별 예측 유틸리티")
plt.xticks(rotation=30)
plt.legend()
plt.tight_layout()
plt.savefig("static/utility_bar.png")
plt.close()
# 레이더 차트 저장
values = loc_features[chosen_idx, :]
num_vars = len(feature_cols)
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
values = np.concatenate((values, [values[0]]))
angles += angles[:1]
plt.figure(figsize=(6, 6))
ax = plt.subplot(111, polar=True)
plt.xticks(angles[:-1], feature_cols)
ax.plot(angles, values, 'b-', linewidth=2)
ax.fill(angles, values, 'b', alpha=0.15)
plt.title("선택된 아파트 특성 레이더 차트", size=15, y=1.10)
ax.set_rlabel_position(30)
plt.tight_layout()
plt.savefig("static/apt_radar.png")
plt.close()
# 선택 아파트 특성값 dict 반환
chosen_features = dict(zip(feature_cols, loc_features[chosen_idx]))
return chosen_name, chosen_score, chosen_features
# QAOA 서브함수
def build_qaoa(gamma, beta, h_vec, J_mat):
n_qubits = len(h_vec)
qc = QuantumCircuit(n_qubits)
qc.h(range(n_qubits))
for i in range(n_qubits):
angle = 2 * gamma * h_vec[i]
if abs(angle) > 1e-8:
qc.rz(angle, i)
for i in range(n_qubits):
for j in range(i+1, n_qubits):
angle = 2 * gamma * J_mat[i, j]
qc.cx(i, j)
qc.rz(angle, j)
qc.cx(i, j)
for i in range(n_qubits):
qc.rx(2 * beta, i)
return qc
def compute_expectation(sv):
n_qubits = int(np.log2(len(sv.data)))
probs = np.abs(sv.data)**2
z_exp = np.zeros(n_qubits)
zz_exp = np.zeros((n_qubits, n_qubits))
for idx, p in enumerate(probs):
if p < 1e-12: continue
bits = [(idx >> (n_qubits-1-k)) & 1 for k in range(n_qubits)]
z_vals = np.array([1 if b==0 else -1 for b in bits])
z_exp += z_vals * p
zz_exp += np.outer(z_vals, z_vals) * p
return z_exp, zz_exp
def qaoa_energy(gamma, beta, h, J):
qc = build_qaoa(gamma, beta, h, J)
sv = Statevector.from_instruction(qc)
z_exp, zz_exp = compute_expectation(sv)
E = float(np.dot(h, z_exp))
for i in range(len(h)):
for j in range(i+1, len(h)):
E += J[i, j] * zz_exp[i, j]
return E, sv
# --- Flask/UWSGI/외부에서 호출: 통합 추천 인터페이스 ---
def qaoa_recommend_main():
# 모델이 없으면 학습 및 저장
if not os.path.exists("model.pt"):
train_and_save_model()
utility_pred = predict_utilities()
chosen_name, chosen_score, chosen_features = select_apt_qaoa_and_plot(utility_pred)
return chosen_name, chosen_score, chosen_features
# Flask에서는 아래처럼 호출
# name, score, features = qaoa_recommend_main()
index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>AI 기반 아파트 추천 시스템</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {background: #f2f4fc; font-family: 'Noto Sans KR', sans-serif;}
.container {width: 400px; margin: 80px auto; background: #fff; border-radius: 16px; box-shadow: 0 4px 24px #b0b5e2; text-align: center;}
.header {background: linear-gradient(90deg, #5d99ff, #a47fff 60%); color: #fff; padding: 32px 0; border-radius: 16px 16px 0 0;}
form {padding: 28px 24px 16px;}
label {display: block; font-size: 1.02em; font-weight: 600; margin-top: 16px; margin-bottom: 6px;}
input {width: 100%; padding: 7px 9px; font-size: 1em; border: 1px solid #b7bad9; border-radius: 6px;}
button {margin-top: 24px; width: 100%; background: #398efe; color: #fff; font-size: 1.15em; font-weight: bold; padding: 13px 0; border: none; border-radius: 7px; cursor: pointer;}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>AI 기반 아파트 추천 시스템</h2>
<p style="font-size:1em;margin-top:4px;">딥러닝 + QAOA로 장기 만족도를 예측하고 최적의 아파트를 추천합니다.</p>
</div>
<form method="POST" action="/recommend">
<label for="price">매매가 (억원)</label>
<input type="number" step="0.01" name="price" id="price" placeholder="예: 45.5" required>
<label for="loan">대출금 (억원)</label>
<input type="number" step="0.01" name="loan" id="loan" placeholder="예: 30.0" required>
<label for="commute">출퇴근 시간 (분)</label>
<input type="number" step="0.01" name="commute" id="commute" placeholder="예: 40" required>
<label for="school">학교까지 거리 (분)</label>
<input type="number" step="0.01" name="school" id="school" placeholder="예: 7" required>
<label for="maintenance">관리비 (만원)</label>
<input type="number" step="0.01" name="maintenance" id="maintenance" placeholder="예: 15" required>
<label for="amenity">편의시설 점수 [0~1]</label>
<input type="number" step="0.01" min="0" max="1" name="amenity" id="amenity" placeholder="예: 0.85" required>
<label for="safety">안전도 점수 [0~1]</label>
<input type="number" step="0.01" min="0" max="1" name="safety" id="safety" placeholder="예: 0.92" required>
<label for="future">미래가치 점수 [0~1]</label>
<input type="number" step="0.01" min="0" max="1" name="future" id="future" placeholder="예: 0.88" required>
<button type="submit">AI 추천 받기</button>
</form>
</div>
</body>
</html>
result.hml
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>추천 결과</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {background: #f2f4fc; font-family: 'Noto Sans KR', sans-serif;}
.container {width: 900px; margin: 60px auto; background: #fff; border-radius: 16px; box-shadow: 0 4px 24px #b0b5e2;}
.header {background: linear-gradient(90deg, #5d99ff, #a47fff 60%); color: #fff; padding: 30px 0 18px; border-radius: 16px 16px 0 0; text-align:center;}
.flex-row {display: flex; justify-content: space-between; margin: 24px;}
.charts {display: flex; gap: 28px; margin-top: 20px; justify-content: center;}
.chart-box {flex: 1; background: #f8fbff; border-radius: 14px; box-shadow: 0 2px 10px #b0b5e2; padding: 20px;}
table {margin: 20px auto; border-collapse: collapse;}
th, td {padding: 8px 14px; border: 1px solid #b7bad9;}
th {background: #eaf7ff;}
.back-link {display: block; margin:34px auto 18px; text-align: left; color: #398efe; font-size: 1.02em;}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>추천 결과</h2>
</div>
<div class="flex-row">
<table>
<tr><th colspan="2">선택된 아파트 특성값</th></tr>
<tr><td>매매가 (억원)</td><td>{{ price }}</td></tr>
<tr><td>대출금 (억원)</td><td>{{ loan }}</td></tr>
<tr><td>출퇴근 시간 (분)</td><td>{{ commute }}</td></tr>
<tr><td>학교까지 거리 (분)</td><td>{{ school }}</td></tr>
<tr><td>연식 (년)</td><td>{{ age }}</td></tr>
<tr><td>관리비 (만원)</td><td>{{ maintenance }}</td></tr>
<tr><td>편의시설 점수</td><td>{{ amenity }}</td></tr>
<tr><td>안전도 점수</td><td>{{ safety }}</td></tr>
<tr><td>미래가치 점수</td><td>{{ future }}</td></tr>
<tr><th>예측 만족도</th><th>{{ chosen_score }}</th></tr>
<tr><th>추천 아파트명</th><th>{{ chosen_name }}</th></tr>
</table>
</div>
<div class="charts">
<div class="chart-box">
<div>아파트별 예측 유틸리티</div>
<img src="{{ url_for('static', filename='utility_bar.png') }}" alt="아파트 유틸리티 바차트" style="width:100%;max-width:320px;margin-top:10px;">
</div>
<div class="chart-box">
<div>선택된 아파트 특성 레이더 차트</div>
<img src="{{ url_for('static', filename='apt_radar.png') }}" alt="아파트 특성 레이더" style="width:100%;max-width:320px;margin-top:10px;">
</div>
</div>
<a class="back-link" href="/">← 다시 입력하기</a>
</div>
</body>
</html>

