Skip to content

classification

src.generators.committee_stats.classification

Committee classification & aggregation.

Builds the institution name index, classifies each committee member's affiliation to a country / continent / institution, and computes per-area aggregates, yearly time-series, recurring-member rankings and institution timelines.

classify_member(affiliation, prefix_tree, name_index)

Classify a single member's affiliation to a country.

Returns (country, institution_name) or (None, None) on failure.

Source code in src/generators/committee_stats/classification.py
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def classify_member(affiliation, prefix_tree, name_index):
    """Classify a single member's affiliation to a country.

    Returns (country, institution_name) or (None, None) on failure.
    """
    aff_lower = affiliation.lower().strip()
    if not aff_lower:
        return None, None

    # Try prefix-tree match first
    matches = prefix_tree.values(prefix=aff_lower)
    if matches:
        uni = matches[0]
        return uni["country"], uni.get("name", affiliation)

    # Fall back to fuzzy matching
    best_match = None
    best_ratio = 0
    aff_tokens = _distinctive_tokens(aff_lower)
    for name, uni in name_index.items():
        # Avoid fuzzy matches on low-signal aliases/tokens; require at least one
        # distinctive token overlap so "graz" cannot map to "arak".
        cand_tokens = _distinctive_tokens(name)
        if aff_tokens and cand_tokens and not (aff_tokens & cand_tokens):
            continue
        ratio = fuzz.ratio(name, aff_lower)
        if ratio > best_ratio:
            best_ratio = ratio
            best_match = uni

    if best_ratio > 80 and best_match:
        return best_match["country"], best_match.get("name", affiliation)

    return None, None

classify_committees(all_results: dict) -> dict

Classify all committee members by country, continent, and institution.

Parameters

all_results : dict {conf_year: [{name, affiliation}, ...]}

Returns

dict with keys: by_country, by_continent, by_institution, failed

Source code in src/generators/committee_stats/classification.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
def classify_committees(all_results: dict) -> dict:
    """Classify all committee members by country, continent, and institution.

    Parameters
    ----------
    all_results : dict
        {conf_year: [{name, affiliation}, ...]}

    Returns
    -------
    dict with keys: by_country, by_continent, by_institution, failed
    """
    name_index = _build_university_index()
    prefix_tree = Trie(**name_index)

    by_conf_country: dict = {}
    by_conf_continent: dict = {}
    by_conf_institution: dict = {}
    failed: list = []

    for conf_year, members in all_results.items():
        by_conf_country[conf_year] = defaultdict(int)
        by_conf_continent[conf_year] = defaultdict(int)
        by_conf_institution[conf_year] = defaultdict(int)

        for member in members:
            affiliation = _clean_affiliation(member["affiliation"])
            country, inst_name = classify_member(affiliation, prefix_tree, name_index)
            if country:
                by_conf_country[conf_year][country] += 1
                continent = _country_to_continent(country) or "Unknown"
                by_conf_continent[conf_year][continent] += 1
                by_conf_institution[conf_year][inst_name or member["affiliation"]] += 1
            else:
                failed.append(
                    {
                        "conference": conf_year,
                        "name": member["name"],
                        "affiliation": affiliation,
                    }
                )

    return {
        "by_country": by_conf_country,
        "by_continent": by_conf_continent,
        "by_institution": by_conf_institution,
        "failed": failed,
    }