619 lines
24 KiB
Python
619 lines
24 KiB
Python
# ==========================================
|
|
# crawlers/nrems.py - NREMS 크롤러 (1,2,3,4,9호기)
|
|
# ==========================================
|
|
|
|
import requests
|
|
import json
|
|
import re
|
|
from datetime import datetime
|
|
from .base import safe_float, create_session, format_result
|
|
|
|
def _get_inverter_sums(session, pscode, system_config):
|
|
"""
|
|
1, 2호기 인버터별 일일 발전량 추출 (JSON API 사용)
|
|
"""
|
|
try:
|
|
today_str = datetime.now().strftime('%Y-%m-%d')
|
|
month_str = datetime.now().strftime('%Y-%m')
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
'Referer': f'http://www.nrems.co.kr/v2/local/comp/cp_inv_time.php?pscode={pscode}'
|
|
}
|
|
|
|
data = {
|
|
'act': 'getList',
|
|
's_day': today_str,
|
|
's_date': today_str,
|
|
'e_date': today_str,
|
|
's_mon': month_str,
|
|
'e_mon': month_str,
|
|
'pscode': pscode,
|
|
'dispType': 'time'
|
|
}
|
|
|
|
inv_proc_url = system_config.get('inv_proc_url', '')
|
|
res = session.post(inv_proc_url, data=data, headers=headers, timeout=10)
|
|
|
|
if res.status_code == 200:
|
|
try:
|
|
json_data = res.json()
|
|
invlist = json_data.get('invlist', [])
|
|
|
|
sum_1 = 0.0
|
|
sum_2 = 0.0
|
|
|
|
for inv in invlist:
|
|
tidx = str(inv.get('tidx', ''))
|
|
sum_pw = safe_float(inv.get('sumPw'))
|
|
|
|
if tidx == '1':
|
|
sum_1 = sum_pw
|
|
elif tidx == '2':
|
|
sum_2 = sum_pw
|
|
|
|
if sum_1 > 0 or sum_2 > 0:
|
|
print(f" [API] 인버터 합계 추출 성공! (인버터1: {sum_1} kWh / 인버터2: {sum_2} kWh)")
|
|
return sum_1, sum_2
|
|
else:
|
|
print(f" ⚠️ API 응답에 인버터 데이터 없음")
|
|
return 0.0, 0.0
|
|
|
|
except json.JSONDecodeError:
|
|
print(f" ⚠️ JSON 파싱 실패")
|
|
return 0.0, 0.0
|
|
else:
|
|
print(f" ⚠️ API 응답 오류: {res.status_code}")
|
|
return 0.0, 0.0
|
|
|
|
except Exception as e:
|
|
print(f" [에러] {e}")
|
|
return 0.0, 0.0
|
|
|
|
def fetch_data(plant_info):
|
|
"""
|
|
NREMS 발전소 데이터 수집
|
|
|
|
Args:
|
|
plant_info: {
|
|
'id': 'nrems-03', # DB용 고유 ID (is_split인 경우 없음)
|
|
'name': '...',
|
|
'type': 'nrems',
|
|
'auth': {'pscode': '...'},
|
|
'options': {'is_split': True/False},
|
|
'system': {'api_url': '...', 'inv_proc_url': '...'},
|
|
'company_name': '...'
|
|
}
|
|
|
|
Returns:
|
|
list: [{'id': '...', 'name': '...', 'kw': 10.5, 'today': 100.0, 'status': '...'}]
|
|
"""
|
|
results = []
|
|
|
|
# 설정 추출
|
|
plant_id = plant_info.get('id', '') # DB용 고유 ID
|
|
pscode = plant_info['auth'].get('pscode', '')
|
|
is_split = plant_info['options'].get('is_split', False)
|
|
system_config = plant_info.get('system', {})
|
|
company_name = plant_info.get('company_name', '태양과바람')
|
|
plant_name = plant_info.get('name', '')
|
|
|
|
session = create_session()
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
|
}
|
|
|
|
try:
|
|
# 메인 데이터 요청
|
|
api_url = system_config.get('api_url', '')
|
|
res = session.post(api_url, data={'pscode': pscode}, headers=headers, timeout=10)
|
|
|
|
if res.status_code != 200:
|
|
return results
|
|
|
|
try:
|
|
data = res.json()
|
|
except:
|
|
return results
|
|
|
|
# 데이터 찾기
|
|
ps_list = data.get('ps_status')
|
|
target_data = None
|
|
if isinstance(ps_list, list):
|
|
for item in ps_list:
|
|
code_in_res = item.get('pscode')
|
|
wmu_in_res = item.get('WMU_CODE')
|
|
|
|
# Case-insensitive comparison
|
|
if (code_in_res and code_in_res.lower() == pscode.lower()) or \
|
|
(wmu_in_res and wmu_in_res.lower() == pscode.lower()):
|
|
target_data = item
|
|
break
|
|
|
|
if not target_data and len(ps_list) > 0:
|
|
print(f" ⚠️ Target pscode '{pscode}' not found in response. Available: {[i.get('pscode') for i in ps_list]}")
|
|
target_data = ps_list[0] # Fallback
|
|
print(f" ⚠️ Using fallback: {target_data.get('pscode')}")
|
|
elif isinstance(ps_list, dict):
|
|
target_data = ps_list
|
|
if not target_data:
|
|
target_data = {}
|
|
|
|
total_kw = safe_float(target_data.get('KW'))
|
|
total_today = safe_float(target_data.get('TDayKWH'))
|
|
inverters = data.get('ivt_value', [])
|
|
|
|
# Case A: 1, 2호기 분리 처리
|
|
if is_split:
|
|
real_sum_1, real_sum_2 = _get_inverter_sums(session, pscode, system_config)
|
|
|
|
kw_1 = safe_float(inverters[0].get('KW')) if len(inverters) >= 1 else 0.0
|
|
kw_2 = safe_float(inverters[1].get('KW')) if len(inverters) >= 2 else 0.0
|
|
|
|
if (real_sum_1 + real_sum_2) > 0:
|
|
today_1 = real_sum_1
|
|
today_2 = real_sum_2
|
|
else:
|
|
print(" ⚠️ 백업 로직(비율) 가동")
|
|
inv_total = kw_1 + kw_2
|
|
if inv_total > 0:
|
|
today_1 = total_today * (kw_1 / inv_total)
|
|
today_2 = total_today * (kw_2 / inv_total)
|
|
else:
|
|
today_1 = total_today / 2
|
|
today_2 = total_today / 2
|
|
|
|
# [중요] 1, 2호기는 ID를 강제 지정
|
|
results.append({
|
|
'id': 'nrems-01', # 1호기 고정 ID
|
|
'name': f'{company_name} 1호기',
|
|
'kw': kw_1,
|
|
'today': round(today_1, 2),
|
|
'status': "🟢 정상" if kw_1 > 0 else "💤 대기"
|
|
})
|
|
results.append({
|
|
'id': 'nrems-02', # 2호기 고정 ID
|
|
'name': f'{company_name} 2호기',
|
|
'kw': kw_2,
|
|
'today': round(today_2, 2),
|
|
'status': "🟢 정상" if kw_2 > 0 else "💤 대기"
|
|
})
|
|
|
|
# Case B: 3, 4, 9호기
|
|
else:
|
|
results.append({
|
|
'id': plant_id, # config에서 정의된 ID 사용
|
|
'name': f'{company_name} {plant_name}',
|
|
'kw': total_kw,
|
|
'today': total_today,
|
|
'status': "🟢 정상" if total_kw > 0 else "💤 대기"
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f"❌ NREMS {plant_name} 오류: {e}")
|
|
if not is_split:
|
|
results.append({
|
|
'id': plant_id,
|
|
'name': f'{company_name} {plant_name}',
|
|
'kw': 0.0,
|
|
'today': 0.0,
|
|
'status': '🔴 오류'
|
|
})
|
|
|
|
return results
|
|
|
|
|
|
def fetch_history_hourly(plant_info, start_date, end_date):
|
|
"""
|
|
NREMS 발전소의 시간대별 과거 데이터 수집
|
|
|
|
Args:
|
|
plant_info: {
|
|
'id': 'nrems-03',
|
|
'name': '...',
|
|
'type': 'nrems',
|
|
'auth': {'pscode': '...'},
|
|
'options': {'is_split': True/False},
|
|
'system': {'api_url': '...', 'inv_proc_url': '...'},
|
|
'company_name': '...'
|
|
}
|
|
start_date: str, 시작일 (YYYY-MM-DD)
|
|
end_date: str, 종료일 (YYYY-MM-DD)
|
|
|
|
Returns:
|
|
list: [{
|
|
'plant_id': 'nrems-03',
|
|
'timestamp': '2026-01-15 14:00:00',
|
|
'generation_kwh': 123.5,
|
|
'current_kw': 15.2
|
|
}, ...]
|
|
"""
|
|
results = []
|
|
|
|
# 설정 추출
|
|
plant_id = plant_info.get('id', '')
|
|
pscode = plant_info['auth'].get('pscode', '')
|
|
is_split = plant_info['options'].get('is_split', False)
|
|
plant_name = plant_info.get('name', '')
|
|
|
|
# 날짜 범위 생성
|
|
from datetime import datetime, timedelta
|
|
current_date = datetime.strptime(start_date, '%Y-%m-%d')
|
|
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[NREMS Hourly] {plant_name} ({start_date} ~ {end_date})")
|
|
print(f"{'='*60}")
|
|
|
|
while current_date <= end_dt:
|
|
date_str = current_date.strftime('%Y-%m-%d')
|
|
print(f"\n[Processing Date] {date_str}")
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
|
|
try:
|
|
if is_split:
|
|
# 1,2호기: cp_inv_proc.php with dispType=time
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/cp_inv_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/comp/cp_inv_time.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'getList',
|
|
's_day': date_str,
|
|
's_date': date_str,
|
|
'e_date': date_str,
|
|
's_mon': date_str[:7],
|
|
'e_mon': date_str[:7],
|
|
'pscode': pscode,
|
|
'dispType': 'time'
|
|
}
|
|
else:
|
|
# 3,4,9호기: pl_time_proc.php with act=empty
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/pl_time_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/plant/pl_time.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'empty',
|
|
's_date': date_str,
|
|
'pscode': pscode
|
|
}
|
|
|
|
response = session.post(url, data=payload, headers=headers, timeout=10)
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
|
|
# 데이터 구조 확인
|
|
if is_split:
|
|
# 1,2호기: pwdata 키 사용
|
|
hourly_records = data.get('pwdata', [])
|
|
else:
|
|
# 3,4,9호기: pdata 키 사용
|
|
hourly_records = data.get('pdata', [])
|
|
|
|
if hourly_records:
|
|
print(f" ✓ Found {len(hourly_records)} hourly records")
|
|
|
|
for hour_data in hourly_records:
|
|
if is_split:
|
|
# 1,2호기: DATE, PW1, PW2
|
|
hour = hour_data.get('DATE', '00')
|
|
inv1_gen = safe_float(hour_data.get('PW1', 0))
|
|
inv2_gen = safe_float(hour_data.get('PW2', 0))
|
|
|
|
# timestamp 생성
|
|
timestamp = f"{date_str} {str(hour).zfill(2)}:00:00"
|
|
|
|
results.append({
|
|
'plant_id': 'nrems-01',
|
|
'timestamp': timestamp,
|
|
'generation_kwh': inv1_gen,
|
|
'current_kw': 0
|
|
})
|
|
results.append({
|
|
'plant_id': 'nrems-02',
|
|
'timestamp': timestamp,
|
|
'generation_kwh': inv2_gen,
|
|
'current_kw': 0
|
|
})
|
|
else:
|
|
# 3,4,9호기: TIME, INV
|
|
time_str = hour_data.get('TIME', '00:00')
|
|
hour = time_str.split(':')[0] # "14:00" -> "14"
|
|
generation_kwh = safe_float(hour_data.get('INV', 0))
|
|
|
|
# timestamp 생성
|
|
timestamp = f"{date_str} {str(hour).zfill(2)}:00:00"
|
|
|
|
results.append({
|
|
'plant_id': plant_id,
|
|
'timestamp': timestamp,
|
|
'generation_kwh': generation_kwh,
|
|
'current_kw': 0
|
|
})
|
|
|
|
print(f" → Collected {len(hourly_records)} records")
|
|
else:
|
|
print(f" ⚠ No hourly data for {date_str}")
|
|
else:
|
|
print(f" ✗ HTTP {response.status_code}")
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
|
|
current_date += timedelta(days=1)
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[Total] Collected {len(results)} hourly records")
|
|
print(f"{'='*60}\n")
|
|
|
|
return results
|
|
|
|
|
|
def fetch_history_daily(plant_info, start_date, end_date):
|
|
"""
|
|
NREMS 발전소의 일별 과거 데이터 수집 (월 단위 루프)
|
|
|
|
Args:
|
|
plant_info: 발전소 정보
|
|
start_date: str, 시작일 (YYYY-MM-DD)
|
|
end_date: str, 종료일 (YYYY-MM-DD)
|
|
|
|
Returns:
|
|
list: [{'plant_id': '...', 'date': '2026-01-15', 'generation_kwh': 123.5}, ...]
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
import calendar
|
|
|
|
results = []
|
|
|
|
# 설정 추출
|
|
plant_id = plant_info.get('id', '')
|
|
pscode = plant_info['auth'].get('pscode', '')
|
|
is_split = plant_info['options'].get('is_split', False)
|
|
plant_name = plant_info.get('name', '')
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[NREMS Daily] {plant_name} ({start_date} ~ {end_date}) - Looping by Month")
|
|
print(f"{'='*60}")
|
|
|
|
start_dt = datetime.strptime(start_date, '%Y-%m-%d')
|
|
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
|
|
|
|
current_dt = start_dt
|
|
|
|
while current_dt <= end_dt:
|
|
# 현재 처리할 달의 시작일과 종료일 계산
|
|
# 이번 달의 마지막 날
|
|
last_day_of_month = calendar.monthrange(current_dt.year, current_dt.month)[1]
|
|
chunk_end_dt = current_dt.replace(day=last_day_of_month)
|
|
|
|
# 요청 종료일이 전체 종료일보다 뒤면 전체 종료일로 제한
|
|
if chunk_end_dt > end_dt:
|
|
chunk_end_dt = end_dt
|
|
|
|
s_date_str = current_dt.strftime('%Y-%m-%d')
|
|
e_date_str = chunk_end_dt.strftime('%Y-%m-%d')
|
|
month_str = current_dt.strftime('%Y-%m')
|
|
|
|
print(f" [Fetching] {s_date_str} ~ {e_date_str} ...", end="", flush=True)
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
|
|
try:
|
|
if is_split:
|
|
# 1,2호기: cp_inv_proc.php with dispType=day
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/cp_inv_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/comp/cp_inv_day.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'getList',
|
|
's_day': s_date_str, # s_day를 시작일로 변경
|
|
's_date': s_date_str,
|
|
'e_date': e_date_str,
|
|
's_mon': s_date_str[:7],
|
|
'e_mon': e_date_str[:7],
|
|
'pscode': pscode,
|
|
'dispType': 'day'
|
|
}
|
|
else:
|
|
# 3,4,9호기: pl_day_proc.php with s_day/e_day range
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/pl_day_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/plant/pl_day.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'empty',
|
|
's_day': s_date_str,
|
|
'e_day': e_date_str,
|
|
'pscode': pscode
|
|
}
|
|
|
|
response = session.post(url, data=payload, headers=headers, timeout=15)
|
|
|
|
if response.status_code == 200:
|
|
try:
|
|
data = response.json()
|
|
|
|
# 데이터 구조 확인
|
|
if is_split:
|
|
daily_records = data.get('pwdata', [])
|
|
else:
|
|
daily_records = data.get('pdata', [])
|
|
|
|
if daily_records:
|
|
count = 0
|
|
for day_data in daily_records:
|
|
# 날짜 추출
|
|
date_raw = day_data.get('DATE', '')
|
|
if not date_raw:
|
|
continue
|
|
|
|
# 날짜 형식 변환: "12-28" -> "2025-12-28" 보정
|
|
clean_date = date_raw
|
|
if '-' in date_raw and len(date_raw.split('-')[0]) <= 2:
|
|
mm, dd = date_raw.split('-')
|
|
year = current_dt.year
|
|
# 만약 12월 데이터인데 1월에 긁으면... 루프 변수 current_dt.year 사용하면 안전
|
|
clean_date = f"{year}-{mm.zfill(2)}-{dd.zfill(2)}"
|
|
|
|
if is_split:
|
|
inv1_gen = safe_float(day_data.get('PW1', 0))
|
|
inv2_gen = safe_float(day_data.get('PW2', 0))
|
|
|
|
results.append({'plant_id': 'nrems-01', 'date': clean_date, 'generation_kwh': inv1_gen})
|
|
results.append({'plant_id': 'nrems-02', 'date': clean_date, 'generation_kwh': inv2_gen})
|
|
count += 1
|
|
else:
|
|
generation_kwh = safe_float(day_data.get('INV', 0))
|
|
results.append({'plant_id': plant_id, 'date': clean_date, 'generation_kwh': generation_kwh})
|
|
count += 1
|
|
|
|
print(f" OK ({count} days)")
|
|
else:
|
|
print(f" No data")
|
|
except Exception as json_err:
|
|
print(f" JSON Error: {json_err}")
|
|
else:
|
|
print(f" HTTP {response.status_code}")
|
|
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
|
|
# 다음 달 1일로 이동
|
|
current_dt = (current_dt.replace(day=1) + timedelta(days=32)).replace(day=1)
|
|
|
|
print(f"\n[Total] Collected {len(results)} daily records\n")
|
|
return results
|
|
|
|
|
|
def fetch_history_monthly(plant_info, start_month, end_month):
|
|
"""
|
|
NREMS 발전소의 월별 과거 데이터 수집
|
|
|
|
Args:
|
|
plant_info: 발전소 정보
|
|
start_month: str, 시작월 (YYYY-MM)
|
|
end_month: str, 종료월 (YYYY-MM)
|
|
|
|
Returns:
|
|
list: [{'plant_id': '...', 'month': '2026-01', 'generation_kwh': 12345.6}, ...]
|
|
"""
|
|
from datetime import datetime
|
|
|
|
results = []
|
|
|
|
# 설정 추출
|
|
plant_id = plant_info.get('id', '')
|
|
pscode = plant_info['auth'].get('pscode', '')
|
|
is_split = plant_info['options'].get('is_split', False)
|
|
plant_name = plant_info.get('name', '')
|
|
|
|
session = create_session()
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"[NREMS Monthly] {plant_name} ({start_month} ~ {end_month})")
|
|
print(f"{'='*60}")
|
|
|
|
headers = {
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36',
|
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
|
|
try:
|
|
if is_split:
|
|
# 1,2호기: cp_inv_proc.php with dispType=mon
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/cp_inv_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/comp/cp_inv_month.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'getList',
|
|
's_day': f"{end_month}-01",
|
|
's_date': f"{start_month}-01",
|
|
'e_date': f"{end_month}-01",
|
|
's_mon': start_month,
|
|
'e_mon': end_month,
|
|
'pscode': pscode,
|
|
'dispType': 'mon'
|
|
}
|
|
else:
|
|
# 3,4,9호기: pl_month_proc.php with s_date/e_date (YYYY-MM)
|
|
url = 'http://www.nrems.co.kr/v2/local/proc/pl_month_proc.php'
|
|
headers['Referer'] = f'http://www.nrems.co.kr/v2/local/plant/pl_month.php?pscode={pscode}'
|
|
payload = {
|
|
'act': 'empty',
|
|
's_date': start_month,
|
|
'e_date': end_month,
|
|
'pscode': pscode
|
|
}
|
|
|
|
response = session.post(url, data=payload, headers=headers, timeout=15)
|
|
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
|
|
# 데이터 구조 확인
|
|
if is_split:
|
|
# 1,2호기: pwdata 키 사용
|
|
monthly_records = data.get('pwdata', [])
|
|
else:
|
|
# 3,4,9호기: pdata 키 사용
|
|
monthly_records = data.get('pdata', [])
|
|
|
|
if monthly_records:
|
|
print(f" ✓ Found {len(monthly_records)} monthly records")
|
|
|
|
for month_data in monthly_records:
|
|
# 월 추출
|
|
month_str = month_data.get('DATE', '')
|
|
if not month_str:
|
|
continue
|
|
|
|
if is_split:
|
|
# 1,2호기: PW1, PW2 분리
|
|
inv1_gen = safe_float(month_data.get('PW1', 0))
|
|
inv2_gen = safe_float(month_data.get('PW2', 0))
|
|
|
|
results.append({
|
|
'plant_id': 'nrems-01',
|
|
'month': month_str,
|
|
'generation_kwh': inv1_gen
|
|
})
|
|
results.append({
|
|
'plant_id': 'nrems-02',
|
|
'month': month_str,
|
|
'generation_kwh': inv2_gen
|
|
})
|
|
print(f" ✓ {month_str}: Unit1={inv1_gen}kWh, Unit2={inv2_gen}kWh")
|
|
else:
|
|
# 3,4,9호기: INV 단일값
|
|
generation_kwh = safe_float(month_data.get('INV', 0))
|
|
results.append({
|
|
'plant_id': plant_id,
|
|
'month': month_str,
|
|
'generation_kwh': generation_kwh
|
|
})
|
|
print(f" ✓ {month_str}: {generation_kwh}kWh")
|
|
|
|
print(f" → Collected {len(monthly_records)} records")
|
|
else:
|
|
print(f" ⚠ No monthly data found")
|
|
else:
|
|
print(f" ✗ HTTP {response.status_code}")
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
|
|
print(f"\n[Total] Collected {len(results)} monthly records\n")
|
|
return results
|