diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index de9cf9a6..3f31c27b 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.v1 import auth, users, studies, sites, members, comments, attachments, audit_logs, milestones, tasks, dashboard +from app.api.v1 import auth, users, studies, sites, members, comments, attachments, audit_logs, milestones, tasks, dashboard, subjects, visits api_router = APIRouter() api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) @@ -14,3 +14,5 @@ api_router.include_router(audit_logs.router, prefix="/studies/{study_id}/audit-l api_router.include_router(milestones.router, prefix="/studies/{study_id}/milestones", tags=["milestones"]) api_router.include_router(tasks.router, prefix="/studies/{study_id}/tasks", tags=["tasks"]) api_router.include_router(dashboard.router, prefix="/studies/{study_id}/dashboard", tags=["dashboard"]) +api_router.include_router(subjects.router, prefix="/studies/{study_id}/subjects", tags=["subjects"]) +api_router.include_router(visits.router, prefix="/studies/{study_id}/subjects/{subject_id}/visits", tags=["visits"]) diff --git a/backend/app/api/v1/subjects.py b/backend/app/api/v1/subjects.py new file mode 100644 index 00000000..fb832180 --- /dev/null +++ b/backend/app/api/v1/subjects.py @@ -0,0 +1,102 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_current_user, get_db_session, require_study_member, require_study_roles +from app.crud import audit as audit_crud +from app.crud import subject as subject_crud +from app.crud import study as study_crud +from app.schemas.subject import SubjectCreate, SubjectRead, SubjectUpdate + +router = APIRouter() + + +async def _ensure_study_exists(db: AsyncSession, study_id: uuid.UUID): + study = await study_crud.get(db, study_id) + if not study: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Study not found") + return study + + +@router.post( + "/", + response_model=SubjectRead, + status_code=status.HTTP_201_CREATED, + dependencies=[Depends(require_study_roles(["PM", "CRA"]))], +) +async def create_subject( + study_id: uuid.UUID, + subject_in: SubjectCreate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> SubjectRead: + await _ensure_study_exists(db, study_id) + try: + subject = await subject_crud.create_subject(db, study_id, subject_in) + except ValueError as exc: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="subject", + entity_id=subject.id, + action="CREATE_SUBJECT", + detail=f"Subject {subject.subject_no} created", + operator_id=current_user.id, + operator_role=current_user.role, + ) + return subject + + +@router.get( + "/", + response_model=list[SubjectRead], + dependencies=[Depends(require_study_member())], +) +async def list_subjects( + study_id: uuid.UUID, + site_id: uuid.UUID | None = None, + status_filter: str | None = None, + db: AsyncSession = Depends(get_db_session), +) -> list[SubjectRead]: + await _ensure_study_exists(db, study_id) + subjects = await subject_crud.list_subjects(db, study_id, site_id=site_id, status=status_filter) + return list(subjects) + + +@router.patch( + "/{subject_id}", + response_model=SubjectRead, + dependencies=[Depends(require_study_roles(["PM", "CRA"]))], +) +async def update_subject( + study_id: uuid.UUID, + subject_id: uuid.UUID, + subject_in: SubjectUpdate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> SubjectRead: + await _ensure_study_exists(db, study_id) + subject = await subject_crud.get_subject(db, subject_id) + if not subject or subject.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found") + old_status = subject.status + updated = await subject_crud.update_subject(db, subject, subject_in) + # auto-generate visits when enrolled + if subject_in.status and subject_in.status == "ENROLLED" and (subject_in.enrollment_date or updated.enrollment_date): + await subject_crud.generate_default_visits(db, updated) + detail = None + if subject_in.status and subject_in.status != old_status: + detail = f"subject {updated.subject_no} status {old_status} -> {subject_in.status}" + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="subject", + entity_id=subject_id, + action="SUBJECT_STATUS_CHANGE" if detail else "UPDATE_SUBJECT", + detail=detail or "Subject updated", + operator_id=current_user.id, + operator_role=current_user.role, + ) + return updated diff --git a/backend/app/api/v1/visits.py b/backend/app/api/v1/visits.py new file mode 100644 index 00000000..e38beeb6 --- /dev/null +++ b/backend/app/api/v1/visits.py @@ -0,0 +1,70 @@ +import uuid + +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.deps import get_current_user, get_db_session, require_study_member, require_study_roles +from app.crud import audit as audit_crud +from app.crud import subject as subject_crud +from app.crud import visit as visit_crud +from app.schemas.visit import VisitRead, VisitUpdate + +router = APIRouter() + + +async def _ensure_subject(db: AsyncSession, study_id: uuid.UUID, subject_id: uuid.UUID): + subject = await subject_crud.get_subject(db, subject_id) + if not subject or subject.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Subject not found") + return subject + + +@router.get( + "/", + response_model=list[VisitRead], + dependencies=[Depends(require_study_member())], +) +async def list_visits( + study_id: uuid.UUID, + subject_id: uuid.UUID, + db: AsyncSession = Depends(get_db_session), +) -> list[VisitRead]: + await _ensure_subject(db, study_id, subject_id) + visits = await visit_crud.list_visits(db, subject_id) + return list(visits) + + +@router.patch( + "/{visit_id}", + response_model=VisitRead, + dependencies=[Depends(require_study_roles(["PM", "CRA"]))], +) +async def update_visit( + study_id: uuid.UUID, + subject_id: uuid.UUID, + visit_id: uuid.UUID, + visit_in: VisitUpdate, + db: AsyncSession = Depends(get_db_session), + current_user=Depends(get_current_user), +) -> VisitRead: + await _ensure_subject(db, study_id, subject_id) + visit = await visit_crud.get_visit(db, visit_id) + if not visit or visit.subject_id != subject_id or visit.study_id != study_id: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Visit not found") + if visit_in.status == "DONE" and not visit_in.actual_date: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="actual_date required when DONE") + updated = await visit_crud.update_visit(db, visit, visit_in) + detail = None + if visit_in.status: + detail = f"visit {visit.visit_code} {visit.status} -> {visit_in.status}" + await audit_crud.log_action( + db, + study_id=study_id, + entity_type="visit", + entity_id=visit_id, + action="VISIT_STATUS_CHANGE" if detail else "UPDATE_VISIT", + detail=detail or "Visit updated", + operator_id=current_user.id, + operator_role=current_user.role, + ) + return updated diff --git a/backend/app/crud/subject.py b/backend/app/crud/subject.py new file mode 100644 index 00000000..b9ce3d2c --- /dev/null +++ b/backend/app/crud/subject.py @@ -0,0 +1,104 @@ +import uuid +from datetime import date, timedelta +from typing import Sequence + +from sqlalchemy import select, update as sa_update +from sqlalchemy.ext.asyncio import AsyncSession + +from app.crud import visit as visit_crud +from app.models.site import Site +from app.models.subject import Subject +from app.schemas.subject import SubjectCreate, SubjectUpdate + + +async def _validate_site(db: AsyncSession, study_id: uuid.UUID, site_id: uuid.UUID) -> None: + result = await db.execute(select(Site).where(Site.id == site_id)) + site = result.scalar_one_or_none() + if not site or site.study_id != study_id: + raise ValueError("Site not found in study") + + +async def create_subject(db: AsyncSession, study_id: uuid.UUID, subject_in: SubjectCreate) -> Subject: + await _validate_site(db, study_id, subject_in.site_id) + subject = Subject( + study_id=study_id, + site_id=subject_in.site_id, + subject_no=subject_in.subject_no, + status="SCREENING", + screening_date=subject_in.screening_date, + enrollment_date=None, + completion_date=None, + drop_reason=None, + ) + db.add(subject) + await db.commit() + await db.refresh(subject) + + # initial visit: Screening (V0) + await visit_crud.create_visit( + db, + study_id=study_id, + visit_in=None, + subject=subject, + visit_code="V0", + visit_name="Screening", + planned_date=subject.screening_date, + ) + return subject + + +async def get_subject(db: AsyncSession, subject_id: uuid.UUID) -> Subject | None: + result = await db.execute(select(Subject).where(Subject.id == subject_id)) + return result.scalar_one_or_none() + + +async def list_subjects( + db: AsyncSession, + study_id: uuid.UUID, + site_id: uuid.UUID | None = None, + status: str | None = None, +) -> Sequence[Subject]: + stmt = select(Subject).where(Subject.study_id == study_id) + if site_id: + stmt = stmt.where(Subject.site_id == site_id) + if status: + stmt = stmt.where(Subject.status == status) + result = await db.execute(stmt) + return result.scalars().all() + + +async def generate_default_visits(db: AsyncSession, subject: Subject) -> None: + # Baseline + Follow-up visits based on enrollment_date + if not subject.enrollment_date: + return + baseline_date = subject.enrollment_date + follow1 = baseline_date + timedelta(days=30) + follow2 = baseline_date + timedelta(days=60) + visits_data = [ + ("V1", "Baseline", baseline_date), + ("FU1", "Follow-up 1", follow1), + ("FU2", "Follow-up 2", follow2), + ] + for code, name, plan_date in visits_data: + await visit_crud.create_visit( + db, + study_id=subject.study_id, + visit_in=None, + subject=subject, + visit_code=code, + visit_name=name, + planned_date=plan_date, + ) + + +async def update_subject(db: AsyncSession, subject: Subject, subject_in: SubjectUpdate) -> Subject: + update_data = subject_in.model_dump(exclude_unset=True) + if update_data: + await db.execute( + sa_update(Subject) + .where(Subject.id == subject.id) + .values(**update_data) + ) + await db.commit() + await db.refresh(subject) + return subject diff --git a/backend/app/crud/visit.py b/backend/app/crud/visit.py new file mode 100644 index 00000000..8d6f55ac --- /dev/null +++ b/backend/app/crud/visit.py @@ -0,0 +1,60 @@ +import uuid +from typing import Sequence + +from sqlalchemy import select, update as sa_update +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.subject import Subject +from app.models.visit import Visit +from app.schemas.visit import VisitCreate, VisitUpdate + + +async def create_visit( + db: AsyncSession, + *, + study_id: uuid.UUID, + visit_in: VisitCreate | None, + subject: Subject, + visit_code: str, + visit_name: str, + planned_date, +) -> Visit: + visit = Visit( + study_id=study_id, + subject_id=subject.id, + visit_code=visit_code, + visit_name=visit_name, + planned_date=planned_date, + actual_date=None, + status="PLANNED", + window_start=visit_in.window_start if visit_in else None, + window_end=visit_in.window_end if visit_in else None, + notes=None, + ) + db.add(visit) + await db.commit() + await db.refresh(visit) + return visit + + +async def list_visits(db: AsyncSession, subject_id: uuid.UUID) -> Sequence[Visit]: + result = await db.execute(select(Visit).where(Visit.subject_id == subject_id).order_by(Visit.planned_date)) + return result.scalars().all() + + +async def get_visit(db: AsyncSession, visit_id: uuid.UUID) -> Visit | None: + result = await db.execute(select(Visit).where(Visit.id == visit_id)) + return result.scalar_one_or_none() + + +async def update_visit(db: AsyncSession, visit: Visit, visit_in: VisitUpdate) -> Visit: + update_data = visit_in.model_dump(exclude_unset=True) + if update_data: + await db.execute( + sa_update(Visit) + .where(Visit.id == visit.id) + .values(**update_data) + ) + await db.commit() + await db.refresh(visit) + return visit diff --git a/backend/app/db/base.py b/backend/app/db/base.py index 71a6b635..6cafdf6c 100644 --- a/backend/app/db/base.py +++ b/backend/app/db/base.py @@ -10,3 +10,5 @@ from app.models.attachment import Attachment # noqa: F401 from app.models.audit_log import AuditLog # noqa: F401 from app.models.milestone import Milestone # noqa: F401 from app.models.task import Task # noqa: F401 +from app.models.subject import Subject # noqa: F401 +from app.models.visit import Visit # noqa: F401 diff --git a/backend/app/models/subject.py b/backend/app/models/subject.py new file mode 100644 index 00000000..86601a67 --- /dev/null +++ b/backend/app/models/subject.py @@ -0,0 +1,27 @@ +import uuid +from datetime import datetime, date + +from sqlalchemy import Date, DateTime, ForeignKey, String, Text, UniqueConstraint, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base_class import Base + + +class Subject(Base): + __tablename__ = "subjects" + __table_args__ = (UniqueConstraint("study_id", "subject_no", name="uq_subject_study_no"),) + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), index=True, nullable=False) + site_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sites.id"), index=True, nullable=False) + subject_no: Mapped[str] = mapped_column(String(50), nullable=False) + status: Mapped[str] = mapped_column(String(20), nullable=False, default="SCREENING") + screening_date: Mapped[date | None] = mapped_column(Date, nullable=True) + enrollment_date: Mapped[date | None] = mapped_column(Date, nullable=True) + completion_date: Mapped[date | None] = mapped_column(Date, nullable=True) + drop_reason: Mapped[str | None] = mapped_column(Text, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() + ) diff --git a/backend/app/models/visit.py b/backend/app/models/visit.py new file mode 100644 index 00000000..305c3146 --- /dev/null +++ b/backend/app/models/visit.py @@ -0,0 +1,28 @@ +import uuid +from datetime import datetime, date + +from sqlalchemy import Date, DateTime, ForeignKey, String, Text, func +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base_class import Base + + +class Visit(Base): + __tablename__ = "visits" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + study_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("studies.id"), nullable=False) + subject_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("subjects.id"), index=True, nullable=False) + visit_code: Mapped[str] = mapped_column(String(50), nullable=False) + visit_name: Mapped[str] = mapped_column(String(200), nullable=False) + planned_date: Mapped[date | None] = mapped_column(Date, nullable=True) + actual_date: Mapped[date | None] = mapped_column(Date, nullable=True) + status: Mapped[str] = mapped_column(String(20), nullable=False, default="PLANNED") + window_start: Mapped[date | None] = mapped_column(Date, nullable=True) + window_end: Mapped[date | None] = mapped_column(Date, nullable=True) + notes: Mapped[str | None] = mapped_column(Text, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() + ) diff --git a/backend/app/schemas/subject.py b/backend/app/schemas/subject.py new file mode 100644 index 00000000..2e690732 --- /dev/null +++ b/backend/app/schemas/subject.py @@ -0,0 +1,34 @@ +import uuid +from datetime import date, datetime +from typing import Optional + +from pydantic import BaseModel, ConfigDict + + +class SubjectCreate(BaseModel): + site_id: uuid.UUID + subject_no: str + screening_date: Optional[date] = None + + +class SubjectUpdate(BaseModel): + status: Optional[str] = None + enrollment_date: Optional[date] = None + completion_date: Optional[date] = None + drop_reason: Optional[str] = None + + +class SubjectRead(BaseModel): + id: uuid.UUID + study_id: uuid.UUID + site_id: uuid.UUID + subject_no: str + status: str + screening_date: Optional[date] + enrollment_date: Optional[date] + completion_date: Optional[date] + drop_reason: Optional[str] + created_at: datetime + updated_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/schemas/visit.py b/backend/app/schemas/visit.py new file mode 100644 index 00000000..f9170871 --- /dev/null +++ b/backend/app/schemas/visit.py @@ -0,0 +1,38 @@ +import uuid +from datetime import date, datetime +from typing import Optional + +from pydantic import BaseModel, ConfigDict + + +class VisitCreate(BaseModel): + subject_id: uuid.UUID + visit_code: str + visit_name: str + planned_date: Optional[date] = None + window_start: Optional[date] = None + window_end: Optional[date] = None + + +class VisitUpdate(BaseModel): + actual_date: Optional[date] = None + status: Optional[str] = None + notes: Optional[str] = None + + +class VisitRead(BaseModel): + id: uuid.UUID + study_id: uuid.UUID + subject_id: uuid.UUID + visit_code: str + visit_name: str + planned_date: Optional[date] + actual_date: Optional[date] + status: str + window_start: Optional[date] + window_end: Optional[date] + notes: Optional[str] + created_at: datetime + updated_at: datetime + + model_config = ConfigDict(from_attributes=True) diff --git a/pg_data/base/16384/1247 b/pg_data/base/16384/1247 index f8480d63..f0a36072 100644 Binary files a/pg_data/base/16384/1247 and b/pg_data/base/16384/1247 differ diff --git a/pg_data/base/16384/1249 b/pg_data/base/16384/1249 index 1d3faed6..7dc0cb05 100644 Binary files a/pg_data/base/16384/1249 and b/pg_data/base/16384/1249 differ diff --git a/pg_data/base/16384/1259 b/pg_data/base/16384/1259 index 9e639dd9..1084bfa1 100644 Binary files a/pg_data/base/16384/1259 and b/pg_data/base/16384/1259 differ diff --git a/pg_data/base/16384/24576 b/pg_data/base/16384/24576 index 1250058c..951fd30a 100644 Binary files a/pg_data/base/16384/24576 and b/pg_data/base/16384/24576 differ diff --git a/pg_data/base/16384/24584 b/pg_data/base/16384/24584 index a6c7a595..303b88fe 100644 Binary files a/pg_data/base/16384/24584 and b/pg_data/base/16384/24584 differ diff --git a/pg_data/base/16384/24590 b/pg_data/base/16384/24590 index 0fdee861..d0dd8611 100644 Binary files a/pg_data/base/16384/24590 and b/pg_data/base/16384/24590 differ diff --git a/pg_data/base/16384/24597 b/pg_data/base/16384/24597 index 53d093e0..139634bc 100644 Binary files a/pg_data/base/16384/24597 and b/pg_data/base/16384/24597 differ diff --git a/pg_data/base/16384/24598 b/pg_data/base/16384/24598 index e69de29b..b002697b 100644 Binary files a/pg_data/base/16384/24598 and b/pg_data/base/16384/24598 differ diff --git a/pg_data/base/16384/24605 b/pg_data/base/16384/24605 index c12f902c..b0e55b49 100644 Binary files a/pg_data/base/16384/24605 and b/pg_data/base/16384/24605 differ diff --git a/pg_data/base/16384/24612 b/pg_data/base/16384/24612 index be5a1061..9b8df737 100644 Binary files a/pg_data/base/16384/24612 and b/pg_data/base/16384/24612 differ diff --git a/pg_data/base/16384/24613 b/pg_data/base/16384/24613 index f3bda7cf..c9f41a30 100644 Binary files a/pg_data/base/16384/24613 and b/pg_data/base/16384/24613 differ diff --git a/pg_data/base/16384/24618 b/pg_data/base/16384/24618 index 08f3afb3..3776bd5c 100644 Binary files a/pg_data/base/16384/24618 and b/pg_data/base/16384/24618 differ diff --git a/pg_data/base/16384/24620 b/pg_data/base/16384/24620 index 56a82382..785f15b4 100644 Binary files a/pg_data/base/16384/24620 and b/pg_data/base/16384/24620 differ diff --git a/pg_data/base/16384/24632 b/pg_data/base/16384/24632 index 541b050c..dc59b93f 100644 Binary files a/pg_data/base/16384/24632 and b/pg_data/base/16384/24632 differ diff --git a/pg_data/base/16384/24672 b/pg_data/base/16384/24672 index f69e66b7..b71b00bf 100644 Binary files a/pg_data/base/16384/24672 and b/pg_data/base/16384/24672 differ diff --git a/pg_data/base/16384/24678 b/pg_data/base/16384/24678 index a449fd6a..4c3385a9 100644 Binary files a/pg_data/base/16384/24678 and b/pg_data/base/16384/24678 differ diff --git a/pg_data/base/16384/24740 b/pg_data/base/16384/24740 new file mode 100644 index 00000000..2b45926a Binary files /dev/null and b/pg_data/base/16384/24740 differ diff --git a/pg_data/base/16384/24745 b/pg_data/base/16384/24745 new file mode 100644 index 00000000..e69de29b diff --git a/pg_data/base/16384/24746 b/pg_data/base/16384/24746 new file mode 100644 index 00000000..d0d7512f Binary files /dev/null and b/pg_data/base/16384/24746 differ diff --git a/pg_data/base/16384/24747 b/pg_data/base/16384/24747 new file mode 100644 index 00000000..89f48a31 Binary files /dev/null and b/pg_data/base/16384/24747 differ diff --git a/pg_data/base/16384/24749 b/pg_data/base/16384/24749 new file mode 100644 index 00000000..728d8b8c Binary files /dev/null and b/pg_data/base/16384/24749 differ diff --git a/pg_data/base/16384/24761 b/pg_data/base/16384/24761 new file mode 100644 index 00000000..be0707f7 Binary files /dev/null and b/pg_data/base/16384/24761 differ diff --git a/pg_data/base/16384/24762 b/pg_data/base/16384/24762 new file mode 100644 index 00000000..50be9fb3 Binary files /dev/null and b/pg_data/base/16384/24762 differ diff --git a/pg_data/base/16384/24763 b/pg_data/base/16384/24763 new file mode 100644 index 00000000..a6b4f313 Binary files /dev/null and b/pg_data/base/16384/24763 differ diff --git a/pg_data/base/16384/24768 b/pg_data/base/16384/24768 new file mode 100644 index 00000000..e69de29b diff --git a/pg_data/base/16384/24769 b/pg_data/base/16384/24769 new file mode 100644 index 00000000..abb43cc6 Binary files /dev/null and b/pg_data/base/16384/24769 differ diff --git a/pg_data/base/16384/24770 b/pg_data/base/16384/24770 new file mode 100644 index 00000000..2fabf3ec Binary files /dev/null and b/pg_data/base/16384/24770 differ diff --git a/pg_data/base/16384/24782 b/pg_data/base/16384/24782 new file mode 100644 index 00000000..4a3d0e43 Binary files /dev/null and b/pg_data/base/16384/24782 differ diff --git a/pg_data/base/16384/2579 b/pg_data/base/16384/2579 index 011f9bef..41b60260 100644 Binary files a/pg_data/base/16384/2579 and b/pg_data/base/16384/2579 differ diff --git a/pg_data/base/16384/2604 b/pg_data/base/16384/2604 index d2397577..7fa50176 100644 Binary files a/pg_data/base/16384/2604 and b/pg_data/base/16384/2604 differ diff --git a/pg_data/base/16384/2606 b/pg_data/base/16384/2606 index 43156c23..12301266 100644 Binary files a/pg_data/base/16384/2606 and b/pg_data/base/16384/2606 differ diff --git a/pg_data/base/16384/2608 b/pg_data/base/16384/2608 index bd0ac36f..b4891f9a 100644 Binary files a/pg_data/base/16384/2608 and b/pg_data/base/16384/2608 differ diff --git a/pg_data/base/16384/2610 b/pg_data/base/16384/2610 index 09ef1dd4..b2dae6e0 100644 Binary files a/pg_data/base/16384/2610 and b/pg_data/base/16384/2610 differ diff --git a/pg_data/base/16384/2619 b/pg_data/base/16384/2619 index 3f1ad759..7699405c 100644 Binary files a/pg_data/base/16384/2619 and b/pg_data/base/16384/2619 differ diff --git a/pg_data/base/16384/2620 b/pg_data/base/16384/2620 index 63585763..35bb7933 100644 Binary files a/pg_data/base/16384/2620 and b/pg_data/base/16384/2620 differ diff --git a/pg_data/base/16384/2656 b/pg_data/base/16384/2656 index a9295371..ef92e5d0 100644 Binary files a/pg_data/base/16384/2656 and b/pg_data/base/16384/2656 differ diff --git a/pg_data/base/16384/2657 b/pg_data/base/16384/2657 index daa57351..73f559f9 100644 Binary files a/pg_data/base/16384/2657 and b/pg_data/base/16384/2657 differ diff --git a/pg_data/base/16384/2658 b/pg_data/base/16384/2658 index 7e47dc80..3d556622 100644 Binary files a/pg_data/base/16384/2658 and b/pg_data/base/16384/2658 differ diff --git a/pg_data/base/16384/2659 b/pg_data/base/16384/2659 index 274219f0..218fe35c 100644 Binary files a/pg_data/base/16384/2659 and b/pg_data/base/16384/2659 differ diff --git a/pg_data/base/16384/2662 b/pg_data/base/16384/2662 index a61642ce..bfb5ecc3 100644 Binary files a/pg_data/base/16384/2662 and b/pg_data/base/16384/2662 differ diff --git a/pg_data/base/16384/2663 b/pg_data/base/16384/2663 index 2cfdab42..d47deaaa 100644 Binary files a/pg_data/base/16384/2663 and b/pg_data/base/16384/2663 differ diff --git a/pg_data/base/16384/2664 b/pg_data/base/16384/2664 index 86d19ef4..b5eae30d 100644 Binary files a/pg_data/base/16384/2664 and b/pg_data/base/16384/2664 differ diff --git a/pg_data/base/16384/2665 b/pg_data/base/16384/2665 index 32671e38..72b32dc2 100644 Binary files a/pg_data/base/16384/2665 and b/pg_data/base/16384/2665 differ diff --git a/pg_data/base/16384/2666 b/pg_data/base/16384/2666 index 49c8af2e..72c3e8c1 100644 Binary files a/pg_data/base/16384/2666 and b/pg_data/base/16384/2666 differ diff --git a/pg_data/base/16384/2667 b/pg_data/base/16384/2667 index 2f2ac904..c02850a5 100644 Binary files a/pg_data/base/16384/2667 and b/pg_data/base/16384/2667 differ diff --git a/pg_data/base/16384/2673 b/pg_data/base/16384/2673 index 8a99c40d..6776b597 100644 Binary files a/pg_data/base/16384/2673 and b/pg_data/base/16384/2673 differ diff --git a/pg_data/base/16384/2674 b/pg_data/base/16384/2674 index 26329de2..d7ca0147 100644 Binary files a/pg_data/base/16384/2674 and b/pg_data/base/16384/2674 differ diff --git a/pg_data/base/16384/2678 b/pg_data/base/16384/2678 index 6df278b6..c80d1322 100644 Binary files a/pg_data/base/16384/2678 and b/pg_data/base/16384/2678 differ diff --git a/pg_data/base/16384/2679 b/pg_data/base/16384/2679 index 25b159e1..deb9ea1b 100644 Binary files a/pg_data/base/16384/2679 and b/pg_data/base/16384/2679 differ diff --git a/pg_data/base/16384/2696 b/pg_data/base/16384/2696 index d5ec8ea9..7cd38cf6 100644 Binary files a/pg_data/base/16384/2696 and b/pg_data/base/16384/2696 differ diff --git a/pg_data/base/16384/2699 b/pg_data/base/16384/2699 index 404da348..bf9ae6e5 100644 Binary files a/pg_data/base/16384/2699 and b/pg_data/base/16384/2699 differ diff --git a/pg_data/base/16384/2701 b/pg_data/base/16384/2701 index 3ff383e1..97fbd28d 100644 Binary files a/pg_data/base/16384/2701 and b/pg_data/base/16384/2701 differ diff --git a/pg_data/base/16384/2702 b/pg_data/base/16384/2702 index 4335b8a9..211f248d 100644 Binary files a/pg_data/base/16384/2702 and b/pg_data/base/16384/2702 differ diff --git a/pg_data/base/16384/2703 b/pg_data/base/16384/2703 index 35886a83..5c27ff1b 100644 Binary files a/pg_data/base/16384/2703 and b/pg_data/base/16384/2703 differ diff --git a/pg_data/base/16384/2704 b/pg_data/base/16384/2704 index 7bf3ab6f..11dd0f11 100644 Binary files a/pg_data/base/16384/2704 and b/pg_data/base/16384/2704 differ diff --git a/pg_data/base/16384/3455 b/pg_data/base/16384/3455 index abf293a9..1fbd5bf8 100644 Binary files a/pg_data/base/16384/3455 and b/pg_data/base/16384/3455 differ diff --git a/pg_data/global/pg_control b/pg_data/global/pg_control index f7b477f1..5a2e0b35 100644 Binary files a/pg_data/global/pg_control and b/pg_data/global/pg_control differ diff --git a/pg_data/pg_wal/000000010000000000000001 b/pg_data/pg_wal/000000010000000000000001 index 7410465f..e912bc97 100644 Binary files a/pg_data/pg_wal/000000010000000000000001 and b/pg_data/pg_wal/000000010000000000000001 differ diff --git a/pg_data/pg_xact/0000 b/pg_data/pg_xact/0000 index 2a37dfb4..83f8c3bc 100644 Binary files a/pg_data/pg_xact/0000 and b/pg_data/pg_xact/0000 differ