| 455 | fam_rows = await fetch_all(conn, "select id, slug, canonical_name, organization_id from entities where entity_type = 'model_family' and merged_into is null") |
455 | fam_rows = await fetch_all(conn, "select id, slug, canonical_name, organization_id from entities where entity_type = 'model_family' and merged_into is null") |
| 456 | families: dict[str, dict[str, Any]] = {f["canonical_name"].lower(): f for f in fam_rows} # one family per label |
456 | families: dict[str, dict[str, Any]] = {f["canonical_name"].lower(): f for f in fam_rows} # one family per label |
| 457 | slugs_taken = {r["slug"] for r in await fetch_all(conn, "select slug from entities")} |
457 | slugs_taken = {r["slug"] for r in await fetch_all(conn, "select slug from entities")} |
| |
458 | + |
| |
459 | + def family_slug(label: str) -> str: |
| |
460 | + """slugify(label); on collision with any other entity (the model `gpt-5.5` itself) → `<slug>-family`, then numbered.""" |
| |
461 | + base = slugify(label) |
| |
462 | + for c in [base, f"{base}-family", *(f"{base}-family-{n}" for n in range(2, 20))]: |
| |
463 | + if c not in slugs_taken: |
| |
464 | + return c |
| |
465 | + return f"{base}-family-{new_id('model_family')[-6:].lower()}" |
| |
466 | + |
| |
467 | + # re-slug families created under the former rule (organisation prefix on collision: `qwen-qwen3`, `mistral-mistral`) |
| |
468 | + for fam in fam_rows: |
| |
469 | + base = slugify(fam["canonical_name"]) |
| |
470 | + if fam["slug"] in (base, f"{base}-family") or not fam["slug"].endswith(base): |
| |
471 | + continue |
| |
472 | + slugs_taken.discard(fam["slug"]) |
| |
473 | + new_slug = family_slug(fam["canonical_name"]) |
| |
474 | + slugs_taken.add(new_slug) |
| |
475 | + rep.bump("families_reslugged") |
| |
476 | + rep.example(f"family slug {fam['slug']} → {new_slug}") |
| |
477 | + if apply: |
| |
478 | + await execute(conn, "update entities set slug = :s, updated_at = now() where id = :id", s=new_slug, id=fam["id"]) |
| |
479 | + await execute(conn, "insert into entity_aliases (entity_id, alias, alias_norm, kind) values (:e, :a, :n, 'former_name') on conflict (entity_id, alias_norm) do nothing", |
| |
480 | + e=fam["id"], a=fam["slug"], n=normalize_alias(fam["slug"])) |
| |
481 | + fam["slug"] = new_slug |
| 458 | for m in models: |
482 | for m in models: |
| 459 | label = family_release_hint(m["canonical_name"]) |
483 | label = family_release_hint(m["canonical_name"]) |
| 460 | if not label: |
484 | if not label: |
| 465 | org_id = next((org_by_key[k] for k in official if k in org_by_key), None) or m["organization_id"] |
489 | org_id = next((org_by_key[k] for k in official if k in org_by_key), None) or m["organization_id"] |
| 466 | fam = families.get(label.lower()) |
490 | fam = families.get(label.lower()) |
| 467 | if fam is None: |
491 | if fam is None: |
| 468 | − # slug = slugify(label); on collision with any other entity (the model "gpt-5.5" itself) prefix with the organisation slug, then "family-" |
492 | + slug = family_slug(label) |
| 469 | − base_slug = slugify(label) |
| |
| 470 | − org_slug = next((o["slug"] for o in orgs if o["id"] == org_id), None) |
| |
| 471 | − candidates = [base_slug] + ([f"{org_slug}-{base_slug}"] if org_slug else []) + [f"family-{base_slug}"] + [f"family-{base_slug}-{n}" for n in range(2, 20)] |
| |
| 472 | − slug = next(c for c in candidates if c not in slugs_taken) |
| |
| 473 | rep.bump("families_created") |
493 | rep.bump("families_created") |
| 474 | rep.example(f"family '{label}' ({slug}) root={root}") |
494 | rep.example(f"family '{label}' ({slug}) root={root}") |
| 475 | fam = {"id": new_id("model_family"), "slug": slug, "canonical_name": label, "organization_id": org_id, "_new": True} |
495 | fam = {"id": new_id("model_family"), "slug": slug, "canonical_name": label, "organization_id": org_id, "_new": True} |
| 650 | |
670 | |
| 651 | |
671 | |
| 652 | # ---------------------------------------------------------------------------------------------- step: results |
672 | # ---------------------------------------------------------------------------------------------- step: results |
| |
673 | +async def _rehome_results(conn: AsyncConnection, rep: StepReport, apply: bool, scope: set[str] | None) -> None: |
| |
674 | + """Move results from a benchmark *family head* to the registry's variant entity when the row says which variant it measured: |
| |
675 | + (a) config.variant / config.board equals (case-insensitively) the variant entity's name, an alias or its `variant` attribute; |
| |
676 | + (b) LiveBench `category:<Name>` metrics → `livebench-<slug(Name)>` with the registry metric (`average score`), variant = Name; |
| |
677 | + (c) aider `percent_cases_well_formed` → `aider-polyglot-well-formed`. |
| |
678 | + Then dedupe/config keys, one-current-row and `evaluated_on` relations are recomputed for every touched model.""" |
| |
679 | + variants = await fetch_all(conn, """select v.id, v.slug, v.canonical_name, v.attributes, r.object_id as head_id, |
| |
680 | + (select array_agg(alias) from entity_aliases a where a.entity_id = v.id) as aliases |
| |
681 | + from entities v join relations r on r.subject_id = v.id and r.predicate = 'variant_of' and r.valid_to is null |
| |
682 | + where v.entity_type = 'benchmark' and v.merged_into is null""") |
| |
683 | + if not variants: |
| |
684 | + return |
| |
685 | + by_slug: dict[str, dict[str, Any]] = {v["slug"]: v for v in variants} |
| |
686 | + by_head: dict[str, list[tuple[set[str], dict[str, Any]]]] = defaultdict(list) |
| |
687 | + for v in variants: |
| |
688 | + keys = {v["canonical_name"].lower(), v["slug"], *(a.lower() for a in (v["aliases"] or []))} |
| |
689 | + var_attr = (v["attributes"] or {}).get("variant") |
| |
690 | + if isinstance(var_attr, str): |
| |
691 | + keys.add(var_attr.lower()) |
| |
692 | + by_head[v["head_id"]].append((keys, v)) |
| |
693 | + heads = list(by_head) |
| |
694 | + rows = await fetch_all(conn, """select r.id, r.model_id, r.benchmark_id, r.metric, r.config from benchmark_results r |
| |
695 | + where r.benchmark_id = any(cast(:h as text[]))""", h=heads) |
| |
696 | + moves: list[tuple[dict[str, Any], dict[str, Any], str | None, dict[str, Any]]] = [] |
| |
697 | + for r in rows: |
| |
698 | + if scope is not None and r["model_id"] not in scope: |
| |
699 | + continue |
| |
700 | + cfg = dict(r["config"] or {}) |
| |
701 | + metric = r["metric"] or "" |
| |
702 | + target: dict[str, Any] | None = None |
| |
703 | + new_metric: str | None = None |
| |
704 | + label = next((str(cfg[k]) for k in ("variant", "board") if isinstance(cfg.get(k), str) and cfg[k].strip()), None) |
| |
705 | + if label: |
| |
706 | + target = next((v for keys, v in by_head[r["benchmark_id"]] if label.lower() in keys), None) |
| |
707 | + if target is None and metric.lower().startswith("category:"): |
| |
708 | + name = metric.split(":", 1)[1].strip() |
| |
709 | + cand = by_slug.get(f"livebench-{slugify(name)}") |
| |
710 | + if cand and cand["head_id"] == r["benchmark_id"]: |
| |
711 | + target = cand |
| |
712 | + new_metric = (cand["attributes"] or {}).get("metric") or "average score" |
| |
713 | + cfg["variant"] = name |
| |
714 | + if target is None and metric == "percent_cases_well_formed": |
| |
715 | + cand = by_slug.get("aider-polyglot-well-formed") |
| |
716 | + if cand and cand["head_id"] == r["benchmark_id"]: |
| |
717 | + target = cand |
| |
718 | + if target is None: |
| |
719 | + continue |
| |
720 | + moves.append((r, target, new_metric, cfg)) |
| |
721 | + if not moves: |
| |
722 | + return |
| |
723 | + touched: set[tuple[str, str, str]] = set() |
| |
724 | + for r, target, new_metric, cfg in moves: |
| |
725 | + rep.bump(f"results_rehomed:{target['slug']}") |
| |
726 | + touched.add((r["model_id"], r["benchmark_id"], target["id"])) |
| |
727 | + if apply: |
| |
728 | + await execute(conn, "update benchmark_results set benchmark_id = :b, metric = coalesce(:m, metric), config = cast(:c as jsonb), variant = :v where id = :id", |
| |
729 | + b=target["id"], m=new_metric, c=jsonb(cfg), v=bench_ontology.variant_from_config(cfg), id=r["id"]) |
| |
730 | + if not apply: |
| |
731 | + return |
| |
732 | + source_id = await registry_source_id(conn) |
| |
733 | + for model_id in sorted({m for m, _, _ in touched}): |
| |
734 | + from aiatlas.services.merge import recompute_result_keys |
| |
735 | + |
| |
736 | + await recompute_result_keys(conn, model_id) |
| |
737 | + await enforce_current_results(conn, model_id=model_id) |
| |
738 | + for model_id, head_id, target_id in sorted(touched): |
| |
739 | + await upsert_relation(conn, model_id, "evaluated_on", target_id, source_id=source_id) |
| |
740 | + left = await fetch_one(conn, "select 1 from benchmark_results where model_id = :m and benchmark_id = :b and valid_to is null limit 1", m=model_id, b=head_id) |
| |
741 | + if not left: |
| |
742 | + await execute(conn, "update relations set valid_to = now() where subject_id = :m and predicate = 'evaluated_on' and object_id = :b and valid_to is null", |
| |
743 | + m=model_id, b=head_id) |
| |
744 | + rep.bump("evaluated_on_repointed") |
| |
745 | + |
| |
746 | + |
| 653 | async def step_results(conn: AsyncConnection, rep: StepReport, apply: bool, *, scope: set[str] | None = None) -> None: |
747 | async def step_results(conn: AsyncConnection, rep: StepReport, apply: bool, *, scope: set[str] | None = None) -> None: |
| |
748 | + await _rehome_results(conn, rep, apply, scope) |
| 654 | rows = await fetch_all(conn, """select r.id, r.model_id, r.config, r.metric, r.unit, r.score, r.config_key, r.trust_level, r.variant, r.run_group, r.extractor, r.valid_to, |
749 | rows = await fetch_all(conn, """select r.id, r.model_id, r.config, r.metric, r.unit, r.score, r.config_key, r.trust_level, r.variant, r.run_group, r.extractor, r.valid_to, |
| 655 | r.is_current, s.key as source_key from benchmark_results r left join sources s on s.id = r.source_id""") |
750 | r.is_current, s.key as source_key from benchmark_results r left join sources s on s.id = r.source_id""") |
| 656 | if scope is not None: |
751 | if scope is not None: |