"""SCPPID DB Auditor."""
import json
import re
from datetime import datetime
from os.path import exists, split, join, splitext
from sys import stderr
from typing import Tuple, Match, NamedTuple, List, Any
from loguru import logger as log
import pandas as pd
from config import SCPP_AUDIT_FILE, OUTPUT_FILE_DIR, PPB_DATABASE, \
SCPPID_TABLE, PPB_HOST
from connectors.mysql import rds_cursor, rds_con, ar_con, ar_cursor
from constants import INPUT_FILE_EXT_LIST, MYSQL_SELECT_LIMIT
from sql.select_count_scppid import SELECT_COUNT_SCPPID
from sql.select_scppid_by_chunks import SELECT_SCPPID_BY_CHUNKS
from sql.select_track_metadata_by_isrc import SELECT_TRACK_METADATA_BY_ISRC
from sql.select_track_metadata_by_isrc_list \
import SELECT_TRACK_METADATA_BY_ISRC_LIST
from sql.select_track_isrc_by_isrc_list import SELECT_TRACK_ISRC_BY_ISRC_LIST
# Loguru log formatting constant
default_format = '{time:YYYY-MM-DD at h:mm:ss A zz} | {' \
'level: ^10} | {message}'
# Get script run datetime
script_run_at = datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
# Remove and replace default logger
log.remove(0)
# Default Main Logger
log.add(stderr, format=default_format, colorize=True)
input_file_name = split(SCPP_AUDIT_FILE)[1]
output_path = split(SCPP_AUDIT_FILE)[0]
processing_errors = []
def is_isrc(isrc: str) -> Match:
"""Verify the string is an ISRC."""
return re.match(
r'^[A-Za-z]{2}[-]?[0-9A-Za-z]{3}[-]?[0-9]{2}[-]?[0-9]{5}$', isrc)
def ingest_audit_file(scpp_audit_file: str) -> dict:
"""Ingest an Excel file to a dict, sifted and pivoted on various fields.
Args:
scpp_audit_file (string): The path and filename of the file to ingest.
Raises:
FileNotFoundError: If the passed file does not exist.
Returns:
dict: The contents of the file, sifted and pivoted.
"""
# Init vars
verified_file_scpp_ids = []
verified_file_isrcs = []
scpp_id_to_isrc_dict = {}
isrc_to_scpp_id_dict = {}
# Log opening and loading file to user
msg = f'Opening and loading \'{scpp_audit_file}\'.'
log.info(msg)
# Check path exists
if not exists(scpp_audit_file):
raise FileNotFoundError(
f'SCPP Audit file \'{scpp_audit_file}\' not found.')
# Check valid extension
if not splitext(scpp_audit_file)[1].lower() in INPUT_FILE_EXT_LIST:
extension_list = '\', \''.join(INPUT_FILE_EXT_LIST)
raise RuntimeError(
f'SCPP Audit file \'{scpp_audit_file}\' extension is not one '
f'of (\'{extension_list}\').')
# Open Excel file as dataframe
with pd.ExcelFile(scpp_audit_file) as xl:
# TODO: Check format of sheet
# Loop through all sheets
for sheet_name in xl.sheet_names:
log.info(f'Processing sheet: \'{sheet_name}\'')
sheet = pd.read_excel(xl, sheet_name=sheet_name)
# Convert sheet to dict with separated fields
sheet_dict = sheet.to_dict('split')
# Generate various slices
for item in sheet_dict['data']:
try:
scpp_id = int(item[0])
except Exception as e:
processing_errors.append(
f'Error while processing SCPPID \'{item[0]}\' from '
f'input file: {str(e)}.'
)
continue
# Add SCPPID to full lists of input file's SCPPID's
verified_file_scpp_ids.append(scpp_id)
# Compile list of SCPPID -> ISRC's in input file
if not scpp_id_to_isrc_dict.get(scpp_id):
scpp_id_to_isrc_dict[scpp_id] = dict(file=[], db=[])
# Ensure all ISRC's have UPPERCASE alpha component
isrc = str(item[1]).strip().upper()
if not isrc:
continue
# Add ISRC to full lists of input file's ISRC's
verified_file_isrcs.append(isrc)
if not isrc in scpp_id_to_isrc_dict[scpp_id]['file']:
scpp_id_to_isrc_dict[scpp_id]['file'].append(isrc)
# Compile list of ISRC -> SCPPID's in input file
if not isrc_to_scpp_id_dict.get(isrc):
isrc_to_scpp_id_dict[isrc] = dict(file=[], db=[])
if not scpp_id in isrc_to_scpp_id_dict[isrc]['file']:
isrc_to_scpp_id_dict[isrc]['file'].append(scpp_id)
# Clean up memory
del(sheet_dict)
return verified_file_scpp_ids, verified_file_isrcs, scpp_id_to_isrc_dict, \
isrc_to_scpp_id_dict
def dedupe_list(items: list) -> Tuple[int, list]:
"""De-dupe a list of items."""
deduped_item_list = list(set(items))
diff = len(items) - len(deduped_item_list)
return diff, deduped_item_list
def process_db_items(
scppid_isrc_db_list: list, full_audit_dict: dict) -> Tuple[dict, list, list]:
"""Process the items from the DB."""
# Init vars
db_isrcs = []
db_scpp_ids = []
# Split DB responses into groups
for item in scppid_isrc_db_list:
# Cast double form DB to int
try:
scpp_id = int(item.scpp_id)
except Exception as e:
processing_errors.append(
f'Error while processing SCPPID \'{item.scpp_id}\' in DB: ',
str(e)
)
continue
# Add SCPPID to full lists of db's SCPPID's
db_scpp_ids.append(int(scpp_id))
# Compile list of SCPPID -> ISRC's in db
if not full_audit_dict['scpp_id_to_isrc_dict'].get(scpp_id):
full_audit_dict['scpp_id_to_isrc_dict'][scpp_id] = \
dict(file=[], db=[])
isrc = str(item.isrc).strip().upper()
if not isrc:
continue
# Add SCPPID to full lists of db's SCPPID's
db_isrcs.append(isrc)
if not isrc in full_audit_dict['scpp_id_to_isrc_dict'][scpp_id]['db']:
full_audit_dict['scpp_id_to_isrc_dict'][scpp_id]['db'].append(isrc)
# Compile list of ISRC -> SCPPID's in db
if not full_audit_dict['isrc_to_scpp_id_dict'].get(isrc):
full_audit_dict['isrc_to_scpp_id_dict'][isrc] = dict(file=[], db=[])
if not scpp_id in full_audit_dict['isrc_to_scpp_id_dict'][isrc]['db']:
full_audit_dict['isrc_to_scpp_id_dict'][isrc]['db'].append(scpp_id)
return full_audit_dict, db_isrcs, db_scpp_ids
def get_db_items(item_count: int) -> list:
"""Get rows from the DB.
Args:
item_count (int): The count of rows in the DB
Returns:
list: The list of items from the DB (as named tuples in current config)
"""
scppid_isrc_db_list = []
# Query DB for all SCPPID's and ISRC's in chunks
for i in range(0, item_count, MYSQL_SELECT_LIMIT):
log.info(f'Retrieving rows {i + 1} - {i + MYSQL_SELECT_LIMIT}')
rds_cursor.execute(SELECT_SCPPID_BY_CHUNKS, (MYSQL_SELECT_LIMIT, i))
scppid_isrc_db_list = scppid_isrc_db_list + rds_cursor.fetchall()
return scppid_isrc_db_list
def get_multiples(elements: dict, type: str) -> list:
"""Create a list of items that have multiple associations"""
return [k for k,v in elements.items() if len(v[type]) > 1]
def check_isrc(isrc: str) -> List[NamedTuple]:
"""Get the metadata for a given isrc."""
tracks = []
ar_cursor.execute(SELECT_TRACK_METADATA_BY_ISRC, (isrc))
for item in ar_cursor.fetchall():
tracks.append(item)
return tracks
def check_isrc_list(isrc_list: list) -> List[NamedTuple]:
"""Get the metadata for a given isrc."""
tracks = []
item_count = len(isrc_list)
log.info(f'Getting metadata for {item_count} ISRC\'s.')
for i in range(0, item_count, MYSQL_SELECT_LIMIT):
log.info(f'Retrieving metadata for ISRC\'s '
f'{i + 1} - {i + MYSQL_SELECT_LIMIT}')
# Craft MySQL list substitution fields
sub_values = ['%s'] * len(isrc_list[i:i+MYSQL_SELECT_LIMIT])
ar_cursor.execute(
SELECT_TRACK_METADATA_BY_ISRC_LIST.format(
isrc_list=','.join(sub_values)),
(isrc_list[i:i+MYSQL_SELECT_LIMIT])
)
for item in ar_cursor.fetchall():
tracks.append(item)
return tracks
def check_isrc_only_list(isrc_list: list) -> List[str]:
"""Get the metadata for a given isrc."""
isrcs = []
item_count = len(isrc_list)
log.info(f'Checking catalog for {item_count} ISRC\'s.')
for i in range(0, item_count, MYSQL_SELECT_LIMIT):
log.info(f'Retrieving ISRC\'s {i + 1} - {i + MYSQL_SELECT_LIMIT}')
# Craft MySQL list substitution fields
sub_values = ['%s'] * len(isrc_list[i:i+MYSQL_SELECT_LIMIT])
ar_cursor.execute(
SELECT_TRACK_ISRC_BY_ISRC_LIST.format(
isrc_list=','.join(sub_values)),
(isrc_list[i:i+MYSQL_SELECT_LIMIT])
)
for item in ar_cursor.fetchall():
isrcs.append(str(item.isrc).upper())
return isrcs
def audit_scpp_ids(full_audit_dict: dict) -> Tuple[dict, dict]:
"""Perform auditing and cross-checking between file and db."""
audit_output = {}
not_in_catalog_isrcs = full_audit_dict['not_in_catalog_isrcs']
scpp_id_to_isrc_dict = full_audit_dict['scpp_id_to_isrc_dict']
# Find SCPP mismatches
for scpp_id, isrcs in scpp_id_to_isrc_dict.items():
# Init loop vars
msg = []
matched_isrcs = set()
missing_isrcs = dict(not_in_db = [], not_in_file=[])
bad_isrcs = dict(invalid=set(), non_orchard=set())
candidate_isrcs = dict(db_update=[], resend=[])
use_isrc = None
missing_in_db = False
missing_in_file = False
perfect_match = None
mismatches = []
file_match = False
db_match = False
# Audit ISRC's listed in the File for this scpp_ID
if len(isrcs['file']) > 1:
msg.append('Provided audit file lists multiple ISRC\'s:')
for isrc in isrcs['file']:
msg.append(isrc)
msg.append('\n')
elif len(isrcs['file']) == 0:
missing_in_file = True
msg.append('This SCPPID is not listed in the provided audit file.')
# Audit ISRC's listed in the DB for this scpp_ID
if len(isrcs['db']) > 1:
msg.append('DB lists multiple ISRC\'s:')
for isrc in isrcs['db']:
msg.append(isrc)
msg.append('\n')
elif len(isrcs['db']) == 0:
missing_in_db = True
msg.append('This SCPPID is not listed in the DB.')
# Cross-check ISRC's from file with DB
for isrc in isrcs['file']:
# Check ISRC is valid
if not is_isrc(isrc):
msg.append(f'\'{isrc}\' from file is not a valid ISRC.')
bad_isrcs['invalid'].add(isrc)
# Check if ISRC is the Orchard catalog
elif isrc in not_in_catalog_isrcs['file']:
msg.append(f'\'{isrc}\' from file is not in the Orchard '
'catalog.')
bad_isrcs['non_orchard'].add(isrc)
elif isrc not in isrcs['db']:
msg.append(f'{isrc} is associated with this SCPPPID in the '
'provided audit file, but is not listed in the DB.')
missing_isrcs['not_in_db'].append(isrc)
else:
matched_isrcs.add(isrc)
# Cross-check ISRC's from DB with file
for isrc in isrcs['db']:
# Check ISRC is valid
if not is_isrc(isrc):
msg.append(f'\'{isrc}\' from DB is not a valid ISRC.')
bad_isrcs['invalid'].add(isrc)
# Check if ISRC is the Orchard catalog
elif isrc in not_in_catalog_isrcs['db']:
msg.append(f'\'{isrc}\' from DB is not in the Orchard '
'catalog.')
bad_isrcs['non_orchard'].add(isrc)
elif isrc not in isrcs['file']:
msg.append(f'\'{isrc}\' is associated with this SCPPPID in the DB, '
'but is not listed in the provided audit file.')
missing_isrcs['not_in_file'].append(isrc)
else:
matched_isrcs.add(isrc)
# Validate ISRC's across both the file and the DB
if len(matched_isrcs) > 1:
msg.append('There are multiple ISRC\'s present in both the DB and '
'the file:')
for isrc in matched_isrcs:
msg.append(isrc)
msg.append('\n')
elif len(matched_isrcs) == 0:
if not missing_in_db and not missing_in_file:
msg.append('All ISRC\'s for this SCPPID are mismatched.')
for k,v in isrcs.items():
mismatches = list(set(mismatches + v))
elif len(matched_isrcs) == 1 and len(msg) == 0:
perfect_match = list(matched_isrcs)[0]
# Recommendations
if len(msg) > 0:
if len(matched_isrcs) == 1:
msg.append(
f'Recommend using {list(matched_isrcs)[0]} as correct ISRC.')
if len(matched_isrcs) == 0:
msg.append(f'This SCPPID entry needs to be repaired.')
if len(missing_isrcs['not_in_db']) == 1:
candidate_isrcs['db_update'] = missing_isrcs['not_in_db'][0]
use_isrc = missing_isrcs['not_in_db'][0]
msg.append(
f'Recommend using {use_isrc} as correct ISRC and '
'updating the DB.')
elif len(missing_isrcs['not_in_db']) > 1:
msg.append(
f'Recommend choosing one of the following ISRC\'s, and '
'updating the DB.')
for isrc in missing_isrcs['not_in_db']:
msg.append(isrc)
else:
if len(missing_isrcs['not_in_file']) == 1:
candidate_isrcs['resend'] = missing_isrcs['not_in_file'][0]
use_isrc = missing_isrcs['not_in_file'][0]
msg.append(
f'Recommend marking {use_isrc} as correct ISRC '
'and resending to SCPP.')
elif len(missing_isrcs['not_in_file']) > 1:
msg.append(
f'Recommend choosing one of the following ISRC\'s, '
'resending to SCPP, and marking the other as '
'in the DB.')
for isrc in missing_isrcs['not_in_file']:
msg.append(isrc)
else:
msg.append(
f'There is no good candidate ISRC for this SCPPID.')
if len(matched_isrcs) > 1:
msg.append('This SCPPID\'s multiple ISRC\'s must be corrected.')
# Add newline for legibility
msg.append('\n')
# Add SCPPID to audit output (Convert sets for JSON output)
audit_output[scpp_id] = \
dict(msg=msg,
matched_isrcs=list(matched_isrcs), # Can't serialize a set
bad_isrcs=\
dict(zip(bad_isrcs.keys(), map(list, bad_isrcs.values()))),
missing_isrcs=missing_isrcs,
candidate_isrcs=candidate_isrcs,
perfect_match=perfect_match,
mismatches=mismatches)
return audit_output
def write_var_to_file(var: Any) -> str:
"""Writes a variable to a file.
Args:
var (Any): The variable to write
Returns:
str: The filename generated
"""
# Get a timestamp
output_var_time = datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
# Write var to file
output_file = \
join(OUTPUT_FILE_DIR, f'output_var_{output_var_time}.json')
log.info(f'Writing {output_file}.')
with open(output_file, 'w') as var_file:
json.dump(var, var_file, 2)
log.info(f'{output_file} written.')
return output_file
def main():
"""The main function."""
# Final Audit Data Structure
full_audit_dict = dict(
isrcs={},
scpp_ids={},
deduped_scpp_ids={},
deduped_isrcs={},
scpp_ids_with_dupe_isrc={},
isrcs_with_dupe_scpp_id={},
item_count = {},
not_in_catalog_isrcs = {}
)
# Log to user
msg = f'Using DB: \'{PPB_HOST}\'.'
log.info(msg)
# Ingest the audit file
verified_file_scpp_ids, verified_file_isrcs, scpp_id_to_isrc_dict, \
isrc_to_scpp_id_dict = \
ingest_audit_file(SCPP_AUDIT_FILE)
verified_file_isrcs_len = len(verified_file_isrcs)
verified_file_scpp_ids_len = len(verified_file_scpp_ids)
# Report count of items.
log.info(f"{verified_file_isrcs_len} ISRC's found in file.")
log.info(f"{verified_file_scpp_ids_len} SCPPID's found in file.")
# Add to full audit data structure
full_audit_dict['item_count']['file'] = verified_file_scpp_ids_len
# Add to full audit data structure
full_audit_dict['isrcs']['file'] = verified_file_isrcs
full_audit_dict['scpp_ids']['file'] = verified_file_scpp_ids
full_audit_dict['scpp_id_to_isrc_dict'] = scpp_id_to_isrc_dict
full_audit_dict['isrc_to_scpp_id_dict'] = isrc_to_scpp_id_dict
# Clear Memory
del(verified_file_scpp_ids)
del(verified_file_isrcs)
del(scpp_id_to_isrc_dict)
del(isrc_to_scpp_id_dict)
# De-dupe SCPPID's
diff, deduped_file_scppid_list = \
dedupe_list(full_audit_dict['scpp_ids']['file'])
# Add de-duped SCPPID list to full audit data structure
full_audit_dict['deduped_scpp_ids']['file'] = deduped_file_scppid_list
# Report number of SCPPID duplicates
if diff != 0:
log.warning(f'Verified file\'s SCPPID list contains {diff} duplicates')
# De-dupe ISRC's
diff, deduped_file_isrc_list = dedupe_list(full_audit_dict['isrcs']['file'])
# Add de-duped ISRC list to full audit data structure
full_audit_dict['deduped_isrcs']['file'] = deduped_file_isrc_list
# Clear Memory
del(deduped_file_scppid_list)
# Report number of ISRC duplicates
if diff != 0:
log.warning(f'Verified file\'s ISRC list contains {diff} duplicates')
log.info('Begin checking ISRC\'s (from file) in The Orchard\'s catalog.')
# Check if ISRC's are in the Catalog
catalog_file_isrcs = \
check_isrc_only_list(full_audit_dict['deduped_isrcs']['file'])
# Get all ISRCS not in the Orchard Catalog
not_in_catalog_file_isrcs = \
set(deduped_file_isrc_list) - set(catalog_file_isrcs)
# Add all non-Orchard isrcs to the full audit data structure
full_audit_dict['not_in_catalog_isrcs']['file'] = \
list(not_in_catalog_file_isrcs)
# Clear Memory
del(not_in_catalog_file_isrcs)
del(deduped_file_isrc_list)
# Check for SCPPID's with multiple ISRCS
scpp_ids_with_dupe_isrc = \
get_multiples(full_audit_dict['scpp_id_to_isrc_dict'], 'file')
if len(scpp_ids_with_dupe_isrc) > 0:
log.warning(
f'{len(scpp_ids_with_dupe_isrc)} SCPPID\'s ' \
'found in file with more than one ISRC defined.')
# Add SCPPID's with duplicate ISRC's from file to full audit data structure
full_audit_dict['scpp_ids_with_dupe_isrc']['file'] = scpp_ids_with_dupe_isrc
# Check for ISRC's with multiple SCPPID's
isrcs_with_dupe_scpp_id = \
get_multiples(full_audit_dict['isrc_to_scpp_id_dict'], 'file')
if len(isrcs_with_dupe_scpp_id) > 0:
log.warning(
f'{len(isrcs_with_dupe_scpp_id)} ISRC\'s ' \
'found in file with more than one SCPPID defined.')
# Add ISRC's with duplicate SCPPID's from file to full audit data structure
full_audit_dict['isrcs_with_dupe_scpp_id']['file'] = isrcs_with_dupe_scpp_id
# ------------------------------------------------------------------------
# -- DB Processing -------------------------------------------------------
# ------------------------------------------------------------------------
log.info(f'Begin processing DB table \'{PPB_DATABASE}.{SCPPID_TABLE}\'')
# Get count of SCPPID's in DB
rds_cursor.execute(SELECT_COUNT_SCPPID)
item_count = rds_cursor.fetchone().scppid_count
# Add to full audit data structure
full_audit_dict['item_count']['db'] = item_count
log.info(
f'{item_count} SCPPID\'s found in \'{PPB_DATABASE}.{SCPPID_TABLE}\'')
# Query DB for SCPPIDS and ISRCS
scppid_isrc_db_list = get_db_items(item_count)
full_audit_dict, db_isrcs, db_scpp_ids = \
process_db_items(scppid_isrc_db_list, full_audit_dict)
# Report count of items.
log.info(f'{len(db_isrcs)} ISRC\'s found in db.')
log.info(f'{len(db_scpp_ids)} SCPPID\'s found in db.')
# Add to full audit data structure
full_audit_dict['isrcs']['db'] = db_isrcs
full_audit_dict['scpp_ids']['db'] = db_scpp_ids
# Clear Memory
del(db_isrcs)
del(db_scpp_ids)
# De-dupe SCPPID's
diff, deduped_db_scpp_id_list = \
dedupe_list(full_audit_dict['scpp_ids']['db'])
# Report number of SCPPID duplicates
if diff != 0:
log.warning(f'DB\'s SCPPID list contains {diff} duplicates')
# Add de-duped SCPPID list to full audit data structure
full_audit_dict['deduped_scpp_ids']['db'] = deduped_db_scpp_id_list
# De-dupe ISRC's
diff, deduped_db_isrc_list = dedupe_list(full_audit_dict['isrcs']['db'])
# Report number of ISRC duplicates
if diff != 0:
log.warning(f'DB\'s ISRC list contains {diff} duplicates')
log.info('Begin checking ISRC\'s (from DB SCPPID list) in The Orchard\'s '
'catalog.')
# Add de-duped ISRC list to full audit data structure
full_audit_dict['deduped_isrcs']['db'] = deduped_db_isrc_list
# Clear Memory
del(deduped_db_isrc_list)
del(deduped_db_scpp_id_list)
# Check if ISRC's are in the Catalog
catalog_db_isrcs = \
check_isrc_only_list(full_audit_dict['deduped_isrcs']['db'])
# Get all ISRCS not in the Orchard Catalog
not_in_catalog_db_isrcs = \
set(full_audit_dict['deduped_isrcs']['db']) - set(catalog_db_isrcs)
# Add all non-Orchard isrcs to the full audit data structure
full_audit_dict['not_in_catalog_isrcs']['db'] = \
list(not_in_catalog_db_isrcs)
# Clear Memory
del(not_in_catalog_db_isrcs)
# Check for SCPPID's with multiple ISRCS
scpp_ids_with_dupe_isrc = \
get_multiples(full_audit_dict['scpp_id_to_isrc_dict'], 'db')
if len(scpp_ids_with_dupe_isrc) > 0:
log.warning(
f'{len(scpp_ids_with_dupe_isrc)} SCPPID\'s ' \
'found in DB with more than one ISRC defined.')
# Add SCPPID's with duplicate ISRC's from DB to full audit data structure
full_audit_dict['scpp_ids_with_dupe_isrc']['db'] = scpp_ids_with_dupe_isrc
# Check for ISRC's with multiple SCPPID's
isrcs_with_dupe_scpp_id = \
get_multiples(full_audit_dict['isrc_to_scpp_id_dict'], 'db')
if len(isrcs_with_dupe_scpp_id) > 0:
log.warning(
f'{len(isrcs_with_dupe_scpp_id)} ISRC\'s ' \
'found in DB with more than one SCPPID defined.')
# Add ISRC's with duplicate SCPPID's from DB to full audit data structure
full_audit_dict['isrcs_with_dupe_scpp_id']['db'] = isrcs_with_dupe_scpp_id
# Clear Memory
del(scpp_ids_with_dupe_isrc)
del(isrcs_with_dupe_scpp_id)
# ------------------------------------------------------------------------
# Perform Audit Analysis -------------------------------------------------
# ------------------------------------------------------------------------
log.info('Beginning audit of SCPPID\'s...')
full_scppid_audit_report = audit_scpp_ids(full_audit_dict)
full_audit_dict['scpp_id_report'] = full_scppid_audit_report
log.info('Audit of SCPPID\'s complete.')
# TODO ISRC-side-based audit?
# Produce output of all ISRC's with multiple SCPPID's
# ------------------------------------------------------------------------
# Write output files -----------------------------------------------------
# ------------------------------------------------------------------------
log.info(f'Writing output files to \'{OUTPUT_FILE_DIR}\'.')
# Write SCPPID Errors to file
scpp_error_output_file = \
join(OUTPUT_FILE_DIR, f'scpp_audit_errors_{script_run_at}.txt')
log.info(f'Writing \'scpp_audit_errors_{script_run_at}.txt\'.')
with open(scpp_error_output_file, 'w') as scpp_error_file:
for scpp_id, audit in full_scppid_audit_report.items():
if len(audit['msg']) > 0:
scpp_error_file.write(str(scpp_id) + ':\n')
scpp_error_file.write('\n'.join(audit['msg']))
scpp_error_file.write('\n')
log.info(f'\'scpp_audit_errors_{script_run_at}.txt\' written.')
# Write 100% Valid SCPPID's to CSV
scpp_valid_output_file = \
join(OUTPUT_FILE_DIR, f'scpp_audit_valid_{script_run_at}.csv')
log.info(f'Writing \'scpp_audit_valid_{script_run_at}.csv\'.')
with open(scpp_valid_output_file, 'w') as scpp_valid_file:
scpp_valid_file.write('SCPPID,ISRC\n')
for scpp_id, audit in full_scppid_audit_report.items():
if audit['perfect_match']:
scpp_valid_file.write(
f"{str(scpp_id)},{audit['perfect_match']}\n")
log.info(f'\'scpp_audit_valid_{script_run_at}.csv\' written.')
# Write recommended Updated SCPPID's to CSV
scpp_recommend_output_file = \
join(OUTPUT_FILE_DIR, f'scpp_audit_recommendations_{script_run_at}.csv')
log.info(f'Writing \'scpp_audit_recommendations_{script_run_at}.csv\'.')
with open(scpp_recommend_output_file, 'w') as scpp_recommend_file:
scpp_recommend_file.write('SCPPID,ISRC,METHOD\n')
for scpp_id, audit in full_scppid_audit_report.items():
for method, isrc in audit['candidate_isrcs'].items():
if isrc:
scpp_recommend_file.write(
f'{str(scpp_id)},{isrc},{method}\n')
log.info(f'\'scpp_audit_recommendations_{script_run_at}.csv\' written.')
# Write Invalid ISRC's to CSV
isrc_invalid_output_file = \
join(OUTPUT_FILE_DIR, f'isrc_invalid_{script_run_at}.csv')
log.info(f'Writing \'isrc_invalid_{script_run_at}.csv\'.')
with open(isrc_invalid_output_file, 'w') as isrc_invalid_file:
isrc_invalid_file.write('SCPPID,ISRC,REASON\n')
for scpp_id, audit in full_scppid_audit_report.items():
for reason, isrcs in audit['bad_isrcs'].items():
for isrc in isrcs:
isrc_invalid_file.write(f"{str(scpp_id)},{isrc},{reason}\n")
log.info(f'\'isrc_invalid_{script_run_at}.csv\' written.')
# Write Mismatched ISRC's to CSV
mismatched_output_file = \
join(OUTPUT_FILE_DIR, f'isrc_mismatched_{script_run_at}.csv')
log.info(f'Writing \'isrc_mismatched_{script_run_at}.csv\'.')
with open(mismatched_output_file, 'w') as mismatched_file:
# Find max number of mismatched isrcs across all SCPPID's
max_count = \
max([len(audit['mismatches']) for scppid, audit in full_scppid_audit_report.items()])
# Format a string to write to the file with that many items
isrc_header = \
','.join(['ISRC_'+str(n).zfill(2) for n in range(1, max_count+1)])
mismatched_file.write(f'SCPPID,{isrc_header}\n')
for scpp_id, audit in full_scppid_audit_report.items():
if len(audit['mismatches']):
mismatch_values = \
audit['mismatches'] + [' '*(max_count-len(audit['mismatches']))]
isrc_values = ','.join(mismatch_values)
mismatched_file.write(f"{str(scpp_id)},{isrc_values}\n")
log.info(f'\'isrc_mismatched_{script_run_at}.csv\' written.')
# Write Processing errors to file
processing_error_output_file = \
join(OUTPUT_FILE_DIR, f'processing_errors_{script_run_at}.txt')
log.info(f'Writing \'processing_errors_{script_run_at}.txt\'.')
with open(processing_error_output_file, 'w') as proc_error_file:
proc_error_file.writelines(s + '\n' for s in processing_errors)
log.info(f'\'processing_errors_{script_run_at}.txt\' written.')
rds_con.close()
ar_con.close()
log.info('Finished.')
###########
# DEBUG
###########
# Write SCPPID audit output data structure to file
# json_output_file = \
# join(OUTPUT_FILE_DIR, f'full_scppid_audit_report_{script_run_at}.json')
# log.info(f'Writing \'full_scppid_audit_report_{script_run_at}.json\'.')
# with open(json_output_file, 'w') as f:
# json.dump(full_scppid_audit_report, f, indent=2)
# log.info(f'\'full_scppid_audit_report_{script_run_at}.json\' written.')
###########
# END DEBUG
###########
###########
# DEBUG
###########
# Write full audit output data structure to file
# json_output_file = \
# join(OUTPUT_FILE_DIR, f'full_audit_dict_{script_run_at}.json')
# log.info(f'Writing {json_output_file}.')
# with open(json_output_file, 'w') as f:
# json.dump(full_audit_dict, f, indent=2)
# log.info(f'{json_output_file} written.')
###########
# END DEBUG
###########
if __name__ == '__main__':
# Run main function
main()