mirror of
https://github.com/Hestia-Homes/survey-extraction.git
synced 2026-06-08 11:17:29 +00:00
454 lines
18 KiB
Python
454 lines
18 KiB
Python
# Wave 3's month end automation
|
|
|
|
from tqdm import tqdm
|
|
from monday import MondayClient
|
|
from etl.osmosis_complaince_address_to_files import get_all_items, extract_asset_ids
|
|
from pprint import pprint
|
|
import pandas as pd
|
|
import json
|
|
import os
|
|
from etl.MonthEndUploader import upload_to_month_end_folder
|
|
from enum import Enum
|
|
|
|
|
|
monday_key = "eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjQ5ODc2ODQxOCwiYWFpIjoxMSwidWlkIjozNjE3ODAzNCwiaWFkIjoiMjAyNS0wNC0xMVQxMToyMzoxNy40NjdaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MTM5OTc4MjMsInJnbiI6InVzZTEifQ.-2Lit4s46ZF6AXuMW9t0TxIaFLkHqD4Yo-PyM9i2XZY"
|
|
monday = MondayClient(monday_key)
|
|
|
|
class BoardID(Enum):
|
|
_9349630181 = "WCHG_WALKUPS_OPERATIONS"
|
|
_8830772914 = "LQ_LONDON"
|
|
_9601691730 = "CARDO_WALES_WEST_WAVE3"
|
|
_9660895490 = "NORTHUMBERLAND_COUNTY_SHDF_WAVE3"
|
|
_9641491000 = "WATFORD_WARM_HOMES"
|
|
_9671463094 = "SEDDON"
|
|
_9929454382 = "NCHA SHDF Wave 3 Main Operation Board"
|
|
_18232420839 = "Powys County Council"
|
|
_18380751311 = "Devon County Council"
|
|
_18397425376 = "CRG"
|
|
|
|
|
|
board_ids = [
|
|
"9349630181", # WCHG Walkups-Operations
|
|
"8830772914", # "L&Q London"
|
|
"9601691730", # Cardo Wales & West - Wave 3
|
|
"9660895490", # Northumberland County SHDF Wave 3
|
|
"9641491000", # Watford Warm Homes
|
|
"9671463094", # Seddon
|
|
"9929454382", # NCHA SHDF Wave 3 Main Operation Board
|
|
"18232420839", # Powys County Council:
|
|
"18380751311", # Devon County Council
|
|
"", # CRG
|
|
]
|
|
|
|
empty = "Rate card info missing"
|
|
|
|
rate_cards = {}
|
|
|
|
check_with_andres = "Check with Andres!!!"
|
|
rate_card_data_powys = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125,280, 125, 125,
|
|
650, check_with_andres, check_with_andres, 225, 175,
|
|
95, 60, empty, empty, 95,
|
|
60, 40, 40, 40, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
rate_cards.update({"18232420839": pd.DataFrame(rate_card_data_powys)})
|
|
|
|
rate_card_data_devon = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
empty, empty,empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
rate_cards.update({"18380751311": pd.DataFrame(rate_card_data_devon)})
|
|
|
|
|
|
rate_card_data_watford_warm_homes = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
165, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
rate_cards.update({"9641491000": pd.DataFrame(rate_card_data_watford_warm_homes)})
|
|
|
|
rate_card_data_ncha_wave_3 = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125, 280, 125, 125,
|
|
650, 415, 195, 175, 135,
|
|
120, "60 - check if EPC or EPR", "150 - check if EPC or EPR", 125, 60,
|
|
45, 45, 45, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
rate_cards.update({"9929454382": pd.DataFrame(rate_card_data_ncha_wave_3)})
|
|
|
|
|
|
|
|
rate_card_data_l_and_q_london = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, 280, 150, check_with_andres
|
|
]
|
|
}
|
|
|
|
rate_cards.update({"8830772914": pd.DataFrame(rate_card_data_l_and_q_london)})
|
|
|
|
|
|
rate_card_data_northhumberland_country_shdf_wave_3 = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125, "280 or 310 depending on SH or LA", 125, 125,
|
|
650, 415, 195, 225, 175,
|
|
135, 120, "60 - check", "85 or 90 depending on SH or LA", "110 or 125 depending on SH or LA",
|
|
60, 45, 45, 45, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
|
|
pd.DataFrame(rate_card_data_northhumberland_country_shdf_wave_3)
|
|
rate_cards.update({"9660895490": pd.DataFrame(rate_card_data_northhumberland_country_shdf_wave_3)})
|
|
|
|
rate_card_data_walk_ups = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125, 280, 125, 125,
|
|
650, 415, 195, 175, 135,
|
|
120, "60 to check", 85, 125, 60,
|
|
45, 45, 45, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
|
|
rate_cards.update({"9349630181": pd.DataFrame(rate_card_data_walk_ups)})
|
|
|
|
rate_card_data_empty = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty,empty , empty, empty, empty,
|
|
empty, empty, empty, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
|
|
rate_card_data_cardo = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple","Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125, 260, 125, 125,
|
|
415, 415, 225, 225, 175,
|
|
135, 120 , "60 - check", 85, 125,
|
|
60, 45, 45, 45, empty, empty, check_with_andres
|
|
]
|
|
}
|
|
|
|
rate_cards.update({"9601691730": pd.DataFrame(rate_card_data_cardo)})
|
|
|
|
rate_card_data_seddon = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple","Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
259, 125, 280, 125, 125,
|
|
650, 415, 195, 225, 175,
|
|
135, 120 , "60 - check", 85, 125,
|
|
60, 45, 45, 45, empty, empty,check_with_andres
|
|
]
|
|
}
|
|
|
|
rate_cards.update({"9671463094": pd.DataFrame(rate_card_data_seddon)})
|
|
|
|
rate_card_data_seddon = {
|
|
"job_type": [
|
|
"RA", "ATT", "Coordination Stage 1 v1", "Coordination Stage 1 v2 remodel", "Coordination Stage 1 v3 remodel",
|
|
"Design Archetype Complex", "Design Archetype Simple", "Design Repetitive Simple","Design Repetitive Complex", "Coordination Stage 2", "Lodgement", "Full lodgement phase 2",
|
|
"Post EPR", "Post EPC", "Post ATT", "retrofit evaluation",
|
|
"RA no show", "ATT no show", "post EPC no show", "Full cost MTP", "measure modelling", "design type not specified"
|
|
],
|
|
"rate": [
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty, empty, empty, empty,
|
|
empty, empty , empty, empty, empty,
|
|
empty, empty, empty, empty, empty, empty,empty
|
|
]
|
|
}
|
|
|
|
rate_cards.update({"18397425376": pd.DataFrame(rate_card_data_seddon)})
|
|
|
|
|
|
board_to_record = {}
|
|
for board in tqdm(board_ids):
|
|
print(f"working on board {board}")
|
|
board_data = monday.boards.fetch_boards_by_id(board)
|
|
columns = board_data["data"]["boards"][0]["columns"]
|
|
col_id_map = {col["title"].lower(): col["id"] for col in columns}
|
|
reversed_col_id_map = {v: k for k, v in col_id_map.items()}
|
|
|
|
|
|
items = get_all_items(board, monday)
|
|
|
|
all_records = []
|
|
for row in tqdm(items):
|
|
data = {}
|
|
data.update({"address": row['name']})
|
|
client = row['group']['title']
|
|
if client != "Removed From Program" and client != "Removed From Project":
|
|
data.update({"client": row['group']['title']})
|
|
for col in row.get("column_values", []):
|
|
if col.get("id") in reversed_col_id_map:
|
|
if col.get("type") == "file":
|
|
value = col.get("value")
|
|
no_of_files = 0
|
|
|
|
if value:
|
|
value = json.loads(col["value"])
|
|
no_of_files = len(value.get('files', []))
|
|
data.update({reversed_col_id_map[col.get("id")]: no_of_files})
|
|
else:
|
|
data.update({
|
|
reversed_col_id_map[col.get("id")]: col.get("text")
|
|
})
|
|
all_records.append(data)
|
|
board_to_record.update({board: all_records})
|
|
|
|
# Convert to DataFrame
|
|
for board, all_records in board_to_record.items():
|
|
df = pd.DataFrame(all_records)
|
|
filtered_dfs = []
|
|
|
|
|
|
def get_df(df, column_name, success_critera, job_name=None):
|
|
_ = pd.DataFrame()
|
|
if column_name in col_id_map:
|
|
_ = df[
|
|
df[column_name].str.lower().isin(success_critera)
|
|
].copy()
|
|
if job_name:
|
|
_["job_type"] = job_name
|
|
|
|
|
|
return _
|
|
|
|
|
|
# RA
|
|
ra = get_df(df, "ra invoicing status", ["to invoice"], "RA")
|
|
if not ra.empty:
|
|
filtered_dfs.append(ra)
|
|
|
|
|
|
att = get_df(df, "post att invoicing status", ["to invoice"], "ATT")
|
|
if not att.empty:
|
|
filtered_dfs.append(att)
|
|
|
|
modeling = get_df(df, "mtp invoicing status", ["modelling to invoice"], "Measure Modelling")
|
|
if not modeling.empty:
|
|
filtered_dfs.append(modeling)
|
|
|
|
try:
|
|
# Only needed for one board in wave 3
|
|
full_cost = get_df(df, "mtp invoicing status", ["(V1) Full cost MTP to invoice (no previous modelling)".lower()], "full cost mtp")
|
|
if not full_cost.empty:
|
|
filtered_dfs(full_cost)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
v1 = get_df(df, "mtp invoicing status", ["(v1) ioe/mtp to invoice"], "Coordination Stage 1 v1")
|
|
if not v1.empty:
|
|
filtered_dfs.append(v1)
|
|
|
|
v2 = get_df(df, "mtp invoicing status", ["(v2) ioe/mtp to invoice"], "Coordination Stage 1 v2 remodel")
|
|
if not v2.empty:
|
|
filtered_dfs.append(v2)
|
|
|
|
v3 = get_df(df, "mtp invoicing status", ["(v3) ioe/mtp to invoice"], "Coordination Stage 1 v3 remodel")
|
|
if not v3.empty:
|
|
filtered_dfs.append(v3)
|
|
|
|
# Base filter
|
|
design = get_df(df, "design invoicing status", ["to invoice"])
|
|
|
|
# Build each slice
|
|
design1 = get_df(design, "design invoice type", ["archetype (complex)"], "Design Archetype Complex")
|
|
if not design1.empty:
|
|
filtered_dfs.append(design1)
|
|
|
|
design2 = get_df(design, "design invoice type", ["archetype (simple)"], "Design Archetype Simple")
|
|
if not design2.empty:
|
|
filtered_dfs.append(design2)
|
|
|
|
design3 = get_df(design, "design invoice type", ["repetitive (simple)"], "Design Repetitive Simple")
|
|
if not design3.empty:
|
|
filtered_dfs.append(design3)
|
|
|
|
design4 = get_df(design, "design invoice type", ["repetitive (complex)"], "Design Repetitive Complex")
|
|
if not design4.empty:
|
|
filtered_dfs.append(design4)
|
|
|
|
# Safe concat of non-empty slices
|
|
parts = [d for d in (design1, design2, design3, design4) if not d.empty]
|
|
if parts:
|
|
all_filtered = pd.concat(parts, ignore_index=False) # keep original index so we can subtract cleanly
|
|
# Anything not matched by the 4 categories
|
|
remaining_idx = design.index.difference(all_filtered.index)
|
|
else:
|
|
# No matches in any category
|
|
all_filtered = design.iloc[0:0] # empty DF with same columns
|
|
remaining_idx = design.index
|
|
|
|
design_remaining = design.loc[remaining_idx].copy()
|
|
if not design_remaining.empty:
|
|
design_remaining["job_type"] = "Design Type Not Specified"
|
|
filtered_dfs.append(design_remaining)
|
|
|
|
# Design Revision
|
|
revision_letter = ['a', 'b', 'c', 'd']
|
|
for letter in revision_letter:
|
|
design = get_df(df, "design revision invoice", [f"rev. {letter} to invoice"], "Design Revision")
|
|
if not design.empty:
|
|
filtered_dfs.append(design)
|
|
|
|
ignore = [
|
|
"9349630181", # WCHG Walkups-Operations
|
|
"18232420839", # Powys County Council:
|
|
"8830772914", # "L&Q London"
|
|
"9641491000", # Watford Warm Homes
|
|
"9601691730",
|
|
"9671463094", # Seddon
|
|
]
|
|
if board not in ignore:
|
|
# Lodgement
|
|
lodg1 = get_df(df, "lodgement invoicing status", ["to invoice"], "Lodgement")
|
|
if not lodg1.empty:
|
|
filtered_dfs.append(lodg1)
|
|
|
|
# Retrofit Evaluation
|
|
if board not in ignore:
|
|
re = get_df(df, "evaluation invoicing status", ["to invoice"], "retrofit evaluation")
|
|
if not re.empty:
|
|
filtered_dfs.append(re)
|
|
|
|
# POST EPC
|
|
post_epc = get_df(df, "Post EPC invoicing status", ["epc to invoice"], "POST EPC")
|
|
if not post_epc.empty:
|
|
filtered_dfs.append(post_epc)
|
|
|
|
# POST EPR
|
|
post_epr = get_df(df, "Post EPR invoicing status", ["epr to invoice"], "POST EPR")
|
|
if not post_epr.empty:
|
|
filtered_dfs.append(post_epr)
|
|
|
|
# Retrofit Evaluation might be need for just EPR
|
|
|
|
# post att
|
|
post_att = get_df(df, "post att invoicing status", ["to invoice"], "POST ATT")
|
|
if not post_att.empty:
|
|
filtered_dfs.append(post_epc)
|
|
|
|
# Coordination Stage 2
|
|
rc = get_df(df, "rc stage 2 invoicing status", ["to invoice"], "Coordination Stage 2")
|
|
if not rc.empty:
|
|
filtered_dfs.append(rc)
|
|
|
|
# RA NO Show
|
|
ra_ns = get_df(df,"ra no show invoice", ["to invoice","to invoice (+1 previous no show)", "to invoice (+2 previous no shows)"], "RA NO SHOW")
|
|
if not ra_ns.empty:
|
|
filtered_dfs.append(ra_ns)
|
|
|
|
|
|
# ATT NO Show
|
|
att_ns = get_df(df, "pre att no show invoice", ["to invoice","to invoice (+1 previous no show)", "to invoice (+2 previous no shows)"], "ATT NO SHOW")
|
|
if not att_ns.empty:
|
|
filtered_dfs.append(att_ns)
|
|
|
|
|
|
# Post visit no show
|
|
epc_ns = get_df(df, "post works no show invoice", ["to invoice","to invoice (+1 previous no show)", "to invoice (+2 previous no shows)"], "post EPC NO SHOW")
|
|
if not epc_ns.empty:
|
|
filtered_dfs.append(epc_ns)
|
|
|
|
if len(filtered_dfs) != 0:
|
|
final_df = pd.concat(filtered_dfs).reset_index(drop=True)
|
|
|
|
final_df["job_type"] = final_df["job_type"].str.lower()
|
|
rate_card_df = rate_cards[board]
|
|
rate_card_df["job_type"] = rate_card_df["job_type"].str.lower()
|
|
|
|
# Now perform the merge
|
|
combined_with_rates = final_df.merge(rate_card_df, on="job_type", how="left")
|
|
import datetime
|
|
timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M')
|
|
|
|
attribute = ['address', 'client', 'job_type', 'rate']
|
|
combined_with_rates[attribute].to_excel(f'Watford Warm Homes {timestamp}.xlsx', index=False)
|
|
|
|
# Upload to sharepoint
|
|
attribute = ['address', 'client', 'job_type', 'rate']
|
|
master_folder_name = BoardID[f"_{board}"].value
|
|
file_name = f"{master_folder_name}_{timestamp}.xlsx"
|
|
combined_with_rates[attribute].to_excel(file_name, index=False)
|
|
|
|
file_path = os.path.abspath(file_name)
|
|
|
|
upload_to_month_end_folder(file_name, file_path, master_folder_name)
|
|
|
|
invoice_name = "rate_card.xlsx"
|
|
file_path = os.path.abspath(invoice_name)
|
|
rate_card_df.to_excel(invoice_name, index=False)
|
|
upload_to_month_end_folder(invoice_name, file_path, master_folder_name)
|
|
|