





import argparse
import dataclasses
import json
import logging
import os
import shutil
from typing import List, Optional

import datasets
from accelerate import Accelerator
from datasets import load_dataset
from finetuning import finetune
from tqdm.auto import tqdm

import transformers
from transformers import AutoConfig, set_seed
from transformers.trainer_utils import IntervalStrategy


_SCREAMING_SNAKE_CASE 		=						logging.getLogger(__name__)

_SCREAMING_SNAKE_CASE 		=						"""pytorch_model.bin"""
@dataclasses.dataclass
class SCREAMING_SNAKE_CASE_  :
						__magic_name__:     str	 =    dataclasses.field(
						    metadata={"help": "Path to pretrained model or model identifier from huggingface.co/models."}   )
						__magic_name__:     Optional[str]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "Where do you want to store the pretrained models downloaded from huggingface.co."}    ,			)
@dataclasses.dataclass
class SCREAMING_SNAKE_CASE_  :
						__magic_name__:     str	 =    dataclasses.field(metadata={"help": "A csv or a json file containing the training data."}   )
						__magic_name__:     str	 =    dataclasses.field(metadata={"help": "A csv or a json file containing the data to predict on."}   )
						__magic_name__:     Optional[str]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "A csv or a json file containing the validation data."}   )
						__magic_name__:     Optional[str]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "The name of the task to train on."}    ,			)
						__magic_name__:     Optional[List[str]]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "The list of labels for the task."}   )
