Skip to content

Commit 1927b3c

Browse files
committed
feat: support affiliation stints and improve extraction coverage
Signed-off-by: Yeganathan S <63534555+skwowet@users.noreply.github.com>
1 parent 987ddbb commit 1927b3c

3 files changed

Lines changed: 357 additions & 314 deletions

File tree

services/apps/git_integration/src/crowdgit/database/crud.py

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -726,60 +726,98 @@ async def insert_member_organizations(rows: list[dict]) -> None:
726726
if not rows:
727727
return
728728

729-
sql_query = """
729+
undated_rows: list[tuple] = []
730+
open_ended_rows: list[tuple] = []
731+
dated_rows: list[tuple] = []
732+
733+
for row in rows:
734+
params = (
735+
row["member_id"],
736+
row["organization_id"],
737+
row.get("date_start"),
738+
row.get("date_end"),
739+
row["source"],
740+
)
741+
date_start = row.get("date_start")
742+
date_end = row.get("date_end")
743+
if date_start is None and date_end is None:
744+
undated_rows.append(params)
745+
elif date_end is None:
746+
open_ended_rows.append(params)
747+
else:
748+
dated_rows.append(params)
749+
750+
insert_sql = """
730751
INSERT INTO "memberOrganizations"(
731752
"memberId",
732753
"organizationId",
733754
"dateStart",
734755
"dateEnd",
735-
"title",
756+
title,
736757
source,
737-
verified,
738758
"createdAt",
739759
"updatedAt"
740760
)
741-
VALUES ($1, $2, NULL, NULL, NULL, $3, false, NOW(), NOW())
742-
ON CONFLICT ("memberId", "organizationId")
743-
WHERE ("dateStart" IS NULL AND "dateEnd" IS NULL)
744-
DO NOTHING
761+
VALUES ($1, $2, $3, $4, NULL, $5, NOW(), NOW())
745762
"""
746-
await executemany(
747-
sql_query,
748-
[
749-
(
750-
row["member_id"],
751-
row["organization_id"],
752-
row.get("source", "project-registry"),
753-
)
754-
for row in rows
755-
],
756-
)
763+
764+
if undated_rows:
765+
sql = (
766+
insert_sql
767+
+ """
768+
ON CONFLICT ("memberId", "organizationId")
769+
WHERE ("dateStart" IS NULL AND "dateEnd" IS NULL AND "deletedAt" IS NULL)
770+
DO NOTHING
771+
"""
772+
)
773+
await executemany(sql, undated_rows)
774+
775+
if open_ended_rows:
776+
sql = (
777+
insert_sql
778+
+ """
779+
ON CONFLICT ("memberId", "organizationId", "dateStart")
780+
WHERE ("dateEnd" IS NULL AND "deletedAt" IS NULL)
781+
DO NOTHING
782+
"""
783+
)
784+
await executemany(sql, open_ended_rows)
785+
786+
if dated_rows:
787+
sql = (
788+
insert_sql
789+
+ """
790+
ON CONFLICT ("memberId", "organizationId", "dateStart", "dateEnd")
791+
WHERE ("deletedAt" IS NULL)
792+
DO NOTHING
793+
"""
794+
)
795+
await executemany(sql, dated_rows)
757796

758797

759798
async def insert_member_segment_affiliations(rows: list[dict]) -> None:
760799
if not rows:
761800
return
762801

763-
sql_query = """
802+
await executemany(
803+
"""
764804
INSERT INTO "memberSegmentAffiliations"(
765805
id,
766806
"memberId",
767807
"segmentId",
768808
"organizationId",
769809
"dateStart",
770-
"dateEnd",
771-
verified
810+
"dateEnd"
772811
)
773-
VALUES (gen_random_uuid(), $1, $2, $3, NULL, NULL, $4)
774-
"""
775-
await executemany(
776-
sql_query,
812+
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5)
813+
""",
777814
[
778815
(
779816
row["member_id"],
780817
row["segment_id"],
781818
row["organization_id"],
782-
row.get("verified", False),
819+
row.get("date_start"),
820+
row.get("date_end"),
783821
)
784822
for row in rows
785823
],

services/apps/git_integration/src/crowdgit/models/affiliation_info.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from __future__ import annotations
22

33
import uuid
4-
from datetime import datetime
4+
from datetime import date, datetime
55
from typing import Any
66

77
import orjson
88
from loguru import logger
9-
from pydantic import BaseModel, TypeAdapter, ValidationError
9+
from pydantic import BaseModel, Field, TypeAdapter, ValidationError
1010

1111

1212
class AffiliationContributor(BaseModel):
@@ -15,14 +15,36 @@ class AffiliationContributor(BaseModel):
1515
github: str | None = None
1616

1717

18-
class AffiliationOrganization(BaseModel):
18+
class AffiliationOrganizationFields(BaseModel):
19+
"""Organization fields as returned by the parse AI (flat rows)."""
20+
1921
name: str | None = None
2022
domain: str | None = None
23+
date_start: date | None = Field(default=None, alias="dateStart")
24+
date_end: date | None = Field(default=None, alias="dateEnd")
25+
is_unaffiliated: bool = Field(default=False, alias="isUnaffiliated")
26+
27+
model_config = {"populate_by_name": True}
28+
29+
30+
class AffiliationParseRow(BaseModel):
31+
contributor: AffiliationContributor
32+
organization: AffiliationOrganizationFields
33+
34+
35+
class AffiliationOrganizationStint(BaseModel):
36+
name: str | None = None
37+
domain: str
38+
date_start: date | None = Field(default=None, alias="dateStart")
39+
date_end: date | None = Field(default=None, alias="dateEnd")
40+
is_unaffiliated: bool = Field(default=False, alias="isUnaffiliated")
41+
42+
model_config = {"populate_by_name": True}
2143

2244

23-
class AffiliationInfoItem(BaseModel):
45+
class AffiliationContributorEntry(BaseModel):
2446
contributor: AffiliationContributor
25-
organization: AffiliationOrganization
47+
organizations: list[AffiliationOrganizationStint]
2648

2749

2850
class AffiliationFile(BaseModel):
@@ -31,19 +53,19 @@ class AffiliationFile(BaseModel):
3153

3254

3355
class AffiliationParseOutput(BaseModel):
34-
affiliations: list[AffiliationInfoItem] | None = None
56+
affiliations: list[AffiliationParseRow] | None = None
3557
error: str | None = None
3658

3759

38-
_SNAPSHOT_ADAPTER = TypeAdapter(list[AffiliationInfoItem])
60+
_SNAPSHOT_ADAPTER = TypeAdapter(list[AffiliationContributorEntry])
3961

4062

4163
class RepoAffiliationRegistry(BaseModel):
4264
repo_id: str
4365
file_path: str | None = None
4466
file_hash: str | None = None
4567
status: str
46-
snapshot: list[AffiliationInfoItem] | None = None
68+
snapshot: list[AffiliationContributorEntry] | None = None
4769
last_run_at: datetime | None = None
4870

4971
@classmethod
@@ -71,7 +93,7 @@ def from_db(cls, db_data: dict[str, Any]) -> RepoAffiliationRegistry:
7193
return cls(**row)
7294

7395
@staticmethod
74-
def _parse_snapshot(snapshot) -> list[AffiliationInfoItem] | None:
96+
def _parse_snapshot(snapshot) -> list[AffiliationContributorEntry] | None:
7597
if isinstance(snapshot, str | bytes):
7698
try:
7799
snapshot = orjson.loads(snapshot)
@@ -91,4 +113,6 @@ def _parse_snapshot(snapshot) -> list[AffiliationInfoItem] | None:
91113
def snapshot_for_db(self) -> str | None:
92114
if self.snapshot is None:
93115
return None
94-
return orjson.dumps([item.model_dump() for item in self.snapshot]).decode()
116+
return orjson.dumps(
117+
[item.model_dump(mode="json", by_alias=True) for item in self.snapshot]
118+
).decode()

0 commit comments

Comments
 (0)