This commit is contained in:
1 parent
1c61c7ad04
commit
7b2c43ddaf
18 files changed
+1648
-2
No files matched your search
@@ -19,6 +19,18 @@ from pydantic import (
|
||||
)
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
ResearchLens = Literal[
|
||||
"technical",
|
||||
"conceptual",
|
||||
"cultural-historical",
|
||||
"financial-market",
|
||||
]
|
||||
DEFAULT_RESEARCH_LENSES: tuple[ResearchLens, ...] = (
|
||||
"technical",
|
||||
"conceptual",
|
||||
"cultural-historical",
|
||||
"financial-market",
|
||||
)
|
||||
|
||||
RecordId = Annotated[
|
||||
str,
|
||||
@@ -58,6 +70,7 @@ class ProjectRecord(StrictRecord):
|
||||
owners: tuple[RecordId, ...] = ()
|
||||
north_star: str = Field(min_length=1, max_length=4000)
|
||||
key_questions: tuple[str, ...] = Field(min_length=1, max_length=30)
|
||||
research_lenses: tuple[ResearchLens, ...] = DEFAULT_RESEARCH_LENSES
|
||||
watch_list: tuple[str, ...] = Field(default=(), max_length=100)
|
||||
default_includes: tuple[RecordId, ...] = ("shared",)
|
||||
taxonomy_extensions: dict[str, tuple[str, ...]] = Field(default_factory=dict, max_length=100)
|
||||
@@ -71,20 +84,97 @@ class ProjectRecord(StrictRecord):
|
||||
return value
|
||||
|
||||
|
||||
class IngestGuidance(StrictRecord):
|
||||
guidance_id: RecordId
|
||||
lens: ResearchLens
|
||||
rule: str = Field(min_length=1, max_length=1000)
|
||||
created_at: datetime
|
||||
|
||||
@field_validator("created_at")
|
||||
@classmethod
|
||||
def guidance_timestamp_is_utc(cls, value: datetime) -> datetime:
|
||||
offset = value.utcoffset()
|
||||
if offset is None or offset.total_seconds() != 0:
|
||||
raise ValueError("guidance timestamp must be timezone-aware UTC")
|
||||
return value
|
||||
|
||||
|
||||
class ProjectLens(StrictRecord):
|
||||
project_id: RecordId
|
||||
title: str = Field(min_length=1, max_length=200)
|
||||
status: ProjectStatus
|
||||
north_star: str = Field(min_length=1, max_length=4000)
|
||||
key_questions: tuple[str, ...] = Field(min_length=1, max_length=30)
|
||||
research_lenses: tuple[ResearchLens, ...] = DEFAULT_RESEARCH_LENSES
|
||||
watch_list: tuple[str, ...] = Field(default=(), max_length=100)
|
||||
taxonomy_extensions: dict[str, tuple[str, ...]] = Field(default_factory=dict, max_length=100)
|
||||
ingest_guidance: tuple[IngestGuidance, ...] = Field(default=(), max_length=100)
|
||||
|
||||
|
||||
class ProjectContextResponse(StrictRecord):
|
||||
lens: ProjectLens
|
||||
|
||||
|
||||
class IngestReviewItem(StrictRecord):
|
||||
review_id: RecordId
|
||||
project_id: RecordId
|
||||
run_id: RecordId
|
||||
source_revision_id: RecordId
|
||||
extraction_id: Sha256
|
||||
locator: Locator
|
||||
excerpt: str = Field(min_length=1, max_length=4000)
|
||||
lens: ResearchLens
|
||||
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_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)
|
||||
jev_model: str | None = Field(default=None, max_length=100)
|
||||
status: Literal["needs-user-review", "included", "excluded", "deferred"] = "needs-user-review"
|
||||
submitted_by: RecordId
|
||||
created_at: datetime
|
||||
|
||||
@field_validator("created_at")
|
||||
@classmethod
|
||||
def review_timestamp_is_utc(cls, value: datetime) -> datetime:
|
||||
offset = value.utcoffset()
|
||||
if offset is None or offset.total_seconds() != 0:
|
||||
raise ValueError("review timestamp must be timezone-aware UTC")
|
||||
return value
|
||||
|
||||
|
||||
class IngestReviewCreate(StrictRecord):
|
||||
review_id: RecordId
|
||||
project_id: RecordId
|
||||
run_id: RecordId
|
||||
source_revision_id: RecordId
|
||||
extraction_id: Sha256
|
||||
locator: Locator
|
||||
excerpt: str = Field(min_length=1, max_length=4000)
|
||||
lens: ResearchLens
|
||||
summary: str = Field(min_length=1, max_length=2000)
|
||||
relevance_rationale: str = Field(min_length=1, max_length=2000)
|
||||
relevance: Literal["borderline", "low"] = "borderline"
|
||||
|
||||
|
||||
class IngestReviewDecision(StrictRecord):
|
||||
decision_id: RecordId
|
||||
review_id: RecordId
|
||||
decision: Literal["include", "exclude", "defer"]
|
||||
rationale: str = Field(min_length=1, max_length=2000)
|
||||
save_as_guidance: bool = False
|
||||
guidance_rule: str | None = Field(default=None, max_length=1000)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def guidance_requires_explicit_rule(self) -> IngestReviewDecision:
|
||||
if self.save_as_guidance and not (self.guidance_rule or "").strip():
|
||||
raise ValueError("saving project guidance requires a non-empty guidance rule")
|
||||
if not self.save_as_guidance and self.guidance_rule is not None:
|
||||
raise ValueError("guidance_rule requires explicit save_as_guidance")
|
||||
return self
|
||||
|
||||
|
||||
class ReliabilityGrade(StrEnum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
|
||||
Reference in new issue
Block a user