mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-10-02 16:43:23 +00:00
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
|
|
"""Read access to the work-order tables for the procurement API.
|
||
|
|
|
||
|
|
Listing is a cursor-paginated Scan -- deliberately: the WO tables carry no
|
||
|
|
GSIs (removed 2026-06-03 for zero reads), and the API's list consumers
|
||
|
|
(SHOC reconciliation/backfill) walk the full table anyway, which is exactly
|
||
|
|
the access pattern SHOC's retired SyncController used. Listings are therefore
|
||
|
|
UNORDERED across pages; per-work-order comment listing is a cheap Query on
|
||
|
|
the partition key.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
|
||
|
|
import boto3
|
||
|
|
from boto3.dynamodb.conditions import Key
|
||
|
|
|
||
|
|
from pagination import BadCursor, decode_cursor, encode_cursor
|
||
|
|
|
||
|
|
dynamodb = boto3.resource("dynamodb")
|
||
|
|
|
||
|
|
WORK_ORDERS_TABLE = os.environ.get("WORK_ORDERS_TABLE", "WorkOrders")
|
||
|
|
COMMENTS_TABLE = os.environ.get("COMMENTS_TABLE", "WorkOrderComments")
|
||
|
|
|
||
|
|
WO_KEY_ATTRS = frozenset({"work_order_id"})
|
||
|
|
COMMENT_KEY_ATTRS = frozenset({"work_order_id", "comment_id"})
|
||
|
|
|
||
|
|
|
||
|
|
def _page(response) -> tuple[list, str | None]:
|
||
|
|
items = response.get("Items", [])
|
||
|
|
lek = response.get("LastEvaluatedKey")
|
||
|
|
return items, (encode_cursor(lek) if lek else None)
|
||
|
|
|
||
|
|
|
||
|
|
def list_work_orders(limit: int, cursor: str | None) -> tuple[list, str | None]:
|
||
|
|
table = dynamodb.Table(WORK_ORDERS_TABLE)
|
||
|
|
kwargs = {"Limit": limit}
|
||
|
|
if cursor:
|
||
|
|
kwargs["ExclusiveStartKey"] = decode_cursor(cursor, WO_KEY_ATTRS)
|
||
|
|
return _page(table.scan(**kwargs))
|
||
|
|
|
||
|
|
|
||
|
|
def get_work_order(work_order_id: str) -> dict | None:
|
||
|
|
table = dynamodb.Table(WORK_ORDERS_TABLE)
|
||
|
|
return table.get_item(Key={"work_order_id": work_order_id}).get("Item")
|
||
|
|
|
||
|
|
|
||
|
|
def list_comments(
|
||
|
|
work_order_id: str, limit: int, cursor: str | None
|
||
|
|
) -> tuple[list, str | None]:
|
||
|
|
table = dynamodb.Table(COMMENTS_TABLE)
|
||
|
|
kwargs = {
|
||
|
|
"KeyConditionExpression": Key("work_order_id").eq(work_order_id),
|
||
|
|
"Limit": limit,
|
||
|
|
}
|
||
|
|
if cursor:
|
||
|
|
start_key = decode_cursor(cursor, COMMENT_KEY_ATTRS)
|
||
|
|
# Pin the cursor's partition to the path entity: a cursor minted for
|
||
|
|
# WO A must never resume WO B's Query (DynamoDB would reject the
|
||
|
|
# partition mismatch as a 500; reject it as a 400 here instead).
|
||
|
|
if start_key["work_order_id"] != work_order_id:
|
||
|
|
raise BadCursor("cursor is not valid")
|
||
|
|
kwargs["ExclusiveStartKey"] = start_key
|
||
|
|
return _page(table.query(**kwargs))
|