changed models and schemasa

This commit is contained in:
GotthardG 2024-11-11 15:00:20 +01:00
parent 7125cc5b50
commit 52fe68b2bc
10 changed files with 279 additions and 112 deletions

View File

@ -102,38 +102,39 @@ shipments = [
] ]
pucks = [ pucks = [
Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=1), Puck(id=1, puck_name="PUCK001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=1),
Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=1), Puck(id=2, puck_name="PUCK002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=1),
Puck(id=3, puck_name="PUCK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=1), Puck(id=3, puck_name="PUCK003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=1),
Puck(id=4, puck_name="PUCK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=1), Puck(id=4, puck_name="PUCK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=1),
Puck(id=5, puck_name="PUCK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=1), Puck(id=5, puck_name="PUCK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=1),
Puck(id=6, puck_name="PUCK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=1), Puck(id=6, puck_name="PUCK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=1),
Puck(id=7, puck_name="PUCK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=1), Puck(id=7, puck_name="PUCK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=1),
Puck(id=8, puck_name="PK001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=2), Puck(id=8, puck_name="PK001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=2),
Puck(id=9, puck_name="PK002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=2), Puck(id=9, puck_name="PK002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=2),
Puck(id=10, puck_name="PK003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=2), Puck(id=10, puck_name="PK003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=2),
Puck(id=11, puck_name="PK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=2), Puck(id=11, puck_name="PK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=2),
Puck(id=12, puck_name="PK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=2), Puck(id=12, puck_name="PK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=2),
Puck(id=13, puck_name="PK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=2), Puck(id=13, puck_name="PK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=2),
Puck(id=14, puck_name="P001", puck_type="Unipuck", puck_location_in_dewar=1, positions=[], dewar_id=3), Puck(id=14, puck_name="P001", puck_type="Unipuck", puck_location_in_dewar=1, dewar_id=3),
Puck(id=15, puck_name="P002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=3), Puck(id=15, puck_name="P002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=3),
Puck(id=16, puck_name="P003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=3), Puck(id=16, puck_name="P003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=3),
Puck(id=17, puck_name="P004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=3), Puck(id=17, puck_name="P004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=3),
Puck(id=18, puck_name="P005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=3), Puck(id=18, puck_name="P005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=3),
Puck(id=19, puck_name="P006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=3), Puck(id=19, puck_name="P006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=3),
Puck(id=20, puck_name="P007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=3), Puck(id=20, puck_name="P007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=3),
Puck(id=21, puck_name="PC002", puck_type="Unipuck", puck_location_in_dewar=2, positions=[], dewar_id=4), Puck(id=21, puck_name="PC002", puck_type="Unipuck", puck_location_in_dewar=2, dewar_id=4),
Puck(id=22, puck_name="PC003", puck_type="Unipuck", puck_location_in_dewar=3, positions=[], dewar_id=4), Puck(id=22, puck_name="PC003", puck_type="Unipuck", puck_location_in_dewar=3, dewar_id=4),
Puck(id=23, puck_name="PC004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=4), Puck(id=23, puck_name="PC004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=4),
Puck(id=24, puck_name="PC005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=4), Puck(id=24, puck_name="PC005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=4),
Puck(id=25, puck_name="PC006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=4), Puck(id=25, puck_name="PC006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=4),
Puck(id=26, puck_name="PC007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=4), Puck(id=26, puck_name="PC007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=4),
Puck(id=27, puck_name="PKK004", puck_type="Unipuck", puck_location_in_dewar=4, positions=[], dewar_id=5), Puck(id=27, puck_name="PKK004", puck_type="Unipuck", puck_location_in_dewar=4, dewar_id=5),
Puck(id=28, puck_name="PKK005", puck_type="Unipuck", puck_location_in_dewar=5, positions=[], dewar_id=5), Puck(id=28, puck_name="PKK005", puck_type="Unipuck", puck_location_in_dewar=5, dewar_id=5),
Puck(id=29, puck_name="PKK006", puck_type="Unipuck", puck_location_in_dewar=6, positions=[], dewar_id=5), Puck(id=29, puck_name="PKK006", puck_type="Unipuck", puck_location_in_dewar=6, dewar_id=5),
Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, positions=[], dewar_id=5) Puck(id=30, puck_name="PKK007", puck_type="Unipuck", puck_location_in_dewar=7, dewar_id=5)
] ]
samples = [] samples = []
sample_id_counter = 1 sample_id_counter = 1
@ -143,7 +144,11 @@ for puck in pucks:
for pos in range(1, 17): for pos in range(1, 17):
if pos in occupied_positions: if pos in occupied_positions:
sample = Sample(id=sample_id_counter, sample_name=f"Sample{sample_id_counter:03}", puck_id=puck.id) sample = Sample(
puck.positions.append(sample) id=sample_id_counter,
sample_name=f"Sample{sample_id_counter:03}",
position=pos,
puck_id=puck.id
)
samples.append(sample) samples.append(sample)
sample_id_counter += 1 sample_id_counter += 1