@dataclasses.dataclass
class SCREAMING_SNAKE_CASE_  :
						__magic_name__:     str	 =    dataclasses.field(
						    metadata={"help": "The output directory where the model predictions and checkpoints will be written."}   )
						__magic_name__:     Optional[str]	 =    dataclasses.field(
						    default="accuracy"    ,			metadata={"help": "The evaluation metric used for the task."}   )
						__magic_name__:     Optional[str]	 =    dataclasses.field(
						    default="no"    ,			metadata={
						        "help": "The evaluation strategy to adopt during training. Possible values are: [\"no\", \"step\", \"epoch]"
						    }    ,			)
						__magic_name__:     Optional[int]	 =    dataclasses.field(
						    default=10    ,			metadata={"help": "Number of evaluation calls with no improvement after which training will be stopped."}    ,			)
						__magic_name__:     Optional[float]	 =    dataclasses.field(
						    default=0.0    ,			metadata={
						        "help": "How much the specified evaluation metric must improve to satisfy early stopping conditions."
						    }    ,			)
						__magic_name__:     Optional[bool]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "Whether to filter the pseudo-labeled data based on the confidence score."}    ,			)
						__magic_name__:     Optional[bool]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "Whether to filter the pseudo-labeled data based on the validation performance."}    ,			)
						__magic_name__:     Optional[bool]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "Whether to fine-tune on labeled data after pseudo training."}    ,			)
						__magic_name__:     Optional[float]	 =    dataclasses.field(
						    default=0.0    ,			metadata={"help": "Confidence threshold for pseudo-labeled data filtering."}    ,			)
						__magic_name__:     Optional[int]	 =    dataclasses.field(
						    default=100    ,			metadata={"help": "Number of evaluation calls with no improvement after which training will be stopped."}    ,			)
						__magic_name__:     Optional[int]	 =    dataclasses.field(
						    default=snake_case_    ,			metadata={"help": "Random seed for initialization."}    ,			)


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a				,	__a				,	__a				,	__a				,	__a				):
	snake_case_						:     Any     =		datasets.concatenate_datasets([infer_input, infer_output]				,	axis=1				)

	if args.do_filter_by_confidence:
		snake_case_						:     Optional[int]     =		dataset.filter(lambda __a				: example["probability"] > args.confidence_threshold				)

	if args.do_filter_by_val_performance:
		assert eval_result >= 0.0 and eval_result <= 1.0
		snake_case_						:     Optional[Any]     =		int(eval_result * len(__a				)				)
		print(__a				)
		snake_case_						:     Optional[Any]     =		dataset.sort('probability'				,	reverse=__a				)
		snake_case_						:     Dict     =		dataset.select(range(__a				)				)

	snake_case_						:     Union[str, Any]     =		dataset.remove_columns(['label', 'probability']				)
	snake_case_						:     Optional[int]     =		dataset.rename_column('prediction'				,	'label'				)
	snake_case_						:     Any     =		dataset.map(lambda __a				: {"label": idalabel[example["label"]]}				)
	snake_case_						:     List[Any]     =		dataset.shuffle(seed=args.seed				)

	snake_case_						:     List[Any]     =		os.path.join(__a				,	f"""train_pseudo.{args.data_file_extension}"""				)
	if args.data_file_extension == "csv":
		dataset.to_csv(__a				,	index=__a				)
	else:
		dataset.to_json(__a				)


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a				,	__a				,	__a				,	**__a				):
	snake_case_						:     List[str]     =		Accelerator()
	# Make one log on every process with the configuration for debugging.
	logging.basicConfig(
	    format='%(asctime)s - %(levelname)s - %(name)s - %(message)s'				,	datefmt='%m/%d/%Y %H:%M:%S'				,	level=logging.INFO				,	)
	logger.info(accelerator.state				)

	# Setup logging, we only want one process per machine to log things on the
	# screen. accelerator.is_local_main_process is only True for one process per
	# machine.
	logger.setLevel(logging.INFO if accelerator.is_local_main_process else logging.ERROR				)

	if accelerator.is_local_main_process:
		datasets.utils.logging.set_verbosity_warning()
		transformers.utils.logging.set_verbosity_info()
	else:
		datasets.utils.logging.set_verbosity_error()
		transformers.utils.logging.set_verbosity_error()

	snake_case_						:     List[Any]     =		STModelArguments(model_name_or_path=__a				)
	snake_case_						:     str     =		STDataArguments(train_file=__a				,	infer_file=__a				)
	snake_case_						:     str     =		STTrainingArguments(output_dir=__a				)
	snake_case_						:     List[Any]     =		argparse.Namespace()

	for arg_class in (model_args, data_args, training_args):
		for key, value in vars(__a				).items():
			setattr(__a				,	__a				,	__a				)

	for key, value in kwargs.items():
		if hasattr(__a				,	__a				):
			setattr(__a				,	__a				,	__a				)

    # Sanity checks
	snake_case_						:     str     =		{}
	snake_case_						:     Tuple     =		None

	# You need to provide the training data and the data to predict on
	assert args.train_file is not None
	assert args.infer_file is not None
	snake_case_						:     int     =		args.train_file
	snake_case_						:     Tuple     =		args.infer_file

	if args.evaluation_strategy != IntervalStrategy.NO.value:
		assert args.eval_file is not None
		snake_case_						:     Union[str, Any]     =		args.eval_file

	for key in data_files:
		snake_case_						:     Optional[Any]     =		data_files[key].split('.'				)[-1]
		assert extension in ["csv", "json"], f"""`{key}_file` should be a csv or a json file."""
		if args.data_file_extension is None:
			snake_case_						:     Union[str, Any]     =		extension
		else:
			assert extension == args.data_file_extension, f"""`{key}_file` should be a {args.data_file_extension} file`."""

	assert (
	    args.eval_metric in datasets.list_metrics()
	), f"""{args.eval_metric} not in the list of supported metrics {datasets.list_metrics()}."""

	# If passed along, set the training seed now.
	if args.seed is not None:
		set_seed(args.seed				)

	logger.info('Creating the initial data directory for self-training...'				)
	snake_case_						:     List[Any]     =		f"""{args.output_dir}/self-train_iter-{{}}""".format
	snake_case_						:     Optional[Any]     =		data_dir_format(0				)

	if accelerator.is_main_process:
		if args.output_dir is not None:
			os.makedirs(args.output_dir				,	exist_ok=__a				)
			os.makedirs(__a				,	exist_ok=__a				)
	accelerator.wait_for_everyone()

	snake_case_						:     Union[str, Any]     =		None
	snake_case_						:     List[str]     =		None
	snake_case_						:     List[Any]     =		0
	snake_case_						:     Dict     =		False
	# Show the progress bar
	snake_case_						:     Union[str, Any]     =		tqdm(range(args.max_selftrain_iterations				)				,	disable=not accelerator.is_local_main_process				)

	# Self-train
	for iteration in range(0				,	int(args.max_selftrain_iterations				)				):
		snake_case_						:     Optional[int]     =		data_dir_format(__a				)
		assert os.path.exists(__a				)

		# Stage 1: initial fine-tuning for iteration = 0 or pseudo-training for
		# iteration > 0
		snake_case_						:     str     =		os.path.join(__a				,	'stage-1'				)
		snake_case_						:     Tuple     =		{
		    'accelerator': accelerator,
		    'model_name_or_path': args.model_name_or_path,
		    'cache_dir': args.cache_dir,
		    'do_train': True,
		    'train_file': data_files['train'] if iteration == 0 else data_files['train_pseudo'],
		    'do_eval': True if args.eval_file is not None else False,
		    'eval_file': data_files['eval'],
		    'do_predict': True,
		    'infer_file': data_files['infer'],
		    'task_name': args.task_name,
		    'label_list': args.label_list,
		    'output_dir': current_output_dir,
		    'eval_metric': args.eval_metric,
		    'evaluation_strategy': args.evaluation_strategy,
		    'early_stopping_patience': args.early_stopping_patience,
		    'early_stopping_threshold': args.early_stopping_threshold,
		    'seed': args.seed,
		}
		# Add additional training arguments
		for key, value in kwargs.items():
			if key not in arguments_dict and not hasattr(__a				,	__a				):
				arguments_dict.update({key: value}				)

		snake_case_						:     List[Any]     =		os.path.join(__a				,	'best-checkpoint'				,	__a				)
		if os.path.exists(__a				):
			logger.info(
			    'Found existing model checkpoint at %s. Skipping self-training: iteration: %d, stage: 1.'				,	__a				,	__a				,	)
		else:
			logger.info('***** Running self-training: iteration: %d, stage: 1 *****'				,	__a				)
			finetune(**__a				)
			accelerator.wait_for_everyone()
			assert os.path.exists(__a				)
			logger.info('Self-training job completed: iteration: %d, stage: 1.'				,	__a				)

		if iteration > 0 and args.finetune_on_labeled_data:
			# Stage 2 (optional): fine-tuning on the original labeled data
			snake_case_						:     str     =		os.path.join(__a				,	'best-checkpoint'				)
			snake_case_						:     str     =		os.path.join(__a				,	'stage-2'				)
			# Update arguments_dict
			snake_case_						:     Any     =		model_path
			snake_case_						:     int     =		data_files['train']
			snake_case_						:     Dict     =		current_output_dir

			snake_case_						:     Any     =		os.path.join(__a				,	'best-checkpoint'				,	__a				)
			if os.path.exists(__a				):
				logger.info(
				    'Found existing model checkpoint at %s. Skipping self-training: iteration: %d, stage: 2.'				,	__a				,	__a				,	)
			else:
				logger.info('***** Running self-training: iteration: %d, stage: 2 *****'				,	__a				)
				finetune(**__a				)
				accelerator.wait_for_everyone()
				assert os.path.exists(__a				)
				logger.info('Self-training job completed: iteration: %d, stage: 2.'				,	__a				)

		snake_case_						:     Optional[int]     =		iteration
		snake_case_						:     Tuple     =		data_dir_format(iteration + 1				)

		snake_case_						:     int     =		AutoConfig.from_pretrained(os.path.join(__a				,	'best-checkpoint'				)				)
		snake_case_						:     int     =		config.idalabel
		snake_case_						:     Optional[Any]     =		os.path.join(__a				,	'eval_results_best-checkpoint.json'				)
		snake_case_						:     Optional[Any]     =		os.path.join(__a				,	'test_results_best-checkpoint.json'				)
		assert os.path.exists(__a				)

		with open(__a				,	'r'				) as f:
			snake_case_						:     int     =		float(json.load(__a				)[args.eval_metric]				)
		snake_case_						:     List[Any]     =		os.path.join(__a				,	'infer_output_best-checkpoint.csv'				)
		assert os.path.exists(__a				)
		# Loading the dataset from local csv or json files.
		snake_case_						:     Optional[int]     =		load_dataset(args.data_file_extension				,	data_files={'data': data_files['infer']}				)['data']
		snake_case_						:     Union[str, Any]     =		load_dataset('csv'				,	data_files={'data': infer_output_file}				)['data']

		if accelerator.is_main_process:
			os.makedirs(__a				,	exist_ok=__a				)
			shutil.copy(__a				,	os.path.join(__a				,	f"""eval_results_iter-{iteration}.json"""				)				)
			if os.path.exists(__a				):
				shutil.copy(__a				,	os.path.join(__a				,	f"""test_results_iter-{iteration}.json"""				)				)
			create_pseudo_labeled_data(__a				,	__a				,	__a				,	__a				,	__a				,	__a				)
		accelerator.wait_for_everyone()

		snake_case_						:     Optional[int]     =		os.path.join(__a				,	f"""train_pseudo.{args.data_file_extension}"""				)

		if args.evaluation_strategy != IntervalStrategy.NO.value:
			snake_case_						:     int     =		eval_result

			if best_iteration is None:
				snake_case_						:     Tuple     =		new_iteration
				snake_case_						:     Union[str, Any]     =		new_eval_result
			else:
				if new_eval_result - best_eval_result > args.early_stopping_threshold:
					snake_case_						:     List[str]     =		new_iteration
					snake_case_						:     Optional[int]     =		new_eval_result
					snake_case_						:     List[Any]     =		0
				else:
					if new_eval_result == best_eval_result:
						snake_case_						:     Union[str, Any]     =		new_iteration
						snake_case_						:     List[Any]     =		new_eval_result
					early_stopping_patience_counter += 1

				if early_stopping_patience_counter >= args.early_stopping_patience:
					snake_case_						:     Optional[Any]     =		True

		progress_bar.update(1				)

		if should_training_stop:
			break

	if best_iteration is not None:
		# Save the best iteration
		logger.info('Best iteration: %d'				,	__a				)
		logger.info('Best evaluation result: %s = %f'				,	args.eval_metric				,	__a				)
		accelerator.wait_for_everyone()
		if accelerator.is_main_process:
			shutil.copy(
			    os.path.join(__a				,	f"""eval_results_iter-{iteration}.json"""				)				,	os.path.join(__a				,	'eval_results_best-iteration.json'				)				,	)
	else:
		# Assume that the last iteration is the best
		logger.info('Best iteration: %d'				,	args.max_selftrain_iterations - 1				)
		logger.info('Best evaluation result: %s = %f'				,	args.eval_metric				,	__a				)
		accelerator.wait_for_everyone()
		if accelerator.is_main_process:
			shutil.copy(
			    os.path.join(__a				,	f"""eval_results_iter-{args.max_selftrain_iterations - 1}.json"""				)				,	os.path.join(__a				,	'eval_results_best-iteration.json'				)				,	)








