class AEMember(BaseModel):
"""An Artifact Evaluation committee member with service history and institutional affiliation."""
name: str = Field(
description="Full name, e.g. 'Mathias Payer'. May include DBLP disambiguation suffix.",
examples=["Mathias Payer"],
)
display_name: str = Field(
description="Human-readable name without disambiguation suffix, e.g. 'Mathias Payer'.",
examples=["Mathias Payer"],
)
affiliation: str = Field(
description="Current institutional affiliation, e.g. 'EPFL', 'MIT'.", examples=["ETH Zurich"]
)
country: str | None = Field(
default=None,
description="Country of the institution, e.g. 'Switzerland', 'United States'. Null if unresolved.",
examples=["Switzerland"],
)
continent: str | None = Field(
default=None,
description="Continent of the institution, e.g. 'Europe', 'North America'. Null if unresolved.",
examples=["Europe"],
)
total_memberships: int = Field(
ge=0,
description="Total number of AE committee memberships across all conferences and years.",
examples=[4],
)
chair_count: int = Field(
ge=0, description="Number of conference-years served as AE chair (versus regular member).", examples=[1]
)
conferences: list[AEMembership] = Field(
description="List of AE committee memberships, one entry per conference-year served. Each has conference, year, role.",
examples=[["OSDI", "ATC", "USENIXSEC"]],
)
years: dict[str, int] = Field(
description="Year (as string key, e.g. '2023') → number of AE memberships that year. Example: {'2023': 5, '2024': 3}.",
examples=[[2021, 2022, 2023]],
)
area: str = Field(
description="Research area based on conferences served: 'systems', 'security', or 'both' if spanning both.",
examples=["systems"],
)
first_year: int | None = Field(
default=None, description="Earliest year of AE service, e.g. 2020. Null if unknown.", examples=[2019]
)
last_year: int | None = Field(
default=None,
description="Most recent year of AE service, e.g. 2026. Null if unknown.",
examples=[2025],
)
@field_validator("conferences", mode="before")
@classmethod
def _coerce_conferences(cls, v: object) -> object:
"""Accept both [conf, year, role] lists and {conference, year, role} dicts."""
if isinstance(v, list):
return [
{"conference": item[0], "year": item[1], "role": item[2]} if isinstance(item, (list, tuple)) else item
for item in v
]
return v
model_config = {"extra": "forbid"}