Skip to content

Commit 5d95c96

Browse files
committed
Automatically update shipment status, return dewar history
1 parent 127e247 commit 5d95c96

File tree

19 files changed

+387
-72
lines changed

19 files changed

+387
-72
lines changed

.github/workflows/_integration.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,30 @@ on:
77
required: true
88

99
jobs:
10+
get-db-image:
11+
runs-on: ubuntu-latest
12+
env:
13+
BRANCH_NAME: ${{ github.ref_type == 'tag' && 'master' || (github.head_ref || github.ref_name) }}
14+
outputs:
15+
branch: ${{ steps.clean.outputs.branch }}
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
- id: clean
20+
name: "Clean branch name"
21+
continue-on-error: true
22+
run: |
23+
git fetch
24+
if git diff --name-only --exit-code master HEAD database/; then
25+
echo "branch=${BRANCH_NAME//\//-}" >> "$GITHUB_OUTPUT"
26+
else
27+
echo "branch=master" >> "$GITHUB_OUTPUT"
28+
fi
1029
test:
1130
runs-on: ubuntu-latest
1231
services:
1332
integration-db:
14-
image: ghcr.io/diamondlightsource/scaup-backend-db:master
33+
image: ghcr.io/diamondlightsource/scaup-backend-db:${{ needs.get-db-image.outputs.branch }}
1534
ports:
1635
- 5432:5432
1736
strategy:

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ repos:
1010
hooks:
1111
- id: format
1212
name: Format with ruff
13-
stages: [commit]
13+
stages: [pre-commit]
1414
language: system
1515
entry: ruff format --check --diff
1616
exclude: alembic
1717
types: [python]
1818

1919
- id: ruff
2020
name: Run ruff
21-
stages: [commit]
21+
stages: [pre-commit]
2222
language: system
2323
entry: ruff check
2424
exclude: alembic
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Add lastStatusUpdate column
2+
3+
Revision ID: dea9c772d734
4+
Revises: 297144dfe234
5+
Create Date: 2025-06-04 09:21:34.341747
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'dea9c772d734'
16+
down_revision: Union[str, None] = '297144dfe234'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.add_column('Shipment', sa.Column('lastStatusUpdate', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False))
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_column('Shipment', 'lastStatusUpdate')
30+
# ### end Alembic commands ###

database/data.sql

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
-- PostgreSQL database dump
33
--
44

5-
-- Dumped from database version 17.2
6-
-- Dumped by pg_dump version 17.2
5+
-- Dumped from database version 17.4
6+
-- Dumped by pg_dump version 17.4
77

88
SET statement_timeout = 0;
99
SET lock_timeout = 0;
@@ -255,7 +255,8 @@ CREATE TABLE public."Shipment" (
255255
comments character varying(255),
256256
"proposalCode" character varying(2) NOT NULL,
257257
"proposalNumber" integer NOT NULL,
258-
"visitNumber" integer NOT NULL
258+
"visitNumber" integer NOT NULL,
259+
"lastStatusUpdate" timestamp with time zone DEFAULT now() NOT NULL
259260
);
260261

261262

@@ -461,17 +462,17 @@ COPY public."SampleParentChild" ("parentId", "childId", "creationDate") FROM std
461462
-- Data for Name: Shipment; Type: TABLE DATA; Schema: public; Owner: sample_handling
462463
--
463464