import argparse
import json
import math
import os
import time
import traceback
import zipfile
from collections import Counter

import requests


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Optional[int]     =		None
	if token is not None:
		snake_case_						:     List[str]     =		{'Accept': 'application/vnd.github+json', 'Authorization': f"""Bearer {token}"""}

	snake_case_						:     Union[str, Any]     =		f"""https://api.github.com/repos/huggingface/transformers/actions/runs/{workflow_run_id}/jobs?per_page=100"""
	snake_case_						:     Optional[int]     =		requests.get(__a				,	headers=__a				).json()
	snake_case_						:     List[str]     =		{}

	try:
		job_links.update({job['name']: job['html_url'] for job in result['jobs']}				)
		snake_case_						:     Dict     =		math.ceil((result['total_count'] - 1_00) / 1_00				)

		for i in range(__a				):
			snake_case_						:     Optional[Any]     =		requests.get(url + f"""&page={i + 2}"""				,	headers=__a				).json()
			job_links.update({job['name']: job['html_url'] for job in result['jobs']}				)

		return job_links
	except Exception:
		print(f"""Unknown error, could not fetch links:\n{traceback.format_exc()}"""				)

	return {}


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Union[str, Any]     =		None
	if token is not None:
		snake_case_						:     List[Any]     =		{'Accept': 'application/vnd.github+json', 'Authorization': f"""Bearer {token}"""}

	snake_case_						:     Optional[Any]     =		f"""https://api.github.com/repos/huggingface/transformers/actions/runs/{worflow_run_id}/artifacts?per_page=100"""
	snake_case_						:     Union[str, Any]     =		requests.get(__a				,	headers=__a				).json()
	snake_case_						:     Any     =		{}

	try:
		artifacts.update({artifact['name']: artifact['archive_download_url'] for artifact in result['artifacts']}				)
		snake_case_						:     str     =		math.ceil((result['total_count'] - 1_00) / 1_00				)

		for i in range(__a				):
			snake_case_						:     int     =		requests.get(url + f"""&page={i + 2}"""				,	headers=__a				).json()
			artifacts.update({artifact['name']: artifact['archive_download_url'] for artifact in result['artifacts']}				)

		return artifacts
	except Exception:
		print(f"""Unknown error, could not fetch links:\n{traceback.format_exc()}"""				)

	return {}


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a				,	__a				,	__a				):
	snake_case_						:     Dict     =		None
	if token is not None:
		snake_case_						:     List[Any]     =		{'Accept': 'application/vnd.github+json', 'Authorization': f"""Bearer {token}"""}

	snake_case_						:     Optional[int]     =		requests.get(__a				,	headers=__a				,	allow_redirects=__a				)
	snake_case_						:     str     =		result.headers['Location']
	snake_case_						:     List[str]     =		requests.get(__a				,	allow_redirects=__a				)
	snake_case_						:     Optional[Any]     =		os.path.join(__a				,	f"""{artifact_name}.zip"""				)
	with open(__a				,	'wb'				) as fp:
		fp.write(response.content				)


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Any     =		[]
	snake_case_						:     Any     =		[]
	snake_case_						:     Tuple     =		None

	with zipfile.ZipFile(__a				) as z:
		for filename in z.namelist():
			if not os.path.isdir(__a				):
				# read the file
				if filename in ["failures_line.txt", "summary_short.txt", "job_name.txt"]:
					with z.open(__a				) as f:
						for line in f:
							snake_case_						:     Tuple     =		line.decode('UTF-8'				).strip()
							if filename == "failures_line.txt":
								try:
									# `error_line` is the place where `error` occurs
									snake_case_						:     Tuple     =		line[: line.index(': '				)]
									snake_case_						:     Union[str, Any]     =		line[line.index(': '				) + len(': '				) :]
									errors.append([error_line, error]				)
								except Exception:
									# skip un-related lines
									pass
							elif filename == "summary_short.txt" and line.startswith('FAILED '				):
								# `test` is the test method that failed
								snake_case_						:     Any     =		line[len('FAILED '				) :]
								failed_tests.append(__a				)
							elif filename == "job_name.txt":
								snake_case_						:     Union[str, Any]     =		line

	if len(__a				) != len(__a				):
		raise ValueError(
		    f"""`errors` and `failed_tests` should have the same number of elements. Got {len(__a				)} for `errors` """
		    f"""and {len(__a				)} for `failed_tests` instead. The test reports in {artifact_zip_path} have some"""
		    ' problem.'				)

	snake_case_						:     List[str]     =		None
	if job_name and job_links:
		snake_case_						:     Union[str, Any]     =		job_links.get(__a				,	__a				)

	# A list with elements of the form (line of error, error, failed test)
	snake_case_						:     Optional[Any]     =		[x + [y] + [job_link] for x, y in zip(__a				,	__a				)]

	return result


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Any     =		[]

	snake_case_						:     Any     =		[os.path.join(__a				,	__a				) for p in os.listdir(__a				) if p.endswith('.zip'				)]
	for p in paths:
		errors.extend(get_errors_from_single_artifact(__a				,	job_links=__a				)				)

	return errors


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Optional[int]     =		Counter()
	counter.update([x[1] for x in logs]				)
	snake_case_						:     str     =		counter.most_common()
	snake_case_						:     Tuple     =		{}
	for error, count in counts:
		if error_filter is None or error not in error_filter:
			snake_case_						:     int     =		{'count': count, 'failed_tests': [(x[2], x[0]) for x in logs if x[1] == error]}

	snake_case_						:     int     =		dict(sorted(r.items()				,	key=lambda __a				: item[1]["count"]				,	reverse=__a				)				)
	return r


