"""벤치마크(회사) 이름 가명 처리 — Space 표시 전용. 리더보드 컬럼은 `<회사>_<버전/스플릿>_<카테고리>` 형식이고 맨 앞 토큰이 실제 고객/기관명(Coupang, Hyundai, KISA ...)이라, 외부 공개 SPACE 에서는 맨 앞 토큰만 두 글자 코드로 치환한다. 버전/스플릿·카테고리는 그대로 둬서 표의 의미와 기존 동작을 보존한다. Coupang_balanced_test_v1_Fire -> Co_balanced_test_v1_Fire Hyundai_v3_Falldown -> Hy_v3_Falldown Korea_nrf_Smoke -> Ko_nrf_Smoke 구글 시트 원본은 절대 건드리지 않는다 — sheet2df 가 df 를 만든 표시 단계에서만 컬럼명을 치환하고, config 의 데이터셋 목록/숨김 규칙도 같은 코드로 맞춰 갱신한다. """ import re import enviroments.config as config _map = {} # 회사명(lower) -> 2글자 코드 _orig_default_avg = None _orig_default_ds = None _orig_hide_exact = None # ── 모델명 토큰 가명 (셀 값 치환) ── # 하이퍼링크 모델명 안의 특정 모델 토큰을 가린다. 예: PE(Perception Encoder) -> CLIP. # 앞으로 가릴 모델이 생기면 여기에 {"원본토큰": "표시토큰"} 으로 추가만 하면 된다. # 토큰 경계는 문자열 시작/끝 또는 '-' '_' 로 본다 (PE-Core, FT_PE-Core, PE_Large 모두 매칭, # PETA·PErson 같은 다른 단어는 건드리지 않음). 대소문자 구분. MODEL_TOKEN_SUBS = { "PE": "CLIP", } _token_re = re.compile( r"(? str: return _token_re.sub(lambda m: MODEL_TOKEN_SUBS[m.group(1)], s) def _base_code(company: str) -> str: """회사명 -> 앞 2글자 코드 (첫 글자 대문자 + 둘째 소문자). 예: Coupang -> Co.""" alnum = [c for c in company if c.isalnum()] if not alnum: return "Xx" if len(alnum) == 1: return alnum[0].upper() + "x" return alnum[0].upper() + alnum[1].lower() def _leading_token(col: str): """벤치마크 컬럼이면 맨 앞 회사 토큰을 반환, 아니면 None. 공백 포함(예: 'Model name', 'Datasets Used')·'_' 없음(TASK, Model)· AVG_* (가상 데이터셋) 은 치환 대상이 아니다. """ if " " in col or "_" not in col: return None head = col.split("_", 1)[0] if head == "AVG": return None return head def _build_map(companies): """회사명 목록 -> {회사명(lower): 코드}. 대소문자만 다른 이름은 같은 회사로 보고 하나의 코드로 합친다. 코드가 겹치면 뒤에 숫자를 붙여 유일성 보장.""" m, used = {}, set() for name in sorted(set(companies), key=str.lower): key = name.lower() if key in m: continue base = _base_code(name) code, n = base, 1 while code in used: n += 1 code = f"{base}{n}" used.add(code) m[key] = code return m def alias_of(company: str) -> str: return _map.get(company.lower()) or _base_code(company) def _anon_col(col: str) -> str: """컬럼명의 맨 앞 회사 토큰만 코드로 치환. 나머지(버전/카테고리)는 유지.""" head = _leading_token(col) if head is None: return col return f"{alias_of(head)}{col[len(head):]}" def anonymize_df(df): """df 벤치마크 컬럼의 맨 앞 회사 토큰을 2글자 코드로 치환하고, config 의 데이터셋 목록/숨김 규칙도 동일 코드로 갱신한다 (idempotent). """ global _map, _orig_default_avg, _orig_default_ds, _orig_hide_exact if _orig_default_avg is None: _orig_default_avg = list(config.DEFAULT_AVG_DATASETS) _orig_default_ds = list(config.DEFAULT_DATASETS) _orig_hide_exact = list(config.HIDE_COLUMN_EXACT) companies = [t for t in (_leading_token(c) for c in df.columns) if t] _map = _build_map(companies + list(_orig_default_avg)) df = df.rename(columns={c: _anon_col(c) for c in df.columns}) # 모델명 토큰 가명: 하이퍼링크(Model/Model name/Model link)와 셀 값 전반에서 # PE -> CLIP 등 치환. 표시 텍스트와 링크 URL 을 함께 바꿔 노출을 남기지 않는다. for col in df.columns: if df[col].dtype == object: df[col] = df[col].map( lambda v: _sub_model_tokens(v) if isinstance(v, str) else v ) # config 도 같은 코드 기준으로 맞춰 리더보드 셀렉터/AVG/숨김이 일관되게 동작 config.DEFAULT_AVG_DATASETS = [alias_of(d) for d in _orig_default_avg] config.DEFAULT_DATASETS = [ d if d == "AVG" else alias_of(d) for d in _orig_default_ds ] config.HIDE_COLUMN_EXACT = [_anon_col(c) for c in _orig_hide_exact] return df