View File

@ -69,11 +69,13 @@ class Dewar(Base):
@property @property
def number_of_pucks(self) -> int: def number_of_pucks(self) -> int:
return calculate_number_of_pucks(self) return len(self.pucks) if self.pucks else 0
@property @property
def number_of_samples(self) -> int: def number_of_samples(self) -> int:
return calculate_number_of_samples(self) if not self.pucks:
return 0
return sum(len(puck.samples) for puck in self.pucks)
class Proposal(Base): class Proposal(Base):
@ -88,20 +90,24 @@ class Proposal(Base):
class Puck(Base): class Puck(Base):
__tablename__ = 'pucks' __tablename__ = 'pucks'
id = Column(String, primary_key=True) id = Column(Integer, primary_key=True, index=True)
puck_name = Column(String) puck_name = Column(String, index=True)
puck_type = Column(String) puck_type = Column(String)
puck_location_in_dewar = Column(Integer) puck_location_in_dewar = Column(Integer)
dewar_id = Column(Integer, ForeignKey('dewars.id')) # Note: changed to String
positions = relationship("Sample", back_populates="puck") # Foreign keys and relationships
dewar = relationship("Dewar", back_populates="pucks") dewar_id = Column(Integer, ForeignKey('dewars.id'))
dewar = relationship("Dewar", back_populates="pucks") # Properly define the other side of the relationship
samples = relationship("Sample", back_populates="puck")
class Sample(Base): class Sample(Base):
__tablename__ = 'samples' __tablename__ = 'samples'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True, index=True)
sample_name = Column(String) sample_name = Column(String, index=True) # Matches `sample_name` in data creation
position = Column(Integer) # Matches `position` in data creation script
# Foreign keys and relationships
puck_id = Column(Integer, ForeignKey('pucks.id')) puck_id = Column(Integer, ForeignKey('pucks.id'))
puck = relationship("Puck", back_populates="positions") puck = relationship("Puck", back_populates="samples")

View File

