| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- """论文领域模型 —— Paper 数据类及其序列化/反序列化."""
- from dataclasses import dataclass, field
- from typing import Any
- from datetime import datetime
- from .author import Author
- @dataclass
- class Paper:
- title: str
- authors: list[Author] = field(default_factory=list)
- abstract: str | None = None
- doi: str | None = None
- pmid: str | None = None
- arxiv_id: str | None = None
- pmc_id: str | None = None
- journal: str | None = None
- venue_type: str | None = None
- year: int | None = None
- volume: str | None = None
- issue: str | None = None
- pages: str | None = None
- publisher: str | None = None
- pdf_url: str | None = None
- source_url: str | None = None
- local_pdf_path: str | None = None
- keywords: list[str] = field(default_factory=list)
- mesh_terms: list[str] = field(default_factory=list)
- references: list[str] = field(default_factory=list)
- citations: int = 0
- source: str = "unknown"
- relevance_score: int = 0
- notes: str | None = None
- tags: list[str] = field(default_factory=list)
- category: str | None = None
- rating: int | None = None
- read_status: str = "unread"
- importance: str = "normal"
- id: int | None = None
- created_at: datetime = field(default_factory=datetime.now)
- updated_at: datetime = field(default_factory=datetime.now)
- def to_dict(self) -> dict[str, Any]:
- return {
- "id": self.id,
- "title": self.title,
- "authors": [a.to_dict() if hasattr(a, "to_dict") else {"name": str(a)} for a in self.authors],
- "abstract": self.abstract,
- "doi": self.doi,
- "pmid": self.pmid,
- "arxiv_id": self.arxiv_id,
- "pmc_id": self.pmc_id,
- "journal": self.journal,
- "venue_type": self.venue_type,
- "year": self.year,
- "volume": self.volume,
- "issue": self.issue,
- "pages": self.pages,
- "publisher": self.publisher,
- "pdf_url": self.pdf_url,
- "source_url": self.source_url,
- "local_pdf_path": self.local_pdf_path,
- "keywords": self.keywords,
- "mesh_terms": self.mesh_terms,
- "references": self.references,
- "citations": self.citations,
- "source": self.source,
- "relevance_score": self.relevance_score,
- "notes": self.notes,
- "tags": self.tags,
- "category": self.category,
- "rating": self.rating,
- "read_status": self.read_status,
- "importance": self.importance,
- "created_at": self.created_at.isoformat() if self.created_at else None,
- "updated_at": self.updated_at.isoformat() if self.updated_at else None,
- }
- @classmethod
- def from_dict(cls, data: dict[str, Any]) -> "Paper":
- def _parse_dt(val: Any) -> datetime | None:
- if val is None:
- return None
- if isinstance(val, datetime):
- return val
- if isinstance(val, str):
- s = val.strip()
- if not s:
- return None
- try:
- if s.endswith("Z"):
- s = s[:-1] + "+00:00"
- return datetime.fromisoformat(s)
- except ValueError:
- return None
- return None
- def _safe_int(val: Any, default: int = 0) -> int:
- if val is None:
- return default
- if isinstance(val, bool):
- return int(val)
- if isinstance(val, int):
- return val
- try:
- return int(float(val))
- except (TypeError, ValueError):
- return default
- def _safe_year(val: Any) -> int | None:
- if val is None or val == "":
- return None
- y = _safe_int(val, -1)
- return y if 1000 <= y <= 3000 else None
- def _str_list(val: Any) -> list[str]:
- if not isinstance(val, list):
- return []
- out: list[str] = []
- for x in val:
- if x is None:
- continue
- if isinstance(x, str):
- t = x.strip()
- if t:
- out.append(t)
- else:
- out.append(str(x).strip())
- return out
- def _safe_id(val: Any) -> int | None:
- if val is None or val == "":
- return None
- i = _safe_int(val, -1)
- return i if i >= 0 else None
- created_at = _parse_dt(data.get("created_at"))
- updated_at = _parse_dt(data.get("updated_at"))
- return cls(
- title=data.get("title", ""),
- authors=[Author.from_dict(a) if isinstance(a, dict) else Author(name=str(a)) for a in data.get("authors", [])],
- abstract=data.get("abstract"),
- doi=data.get("doi"),
- pmid=data.get("pmid"),
- arxiv_id=data.get("arxiv_id"),
- pmc_id=data.get("pmc_id"),
- journal=data.get("journal"),
- venue_type=data.get("venue_type"),
- year=_safe_year(data.get("year")),
- volume=data.get("volume"),
- issue=data.get("issue"),
- pages=data.get("pages"),
- publisher=data.get("publisher"),
- pdf_url=data.get("pdf_url"),
- source_url=data.get("source_url"),
- local_pdf_path=data.get("local_pdf_path"),
- keywords=_str_list(data.get("keywords")),
- mesh_terms=_str_list(data.get("mesh_terms")),
- references=_str_list(data.get("references")),
- citations=_safe_int(data.get("citations"), 0),
- source=data.get("source", "unknown"),
- relevance_score=_safe_int(data.get("relevance_score"), 0),
- notes=data.get("notes"),
- tags=_str_list(data.get("tags")),
- category=data.get("category"),
- rating=_safe_int(data.get("rating"), 0) if data.get("rating") is not None else None,
- read_status=data.get("read_status", "unread"),
- importance=data.get("importance", "normal"),
- id=_safe_id(data.get("id")),
- created_at=created_at or datetime.now(),
- updated_at=updated_at or datetime.now(),
- )
|