Skip to content

Commit 1f6cda9

Browse files
committed
fix(etl): rebuild results from artifacts when logs are unavailable in CI
1 parent b8fbc91 commit 1f6cda9

1 file changed

Lines changed: 168 additions & 3 deletions

File tree

src/etl.py

Lines changed: 168 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
EXCEPTION_RE = re.compile(r"([A-Za-z_]\w*(?:Error|Exception)):\s*(.+)")
2222
RUN_ID_REGIME_RE = re.compile(r"^(R\d+)_")
2323
TOKEN_REGIME_RE = re.compile(r"(?:^|[_-])(R\d+)(?:[_-]|$)", re.IGNORECASE)
24+
REGIME_ONLY_RE = re.compile(r"^R(\d+)$", re.IGNORECASE)
2425

2526

2627
def to_float(value):
@@ -770,6 +771,171 @@ def infer_target_regime(run_id, run_payload):
770771
return None
771772

772773

774+
def normalize_target_regime(value):
775+
if value is None:
776+
return None
777+
regime = str(value).strip().upper()
778+
if REGIME_ONLY_RE.match(regime):
779+
return regime
780+
return None
781+
782+
783+
def regime_sort_key(value):
784+
regime = normalize_target_regime(value)
785+
if not regime:
786+
return (1, str(value or ''))
787+
return (0, int(REGIME_ONLY_RE.match(regime).group(1)))
788+
789+
790+
def pick_numeric(*values):
791+
for value in values:
792+
n = to_float(value)
793+
if n is not None:
794+
return n
795+
return None
796+
797+
798+
def extract_labeled_count(run):
799+
return pick_numeric(
800+
run.get('train_labeled_n'),
801+
run.get('sampling.stats.train_labeled.n'),
802+
run.get('artifacts.sampling.stats.train_labeled.n'),
803+
run.get('sampling.stats.labeled'),
804+
run.get('artifacts.sampling.stats.labeled'),
805+
run.get('sampling.stats.labeled_class_dist.n'),
806+
run.get('artifacts.sampling.stats.labeled_class_dist.n'),
807+
)
808+
809+
810+
def extract_train_count(run):
811+
return pick_numeric(
812+
run.get('train_n'),
813+
run.get('sampling.stats.train.n'),
814+
run.get('artifacts.sampling.stats.train.n'),
815+
run.get('sampling.stats.train'),
816+
run.get('artifacts.sampling.stats.train'),
817+
)
818+
819+
820+
def build_run_summary(run):
821+
summary_fields = [
822+
'run_id',
823+
'method_id',
824+
'dataset_id',
825+
'paradigm',
826+
'modality',
827+
'target_regime',
828+
'test_accuracy',
829+
'test_macro_f1',
830+
'val.accuracy',
831+
'val.macro_f1',
832+
'duration_s',
833+
'seed',
834+
'status',
835+
'exit_code',
836+
'error',
837+
]
838+
839+
summary = {field: run.get(field) for field in summary_fields}
840+
summary['target_regime'] = normalize_target_regime(summary.get('target_regime'))
841+
842+
raw_data_urls = run.get('raw_data_urls')
843+
if isinstance(raw_data_urls, dict):
844+
summary['raw_data_urls'] = {k: v for k, v in raw_data_urls.items() if v}
845+
else:
846+
summary['raw_data_urls'] = {}
847+
848+
train_labeled_n = extract_labeled_count(run)
849+
if train_labeled_n is not None:
850+
summary['train_labeled_n'] = train_labeled_n
851+
852+
train_n = extract_train_count(run)
853+
if train_n is not None:
854+
summary['train_n'] = train_n
855+
856+
return prune_empty(summary)
857+
858+
859+
def infer_regime_label_count(runs):
860+
votes = {}
861+
for run in runs:
862+
labeled = extract_labeled_count(run)
863+
if labeled is None:
864+
continue
865+
key = str(int(round(labeled)))
866+
votes[key] = votes.get(key, 0) + 1
867+
868+
best = None
869+
best_freq = -1
870+
for key, freq in votes.items():
871+
value = int(key)
872+
if freq > best_freq or (freq == best_freq and (best is None or value < best)):
873+
best = value
874+
best_freq = freq
875+
return best
876+
877+
878+
def write_compact_results(all_runs, output_dir):
879+
compact_runs = [build_run_summary(run) for run in all_runs]
880+
compact_runs = [run for run in compact_runs if run.get('run_id')]
881+
compact_runs.sort(
882+
key=lambda run: (
883+
regime_sort_key(run.get('target_regime')),
884+
str(run.get('dataset_id') or ''),
885+
str(run.get('method_id') or ''),
886+
str(run.get('run_id') or ''),
887+
)
888+
)
889+
890+
# Backward-compatible single file (now compact).
891+
with open(output_dir / 'results.json', 'w', encoding='utf-8') as f:
892+
json.dump(compact_runs, f, indent=2)
893+
894+
results_dir = output_dir / 'results'
895+
results_dir.mkdir(parents=True, exist_ok=True)
896+
for stale in results_dir.glob('*.json'):
897+
stale.unlink()
898+
899+
runs_by_regime = {}
900+
for run in compact_runs:
901+
regime = normalize_target_regime(run.get('target_regime'))
902+
if not regime:
903+
continue
904+
runs_by_regime.setdefault(regime, []).append(run)
905+
906+
if not runs_by_regime and compact_runs:
907+
runs_by_regime['UNKNOWN'] = compact_runs
908+
909+
manifest_chunks = []
910+
for regime in sorted(runs_by_regime.keys(), key=regime_sort_key):
911+
regime_runs = runs_by_regime[regime]
912+
chunk_file = results_dir / f'{regime}.json'
913+
with open(chunk_file, 'w', encoding='utf-8') as f:
914+
json.dump(regime_runs, f, indent=2)
915+
916+
manifest_chunks.append(
917+
{
918+
'regime': regime,
919+
'path': f'data/results/{regime}.json',
920+
'run_count': len(regime_runs),
921+
'label_count': infer_regime_label_count(regime_runs),
922+
}
923+
)
924+
925+
manifest_total = sum(chunk['run_count'] for chunk in manifest_chunks)
926+
manifest = {
927+
'schema_version': 1,
928+
'total_runs': manifest_total,
929+
'default_regime': manifest_chunks[0]['regime'] if manifest_chunks else None,
930+
'chunks': manifest_chunks,
931+
}
932+
933+
with open(output_dir / 'results-manifest.json', 'w', encoding='utf-8') as f:
934+
json.dump(manifest, f, indent=2)
935+
936+
return compact_runs
937+
938+
773939
def extract_run_data_from_artifact_dir(artifact_dir):
774940
artifact_dir = Path(artifact_dir)
775941
if not artifact_dir.is_dir():
@@ -925,11 +1091,10 @@ def main():
9251091
all_runs.append(run_data)
9261092
seen_run_ids.add(run_id)
9271093

1094+
compact_runs = write_compact_results(all_runs, output_dir)
9281095
output_file = output_dir / 'results.json'
929-
with open(output_file, 'w', encoding='utf-8') as f:
930-
json.dump(all_runs, f, indent=2)
9311096

932-
print(f"Successfully processed {len(all_runs)} runs. Data saved to {output_file}")
1097+
print(f"Successfully processed {len(compact_runs)} runs. Data saved to {output_file}")
9331098

9341099

9351100
if __name__ == '__main__':

0 commit comments

Comments
 (0)