diff --git a/.claude/settings.json b/.claude/settings.json index a332c01..ec4aab3 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -19,7 +19,11 @@ "Bash(git add app/api/routers/runs.py app/services/arxiv/rate_limit.py tests/integration/test_run_lifecycle_consistency.py docs/website/package-lock.json)", "Bash(git push)", "Bash(uvx ruff format --check app/api/routers/runs.py app/services/arxiv/rate_limit.py tests/integration/test_run_lifecycle_consistency.py)", - "Bash(test -f docs/website/package-lock.json)" + "Bash(test -f docs/website/package-lock.json)", + "Bash(gh run list --branch openalex-size-reduction-scholar-hydration --limit 3 --json databaseId,status,conclusion,name,event)", + "Bash(gh run list --limit 10 --json databaseId,status,conclusion,name,event,headBranch)", + "Bash(gh run view 22554585841 --log-failed)", + "Bash(gh run rerun 22554585841 --failed)" ], "additionalDirectories": [ "/home/jv/src/personal/playground/scholar_scraper/scholar-scraper/frontend/src" diff --git a/app/api/schemas/scholars.py b/app/api/schemas/scholars.py index dda8329..0e19088 100644 --- a/app/api/schemas/scholars.py +++ b/app/api/schemas/scholars.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import datetime -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator from app.api.schemas.common import ApiMeta @@ -128,11 +128,20 @@ class DataExportEnvelope(BaseModel): class DataImportRequest(BaseModel): schema_version: int | None = None + exported_at: str | None = None scholars: list[ScholarExportItemData] = Field(default_factory=list) publications: list[PublicationExportItemData] = Field(default_factory=list) model_config = ConfigDict(extra="forbid") + @model_validator(mode="before") + @classmethod + def unwrap_export_envelope(cls, values: dict) -> dict: # type: ignore[override] + """Accept the full export envelope format (with data/meta wrapper).""" + if isinstance(values, dict) and "data" in values and isinstance(values["data"], dict): + return values["data"] + return values + class DataImportResultData(BaseModel): scholars_created: int diff --git a/frontend/src/pages/ScholarsPage.vue b/frontend/src/pages/ScholarsPage.vue index df2143e..16b2a96 100644 --- a/frontend/src/pages/ScholarsPage.vue +++ b/frontend/src/pages/ScholarsPage.vue @@ -394,11 +394,16 @@ async function onImportFileSelected(event: Event): Promise { clearMessages(); try { const raw = await file.text(); - const parsed = JSON.parse(raw) as DataImportPayload; - if (!parsed || !Array.isArray(parsed.scholars) || !Array.isArray(parsed.publications)) { + let parsed = JSON.parse(raw); + // Accept the full export envelope (with data/meta wrapper) + if (parsed?.data && Array.isArray(parsed.data.scholars)) { + parsed = parsed.data; + } + const payload = parsed as DataImportPayload; + if (!payload || !Array.isArray(payload.scholars) || !Array.isArray(payload.publications)) { throw new Error("Invalid import file: expected scholars[] and publications[] arrays."); } - const result = await importScholarData(parsed); + const result = await importScholarData(payload); successMessage.value = importSummary(result); await loadScholars(); } catch (error) {