feat: 예측 시, sm_hyper > 0이 2개 미만이어도 나머지 예측이 동작되도록 수정

main
thkim 2026-02-12 11:30:35 +09:00
parent 97cb8f5735
commit c660ba22e2
3 changed files with 62 additions and 48 deletions

View File

@ -100,7 +100,12 @@ def settlement_prediction(business_code, cons_code):
final_step_predict_percent=90, final_step_predict_percent=90,
additional_predict_percent=300, additional_predict_percent=300,
asaoka_interval=3)) asaoka_interval=3))
# 결과가 None이면 DB 저장을 건너뛰고 종료 (시스템 crash 방지)
if results is None:
print(f"[Python Log] [Skip] {cons_code}: 유효한 예측 결과가 없어 DB 저장을 중단합니다.")
return
# prediction method code # prediction method code
# 1: original hyperbolic method (쌍곡선법) # 1: original hyperbolic method (쌍곡선법)
# 2: nonlinear hyperbolic method (비선형 쌍곡선법) # 2: nonlinear hyperbolic method (비선형 쌍곡선법)

View File

@ -290,69 +290,78 @@ def run_settle_prediction(point_name, np_time, np_surcharge, np_settlement,
# 초기 침하량에 대한 침하량 조정 # 초기 침하량에 대한 침하량 조정
sm_hyper = sm_hyper - s0_hyper sm_hyper = sm_hyper - s0_hyper
# =========================================================
# 데이터 유효성 검사 및 예측 거부 로직
# =========================================================
# 분석이 완전히 불가능한 경우를 체크합니다.
is_invalid = np.any(settle < 0) or np.max(sm_hyper) <= 0
if is_invalid:
print(f"[Python Log] [Error] {point_name}: 누적침하량이 유효하지 않아 기본 예측으로 대체합니다.")
# 실패 시 시스템이 멈추지 않도록 마지막 계측값을 그대로 유지하는 배열을 생성합니다.
dummy_sp = np.full_like(time_hyper, settle[-1])
dummy_sp_asaoka = np.full_like(time_hyper, settle[-1])
dummy_sp_step = np.full_like(time[step_start_index[0]:], settle[-1])
# None 대신 모든 결과 리스트를 반환하여 controller.py의 에러를 방지합니다.
return [time_hyper, dummy_sp, # Original
time_hyper, dummy_sp, # Nonlinear
time_hyper, dummy_sp, # Weighted
time_hyper, dummy_sp_asaoka, # Asaoka
time[step_start_index[0]:], dummy_sp_step] # Step Loading
# 계수 변수 기본값 초기화 (데이터 부족 시에도 UnboundLocalError 방지)
x_hyper_nonlinear = np.array([1.0, 1.0])
x_hyper_weight_nonlinear = np.array([1.0, 1.0])
x_hyper_original = np.array([1.0, 1.0])
# 회귀분석 시행 (비선형 쌍곡선) # 회귀분석 시행 (비선형 쌍곡선)
x0 = np.ones(2) #x0 = np.ones(2)
res_lsq_hyper_nonlinear = least_squares(fun_hyper_nonlinear, x0, # res_lsq_hyper_nonlinear = least_squares(fun_hyper_nonlinear, x0,
args=(tm_hyper, sm_hyper)) # args=(tm_hyper, sm_hyper))
# 비선형 쌍곡선 법 계수 저장 및 출력 # 비선형 쌍곡선 법 계수 저장 및 출력
x_hyper_nonlinear = res_lsq_hyper_nonlinear.x #x_hyper_nonlinear = res_lsq_hyper_nonlinear.x
if len(tm_hyper) >= 2:
try:
res_lsq = least_squares(fun_hyper_nonlinear, x_hyper_nonlinear, args=(tm_hyper, sm_hyper)) # 결과가 나올 때만
x_hyper_nonlinear = res_lsq.x # 값을 업데이트
except Exception as e:
print(f"[Warning] ... failed: {e}") # 실패하면 초기값(1.0) 유지
# 가중 비선형 쌍곡선 가중치 산정 # 가중 비선형 쌍곡선 가중치 산정
# 시간 합계가 0인 경우(데이터 부족 등) 0으로 나누는 에러 방지 # 시간 합계가 0인 경우(데이터 부족 등) 0으로 나누는 에러 방지
# 가중 비선형 쌍곡선법 (Weighted Nonlinear)
sum_tm = np.sum(tm_hyper) sum_tm = np.sum(tm_hyper)
if sum_tm == 0: if sum_tm > 0 and len(tm_hyper) >= 2:
weight = np.ones_like(tm_hyper) # 가중치를 모두 1로 설정
else:
weight = tm_hyper / sum_tm weight = tm_hyper / sum_tm
try:
# 두 번째 인자인 'x_hyper_weight_nonlinear'가 반드시 포함되어야 함
res_lsq = least_squares(fun_hyper_weight_nonlinear, x_hyper_weight_nonlinear, args=(tm_hyper, sm_hyper, weight))
x_hyper_weight_nonlinear = res_lsq.x
except Exception as e:
print(f"[Warning] Weighted Nonlinear Hyperbolic failed for {point_name}: {e}")
# 회귀분석 시행 (가중 비선형 쌍곡선) # 기존 쌍곡선법 (Original Hyperbolic)
x0 = np.ones(2)
res_lsq_hyper_weight_nonlinear = least_squares(fun_hyper_weight_nonlinear, x0,
args=(tm_hyper, sm_hyper, weight))
# 비선형 쌍곡선 법 계수 저장 및 출력
x_hyper_weight_nonlinear = res_lsq_hyper_weight_nonlinear.x
# 회귀분석 시행 (기존 쌍곡선법) - (0, 0)에 해당하는 초기 데이터를 제외하고 회귀분석 실시
x0 = np.ones(2)
# [로그 추가] 입력 데이터의 상태를 확인
print(f"[LOG] {point_name} - tm_hyper size: {len(tm_hyper)}, sm_hyper contains zero: {np.any(sm_hyper == 0)}")
if np.any(sm_hyper <= 0):
print(f"[LOG] Problematic sm_hyper values: {sm_hyper[sm_hyper <= 0]}")
# 1. 침하량이 0보다 큰 유효한 데이터만 필터링 (0으로 나누기 근본적 방지)
valid_indices = np.where(sm_hyper > 0)[0] valid_indices = np.where(sm_hyper > 0)[0]
# 2. 필터링된 데이터가 최소 2개 이상인지 확인 (회귀분석을 위한 최소 조건)
if len(valid_indices) >= 2: if len(valid_indices) >= 2:
tm_hyper_valid = tm_hyper[valid_indices] tm_hyper_valid = tm_hyper[valid_indices]
sm_hyper_valid = sm_hyper[valid_indices] sm_hyper_valid = sm_hyper[valid_indices]
# (0, 0) 제외 로직이 이미 필터링에 포함되어 있으므로 [1:] 없이 수행 가능
x0 = np.ones(2)
try: try:
res_lsq_hyper_original = least_squares(fun_hyper_original, x0, # 초기값으로 np.array([1.0, 1.0])을 직접 전달
args=(tm_hyper_valid, sm_hyper_valid)) res_lsq = least_squares(fun_hyper_original, np.array([1.0, 1.0]), args=(tm_hyper_valid, sm_hyper_valid))
x_hyper_original = res_lsq_hyper_original.x x_hyper_original = res_lsq.x
except ValueError as e: except Exception as e:
print(f"[Warning] Optimization failed for Original Hyperbolic: {e}") print(f"[Warning] Original Hyperbolic failed for {point_name}: {e}")
x_hyper_original = np.ones(2) * 0.001 # 실패 시 기본값 세팅 x_hyper_original = np.array([0.001, 0.001])
else: else:
# 데이터가 부족할 경우 에러 대신 기본값이나 알림 처리
print(f"[Warning] {point_name}: Not enough valid settlement data (>0) for Original Hyperbolic.") print(f"[Warning] {point_name}: Not enough valid settlement data (>0) for Original Hyperbolic.")
x_hyper_original = np.ones(2) * 0.001 x_hyper_original = np.array([0.001, 0.001])
# 기존 쌍곡선 법 계수 저장 및 출력
x_hyper_original = res_lsq_hyper_original.x
# 현재 단계 예측 침하량 산정 (침하 예측 끝까지) # 최종 예측값 산정
sp_hyper_nonlinear = generate_data_hyper(x_hyper_nonlinear, time_hyper) sp_hyper_nonlinear = generate_data_hyper(x_hyper_nonlinear, time_hyper) + s0_hyper
sp_hyper_weight_nonlinear = generate_data_hyper(x_hyper_weight_nonlinear, time_hyper) sp_hyper_weight_nonlinear = generate_data_hyper(x_hyper_weight_nonlinear, time_hyper) + s0_hyper
sp_hyper_original = generate_data_hyper(x_hyper_original, time_hyper) sp_hyper_original = generate_data_hyper(x_hyper_original, time_hyper) + s0_hyper
# 예측 침하량 산정
sp_hyper_nonlinear = sp_hyper_nonlinear + s0_hyper
sp_hyper_weight_nonlinear = sp_hyper_weight_nonlinear + s0_hyper
sp_hyper_original = sp_hyper_original + s0_hyper
time_hyper = time_hyper + t0_hyper time_hyper = time_hyper + t0_hyper
# =============================== # ===============================