464-
COPY public."Shipment" ("shipmentId", "creationDate", "shipmentRequest", status, name, "externalId", comments, "proposalCode", "proposalNumber", "visitNumber") FROM stdin;
465-
1 2024-05-02 13:12:36.528788+00 \N \N Shipment_01 \N \N cm 1 1
466-
2 2024-05-02 13:12:36.528788+00 \N \N Shipment_02 123 \N cm 2 1
467-
89 2024-05-02 13:12:36.528788+00 \N Booked Shipment_03 256 \N cm 2 1
468-
97 2024-06-26 12:55:39.211687+00 \N \N Shipment_04 \N \N cm 3 1
469-
106 2024-06-26 12:55:39.211687+00 \N \N Shipment_05 789 \N cm 3 1
470-
117 2024-06-26 13:36:53.632782+00 1 Booked 1 63975 \N bi 23047 100
471-
118 2024-06-26 13:40:32.191664+00 \N \N 2 \N \N bi 23047 100
472-
126 2024-07-15 15:35:32.472987+00 \N \N 1 \N \N bi 23047 99
473-
204 2024-07-15 15:35:32.472987+00 \N Created 3 \N \N bi 23047 99
474-
229 2025-01-10 08:54:23.171217+00 \N Created 100 \N \N bi 23047 102
465+
COPY public."Shipment" ("shipmentId", "creationDate", "shipmentRequest", status, name, "externalId", comments, "proposalCode", "proposalNumber", "visitNumber", "lastStatusUpdate") FROM stdin;
466+
1 2024-05-02 13:12:36.528788+00 \N \N Shipment_01 \N \N cm 1 1 2025-06-09 08:29:07.995804+00
467+
2 2024-05-02 13:12:36.528788+00 \N \N Shipment_02 123 \N cm 2 1 2025-06-09 08:29:07.995804+00
468+
89 2024-05-02 13:12:36.528788+00 \N Booked Shipment_03 256 \N cm 2 1 2025-06-09 08:29:07.995804+00
469+
97 2024-06-26 12:55:39.211687+00 \N \N Shipment_04 \N \N cm 3 1 2025-06-09 08:29:07.995804+00
470+
106 2024-06-26 12:55:39.211687+00 \N \N Shipment_05 789 \N cm 3 1 2025-06-09 08:29:07.995804+00
471+
118 2024-06-26 13:40:32.191664+00 \N \N 2 \N \N bi 23047 100 2025-06-09 08:29:07.995804+00
472+
126 2024-07-15 15:35:32.472987+00 \N \N 1 \N \N bi 23047 99 2025-06-09 08:29:07.995804+00
473+
204 2024-07-15 15:35:32.472987+00 \N Created 3 \N \N bi 23047 99 2025-06-09 08:29:07.995804+00
474+
229 2025-01-10 08:54:23.171217+00 \N Created 100 \N \N bi 23047 102 2025-06-09 08:29:07.995804+00
475+
117 2025-06-05 14:15:42.285+00 1 at facility 1 63975 \N bi 23047 100 2025-06-05 14:15:42.285+00
475476
\.
476477

477478

