decompose application.py into enrichment, scholar processing, run completion
This commit is contained in:
parent
9a7fa5004e
commit
6379c97e90
28 changed files with 2982 additions and 2576 deletions
314
app/api/schemas/admin.py
Normal file
314
app/api/schemas/admin.py
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from app.api.schemas.common import ApiMeta
|
||||
from app.api.schemas.publications import DisplayIdentifierData
|
||||
|
||||
|
||||
class AdminUserData(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
is_active: bool
|
||||
is_admin: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminUsersListData(BaseModel):
|
||||
users: list[AdminUserData]
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminUsersListEnvelope(BaseModel):
|
||||
data: AdminUsersListData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminUserCreateRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
is_admin: bool = False
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminUserActiveUpdateRequest(BaseModel):
|
||||
is_active: bool
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminUserEnvelope(BaseModel):
|
||||
data: AdminUserData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminResetPasswordRequest(BaseModel):
|
||||
new_password: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminScholarHttpSettingsData(BaseModel):
|
||||
user_agent: str
|
||||
rotate_user_agent: bool
|
||||
accept_language: str
|
||||
cookie: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminScholarHttpSettingsEnvelope(BaseModel):
|
||||
data: AdminScholarHttpSettingsData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminScholarHttpSettingsUpdateRequest(BaseModel):
|
||||
user_agent: str
|
||||
rotate_user_agent: bool
|
||||
accept_language: str
|
||||
cookie: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbIntegrityCheckData(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
severity: str
|
||||
message: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbIntegrityData(BaseModel):
|
||||
status: str
|
||||
checked_at: datetime
|
||||
failures: list[str] = Field(default_factory=list)
|
||||
warnings: list[str] = Field(default_factory=list)
|
||||
checks: list[AdminDbIntegrityCheckData] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbIntegrityEnvelope(BaseModel):
|
||||
data: AdminDbIntegrityData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbRepairJobData(BaseModel):
|
||||
id: int
|
||||
job_name: str
|
||||
requested_by: str | None
|
||||
dry_run: bool
|
||||
status: str
|
||||
scope: dict[str, Any] = Field(default_factory=dict)
|
||||
summary: dict[str, Any] = Field(default_factory=dict)
|
||||
error_text: str | None
|
||||
started_at: datetime | None
|
||||
finished_at: datetime | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbRepairJobsData(BaseModel):
|
||||
jobs: list[AdminDbRepairJobData] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminDbRepairJobsEnvelope(BaseModel):
|
||||
data: AdminDbRepairJobsData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueItemData(BaseModel):
|
||||
publication_id: int
|
||||
title: str
|
||||
display_identifier: DisplayIdentifierData | None = None
|
||||
pdf_url: str | None
|
||||
status: str
|
||||
attempt_count: int
|
||||
last_failure_reason: str | None
|
||||
last_failure_detail: str | None
|
||||
last_source: str | None
|
||||
requested_by_user_id: int | None
|
||||
requested_by_email: str | None
|
||||
queued_at: datetime | None
|
||||
last_attempt_at: datetime | None
|
||||
resolved_at: datetime | None
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueData(BaseModel):
|
||||
items: list[AdminPdfQueueItemData] = Field(default_factory=list)
|
||||
total_count: int = 0
|
||||
page: int = 1
|
||||
page_size: int = 100
|
||||
has_next: bool = False
|
||||
has_prev: bool = False
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueEnvelope(BaseModel):
|
||||
data: AdminPdfQueueData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueRequeueData(BaseModel):
|
||||
publication_id: int
|
||||
queued: bool
|
||||
status: str
|
||||
message: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueRequeueEnvelope(BaseModel):
|
||||
data: AdminPdfQueueRequeueData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueBulkEnqueueData(BaseModel):
|
||||
requested_count: int
|
||||
queued_count: int
|
||||
message: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminPdfQueueBulkEnqueueEnvelope(BaseModel):
|
||||
data: AdminPdfQueueBulkEnqueueData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminRepairPublicationLinksRequest(BaseModel):
|
||||
scope_mode: Literal["single_user", "all_users"] = "single_user"
|
||||
user_id: int | None = Field(default=None, ge=1)
|
||||
scholar_profile_ids: list[int] = Field(default_factory=list, max_length=200)
|
||||
dry_run: bool = True
|
||||
gc_orphan_publications: bool = False
|
||||
requested_by: str | None = None
|
||||
confirmation_text: str | None = None
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_scope(self) -> AdminRepairPublicationLinksRequest:
|
||||
if self.scope_mode == "single_user" and self.user_id is None:
|
||||
raise ValueError("user_id is required when scope_mode=single_user.")
|
||||
if self.scope_mode == "all_users" and self.user_id is not None:
|
||||
raise ValueError("user_id must be omitted when scope_mode=all_users.")
|
||||
if not self.dry_run and self.scope_mode == "all_users":
|
||||
expected = "REPAIR ALL USERS"
|
||||
provided = (self.confirmation_text or "").strip()
|
||||
if provided != expected:
|
||||
raise ValueError("confirmation_text must equal 'REPAIR ALL USERS' when applying a repair to all users.")
|
||||
return self
|
||||
|
||||
|
||||
class AdminRepairPublicationLinksResultData(BaseModel):
|
||||
job_id: int
|
||||
status: str
|
||||
scope: dict[str, Any] = Field(default_factory=dict)
|
||||
summary: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminRepairPublicationLinksEnvelope(BaseModel):
|
||||
data: AdminRepairPublicationLinksResultData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminNearDuplicateClusterMemberData(BaseModel):
|
||||
publication_id: int
|
||||
title: str
|
||||
year: int | None
|
||||
citation_count: int
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminNearDuplicateClusterData(BaseModel):
|
||||
cluster_key: str
|
||||
winner_publication_id: int
|
||||
member_count: int
|
||||
similarity_score: float = Field(ge=0.0, le=1.0)
|
||||
members: list[AdminNearDuplicateClusterMemberData] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminRepairPublicationNearDuplicatesRequest(BaseModel):
|
||||
dry_run: bool = True
|
||||
similarity_threshold: float = Field(default=0.78, ge=0.5, le=1.0)
|
||||
min_shared_tokens: int = Field(default=3, ge=1, le=8)
|
||||
max_year_delta: int = Field(default=1, ge=0, le=5)
|
||||
max_clusters: int = Field(default=25, ge=1, le=200)
|
||||
selected_cluster_keys: list[str] = Field(default_factory=list, max_length=200)
|
||||
requested_by: str | None = None
|
||||
confirmation_text: str | None = None
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_apply_mode(self) -> AdminRepairPublicationNearDuplicatesRequest:
|
||||
if self.dry_run:
|
||||
return self
|
||||
if not self.selected_cluster_keys:
|
||||
raise ValueError("selected_cluster_keys is required when dry_run=false.")
|
||||
expected = "MERGE SELECTED DUPLICATES"
|
||||
provided = (self.confirmation_text or "").strip()
|
||||
if provided != expected:
|
||||
raise ValueError(
|
||||
"confirmation_text must equal 'MERGE SELECTED DUPLICATES' when applying near-duplicate merges."
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class AdminRepairPublicationNearDuplicatesResultData(BaseModel):
|
||||
job_id: int
|
||||
status: str
|
||||
scope: dict[str, Any] = Field(default_factory=dict)
|
||||
summary: dict[str, Any] = Field(default_factory=dict)
|
||||
clusters: list[AdminNearDuplicateClusterData] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
||||
class AdminRepairPublicationNearDuplicatesEnvelope(BaseModel):
|
||||
data: AdminRepairPublicationNearDuplicatesResultData
|
||||
meta: ApiMeta
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
Loading…
Add table
Add a link
Reference in a new issue