Skip to content

Commit 437c2fe

Browse files
committed
fix: tighten affiliation prompts for precision over recall
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
1 parent 327092a commit 437c2fe

1 file changed

Lines changed: 41 additions & 30 deletions

File tree

services/apps/git_integration/src/crowdgit/services/affiliation/affiliation_service.py

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -142,43 +142,50 @@ def get_file_picker_prompt(
142142
records contributor-to-employer/organization mappings.
143143
"""
144144
return f"""
145-
Your task is to identify the file that records which organization or employer
146-
contributors represent when contributing to this repository.
145+
Identify the repository file that matches the criteria below.
147146
148147
<repository_url>
149148
{repo_url}
150149
</repository_url>
151150
152151
<what_to_find>
153-
The target file records contributor-to-employer/organization mappings.
154-
155-
Contributors may be identified by name, email address, or GitHub username.
156-
Organizations may be identified by their name, domain,
157-
or contact email address.
158-
159-
There is no standard filename or file format. The file may be plain text,
160-
CSV, YAML, JSON, Markdown, or another text-based format.
161-
162-
Judge candidates primarily by their contents. Filenames are only hints.
152+
Find the file that explicitly records contributor affiliations: which
153+
organization or employer a contributor belongs to.
154+
155+
The mapping must be stated by the file, for example:
156+
- an organization, company, employer, or affiliation field on each contributor
157+
- contributors grouped under the organization they belong to
158+
- explicit domain/email-pattern rules that the file defines for assigning
159+
contributors to organizations
163160
</what_to_find>
164161
165162
<what_to_reject>
166-
Reject candidates whose preview shows:
167-
- Source code or scripts (for example, shebangs, imports, or function/class definitions)
168-
- Generic contributor or author credits
169-
- Governance files that lack organization or employer information
163+
Reject candidates whose preview does not explicitly associate contributors
164+
with organizations, including:
165+
- Lists of names, emails, or usernames with no stated organization
166+
(e.g. AUTHORS, CONTRIBUTORS, CREDITS).
167+
- Identity or alias mappings such as .mailmap.
168+
- Governance or ownership files that name people but not their employer
169+
(e.g. OWNERS, CODEOWNERS, MAINTAINERS without organization information).
170+
- Source code, scripts, or configuration files.
171+
172+
Email addresses and email domains alone do not make a file a match, unless
173+
the file explicitly defines those domains or patterns as affiliation rules.
170174
</what_to_reject>
171175
172176
<candidates>
173-
Each candidate includes its repository-relative path and a preview from the
174-
beginning of the file. The preview is only a partial view of the file.
177+
Each candidate includes its repository-relative path and a preview from
178+
the beginning of the file. The preview is only a partial view of the file.
175179
180+
Base your decision only on the provided preview.
176181
{candidates_with_previews}
177182
</candidates>
178183
179184
<rules>
185+
- Judge candidates by their content, not their filename.
180186
- Return the repository-relative path exactly as shown in the candidates.
181187
- If no candidate matches, return {{"error": "not_found"}}.
188+
- Prefer precision over recall. The wrong file is worse than no file.
182189
</rules>
183190
184191
<output_format>
@@ -267,11 +274,10 @@ def get_extraction_prompt(self, content_to_analyze: str) -> str:
267274
"""
268275

269276
return f"""
270-
Your task is to extract contributor-to-employer/organization mappings from the file content below.
271-
272277
<what_to_extract>
273278
274-
Identify contributor-to-organization mappings in the file content.
279+
Extract, per person, the organization or employer the file explicitly
280+
assigns to each contributor.
275281
276282
Emit one entry per contributor-organization pair.
277283
@@ -282,12 +288,19 @@ def get_extraction_prompt(self, content_to_analyze: str) -> str:
282288
- Reproduce identifiers as written. Do not normalize, reformat, or repair them.
283289
284290
Organization:
285-
- Provide the organization name when the file gives one.
286-
- Provide the organization's primary domain: use a domain present in the
287-
file, otherwise infer it from the organization name when you are confident.
288-
- If the file marks a contributor as not employed / independent / unaffiliated
289-
/ personal / no organization, set "isUnaffiliated" to true and set
290-
"domain" to "unknown". Do not invent a company or domain for these.
291+
- Only record an organization the file explicitly ties to the contributor.
292+
Do not infer one from a plain email, email domain, username, or repo/project name.
293+
It is valid to use an email/domain pattern only when the file itself explicitly
294+
defines that pattern as an affiliation rule.
295+
- name: the organization name the file states, else null.
296+
- domain: use a domain the file states; otherwise infer it from the stated
297+
organization name only when confident (e.g. "Google" -> google.com), else null.
298+
Never infer a domain from an email.
299+
- isUnaffiliated: set true only when the file explicitly marks the person as
300+
independent / unaffiliated / personal / no employer — not as a fallback when
301+
the organization is merely missing. When true, set name and domain to null.
302+
- If the file states neither an organization nor explicit unaffiliation for a
303+
person, do not emit a row for them.
291304
292305
Time period (only when the file states it):
293306
- "dateStart" and "dateEnd" as ISO dates (YYYY-MM-DD).
@@ -321,7 +334,7 @@ def get_extraction_prompt(self, content_to_analyze: str) -> str:
321334
}},
322335
"organization": {{
323336
"name": "... or null",
324-
"domain": "...",
337+
"domain": "... or null",
325338
"dateStart": "YYYY-MM-DD or null",
326339
"dateEnd": "YYYY-MM-DD or null",
327340
"isUnaffiliated": false
@@ -384,8 +397,6 @@ def group_parse_rows(
384397
organization = row.organization
385398
is_unaffiliated = organization.is_unaffiliated
386399
domain = cls._strip(organization.domain)
387-
if domain and domain.lower() in {"unknown", "no@organization.net"}:
388-
is_unaffiliated = True
389400

390401
if is_unaffiliated:
391402
stint = AffiliationOrganizationStint(

0 commit comments

Comments
 (0)