feat: score ingest facet suggestions with Jev
CI / quality (push) Canceled after 0s

This commit is contained in:
vasily committed 2026-10-14 20:43:09 +08:00
1 parent 7b2c43ddaf
commit 0e1a2ad008
8 files changed
+191 -27

No files matched your search

+31
View File
@@ -25,6 +25,7 @@ ResearchLens = Literal[
"cultural-historical",
"financial-market",
]
FacetName = Literal["tags", "audience", "use_for", "domain"]
DEFAULT_RESEARCH_LENSES: tuple[ResearchLens, ...] = (
"technical",
"conceptual",
@@ -115,6 +116,12 @@ class ProjectContextResponse(StrictRecord):
lens: ProjectLens
class JevFacetScore(StrictRecord):
facet: FacetName
value: SearchFacetValue
probability: FiniteFloat = Field(ge=0, le=1)
class IngestReviewItem(StrictRecord):
review_id: RecordId
project_id: RecordId
@@ -127,6 +134,7 @@ class IngestReviewItem(StrictRecord):
summary: str = Field(min_length=1, max_length=2000)
relevance_rationale: str = Field(min_length=1, max_length=2000)
relevance: Literal["borderline", "low"] = "borderline"
jev_facet_scores: tuple[JevFacetScore, ...] = Field(default=(), max_length=20)
jev_relation_probability: FiniteFloat | None = Field(default=None, ge=0, le=1)
jev_lens: ResearchLens | None = None
jev_lens_confidence: FiniteFloat | None = Field(default=None, ge=0, le=1)
@@ -156,6 +164,29 @@ class IngestReviewCreate(StrictRecord):
summary: str = Field(min_length=1, max_length=2000)
relevance_rationale: str = Field(min_length=1, max_length=2000)
relevance: Literal["borderline", "low"] = "borderline"
candidate_tags: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
candidate_audience: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
candidate_use_for: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
candidate_domain: tuple[SearchFacetValue, ...] = Field(default=(), max_length=5)
@model_validator(mode="after")
def limit_total_facet_candidates(self) -> IngestReviewCreate:
values = (
*self.candidate_tags,
*self.candidate_audience,
*self.candidate_use_for,
*self.candidate_domain,
)
if len(values) > 20:
raise ValueError("At most 20 facet candidates may be assessed")
if len({(facet, value.casefold()) for facet, candidates in (
("tags", self.candidate_tags),
("audience", self.candidate_audience),
("use_for", self.candidate_use_for),
("domain", self.candidate_domain),
) for value in candidates}) != len(values):
raise ValueError("Facet candidates must be unique within each facet")
return self
class IngestReviewDecision(StrictRecord):