@@ -497,43 +498,43 @@ COPY public."TopLevelContainer" ("topLevelContainerId", "shipmentId", details, c
497498
--
498499

499500
COPY public.alembic_version (version_num) FROM stdin;
500-
297144dfe234
501+
dea9c772d734
501502
\.
502503

503504

504505
--
505506
-- Name: Container_containerId_seq; Type: SEQUENCE SET; Schema: public; Owner: sample_handling
506507
--
507508

508-
SELECT pg_catalog.setval('public."Container_containerId_seq"', 2039, true);
509+
SELECT pg_catalog.setval('public."Container_containerId_seq"', 2238, true);
509510

510511

511512
--
512513
-- Name: PreSession_preSessionId_seq; Type: SEQUENCE SET; Schema: public; Owner: sample_handling
513514
--
514515

515-
SELECT pg_catalog.setval('public."PreSession_preSessionId_seq"', 379, true);
516+
SELECT pg_catalog.setval('public."PreSession_preSessionId_seq"', 423, true);
516517

517518

518519
--
519520
-- Name: Sample_sampleId_seq; Type: SEQUENCE SET; Schema: public; Owner: sample_handling
520521
--
521522

522-
SELECT pg_catalog.setval('public."Sample_sampleId_seq"', 2096, true);
523+
SELECT pg_catalog.setval('public."Sample_sampleId_seq"', 2558, true);
523524

524525

525526
--
526527
-- Name: Shipment_shipmentId_seq; Type: SEQUENCE SET; Schema: public; Owner: sample_handling
527528
--
528529

529-
SELECT pg_catalog.setval('public."Shipment_shipmentId_seq"', 286, true);
530+
SELECT pg_catalog.setval('public."Shipment_shipmentId_seq"', 307, true);
530531

531532

532533
--
533534
-- Name: TopLevelContainer_topLevelContainerId_seq; Type: SEQUENCE SET; Schema: public; Owner: sample_handling
534535
--
535536

536-
SELECT pg_catalog.setval('public."TopLevelContainer_topLevelContainerId_seq"', 825, true);
537+
SELECT pg_catalog.setval('public."TopLevelContainer_topLevelContainerId_seq"', 980, true);
537538

538539

539540
--

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dev = [
4141
"pytest",
4242
"pytest-cov",
4343
"ruff",
44+
"freezegun~=1.5.2",
4445
"sphinx-autobuild",
4546
"sphinx-copybutton",
4647
"sphinx-design",

src/scaup/crud/proposals.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from typing import List
22

33
from fastapi import HTTPException, status
4-
from lims_utils.models import ProposalReference
5-
from sqlalchemy import case, insert, select
4+
from lims_utils.models import Paged, ProposalReference
5+
from sqlalchemy import insert, select
66
from sqlalchemy.exc import MultipleResultsFound
77

88
from ..models.inner_db.tables import Sample, Shipment
99
from ..models.samples import SublocationAssignment
1010
from ..models.shipments import ShipmentIn
1111
from ..utils.crud import assign_dcg_to_sublocation
12-
from ..utils.database import inner_db, paginate, unravel
12+
from ..utils.database import inner_db
13+
from ..utils.external import update_shipment_statuses
1314

1415

1516
def create_shipment(proposal_reference: ProposalReference, params: ShipmentIn):
@@ -30,17 +31,17 @@ def create_shipment(proposal_reference: ProposalReference, params: ShipmentIn):
3031
return new_shipment
3132

3233

33-
def get_shipments(proposal_reference: ProposalReference, limit: int, page: int):
34-
query = select(
35-
*unravel(Shipment),
36-
case((Shipment.externalId.is_not(None), "submitted"), else_="draft").label("creationStatus"),
37-
).filter(
34+
def get_shipments(token: str, proposal_reference: ProposalReference, limit: int, page: int):
35+
query = select(Shipment).filter(
3836
Shipment.proposalCode == proposal_reference.code,
3937
Shipment.proposalNumber == proposal_reference.number,
4038
Shipment.visitNumber == proposal_reference.visit_number,
4139
)
4240

43-
return paginate(query, limit, page, slow_count=False)
41+
shipments: Paged[Shipment] = inner_db.paginate(query, limit, page, slow_count=False, scalar=False)
42+
shipments.items = update_shipment_statuses(shipments.items, token)
43+
44+
return shipments
4445

4546

4647
def assign_dcg_to_sublocation_in_session(session: ProposalReference, parameters: List[SublocationAssignment]):

src/scaup/crud/shipments.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ..utils.config import Config
2525
from ..utils.crud import assert_no_unassigned, assign_dcg_to_sublocation
2626
from ..utils.database import inner_db
27-
from ..utils.external import TYPE_TO_SHIPPING_SERVICE_TYPE, Expeye, ExternalRequest
27+
from ..utils.external import TYPE_TO_SHIPPING_SERVICE_TYPE, Expeye, ExternalRequest, update_shipment_statuses
2828
from ..utils.query import query_result_to_object
2929

3030

@@ -44,7 +44,19 @@ def _get_shipment_tree(shipmentId: int):
4444
return raw_shipment_data
4545

4646

47-
def get_shipment(shipmentId: int):
47+
def get_shipment(token: str, shipmentId: int, get_children: bool = False):
48+
if not get_children:
49+
shipment = inner_db.session.execute(select(Shipment).filter(Shipment.id == shipmentId)).scalar_one()
50+
51+
shipment = update_shipment_statuses(shipments=[shipment], token=token)[0]
52+
53+
return ShipmentChildren(
54+
id=shipmentId,
55+
name=shipment.name,
56+
children=[],
57+
data=ShipmentOut.model_validate(shipment, from_attributes=True).model_dump(mode="json"),
58+
)
59+
4860
raw_shipment_data = _get_shipment_tree(shipmentId)
4961

5062
return ShipmentChildren(

src/scaup/crud/top_level_containers.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
from fastapi import HTTPException, status
22
from lims_utils.logging import app_logger
3+
from lims_utils.models import Paged
34
from sqlalchemy import func, insert, select
45

56
from ..models.inner_db.tables import Shipment, TopLevelContainer
6-
from ..models.top_level_containers import OptionalTopLevelContainer, TopLevelContainerIn
7+
from ..models.top_level_containers import (
8+
OptionalTopLevelContainer,
9+
TopLevelContainerHistory,
10+
TopLevelContainerIn,
11+
TopLevelContainerOut,
12+
)
713
from ..utils.config import Config
814
from ..utils.crud import assert_not_booked, edit_item
9-
from ..utils.database import inner_db, paginate
15+
from ..utils.database import inner_db
1016
from ..utils.external import ExternalRequest
1117
from ..utils.session import retry_if_exists
1218

@@ -119,7 +125,31 @@ def edit_top_level_container(topLevelContainerId: int, params: OptionalTopLevelC
119125
return edit_item(TopLevelContainer, params, topLevelContainerId, token)
120126

121127

122-
def get_top_level_containers(shipmentId: int, limit: int, page: int):
128+
def get_top_level_containers(shipmentId: int, token: str, limit: int, page: int):
123129
query = select(TopLevelContainer).filter(TopLevelContainer.shipmentId == shipmentId).join(Shipment)
124130

125-
return paginate(query, limit, page, slow_count=False, scalar=False)
131+
top_level_containers: Paged[TopLevelContainer | TopLevelContainerOut] = inner_db.paginate(
132+
query, limit, page, slow_count=False, scalar=False
133+
)
134+
135+
for i, tlc in enumerate(top_level_containers.items):
136+
if tlc.externalId is not None:
137+
response = ExternalRequest.request(token=token, url=f"/dewars/{tlc.externalId}/history")
138+
139+
if response.status_code == 200:
140+
# More than 25 items could be returned, but it is statistically unlikely
141+
# (less than 2.2% of dewars have 25 history items or more, and most of these
142+
# are commissioning proposals), so we'll disregard that for now
143+
new_tlc = TopLevelContainerOut.model_validate(tlc, from_attributes=True)
144+
new_tlc.history = [TopLevelContainerHistory.model_validate(item) for item in response.json()["items"]]
145+
146+
top_level_containers.items[i] = new_tlc
147+
else:
148+
app_logger.warning(
149+
"Failed to get history from ISPyB for dewar %i (external ID: %i): %s",
150+
tlc.id,
151+
tlc.externalId,
152+
response.text,
153+
)
154+
155+
return top_level_containers

src/scaup/models/inner_db/tables.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Shipment(Base, BaseColumns):
4242

4343
shipmentRequest: Mapped[int | None] = mapped_column()
4444
status: Mapped[str | None] = mapped_column(String(25), server_default="Created")
45+
lastStatusUpdate: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
4546

4647

4748
class TopLevelContainer(Base, BaseColumns):

src/scaup/models/shipments.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Any, Literal, Optional
2+
from typing import Any, Optional
33

44
from pydantic import BaseModel, ConfigDict, Field, model_validator
55

@@ -34,13 +34,8 @@ class ShipmentOut(BaseModel):
3434
creationDate: Optional[datetime]
3535
status: Optional[str] = None
3636
shipmentRequest: Optional[int] = None
37-
38-
39-
class MixedShipment(ShipmentOut):
40-
id: int = Field(validation_alias="shipmentId")
41-
creationStatus: Literal["draft", "submitted"] = "draft"
42-
43-
model_config = ConfigDict(arbitrary_types_allowed=True)
37+
lastStatusUpdate: datetime
38+
externalId: int | None = None
4439

4540

4641
class GenericItemData(BaseModel):

0 commit comments

Comments
 (0)