Skip to content

Commit db3bcfb

Browse files
committed
adapt paper schema and arxiv_ingest.py
1 parent 2d0f7ff commit db3bcfb

3 files changed

Lines changed: 58 additions & 11 deletions

File tree

backend/app/models/paper.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@
33
from datetime import date, datetime
44
from typing import TYPE_CHECKING, List, Optional
55

6-
from sqlalchemy import JSON, BigInteger, Date, DateTime, String, Text
6+
from pgvector.sqlalchemy import Vector
77
from sqlalchemy import Enum as SqlEnum
8+
from sqlalchemy import (
9+
JSON,
10+
BigInteger,
11+
Date,
12+
DateTime,
13+
String,
14+
Text,
15+
Index,
16+
)
817
from sqlalchemy.orm import Mapped, mapped_column, relationship
918
from sqlalchemy.sql import func
1019

@@ -13,38 +22,68 @@
1322

1423

1524
class Paper(Base):
16-
"""Database representation of a scholarly paper."""
25+
"""Database representation of a scholarly paper with vector embeddings."""
1726

1827
__tablename__ = "paper"
1928

29+
# ------------------
30+
# Columns
31+
# ------------------
2032
paper_id: Mapped[int] = mapped_column(BigInteger, primary_key=True, index=True)
2133
doi: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
34+
2235
source: Mapped[PaperSource] = mapped_column(
2336
SqlEnum(PaperSource, name="paper_source"), nullable=False
2437
)
38+
2539
paper_type: Mapped[PaperType] = mapped_column(
2640
SqlEnum(PaperType, name="paper_type"),
2741
nullable=False,
2842
default=PaperType.PREPRINT,
2943
server_default=PaperType.PREPRINT.value,
3044
)
45+
3146
title: Mapped[str] = mapped_column(String(512), nullable=False)
3247
authors: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
3348
abstract: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
3449
published_at: Mapped[Optional[date]] = mapped_column(Date, nullable=True)
3550
pdf_url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
3651
url: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
52+
3753
fetched_at: Mapped[datetime] = mapped_column(
3854
DateTime(timezone=True), nullable=False, server_default=func.now()
3955
)
4056

57+
# ------------------
58+
# Vector Embedding
59+
# ------------------
60+
embedding: Mapped[Optional[List[float]]] = mapped_column(
61+
Vector(768), nullable=True
62+
)
63+
64+
# ------------------
65+
# Relationships
66+
# ------------------
4167
project_links: Mapped[List["ProjectPaper"]] = relationship(
4268
"ProjectPaper", back_populates="paper", cascade="all, delete-orphan"
4369
)
70+
4471
projects: Mapped[List["Project"]] = relationship(
4572
"Project", secondary="project_paper", back_populates="papers"
4673
)
4774

75+
# ------------------
76+
# Indexes (HNSW)
77+
# ------------------
78+
__table_args__ = (
79+
Index(
80+
"ix_paper_embedding_hnsw",
81+
"embedding",
82+
postgresql_using="hnsw",
83+
postgresql_with={"m": 16, "ef_search": 40, "lists": 100},
84+
),
85+
)
86+
4887

4988
if TYPE_CHECKING:
5089
from .project import Project

backend/ingestion/arxiv_ingest.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,13 @@ def process_batch(batch_buffer: List[Dict], embedder: Specter2Embedder) -> List[
5454

5555
# pylint: disable=too-many-arguments, too-many-positional-arguments, too-many-locals
5656
def ingest_arxiv_metadata_to_parquet(
57-
path: str,
58-
out_dir: str,
59-
embedder: Specter2Embedder,
60-
limit: Optional[int] = None,
61-
batch_size: int = 64,
62-
shard_size: int = 5000,
57+
path: str,
58+
out_dir: str,
59+
embedder: Specter2Embedder,
60+
skip: int = 0,
61+
limit: Optional[int] = None,
62+
batch_size: int = 64,
63+
shard_size: int = 5000,
6364
) -> None:
6465
"""Ingest a JSONL ArXiv dump and write Parquet shards with embeddings."""
6566
json_file = Path(path)
@@ -70,6 +71,8 @@ def ingest_arxiv_metadata_to_parquet(
7071
raise FileNotFoundError(f"Dataset not found: {json_file}")
7172

7273
logger.info("Reading: %s", json_file)
74+
if skip:
75+
logger.info("Skipping first %s lines...", skip)
7376
logger.info("Writing shards: %s", out_path)
7477

7578
batch_buffer: List[Dict] = []
@@ -78,7 +81,10 @@ def ingest_arxiv_metadata_to_parquet(
7881

7982
with json_file.open("r", encoding="utf-8") as f:
8083
for i, line in enumerate(f):
81-
if limit and i >= limit:
84+
if i < skip:
85+
continue
86+
87+
if limit and i >= skip + limit:
8288
break
8389

8490
try:
@@ -115,9 +121,9 @@ def ingest_arxiv_metadata_to_parquet(
115121
parser = argparse.ArgumentParser(description="Ingest ArXiv metadata into Parquet shards.")
116122
parser.add_argument("--path", type=str, required=True)
117123
parser.add_argument("--out", type=str, required=True)
124+
parser.add_argument("--skip", type=int, default=0)
118125
parser.add_argument("--limit", type=int, default=None)
119126
parser.add_argument("--batch-size", type=int, default=64)
120-
parser.add_argument("--shard-size", type=int, default=5000)
121127
parser.add_argument("--device", type=str, default=None)
122128

123129
args = parser.parse_args()
@@ -128,7 +134,8 @@ def ingest_arxiv_metadata_to_parquet(
128134
path=args.path,
129135
out_dir=args.out,
130136
embedder=cli_embedder,
137+
skip=args.skip,
131138
limit=args.limit,
132139
batch_size=args.batch_size,
133-
shard_size=args.shard_size,
140+
shard_size=5000
134141
)

backend/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ uvicorn[standard]==0.38.0
66
sqlalchemy==2.0.44
77
psycopg2-binary==2.9.11
88
alembic==1.17.1
9+
pgvector==0.4.1
910

1011
# --- Data validation & settings ---
1112
pydantic==2.12.3

0 commit comments

Comments
 (0)