def 							SCREAMING_SNAKE_CASE__  (      __a				):
	snake_case_						:     Tuple     =		test.split('::'				)[0]
	if test.startswith('tests/models/'				):
		snake_case_						:     List[str]     =		test.split('/'				)[2]
	else:
		snake_case_						:     Union[str, Any]     =		None

	return test


def 							SCREAMING_SNAKE_CASE__  (      __a				,	__a=None				):
	snake_case_						:     Optional[int]     =		[(x[0], x[1], get_model(x[2]				)) for x in logs]
	snake_case_						:     str     =		[x for x in logs if x[2] is not None]
	snake_case_						:     int     =		{x[2] for x in logs}

	snake_case_						:     Dict     =		{}
	for test in tests:
		snake_case_						:     List[str]     =		Counter()
		# count by errors in `test`
		counter.update([x[1] for x in logs if x[2] == test]				)
		snake_case_						:     Any     =		counter.most_common()
		snake_case_						:     str     =		{error: count for error, count in counts if (error_filter is None or error not in error_filter)}
		snake_case_						:     Tuple     =		sum(error_counts.values()				)
		if n_errors > 0:
			snake_case_						:     List[Any]     =		{'count': n_errors, 'errors': error_counts}

	snake_case_						:     int     =		dict(sorted(r.items()				,	key=lambda __a				: item[1]["count"]				,	reverse=__a				)				)
	return r