@ -1,24 +1,27 @@
# app/routers/shipment.py
from fastapi import APIRouter, HTTPException, status, Query, Depends from fastapi import APIRouter, HTTPException, status, Query, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional from typing import List, Optional
import uuid
import json
from datetime import date
import logging import logging
from pydantic import BaseModel
from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, Proposal as ProposalModel, Dewar as DewarModel from app.models import Shipment as ShipmentModel, ContactPerson as ContactPersonModel, Address as AddressModel, \
from app.schemas import ShipmentCreate, UpdateShipmentComments, Shipment as ShipmentSchema, DewarUpdate, ContactPerson as ContactPersonSchema Proposal as ProposalModel, Dewar as DewarModel
from app.schemas import Sample as SampleSchema from app.schemas import ShipmentCreate, UpdateShipmentComments, Shipment as ShipmentSchema, DewarUpdate, \
ContactPerson as ContactPersonSchema, Sample as SampleSchema, DewarCreate, PuckCreate, SampleCreate
from app.database import get_db from app.database import get_db
from app.crud import get_shipments, get_shipment_by_id from app.crud import get_shipments, get_shipment_by_id
router = APIRouter() router = APIRouter()
def default_serializer(obj): def default_serializer(obj):
if isinstance(obj, date): if isinstance(obj, date):
return obj.isoformat() return obj.isoformat()
raise TypeError(f"Type {type(obj)} not serializable") raise TypeError(f"Type {type(obj)} not serializable")
@router.get("", response_model=List[ShipmentSchema]) @router.get("", response_model=List[ShipmentSchema])
async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends(get_db)): async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends(get_db)):
if id: if id:
@ -35,6 +38,7 @@ async def fetch_shipments(id: Optional[int] = Query(None), db: Session = Depends
logging.info(f"Shipment ID: {shipment.id}, Shipment Name: {shipment.shipment_name}") logging.info(f"Shipment ID: {shipment.id}, Shipment Name: {shipment.shipment_name}")
return shipments return shipments
@router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED) @router.post("", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)): async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db)):
contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == shipment.contact_person_id).first() contact_person = db.query(ContactPersonModel).filter(ContactPersonModel.id == shipment.contact_person_id).first()
@ -68,6 +72,7 @@ async def create_shipment(shipment: ShipmentCreate, db: Session = Depends(get_db
return db_shipment return db_shipment
@router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/{shipment_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_shipment(id: int, db: Session = Depends(get_db)): async def delete_shipment(id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first() shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first()
@ -77,6 +82,7 @@ async def delete_shipment(id: int, db: Session = Depends(get_db)):
db.commit() db.commit()
return return
@router.put("/{shipment_id}", response_model=ShipmentSchema) @router.put("/{shipment_id}", response_model=ShipmentSchema)
async def update_shipment(id: int, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)): async def update_shipment(id: int, updated_shipment: ShipmentCreate, db: Session = Depends(get_db)):
print("Received payload:", json.dumps(updated_shipment.dict(), indent=2, default=default_serializer)) print("Received payload:", json.dumps(updated_shipment.dict(), indent=2, default=default_serializer))
@ -145,6 +151,7 @@ async def add_dewar_to_shipment(id: int, dewar_id: int, db: Session = Depends(ge
db.refresh(shipment) db.refresh(shipment)
return shipment return shipment
@router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema) @router.delete("/{shipment_id}/remove_dewar/{dewar_id}", response_model=ShipmentSchema)
async def remove_dewar_from_shipment(shipment_id: int, dewar_id: int, db: Session = Depends(get_db)): async def remove_dewar_from_shipment(shipment_id: int, dewar_id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first() shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
@ -160,11 +167,13 @@ async def remove_dewar_from_shipment(shipment_id: int, dewar_id: int, db: Sessio
db.refresh(shipment) db.refresh(shipment)
return shipment return shipment
@router.get("/contact_persons", response_model=List[ContactPersonSchema]) @router.get("/contact_persons", response_model=List[ContactPersonSchema])
async def get_shipment_contact_persons(db: Session = Depends(get_db)): async def get_shipment_contact_persons(db: Session = Depends(get_db)):
contact_persons = db.query(ContactPersonModel).all() contact_persons = db.query(ContactPersonModel).all()
return contact_persons return contact_persons
@router.get("/{shipment_id}/samples", response_model=List[SampleSchema]) @router.get("/{shipment_id}/samples", response_model=List[SampleSchema])
def get_samples_in_shipment(id: int, db: Session = Depends(get_db)): def get_samples_in_shipment(id: int, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first() shipment = db.query(ShipmentModel).filter(ShipmentModel.id == id).first()
@ -174,27 +183,25 @@ def get_samples_in_shipment(id: int, db: Session = Depends(get_db)):
samples = [] samples = []
for dewar in shipment.dewars: for dewar in shipment.dewars:
for puck in dewar.pucks: for puck in dewar.pucks:
samples.extend(puck.positions) samples.extend(puck.samples)
return samples return samples
@router.get("/{shipment_id}/dewars/{dewar_id}/samples", response_model=List[SampleSchema])
def get_samples_in_dewar( @router.get("/shipments/{shipment_id}/dewars/{dewar_id}/samples", response_model=List[SampleSchema])
shipment_id: int, dewar_id: int, db: Session = Depends(get_db) def get_samples_in_dewar(shipment_id: int, dewar_id: int, db: Session = Depends(get_db)):
): shipment = get_shipment_by_id(db, shipment_id)
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first() if not shipment:
if shipment is None:
raise HTTPException(status_code=404, detail="Shipment not found") raise HTTPException(status_code=404, detail="Shipment not found")
dewar = db.query(DewarModel).filter( dewar = next((d for d in shipment.dewars if d.id == dewar_id), None)
DewarModel.id == dewar_id, DewarModel.shipment_id == shipment_id if not dewar:
).first() raise HTTPException(status_code=404, detail="Dewar not found")
if dewar is None:
raise HTTPException(status_code=404, detail="Dewar not found in shipment")
samples = [] samples = []
for puck in dewar.pucks: for puck in dewar.pucks:
samples.extend(puck.positions) for sample in puck.samples:
samples.append(sample)
return samples return samples
@ -208,4 +215,51 @@ async def update_shipment_comments(id: int, comments_data: UpdateShipmentComment
shipment.comments = comments_data.comments shipment.comments = comments_data.comments
db.commit() db.commit()
db.refresh(shipment) db.refresh(shipment)
return shipment return shipment
@router.post("/{shipment_id}/add_dewar_puck_sample", response_model=ShipmentSchema, status_code=status.HTTP_201_CREATED)
async def add_dewar_puck_sample_to_shipment(shipment_id: int, payload: DewarCreate, db: Session = Depends(get_db)):
shipment = db.query(ShipmentModel).filter(ShipmentModel.id == shipment_id).first()
if not shipment:
raise HTTPException(status_code=404, detail="Shipment not found")
for dewar_data in payload.dewars:
dewar = Dewar(
shipment_id=shipment_id,
dewar_name=dewar_data.dewar_name,
tracking_number=dewar_data.tracking_number,
status=dewar_data.status,
contact_person_id=dewar_data.contact_person_id,
return_address_id=dewar_data.return_address_id,
)
db.add(dewar)
db.commit()
db.refresh(dewar)
for puck_data in dewar_data.pucks:
puck = Puck(
dewar_id=dewar.id,
puck_name=puck_data.puck_name,
puck_type=puck_data.puck_type,
puck_location_in_dewar=puck_data.puck_location_in_dewar,
)
db.add(puck)
db.commit()
db.refresh(puck)
for sample_data in puck_data.samples:
sample = Sample(
puck_id=puck.id,
sample_name=sample_data.sample_name,
position=sample_data.position,
data_collection_parameters=DataCollectionParameters(
**sample_data.data_collection_parameters
),
)
db.add(sample)
db.commit()
db.refresh(sample)
db.refresh(shipment)
return shipment

View File

@ -1,5 +1,5 @@
from typing import List, Optional from typing import List, Optional
from pydantic import BaseModel, EmailStr, constr from pydantic import BaseModel, EmailStr, constr, Field
from datetime import date from datetime import date
@ -35,9 +35,11 @@ class DataCollectionParameters(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
class Results(BaseModel): class Results(BaseModel):
# Define attributes for Results here # Define attributes for Results here
pass # Placeholder for now, should be expanded later with actual fields pass
# Contact Person schemas # Contact Person schemas
class ContactPersonBase(BaseModel): class ContactPersonBase(BaseModel):
@ -57,12 +59,14 @@ class ContactPerson(ContactPersonBase):
class Config: class Config:
from_attributes = True from_attributes = True
class ContactPersonUpdate(BaseModel): class ContactPersonUpdate(BaseModel):
firstname: str | None = None firstname: str | None = None
lastname: str | None = None lastname: str | None = None
phone_number: str | None = None phone_number: str | None = None
email: EmailStr | None = None email: EmailStr | None = None
# Address schemas # Address schemas
class AddressCreate(BaseModel): class AddressCreate(BaseModel):
street: str street: str
@ -77,21 +81,31 @@ class Address(AddressCreate):
class Config: class Config:
from_attributes = True from_attributes = True
class AddressUpdate(BaseModel): class AddressUpdate(BaseModel):
street: str | None = None street: str | None = None
city: str | None = None city: str | None = None
zipcode: str | None = None zipcode: str | None = None
country: str | None = None country: str | None = None
# Sample schemas
class Sample(BaseModel): class Sample(BaseModel):
id: int id: int
sample_name: str sample_name: str
data_collection_parameters: Optional[DataCollectionParameters] = None position: int # Position within the puck
results: Optional[Results] = None puck_id: int
crystalname: Optional[str] = Field(None)
positioninpuck: Optional[int] = Field(None)
class SampleCreate(BaseModel):
sample_name: str = Field(..., alias="crystalname")
position: int = Field(..., alias="positioninpuck")
data_collection_parameters: DataCollectionParameters
class Config: class Config:
from_attributes = True populate_by_name = True
# Puck schemas # Puck schemas
@ -102,7 +116,7 @@ class PuckBase(BaseModel):
class PuckCreate(PuckBase): class PuckCreate(PuckBase):
positions: List[int] = [] pass
class PuckUpdate(BaseModel): class PuckUpdate(BaseModel):
@ -110,12 +124,15 @@ class PuckUpdate(BaseModel):
puck_type: Optional[str] = None puck_type: Optional[str] = None
puck_location_in_dewar: Optional[int] = None puck_location_in_dewar: Optional[int] = None
dewar_id: Optional[int] = None dewar_id: Optional[int] = None
positions: Optional[List[int]] = None
class Puck(PuckBase): class Puck(BaseModel):
id: int id: int
positions: List[Sample] = [] puck_name: str
puck_type: str
puck_location_in_dewar: int
dewar_id: int
samples: List[Sample] = [] # List of samples within this puck
class Config: class Config:
from_attributes = True from_attributes = True
@ -137,8 +154,13 @@ class DewarBase(BaseModel):
return_address_id: Optional[int] return_address_id: Optional[int]
class DewarCreate(DewarBase): class DewarCreate(BaseModel):
pass dewar_name: str = Field(..., alias="dewarname")
tracking_number: Optional[str]
status: Optional[str] = None
contact_person_id: Optional[int] = None
return_address_id: Optional[int] = None
pucks: List[PuckCreate] = []
class Dewar(DewarBase): class Dewar(DewarBase):
@ -146,7 +168,7 @@ class Dewar(DewarBase):
shipment_id: Optional[int] shipment_id: Optional[int]
contact_person: Optional[ContactPerson] contact_person: Optional[ContactPerson]
return_address: Optional[Address] return_address: Optional[Address]
pucks: Optional[List[Puck]] = [] pucks: List[Puck] = [] # List of pucks within this dewar
class Config: class Config:
from_attributes = True from_attributes = True
@ -184,7 +206,7 @@ class Shipment(BaseModel):
contact_person: Optional[ContactPerson] contact_person: Optional[ContactPerson]
return_address: Optional[Address] return_address: Optional[Address]
proposal: Optional[Proposal] proposal: Optional[Proposal]
dewars: Optional[List[Dewar]] = [] dewars: List[Dewar] = []
class Config: class Config:
from_attributes = True from_attributes = True
@ -198,11 +220,11 @@ class ShipmentCreate(BaseModel):
contact_person_id: int contact_person_id: int
return_address_id: int return_address_id: int
proposal_id: int proposal_id: int
dewars: Optional[List[DewarUpdate]] = [] dewars: List[DewarCreate] = []
class Config: class Config:
from_attributes = True from_attributes = True
class UpdateShipmentComments(BaseModel): class UpdateShipmentComments(BaseModel):
comments: str comments: str

View File

@ -1,31 +1,27 @@
# app/services/shipment_processor.py # Adjusting the ShipmentProcessor for better error handling and alignment
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.models import Shipment, Dewar, Puck, Sample from app.models import Shipment, Dewar, Puck, Sample, DataCollectionParameters
from app.schemas import ShipmentCreate, ShipmentResponse from app.schemas import ShipmentCreate, ShipmentResponse
import logging import logging
logger = logging.getLogger(__name__)
class ShipmentProcessor: class ShipmentProcessor:
def __init__(self, db: Session): def __init__(self, db: Session):
self.db = db self.db = db
def process_shipment(self, shipment: ShipmentCreate) -> ShipmentResponse: def process_shipment(self, shipment: ShipmentCreate) -> ShipmentResponse:
logger = logging.getLogger(__name__)
try: try:
# Creating new Shipment
new_shipment = Shipment( new_shipment = Shipment(
shipment_name=shipment.shipment_name, shipment_name=shipment.shipment_name,
shipment_date=shipment.shipment_date, shipment_date=shipment.shipment_date,
shipment_status=shipment.shipment_status, # Adjusted to match model shipment_status=shipment.shipment_status,
# other shipment-related fields if any
) )
self.db.add(new_shipment) self.db.add(new_shipment)
self.db.commit() self.db.commit()
self.db.refresh(new_shipment) self.db.refresh(new_shipment)
# Link Dewars
for dewar_data in shipment.dewars: for dewar_data in shipment.dewars:
dewar = Dewar( dewar = Dewar(
shipment_id=new_shipment.id, shipment_id=new_shipment.id,
@ -39,7 +35,6 @@ class ShipmentProcessor:
self.db.commit() self.db.commit()
self.db.refresh(dewar) self.db.refresh(dewar)
# Link Pucks
for puck_data in dewar_data.pucks: for puck_data in dewar_data.pucks:
puck = Puck( puck = Puck(
dewar_id=dewar.id, dewar_id=dewar.id,
@ -51,18 +46,19 @@ class ShipmentProcessor:
self.db.commit() self.db.commit()
self.db.refresh(puck) self.db.refresh(puck)
# Link Samples
for sample_data in puck_data.samples: for sample_data in puck_data.samples:
data_collection_params = DataCollectionParameters(
**sample_data.data_collection_parameters.dict(by_alias=True))
sample = Sample( sample = Sample(
puck_id=puck.id, puck_id=puck.id,
sample_name=sample_data.sample_name, sample_name=sample_data.sample_name,
position=sample_data.position, position=sample_data.position,
data_collection_parameters=data_collection_params
) )
self.db.add(sample) self.db.add(sample)
self.db.commit() self.db.commit()
self.db.refresh(sample) self.db.refresh(sample)
# Return a response wrapped in the ShipmentResponse schema
return ShipmentResponse( return ShipmentResponse(
shipment_id=new_shipment.id, shipment_id=new_shipment.id,
status="success", status="success",

View File

@ -42,6 +42,8 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
const [changesMade, setChangesMade] = useState(false); const [changesMade, setChangesMade] = useState(false);
const [feedbackMessage, setFeedbackMessage] = useState(''); const [feedbackMessage, setFeedbackMessage] = useState('');
const [openSnackbar, setOpenSnackbar] = useState(false); const [openSnackbar, setOpenSnackbar] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => { useEffect(() => {
const setInitialContactPerson = () => { const setInitialContactPerson = () => {
@ -92,22 +94,33 @@ const DewarDetails: React.FC<DewarDetailsProps> = ({
useEffect(() => { useEffect(() => {
const fetchSamples = async () => { const fetchSamples = async () => {
if (dewar.id && Array.isArray(dewar.pucks)) { if (dewar.id) {
try { try {
const samples = await ShipmentsService.getSamplesInDewarShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id); const fetchedSamples = await ShipmentsService.getSamplesInDewarShipmentsShipmentsShipmentIdDewarsDewarIdSamplesGet(shipmentId, dewar.id);
console.log("Fetched Samples: ", fetchedSamples);
const updatedPuckStatuses = dewar.pucks.map(puck => { const updatedPuckStatuses = dewar.pucks.map(puck => {
if (!Array.isArray(puck.positions)) return []; // Filter samples for the current puck
return puck.positions.map(position => { const puckSamples = fetchedSamples.filter(sample => sample.puck_id === puck.id);
const isOccupied = samples.some(sample => sample.id === position.id);
return isOccupied ? 'filled' : 'empty'; // Initialize positions as 'empty'
const statusArray = Array(16).fill('empty');
// Update positions based on puckSamples' positions
puckSamples.forEach(sample => {
if (sample.position >= 1 && sample.position <= 16) {
statusArray[sample.position - 1] = 'filled'; // Adjust for 0-based index
}
}); });
return statusArray;
}); });
setPuckStatuses(updatedPuckStatuses); setPuckStatuses(updatedPuckStatuses);
} catch (error) { } catch (error) {
setFeedbackMessage('Failed to load samples. Please try again later.'); setError('Failed to load samples. Please try again later.');
setOpenSnackbar(true); } finally {
setLoading(false);
} }
} }
}; };

View File

@ -190,6 +190,7 @@ const ShipmentPanel: React.FC<ShipmentPanelProps> = ({
<UploadDialog <UploadDialog
open={uploadDialogOpen} open={uploadDialogOpen}
onClose={closeUploadDialog} onClose={closeUploadDialog}
selectedShipment={selectedShipment} // <-- Pass selectedShipment here
/> />
</Box> </Box>
); );

View File

@ -13,11 +13,19 @@ import {
Button, Button,
Box Box
} from '@mui/material'; } from '@mui/material';
import { SpreadsheetService } from '../../openapi'; import { SpreadsheetService, ShipmentsService } from '../../openapi';
import * as ExcelJS from 'exceljs'; import * as ExcelJS from 'exceljs';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fileBlob }) => { const SpreadsheetTable = ({
raw_data,
errors,
headers,
setRawData,
onCancel,
fileBlob,
shipmentId // Accept the shipmentId
}) => {
const [localErrors, setLocalErrors] = useState(errors || []); const [localErrors, setLocalErrors] = useState(errors || []);
const [editingCell, setEditingCell] = useState({}); const [editingCell, setEditingCell] = useState({});
const [nonEditableCells, setNonEditableCells] = useState(new Set()); const [nonEditableCells, setNonEditableCells] = useState(new Set());
@ -25,7 +33,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil
const generateErrorMap = (errorsList) => { const generateErrorMap = (errorsList) => {
const errorMap = new Map(); const errorMap = new Map();
if (Array.isArray(errorsList)) { if (Array.isArray(errorsList)) {
errorsList.forEach(error => { errorsList.forEach((error) => {
const key = `${error.row}-${headers[error.cell]}`; const key = `${error.row}-${headers[error.cell]}`;
errorMap.set(key, error.message); errorMap.set(key, error.message);
}); });
@ -64,7 +72,7 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil
currentRow.data[colIndex] = newValue; currentRow.data[colIndex] = newValue;
setEditingCell(prev => { setEditingCell((prev) => {
const updated = { ...prev }; const updated = { ...prev };
delete updated[`${rowIndex}-${colIndex}`]; delete updated[`${rowIndex}-${colIndex}`];
return updated; return updated;
@ -80,10 +88,10 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil
if (response.is_valid !== undefined) { if (response.is_valid !== undefined) {
if (response.is_valid) { if (response.is_valid) {
const updatedErrors = localErrors.filter( const updatedErrors = localErrors.filter(
error => !(error.row === currentRow.row_num && error.cell === colIndex) (error) => !(error.row === currentRow.row_num && error.cell === colIndex)
); );
setLocalErrors(updatedErrors); setLocalErrors(updatedErrors);
setNonEditableCells(prev => new Set([...prev, `${rowIndex}-${colIndex}`])); setNonEditableCells((prev) => new Set([...prev, `${rowIndex}-${colIndex}`]));
} else { } else {
const updatedErrors = [ const updatedErrors = [
...localErrors, ...localErrors,
@ -107,11 +115,75 @@ const SpreadsheetTable = ({ raw_data, errors, headers, setRawData, onCancel, fil
const handleSubmit = async () => { const handleSubmit = async () => {
if (allCellsValid()) { if (allCellsValid()) {
console.log('All data is valid. Proceeding with submission...'); console.log('All data is valid. Proceeding with submission...');
const processedData = createPayload(raw_data);
try {
const response = await ShipmentsService.addDewarPuckSampleToShipmentShipmentsShipmentIdAddDewarPuckSamplePost(shipmentId, processedData);
console.log('Shipment processed successfully:', response);
// Handle success actions, e.g., display notification, reset state, etc.
} catch (error) {
console.error('Error processing shipment:', error);
// Handle error actions, e.g., display notification, etc.
}
} else { } else {
console.log('There are validation errors in the dataset. Please correct them before submission.'); console.log('There are validation errors in the dataset. Please correct them before submission.');
} }
}; };
const createPayload = (data) => {
const allowedFields = [
'priority', 'comments', 'directory', 'proteinname', 'oscillation', 'aperture',
'exposure', 'totalrange', 'transmission', 'dose', 'targetresolution', 'datacollectiontype',
'processingpipeline', 'spacegroupnumber', 'cellparameters', 'rescutkey', 'rescutvalue',
'userresolution', 'pdbid', 'autoprocfull', 'procfull', 'adpenabled', 'noano',
'ffcscampaign', 'trustedhigh', 'autoprocextraparams', 'chiphiangles'
];
let dewars = {};
data.forEach((row) => {
const dewarname = row.data[headers.indexOf('dewarname')];
const puckname = row.data[headers.indexOf('puckname')];
const crystalname = row.data[headers.indexOf('crystalname')];
const positioninpuck = row.data[headers.indexOf('positioninpuck')];
if (!dewars[dewarname]) {
dewars[dewarname] = {
dewarname: dewarname,
pucks: {}
};
}
if (!dewars[dewarname].pucks[puckname]) {
dewars[dewarname].pucks[puckname] = {
puckname: puckname,
samples: []
};
}
const dataCollectionParams = {};
headers.forEach((header, index) => {
if (allowedFields.includes(header)) {
dataCollectionParams[header] = row.data[index];
}
});
dewars[dewarname].pucks[puckname].samples.push({
crystalname: crystalname,
positioninpuck: positioninpuck,
data_collection_parameters: dataCollectionParams
});
});
const dewarsList = Object.values(dewars).map(dewar => ({
dewarname: dewar.dewarname,
pucks: Object.values(dewar.pucks)
}));
return { dewars: dewarsList };
};
const downloadCorrectedSpreadsheet = async () => { const downloadCorrectedSpreadsheet = async () => {
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(fileBlob); await workbook.xlsx.load(fileBlob);

View File

@ -8,11 +8,8 @@ interface UnipuckProps {
const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => { const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
const renderPuck = (sampleStatus: string[]) => { const renderPuck = (sampleStatus: string[]) => {
if (!sampleStatus) { sampleStatus = sampleStatus || Array(16).fill('empty'); // Ensure no null status array
sampleStatus = Array(16).fill('empty'); return (
}
const puckSVG = (
<svg width="100" height="100" viewBox="0 0 100 100"> <svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" /> <circle cx="50" cy="50" r="45" stroke="black" strokeWidth="2" fill="none" />
{[...Array(11)].map((_, index) => { {[...Array(11)].map((_, index) => {
@ -29,7 +26,6 @@ const Unipuck: React.FC<UnipuckProps> = ({ pucks, samples }) => {
})} })}
</svg> </svg>
); );
return puckSVG;
}; };
if (pucks === 0) { if (pucks === 0) {

View File

@ -23,9 +23,10 @@ import * as ExcelJS from 'exceljs';
interface UploadDialogProps { interface UploadDialogProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
selectedShipment: any; // Adjust the type based on your implementation
} }
const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => { const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose, selectedShipment }) => {
const [uploadError, setUploadError] = useState<string | null>(null); const [uploadError, setUploadError] = useState<string | null>(null);
const [fileSummary, setFileSummary] = useState<{ const [fileSummary, setFileSummary] = useState<{
data: any[]; data: any[];
@ -37,7 +38,7 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
pucks: string[]; pucks: string[];
samples_count: number; samples_count: number;
samples: string[]; samples: string[];
headers: string[]; // Headers must be part of this object headers: string[];
} | null>(null); } | null>(null);
const [fileBlob, setFileBlob] = useState<Blob | null>(null); // New state to store the file blob const [fileBlob, setFileBlob] = useState<Blob | null>(null); // New state to store the file blob
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@ -165,6 +166,7 @@ const UploadDialog: React.FC<UploadDialogProps> = ({ open, onClose }) => {
setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))} setRawData={(newRawData) => setFileSummary((prevSummary) => ({ ...prevSummary, raw_data: newRawData }))}
onCancel={handleCancel} onCancel={handleCancel}
fileBlob={fileBlob} // Pass the original file blob fileBlob={fileBlob} // Pass the original file blob
shipmentId={selectedShipment?.id} // Pass the selected shipment ID
/> />
</Modal> </Modal>
)} )}