Skip to content

Commit bb01419

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

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
@@ -754,60 +754,98 @@ async def insert_member_organizations(rows: list[dict]) -> None:
754754
if not rows:
755755
return
756756

757-
sql_query = """
757+
undated_rows: list[tuple] = []
758+
open_ended_rows: list[tuple] = []
759+
dated_rows: list[tuple] = []
760+
761+
for row in rows:
762+
params = (
763+
row["member_id"],
764+
row["organization_id"],
765+
row.get("date_start"),
766+
row.get("date_end"),
767+
row["source"],
768+
)
769+
date_start = row.get("date_start")
770+
date_end = row.get("date_end")
771+
if date_start is None and date_end is None:
772+
undated_rows.append(params)
773+
elif date_end is None:
774+
open_ended_rows.append(params)
775+
else:
776+
dated_rows.append(params)
777+
778+
insert_sql = """
758779
INSERT INTO "memberOrganizations"(
759780
"memberId",
760781
"organizationId",
761782
"dateStart",
762783
"dateEnd",
763-
"title",
784+
title,
764785
source,
765-
verified,
766786
"createdAt",
767787
"updatedAt"
768788
)
769-
VALUES ($1, $2, NULL, NULL, NULL, $3, false, NOW(), NOW())
770-
ON CONFLICT ("memberId", "organizationId")
771-
WHERE ("dateStart" IS NULL AND "dateEnd" IS NULL)
772-
DO NOTHING
789+
VALUES ($1, $2, $3, $4, NULL, $5, NOW(), NOW())
773790
"""
774-
await executemany(
775-
sql_query,
776-
[
777-
(
778-
row["member_id"],
779-
row["organization_id"],
780-
row.get("source", "project-registry"),
781-
)
782-
for row in rows
783-
],
784-
)
791+
792+
if undated_rows:
793+
sql = (
794+
insert_sql
795+
+ """
796+
ON CONFLICT ("memberId", "organizationId")
797+
WHERE ("dateStart" IS NULL AND "dateEnd" IS NULL AND "deletedAt" IS NULL)
798+
DO NOTHING
799+
"""
800+
)
801+
await executemany(sql, undated_rows)
802+
803+
if open_ended_rows:
804+
sql = (
805+
insert_sql
806+
+ """
807+
ON CONFLICT ("memberId", "organizationId", "dateStart")
808+
WHERE ("dateEnd" IS NULL AND "deletedAt" IS NULL)
809+
DO NOTHING
810+
"""
811+
)
812+
await executemany(sql, open_ended_rows)
813+
814+
if dated_rows:
815+
sql = (
816+
insert_sql
817+
+ """
818+
ON CONFLICT ("memberId", "organizationId", "dateStart", "dateEnd")
819+
WHERE ("deletedAt" IS NULL)
820+
DO NOTHING
821+
"""
822+
)
823+
await executemany(sql, dated_rows)
785824

786825

787826
async def insert_member_segment_affiliations(rows: list[dict]) -> None:
788827
if not rows:
789828
return
790829

791-
sql_query = """
830+
await executemany(
831+
"""
792832
INSERT INTO "memberSegmentAffiliations"(
793833
id,
794834
"memberId",
795835
"segmentId",
796836
"organizationId",
797837
"dateStart",
798-
"dateEnd",
799-
verified
838+
"dateEnd"
800839
)
801-
VALUES (gen_random_uuid(), $1, $2, $3, NULL, NULL, $4)
802-
"""
803-
await executemany(
804-
sql_query,
840+
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5)
841+
""",
805842
[
806843
(
807844
row["member_id"],
808845
row["segment_id"],
809846
row["organization_id"],
810-
row.get("verified", False),
847+
row.get("date_start"),
848+
row.get("date_end"),
811849
)
812850
for row in rows
813851
],

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)