|
|
|
|
@ -22,53 +22,81 @@ def process_spreadsheet_data(rows):
|
|
|
|
|
processed_data = []
|
|
|
|
|
for row in rows:
|
|
|
|
|
processed_row = convert_spreadsheet_data(row)
|
|
|
|
|
processed_data.append(processed_row)
|
|
|
|
|
processed_data.append(processed_row) if processed_row else None
|
|
|
|
|
return processed_data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def convert_spreadsheet_data(data):
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
def convert_spreadsheet_data(data, default_year=None):
|
|
|
|
|
"""
|
|
|
|
|
Convert spreadsheet row into structured data.
|
|
|
|
|
Expected keys: day, date, plan, actual
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Skip header or invalid rows
|
|
|
|
|
if not data.get("day") or not data["day"].isdigit():
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
|
|
# Convert day to integer
|
|
|
|
|
result['day'] = int(data['day'])
|
|
|
|
|
|
|
|
|
|
# Convert time to a datetime object
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
# Assuming Indonesian format with month names
|
|
|
|
|
# Replace Indonesian month names with English if needed
|
|
|
|
|
month_mapping = {
|
|
|
|
|
'Januari': 'January', 'Februari': 'February', 'Maret': 'March',
|
|
|
|
|
'April': 'April', 'Mei': 'May', 'Juni': 'June',
|
|
|
|
|
'Juli': 'July', 'Agustus': 'August', 'September': 'September',
|
|
|
|
|
'Oktober': 'October', 'November': 'November', 'Desember': 'December'
|
|
|
|
|
}
|
|
|
|
|
# Convert day
|
|
|
|
|
result["day"] = int(data["day"])
|
|
|
|
|
|
|
|
|
|
# Determine default year
|
|
|
|
|
if default_year is None:
|
|
|
|
|
default_year = datetime.now().year
|
|
|
|
|
|
|
|
|
|
date_str = data.get("date", "").strip()
|
|
|
|
|
|
|
|
|
|
# ---------- DATE HANDLING ----------
|
|
|
|
|
# Accept formats like: "Nov 20", "Dec 3", "Jan 1"
|
|
|
|
|
parsed_date = None
|
|
|
|
|
|
|
|
|
|
time_str = data['time']
|
|
|
|
|
for indo, eng in month_mapping.items():
|
|
|
|
|
time_str = time_str.replace(indo, eng)
|
|
|
|
|
|
|
|
|
|
# Format: "Sabtu, Juli 13, 2024" -> "Saturday, July 13, 2024"
|
|
|
|
|
# Removing the day of week to simplify parsing
|
|
|
|
|
time_str = time_str.split(', ', 1)[1] # Remove "Sabtu, "
|
|
|
|
|
result['time'] = datetime.strptime(time_str, '%B %d, %Y')
|
|
|
|
|
|
|
|
|
|
# Convert percentage strings to floats
|
|
|
|
|
# Handling format like "0,12%" -> 0.12
|
|
|
|
|
for key in ['plan', 'actual', 'gap']:
|
|
|
|
|
# Replace comma with dot (European to US decimal notation)
|
|
|
|
|
value = data[key].replace(',', '.')
|
|
|
|
|
# Remove percentage sign
|
|
|
|
|
value = value.rstrip('%')
|
|
|
|
|
# Convert to float
|
|
|
|
|
result[key] = float(value) / 100 # Divide by 100 to get the actual decimal value
|
|
|
|
|
if date_str:
|
|
|
|
|
try:
|
|
|
|
|
parsed_date = datetime.strptime(f"{date_str} {default_year}", "%b %d %Y")
|
|
|
|
|
except ValueError:
|
|
|
|
|
try:
|
|
|
|
|
parsed_date = datetime.strptime(f"{date_str} {default_year}", "%B %d %Y")
|
|
|
|
|
except:
|
|
|
|
|
parsed_date = None
|
|
|
|
|
|
|
|
|
|
# YEAR ROLLOVER (Dec → Jan next year)
|
|
|
|
|
if parsed_date and parsed_date.month == 1 and "Dec" in data.get("date", ""):
|
|
|
|
|
parsed_date = parsed_date.replace(year=default_year + 1)
|
|
|
|
|
|
|
|
|
|
result["date"] = parsed_date
|
|
|
|
|
|
|
|
|
|
# ---------- PERCENT HANDLING ----------
|
|
|
|
|
def parse_percent(value):
|
|
|
|
|
if not value:
|
|
|
|
|
return 0.0
|
|
|
|
|
v = value.strip().replace(",", ".").replace("%", "")
|
|
|
|
|
try:
|
|
|
|
|
return float(v) / 100.0
|
|
|
|
|
except:
|
|
|
|
|
return 0.0
|
|
|
|
|
|
|
|
|
|
result["plan"] = parse_percent(data.get("plan", "0"))
|
|
|
|
|
result["actual"] = parse_percent(data.get("actual", "0"))
|
|
|
|
|
|
|
|
|
|
# Gap calculation
|
|
|
|
|
result["gap"] = result["actual"] - result["plan"]
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fetch_all_sections(service, spreadsheet_id, sheet_name):
|
|
|
|
|
# Fetch a wide range including columns A–L
|
|
|
|
|
result = service.spreadsheets().values().get(
|
|
|
|
|
spreadsheetId=spreadsheet_id,
|
|
|
|
|
range=f"{sheet_name}!A18:1000" # start from row 19, adjust Z if needed
|
|
|
|
|
range=f"{sheet_name}!A5:M5000"
|
|
|
|
|
).execute()
|
|
|
|
|
|
|
|
|
|
values = result.get("values", [])
|
|
|
|
|
if not values:
|
|
|
|
|
raise ValueError("No data found in sheet")
|
|
|
|
|
@ -78,50 +106,92 @@ def fetch_all_sections(service, spreadsheet_id, sheet_name):
|
|
|
|
|
current_subsystem = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i, row in enumerate(values):
|
|
|
|
|
row = [c.strip() for c in row] # clean whitespace
|
|
|
|
|
for row in values:
|
|
|
|
|
# Pad missing columns to avoid index errors
|
|
|
|
|
row += [""] * (13 - len(row))
|
|
|
|
|
|
|
|
|
|
# Skip completely empty rows
|
|
|
|
|
if all(c == "" for c in row):
|
|
|
|
|
continue
|
|
|
|
|
colA, colB, colC, colD, colE, colF, colG, colH, colI, colJ, colK, colL, colM = row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Detect section headers (usually in col A, all caps)
|
|
|
|
|
if row[0]:
|
|
|
|
|
# skip unwanted section title
|
|
|
|
|
if row[0] == "U4 PLANNED OUTAGE 2024":
|
|
|
|
|
continue
|
|
|
|
|
current_section = row[0]
|
|
|
|
|
# Detect a SECTION — bold blue rows in Column C
|
|
|
|
|
if colC and not colD and not colE:
|
|
|
|
|
current_section = colC.strip()
|
|
|
|
|
current_subsystem = None
|
|
|
|
|
header = None
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Detect subsystem (non-all-caps in col A)
|
|
|
|
|
if row[1]:
|
|
|
|
|
current_subsystem = row[1]
|
|
|
|
|
# Detect a SUBSYSTEM — indented header in Column D
|
|
|
|
|
if colD and not colE:
|
|
|
|
|
current_subsystem = colD.strip()
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Otherwise, treat as task row (must have header defined)
|
|
|
|
|
if row[2]:
|
|
|
|
|
task = row[2]
|
|
|
|
|
pic = row[3]
|
|
|
|
|
start_date = row[4]
|
|
|
|
|
finish_date = row[5]
|
|
|
|
|
duration = row[6]
|
|
|
|
|
plan = row[7]
|
|
|
|
|
actual = row[8]
|
|
|
|
|
gap = row[9]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Detect a TASK — Column E populated
|
|
|
|
|
if colE:
|
|
|
|
|
task = colE.strip()
|
|
|
|
|
pic = colF.strip()
|
|
|
|
|
start_date = indo_formatted_date(colG.strip())
|
|
|
|
|
finish_date = indo_formatted_date(colH.strip())
|
|
|
|
|
duration = colI.strip()
|
|
|
|
|
plan = colK.strip()
|
|
|
|
|
actual = colL.strip()
|
|
|
|
|
gap = colM.strip()
|
|
|
|
|
|
|
|
|
|
data.append({
|
|
|
|
|
"system": current_section,
|
|
|
|
|
"section": current_section,
|
|
|
|
|
"subsystem": current_subsystem,
|
|
|
|
|
"task": task,
|
|
|
|
|
"PIC": pic,
|
|
|
|
|
"start_date": start_date,
|
|
|
|
|
"end_date": finish_date,
|
|
|
|
|
"duration": duration,
|
|
|
|
|
"duration": int(duration),
|
|
|
|
|
"plan": plan,
|
|
|
|
|
"actual": actual,
|
|
|
|
|
"gap": gap
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def indo_formatted_date(date_str, base_year=2025):
|
|
|
|
|
"""
|
|
|
|
|
Convert short date like 'Nov 20', '30-Dec', 'Jan 1'
|
|
|
|
|
into: 'Rabu, November 20, 2025'
|
|
|
|
|
If month is January, year becomes 2026.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Month mappings
|
|
|
|
|
eng_to_indo_month = {
|
|
|
|
|
"Jan": "Januari", "Feb": "Februari", "Mar": "Maret", "Apr": "April",
|
|
|
|
|
"May": "Mei", "Jun": "Juni", "Jul": "Juli", "Aug": "Agustus",
|
|
|
|
|
"Sep": "September", "Oct": "Oktober", "Nov": "November", "Dec": "Desember"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
indo_days = {
|
|
|
|
|
0: "Senin",
|
|
|
|
|
1: "Selasa",
|
|
|
|
|
2: "Rabu",
|
|
|
|
|
3: "Kamis",
|
|
|
|
|
4: "Jumat",
|
|
|
|
|
5: "Sabtu",
|
|
|
|
|
6: "Minggu"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Normalize formats ("30-Dec" → "Dec 30")
|
|
|
|
|
if "-" in date_str:
|
|
|
|
|
d, m = date_str.split("-")
|
|
|
|
|
date_str = f"{m} {d}"
|
|
|
|
|
|
|
|
|
|
# Parse using English abbreviation
|
|
|
|
|
try:
|
|
|
|
|
dt = datetime.strptime(f"{date_str} {base_year}", "%b %d %Y")
|
|
|
|
|
except:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# Handle year rollover (Jan -> next year)
|
|
|
|
|
if dt.month == 1:
|
|
|
|
|
dt = dt.replace(year=base_year + 1)
|
|
|
|
|
|
|
|
|
|
# Convert to Indonesian components
|
|
|
|
|
day_name = indo_days[dt.weekday()]
|
|
|
|
|
month_name = eng_to_indo_month[dt.strftime("%b")]
|
|
|
|
|
|
|
|
|
|
return f"{day_name}, {month_name} {dt.day}, {dt.year}"
|