def 							SCREAMING_SNAKE_CASE__  (      __a				):
	snake_case_						:     Optional[Any]     =		'| no. | error | status |'
	snake_case_						:     str     =		'|-:|:-|:-|'
	snake_case_						:     Tuple     =		[header, sep]
	for error in reduced_by_error:
		snake_case_						:     Dict     =		reduced_by_error[error]['count']
		snake_case_						:     List[str]     =		f"""| {count} | {error[:1_00]} |  |"""
		lines.append(__a				)

	return "\n".join(__a				)


def 							SCREAMING_SNAKE_CASE__  (      __a				):
	snake_case_						:     Optional[Any]     =		'| model | no. of errors | major error | count |'
	snake_case_						:     Union[str, Any]     =		'|-:|-:|-:|-:|'
	snake_case_						:     Optional[int]     =		[header, sep]
	for model in reduced_by_model:
		snake_case_						:     Any     =		reduced_by_model[model]['count']
		snake_case_  ,snake_case_						:     Dict     =		list(reduced_by_model[model]['errors'].items()				)[0]
		snake_case_						:     Any     =		f"""| {model} | {count} | {error[:60]} | {_count} |"""
		lines.append(__a				)

	return "\n".join(__a				)


if __name__ == "__main__":
						_SCREAMING_SNAKE_CASE 		=						argparse.ArgumentParser()
						# Required parameters
						parser.add_argument("""--workflow_run_id""", type=str, required=True, help="""A GitHub Actions workflow run id.""")
						parser.add_argument(
						    """--output_dir""",
						    type=str,
						    required=True,
						    help="""Where to store the downloaded artifacts and other result files.""",
						)
						parser.add_argument("""--token""", default=None, type=str, help="""A token that has actions:read permission.""")
						_SCREAMING_SNAKE_CASE 		=						parser.parse_args()

						os.makedirs(args.output_dir, exist_ok=True)

						_SCREAMING_SNAKE_CASE 		=						get_job_links(args.workflow_run_id, token=args.token)
						_SCREAMING_SNAKE_CASE 		=						{}
						# To deal with `workflow_call` event, where a job name is the combination of the job names in the caller and callee.
						# For example, `PyTorch 1.11 / Model tests (models/albert, single-gpu)`.
						if _job_links:
												for k, v in _job_links.items():
																		# This is how GitHub actions combine job names.
																		if " / " in k:
																								_SCREAMING_SNAKE_CASE 		=						k.find(""" / """)
																								_SCREAMING_SNAKE_CASE 		=						k[index + len(""" / """) :]
																		_SCREAMING_SNAKE_CASE 		=						v
						with open(os.path.join(args.output_dir, """job_links.json"""), """w""", encoding="""UTF-8""") as fp:
												json.dump(job_links, fp, ensure_ascii=False, indent=4)

						_SCREAMING_SNAKE_CASE 		=						get_artifacts_links(args.workflow_run_id, token=args.token)
						with open(os.path.join(args.output_dir, """artifacts.json"""), """w""", encoding="""UTF-8""") as fp:
												json.dump(artifacts, fp, ensure_ascii=False, indent=4)

						for idx, (name, url) in enumerate(artifacts.items()):
												download_artifact(name, url, args.output_dir, args.token)
												# Be gentle to GitHub
												time.sleep(1)

						_SCREAMING_SNAKE_CASE 		=						get_all_errors(args.output_dir, job_links=job_links)

						# `e[1]` is the error
						_SCREAMING_SNAKE_CASE 		=						Counter()
						counter.update([e[1] for e in errors])

						# print the top 30 most common test errors
						_SCREAMING_SNAKE_CASE 		=						counter.most_common(30)
						for item in most_common:
												print(item)

						with open(os.path.join(args.output_dir, """errors.json"""), """w""", encoding="""UTF-8""") as fp:
												json.dump(errors, fp, ensure_ascii=False, indent=4)

						_SCREAMING_SNAKE_CASE 		=						reduce_by_error(errors)
						_SCREAMING_SNAKE_CASE 		=						reduce_by_model(errors)

						_SCREAMING_SNAKE_CASE 		=						make_github_table(reduced_by_error)
						_SCREAMING_SNAKE_CASE 		=						make_github_table_per_model(reduced_by_model)

						with open(os.path.join(args.output_dir, """reduced_by_error.txt"""), """w""", encoding="""UTF-8""") as fp:
												fp.write(sa)
						with open(os.path.join(args.output_dir, """reduced_by_model.txt"""), """w""", encoding="""UTF-8""") as fp:
												fp.write(sa)

