@freighttech/shipment-tracking 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +25 -0
- package/dist/generated/entities/bic_config/index.js +21 -0
- package/dist/generated/entities/bic_config/index.js.map +7 -0
- package/dist/generated/entities/carrier_config/index.js +27 -0
- package/dist/generated/entities/carrier_config/index.js.map +7 -0
- package/dist/generated/entities/location_override/index.js +33 -0
- package/dist/generated/entities/location_override/index.js.map +7 -0
- package/dist/generated/entities/shipment/index.js +73 -0
- package/dist/generated/entities/shipment/index.js.map +7 -0
- package/dist/generated/entities/ships_go_config/index.js +31 -0
- package/dist/generated/entities/ships_go_config/index.js.map +7 -0
- package/dist/generated/entities/tracking_event/index.js +101 -0
- package/dist/generated/entities/tracking_event/index.js.map +7 -0
- package/dist/generated/entities/tracking_job/index.js +47 -0
- package/dist/generated/entities/tracking_job/index.js.map +7 -0
- package/dist/generated/entities/webhook/index.js +25 -0
- package/dist/generated/entities/webhook/index.js.map +7 -0
- package/dist/generated/entities/webhook_delivery/index.js +25 -0
- package/dist/generated/entities/webhook_delivery/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +21 -0
- package/dist/generated/entities.ids.generated.js.map +7 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/shipment_tracking/acl.js +22 -0
- package/dist/modules/shipment_tracking/acl.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js +176 -0
- package/dist/modules/shipment_tracking/api/bic-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js +95 -0
- package/dist/modules/shipment_tracking/api/bic-configs/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js +93 -0
- package/dist/modules/shipment_tracking/api/cargo-events/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js +139 -0
- package/dist/modules/shipment_tracking/api/carrier-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js +177 -0
- package/dist/modules/shipment_tracking/api/dashboard/ships-map/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js +179 -0
- package/dist/modules/shipment_tracking/api/location-overrides/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/openapi.js +27 -0
- package/dist/modules/shipment_tracking/api/openapi.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js +192 -0
- package/dist/modules/shipment_tracking/api/shipments/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js +185 -0
- package/dist/modules/shipment_tracking/api/shipsgo-configs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js +144 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js +107 -0
- package/dist/modules/shipment_tracking/api/tracking-jobs/sync/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/utils.js +12 -0
- package/dist/modules/shipment_tracking/api/utils.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js +76 -0
- package/dist/modules/shipment_tracking/api/webhooks/deliveries/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js +135 -0
- package/dist/modules/shipment_tracking/api/webhooks/route.js.map +7 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js +71 -0
- package/dist/modules/shipment_tracking/api/webhooks/test/route.js.map +7 -0
- package/dist/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/dist/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/dist/modules/shipment_tracking/assets/index.js +52 -0
- package/dist/modules/shipment_tracking/assets/index.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js +621 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/carrier-configs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/location-overrides/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js +279 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/shipment-tracking/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js +10 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/tracking-jobs/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js +198 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.js.map +7 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js +19 -0
- package/dist/modules/shipment_tracking/backend/webhooks/page.meta.js.map +7 -0
- package/dist/modules/shipment_tracking/ce.js +52 -0
- package/dist/modules/shipment_tracking/ce.js.map +7 -0
- package/dist/modules/shipment_tracking/cli.js +229 -0
- package/dist/modules/shipment_tracking/cli.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js +66 -0
- package/dist/modules/shipment_tracking/commands/bic-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js +65 -0
- package/dist/modules/shipment_tracking/commands/carrier-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/index.js +9 -0
- package/dist/modules/shipment_tracking/commands/index.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js +93 -0
- package/dist/modules/shipment_tracking/commands/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipments.js +200 -0
- package/dist/modules/shipment_tracking/commands/shipments.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js +75 -0
- package/dist/modules/shipment_tracking/commands/shipsgo-configs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js +120 -0
- package/dist/modules/shipment_tracking/commands/tracking-jobs.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/tracking.js +140 -0
- package/dist/modules/shipment_tracking/commands/tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js +115 -0
- package/dist/modules/shipment_tracking/commands/webhooks.js.map +7 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js +168 -0
- package/dist/modules/shipment_tracking/components/CombinedTimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js +202 -0
- package/dist/modules/shipment_tracking/components/FacilityLocationInput.js.map +7 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js +501 -0
- package/dist/modules/shipment_tracking/components/LocationOverrideDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js +818 -0
- package/dist/modules/shipment_tracking/components/ShipmentDetailsDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js +404 -0
- package/dist/modules/shipment_tracking/components/ShipmentDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js +200 -0
- package/dist/modules/shipment_tracking/components/ShipsGoConfigSection.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js +108 -0
- package/dist/modules/shipment_tracking/components/TimestampCell.js.map +7 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js +343 -0
- package/dist/modules/shipment_tracking/components/TrackingJobDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js +496 -0
- package/dist/modules/shipment_tracking/components/VesselTrackingMap.js.map +7 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js +332 -0
- package/dist/modules/shipment_tracking/components/WebhookDrawer.js.map +7 -0
- package/dist/modules/shipment_tracking/data/entities.js +686 -0
- package/dist/modules/shipment_tracking/data/entities.js.map +7 -0
- package/dist/modules/shipment_tracking/data/validators.js +434 -0
- package/dist/modules/shipment_tracking/data/validators.js.map +7 -0
- package/dist/modules/shipment_tracking/di.js +60 -0
- package/dist/modules/shipment_tracking/di.js.map +7 -0
- package/dist/modules/shipment_tracking/encryption.js +12 -0
- package/dist/modules/shipment_tracking/encryption.js.map +7 -0
- package/dist/modules/shipment_tracking/events.js +87 -0
- package/dist/modules/shipment_tracking/events.js.map +7 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js +137 -0
- package/dist/modules/shipment_tracking/hooks/useVesselTracking.js.map +7 -0
- package/dist/modules/shipment_tracking/i18n/de.json +327 -0
- package/dist/modules/shipment_tracking/i18n/en.json +327 -0
- package/dist/modules/shipment_tracking/i18n/es.json +327 -0
- package/dist/modules/shipment_tracking/i18n/pl.json +327 -0
- package/dist/modules/shipment_tracking/index.js +15 -0
- package/dist/modules/shipment_tracking/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js +66 -0
- package/dist/modules/shipment_tracking/lib/adapters/cma-cgm.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js +1 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js +236 -0
- package/dist/modules/shipment_tracking/lib/adapters/cosco.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js +87 -0
- package/dist/modules/shipment_tracking/lib/adapters/evergreen.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js +68 -0
- package/dist/modules/shipment_tracking/lib/adapters/hapag-lloyd.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js +20 -0
- package/dist/modules/shipment_tracking/lib/adapters/index.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js +84 -0
- package/dist/modules/shipment_tracking/lib/adapters/maersk.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js +170 -0
- package/dist/modules/shipment_tracking/lib/adapters/msc.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js +85 -0
- package/dist/modules/shipment_tracking/lib/adapters/zim.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js +20 -0
- package/dist/modules/shipment_tracking/lib/audit-helpers.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js +8 -0
- package/dist/modules/shipment_tracking/lib/auth/base64url.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js +38 -0
- package/dist/modules/shipment_tracking/lib/auth/oauth-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/background.js +15 -0
- package/dist/modules/shipment_tracking/lib/background.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js +106 -0
- package/dist/modules/shipment_tracking/lib/bic-api-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js +17 -0
- package/dist/modules/shipment_tracking/lib/carrier-adapter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js +149 -0
- package/dist/modules/shipment_tracking/lib/current-vessel.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js +124 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-mapping.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js +112 -0
- package/dist/modules/shipment_tracking/lib/dcsa-event-parser.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js +16 -0
- package/dist/modules/shipment_tracking/lib/dcsa-params.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js +48 -0
- package/dist/modules/shipment_tracking/lib/empty-return.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js +85 -0
- package/dist/modules/shipment_tracking/lib/location-overrides.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/location-types.js +59 -0
- package/dist/modules/shipment_tracking/lib/location-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/logger.js +113 -0
- package/dist/modules/shipment_tracking/lib/logger.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js +183 -0
- package/dist/modules/shipment_tracking/lib/poi-event-processor.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js +52 -0
- package/dist/modules/shipment_tracking/lib/poi-nats-config.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js +43 -0
- package/dist/modules/shipment_tracking/lib/poi-types.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js +31 -0
- package/dist/modules/shipment_tracking/lib/rate-limiter.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js +187 -0
- package/dist/modules/shipment_tracking/lib/route-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js +106 -0
- package/dist/modules/shipment_tracking/lib/route-inference.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js +63 -0
- package/dist/modules/shipment_tracking/lib/schedule-generator.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js +143 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-client.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js +204 -0
- package/dist/modules/shipment_tracking/lib/shipsgo-mapper.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js +131 -0
- package/dist/modules/shipment_tracking/lib/status-machine.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js +41 -0
- package/dist/modules/shipment_tracking/lib/time-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js +100 -0
- package/dist/modules/shipment_tracking/lib/timestamp-utils.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js +111 -0
- package/dist/modules/shipment_tracking/lib/vessel-api.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js +50 -0
- package/dist/modules/shipment_tracking/lib/vessel-cache.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js +13 -0
- package/dist/modules/shipment_tracking/lib/vessel-extraction.js.map +7 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js +62 -0
- package/dist/modules/shipment_tracking/lib/webhook-dispatcher.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js +48 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js +15 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.js.map +7 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js +47 -0
- package/dist/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.js.map +7 -0
- package/dist/modules/shipment_tracking/notifications.js +145 -0
- package/dist/modules/shipment_tracking/notifications.js.map +7 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js +24 -0
- package/dist/modules/shipment_tracking/services/carrierRegistry.js.map +7 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js +397 -0
- package/dist/modules/shipment_tracking/services/poiNatsService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js +163 -0
- package/dist/modules/shipment_tracking/services/shipsGoProvider.js.map +7 -0
- package/dist/modules/shipment_tracking/services/trackingService.js +1283 -0
- package/dist/modules/shipment_tracking/services/trackingService.js.map +7 -0
- package/dist/modules/shipment_tracking/services/webhookService.js +263 -0
- package/dist/modules/shipment_tracking/services/webhookService.js.map +7 -0
- package/dist/modules/shipment_tracking/setup.js +72 -0
- package/dist/modules/shipment_tracking/setup.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js +60 -0
- package/dist/modules/shipment_tracking/subscribers/cargo-event-created.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js +58 -0
- package/dist/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js +82 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-notification.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js +53 -0
- package/dist/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js +28 -0
- package/dist/modules/shipment_tracking/subscribers/shipment-status-changed.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js +67 -0
- package/dist/modules/shipment_tracking/subscribers/terminal-event-ingest.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js +30 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-initial-poll.js.map +7 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js +19 -0
- package/dist/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/index.js +6 -0
- package/dist/modules/shipment_tracking/tables/index.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js +284 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.client.js.map +7 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js +36 -0
- package/dist/modules/shipment_tracking/tables/tracking_job/table.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js +25 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/config.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js +355 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.js.map +7 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js +26 -0
- package/dist/modules/shipment_tracking/widgets/dashboard/ships-map/widget.js.map +7 -0
- package/dist/types/assets.d.js +1 -0
- package/dist/types/assets.d.js.map +7 -0
- package/generated/entities/bic_config/index.ts +20 -0
- package/generated/entities/carrier_config/index.ts +23 -0
- package/generated/entities/location_override/index.ts +26 -0
- package/generated/entities/shipment/index.ts +46 -0
- package/generated/entities/ships_go_config/index.ts +25 -0
- package/generated/entities/tracking_event/index.ts +60 -0
- package/generated/entities/tracking_job/index.ts +33 -0
- package/generated/entities/webhook/index.ts +22 -0
- package/generated/entities/webhook_delivery/index.ts +22 -0
- package/generated/entities.ids.generated.ts +47 -0
- package/package.json +81 -0
- package/src/index.ts +39 -0
- package/src/modules/shipment_tracking/__integration__/TC-TRACK-410-shipsgo-config-api.spec.ts +133 -0
- package/src/modules/shipment_tracking/__integration__/meta.ts +4 -0
- package/src/modules/shipment_tracking/__tests__/empty-return-stop.test.ts +231 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/hapag-lloyd-transship-container.json +702 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/index.ts +434 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-multi-transship-completed.json +1518 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/maersk-transship-intransit.json +558 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-direct.json +222 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi-transship-completed.json +974 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-multi.json +4846 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/msc-transship.json +409 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/poi-events.json +240 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic/index.ts +138 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-eta-updates.json +288 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-route-inference.json +201 -0
- package/src/modules/shipment_tracking/__tests__/fixtures/synthetic-voyage-progression.json +410 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-air-mapper.test.ts +157 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-client.test.ts +155 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-mapper.test.ts +201 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-poll.test.ts +357 -0
- package/src/modules/shipment_tracking/__tests__/shipsgo-provider.test.ts +364 -0
- package/src/modules/shipment_tracking/__tests__/trackingService.integration.test.ts +2248 -0
- package/src/modules/shipment_tracking/__tests__/webhookPayload.test.ts +151 -0
- package/src/modules/shipment_tracking/acl.ts +18 -0
- package/src/modules/shipment_tracking/api/bic-configs/route.ts +206 -0
- package/src/modules/shipment_tracking/api/bic-configs/test/route.ts +108 -0
- package/src/modules/shipment_tracking/api/cargo-events/route.ts +101 -0
- package/src/modules/shipment_tracking/api/carrier-configs/route.ts +142 -0
- package/src/modules/shipment_tracking/api/dashboard/ships-map/route.ts +246 -0
- package/src/modules/shipment_tracking/api/location-overrides/route.ts +185 -0
- package/src/modules/shipment_tracking/api/openapi.ts +28 -0
- package/src/modules/shipment_tracking/api/shipments/route.ts +197 -0
- package/src/modules/shipment_tracking/api/shipsgo-configs/route.ts +215 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/route.ts +149 -0
- package/src/modules/shipment_tracking/api/tracking-jobs/sync/route.ts +116 -0
- package/src/modules/shipment_tracking/api/utils.ts +10 -0
- package/src/modules/shipment_tracking/api/webhooks/deliveries/route.ts +82 -0
- package/src/modules/shipment_tracking/api/webhooks/route.ts +138 -0
- package/src/modules/shipment_tracking/api/webhooks/test/route.ts +84 -0
- package/src/modules/shipment_tracking/assets/carriers/cma_cgm.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/cosco.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/evergreen.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/hapag_lloyd.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/maersk.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/msc.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/one.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/oocl.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/yang_ming.png +0 -0
- package/src/modules/shipment_tracking/assets/carriers/zim.png +0 -0
- package/src/modules/shipment_tracking/assets/index.ts +66 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/carrier-configs/page.tsx +782 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/location-overrides/page.tsx +241 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/shipment-tracking/page.tsx +340 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/tracking-jobs/page.tsx +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.meta.ts +16 -0
- package/src/modules/shipment_tracking/backend/webhooks/page.tsx +228 -0
- package/src/modules/shipment_tracking/ce.ts +48 -0
- package/src/modules/shipment_tracking/cli.ts +321 -0
- package/src/modules/shipment_tracking/commands/bic-configs.ts +94 -0
- package/src/modules/shipment_tracking/commands/carrier-configs.ts +89 -0
- package/src/modules/shipment_tracking/commands/index.ts +8 -0
- package/src/modules/shipment_tracking/commands/location-overrides.ts +119 -0
- package/src/modules/shipment_tracking/commands/shipments.ts +247 -0
- package/src/modules/shipment_tracking/commands/shipsgo-configs.ts +101 -0
- package/src/modules/shipment_tracking/commands/tracking-jobs.ts +158 -0
- package/src/modules/shipment_tracking/commands/tracking.ts +230 -0
- package/src/modules/shipment_tracking/commands/webhooks.ts +159 -0
- package/src/modules/shipment_tracking/components/CombinedTimestampCell.tsx +236 -0
- package/src/modules/shipment_tracking/components/FacilityLocationInput.tsx +235 -0
- package/src/modules/shipment_tracking/components/LocationOverrideDrawer.tsx +586 -0
- package/src/modules/shipment_tracking/components/ShipmentDetailsDrawer.tsx +1364 -0
- package/src/modules/shipment_tracking/components/ShipmentDrawer.tsx +498 -0
- package/src/modules/shipment_tracking/components/ShipsGoConfigSection.tsx +266 -0
- package/src/modules/shipment_tracking/components/TimestampCell.tsx +146 -0
- package/src/modules/shipment_tracking/components/TrackingJobDrawer.tsx +467 -0
- package/src/modules/shipment_tracking/components/VesselTrackingMap.tsx +761 -0
- package/src/modules/shipment_tracking/components/WebhookDrawer.tsx +434 -0
- package/src/modules/shipment_tracking/data/entities.ts +748 -0
- package/src/modules/shipment_tracking/data/validators.ts +523 -0
- package/src/modules/shipment_tracking/di.ts +73 -0
- package/src/modules/shipment_tracking/encryption.ts +17 -0
- package/src/modules/shipment_tracking/events.ts +122 -0
- package/src/modules/shipment_tracking/hooks/useVesselTracking.ts +297 -0
- package/src/modules/shipment_tracking/i18n/de.json +327 -0
- package/src/modules/shipment_tracking/i18n/en.json +327 -0
- package/src/modules/shipment_tracking/i18n/es.json +327 -0
- package/src/modules/shipment_tracking/i18n/pl.json +327 -0
- package/src/modules/shipment_tracking/index.ts +13 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-mapping.test.ts +385 -0
- package/src/modules/shipment_tracking/lib/__tests__/dcsa-event-parser.test.ts +398 -0
- package/src/modules/shipment_tracking/lib/__tests__/empty-return.test.ts +174 -0
- package/src/modules/shipment_tracking/lib/__tests__/poi-event-processor.test.ts +699 -0
- package/src/modules/shipment_tracking/lib/__tests__/route-inference.test.ts +402 -0
- package/src/modules/shipment_tracking/lib/__tests__/status-machine.test.ts +447 -0
- package/src/modules/shipment_tracking/lib/__tests__/time-extraction.test.ts +331 -0
- package/src/modules/shipment_tracking/lib/__tests__/timestamp-utils.test.ts +260 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-api.test.ts +125 -0
- package/src/modules/shipment_tracking/lib/__tests__/vessel-extraction.test.ts +108 -0
- package/src/modules/shipment_tracking/lib/adapters/cma-cgm.ts +84 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco-types.ts +145 -0
- package/src/modules/shipment_tracking/lib/adapters/cosco.ts +314 -0
- package/src/modules/shipment_tracking/lib/adapters/evergreen.ts +107 -0
- package/src/modules/shipment_tracking/lib/adapters/hapag-lloyd.ts +88 -0
- package/src/modules/shipment_tracking/lib/adapters/index.ts +18 -0
- package/src/modules/shipment_tracking/lib/adapters/maersk.ts +104 -0
- package/src/modules/shipment_tracking/lib/adapters/msc.ts +223 -0
- package/src/modules/shipment_tracking/lib/adapters/zim.ts +106 -0
- package/src/modules/shipment_tracking/lib/audit-helpers.ts +29 -0
- package/src/modules/shipment_tracking/lib/auth/base64url.ts +4 -0
- package/src/modules/shipment_tracking/lib/auth/oauth-client.ts +53 -0
- package/src/modules/shipment_tracking/lib/background.ts +24 -0
- package/src/modules/shipment_tracking/lib/bic-api-client.ts +199 -0
- package/src/modules/shipment_tracking/lib/carrier-adapter.ts +136 -0
- package/src/modules/shipment_tracking/lib/current-vessel.ts +274 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-mapping.ts +202 -0
- package/src/modules/shipment_tracking/lib/dcsa-event-parser.ts +163 -0
- package/src/modules/shipment_tracking/lib/dcsa-params.ts +14 -0
- package/src/modules/shipment_tracking/lib/empty-return.ts +122 -0
- package/src/modules/shipment_tracking/lib/location-overrides.ts +150 -0
- package/src/modules/shipment_tracking/lib/location-types.ts +154 -0
- package/src/modules/shipment_tracking/lib/logger.ts +195 -0
- package/src/modules/shipment_tracking/lib/poi-event-processor.ts +302 -0
- package/src/modules/shipment_tracking/lib/poi-nats-config.ts +134 -0
- package/src/modules/shipment_tracking/lib/poi-types.ts +198 -0
- package/src/modules/shipment_tracking/lib/rate-limiter.ts +58 -0
- package/src/modules/shipment_tracking/lib/route-extraction.ts +439 -0
- package/src/modules/shipment_tracking/lib/route-inference.ts +238 -0
- package/src/modules/shipment_tracking/lib/schedule-generator.ts +92 -0
- package/src/modules/shipment_tracking/lib/shipsgo-client.ts +350 -0
- package/src/modules/shipment_tracking/lib/shipsgo-mapper.ts +296 -0
- package/src/modules/shipment_tracking/lib/status-machine.ts +230 -0
- package/src/modules/shipment_tracking/lib/time-extraction.ts +85 -0
- package/src/modules/shipment_tracking/lib/timestamp-utils.ts +244 -0
- package/src/modules/shipment_tracking/lib/vessel-api.ts +299 -0
- package/src/modules/shipment_tracking/lib/vessel-cache.ts +102 -0
- package/src/modules/shipment_tracking/lib/vessel-extraction.ts +39 -0
- package/src/modules/shipment_tracking/lib/webhook-dispatcher.ts +93 -0
- package/src/modules/shipment_tracking/migrations/.snapshot-open-mercato.json +2914 -0
- package/src/modules/shipment_tracking/migrations/Migration20260422161208_shipment_tracking.ts +60 -0
- package/src/modules/shipment_tracking/migrations/Migration20260529120000_shipment_tracking_poi_region_names.ts +22 -0
- package/src/modules/shipment_tracking/migrations/Migration20260821120000_shipment_tracking_shipsgo.ts +65 -0
- package/src/modules/shipment_tracking/notifications.ts +151 -0
- package/src/modules/shipment_tracking/services/carrierRegistry.ts +29 -0
- package/src/modules/shipment_tracking/services/poiNatsService.ts +526 -0
- package/src/modules/shipment_tracking/services/shipsGoProvider.ts +231 -0
- package/src/modules/shipment_tracking/services/trackingService.ts +1777 -0
- package/src/modules/shipment_tracking/services/webhookService.ts +347 -0
- package/src/modules/shipment_tracking/setup.ts +99 -0
- package/src/modules/shipment_tracking/subscribers/__tests__/terminal-event-ingest.test.ts +141 -0
- package/src/modules/shipment_tracking/subscribers/cargo-event-created.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/dcsa-event-webhook-dispatch.ts +115 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-notification.ts +144 -0
- package/src/modules/shipment_tracking/subscribers/poi-event-webhook-dispatch.ts +94 -0
- package/src/modules/shipment_tracking/subscribers/shipment-status-changed.ts +41 -0
- package/src/modules/shipment_tracking/subscribers/terminal-event-ingest.ts +109 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-initial-poll.ts +53 -0
- package/src/modules/shipment_tracking/subscribers/tracking-job-webhook-dispatch.ts +50 -0
- package/src/modules/shipment_tracking/tables/index.ts +4 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.client.tsx +337 -0
- package/src/modules/shipment_tracking/tables/tracking_job/table.ts +34 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/config.ts +32 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.client.tsx +448 -0
- package/src/modules/shipment_tracking/widgets/dashboard/ships-map/widget.ts +25 -0
- package/src/types/assets.d.ts +5 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const PROXIMITY_EVENT_CODES = {
|
|
2
|
+
PORT_ARRIVAL: "PARR",
|
|
3
|
+
PORT_PROXIMITY_ARRIVAL: "PPRA",
|
|
4
|
+
PORT_PROXIMITY_DEPARTURE: "PPRD",
|
|
5
|
+
TERMINAL_ARRIVAL: "TARR",
|
|
6
|
+
TERMINAL_PROXIMITY_ARRIVAL: "TPRA",
|
|
7
|
+
TERMINAL_PROXIMITY_DEPARTURE: "TPRD",
|
|
8
|
+
WAYPOINT_REACHED: "WAYR"
|
|
9
|
+
};
|
|
10
|
+
const PROXIMITY_EVENT_CATEGORY = {
|
|
11
|
+
PORT_ARRIVAL: "arrival",
|
|
12
|
+
PORT_PROXIMITY_ARRIVAL: "proximity",
|
|
13
|
+
PORT_PROXIMITY_DEPARTURE: "departure",
|
|
14
|
+
TERMINAL_ARRIVAL: "arrival",
|
|
15
|
+
TERMINAL_PROXIMITY_ARRIVAL: "proximity",
|
|
16
|
+
TERMINAL_PROXIMITY_DEPARTURE: "departure",
|
|
17
|
+
WAYPOINT_REACHED: "waypoint"
|
|
18
|
+
};
|
|
19
|
+
const PROXIMITY_EVENT_TO_STRING = {
|
|
20
|
+
PORT_ARRIVAL: "port_arrival",
|
|
21
|
+
PORT_PROXIMITY_ARRIVAL: "port_proximity_arrival",
|
|
22
|
+
PORT_PROXIMITY_DEPARTURE: "port_departure",
|
|
23
|
+
TERMINAL_ARRIVAL: "terminal_arrival",
|
|
24
|
+
TERMINAL_PROXIMITY_ARRIVAL: "terminal_proximity_arrival",
|
|
25
|
+
TERMINAL_PROXIMITY_DEPARTURE: "terminal_departure",
|
|
26
|
+
WAYPOINT_REACHED: "waypoint_reached"
|
|
27
|
+
};
|
|
28
|
+
function getJobPoiEventId(eventType) {
|
|
29
|
+
const typeString = PROXIMITY_EVENT_TO_STRING[eventType];
|
|
30
|
+
return `shipment_tracking.tracking_job.poi.${typeString}`;
|
|
31
|
+
}
|
|
32
|
+
function getShipmentPoiEventId(eventType) {
|
|
33
|
+
const typeString = PROXIMITY_EVENT_TO_STRING[eventType];
|
|
34
|
+
return `shipment_tracking.poi.${typeString}`;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
PROXIMITY_EVENT_CATEGORY,
|
|
38
|
+
PROXIMITY_EVENT_CODES,
|
|
39
|
+
PROXIMITY_EVENT_TO_STRING,
|
|
40
|
+
getJobPoiEventId,
|
|
41
|
+
getShipmentPoiEventId
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=poi-types.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/poi-types.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * POI Proximity Event Types\n *\n * Type definitions for AIS POI proximity events received from the\n * poi-proximity-detector service via NATS.\n */\n\n// \u2500\u2500\u2500 Enums \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Types of proximity events emitted by the POI detector.\n */\nexport type ProximityEventType =\n | 'PORT_ARRIVAL'\n | 'PORT_PROXIMITY_ARRIVAL'\n | 'PORT_PROXIMITY_DEPARTURE'\n | 'TERMINAL_ARRIVAL'\n | 'TERMINAL_PROXIMITY_ARRIVAL'\n | 'TERMINAL_PROXIMITY_DEPARTURE'\n | 'WAYPOINT_REACHED'\n\n/**\n * Map POI event types to distinct event codes for TrackingEvent records.\n * These codes are AIS-specific and don't conflict with standard DCSA codes.\n * Labels are resolved via i18n: shipment_tracking.event_codes.<CODE>\n */\nexport const PROXIMITY_EVENT_CODES: Record<ProximityEventType, string> = {\n PORT_ARRIVAL: 'PARR',\n PORT_PROXIMITY_ARRIVAL: 'PPRA',\n PORT_PROXIMITY_DEPARTURE: 'PPRD',\n TERMINAL_ARRIVAL: 'TARR',\n TERMINAL_PROXIMITY_ARRIVAL: 'TPRA',\n TERMINAL_PROXIMITY_DEPARTURE: 'TPRD',\n WAYPOINT_REACHED: 'WAYR',\n}\n\n/**\n * Categorize events as arrival, departure, or proximity.\n */\nexport type ProximityEventCategory = 'arrival' | 'departure' | 'proximity' | 'waypoint'\n\nexport const PROXIMITY_EVENT_CATEGORY: Record<ProximityEventType, ProximityEventCategory> = {\n PORT_ARRIVAL: 'arrival',\n PORT_PROXIMITY_ARRIVAL: 'proximity',\n PORT_PROXIMITY_DEPARTURE: 'departure',\n TERMINAL_ARRIVAL: 'arrival',\n TERMINAL_PROXIMITY_ARRIVAL: 'proximity',\n TERMINAL_PROXIMITY_DEPARTURE: 'departure',\n WAYPOINT_REACHED: 'waypoint',\n}\n\n// \u2500\u2500\u2500 Event Payload \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * POI proximity event payload received from the AIS POI detector.\n *\n * This matches the ShipPoiProximityData interface from the poi-proximity-detector service.\n */\nexport interface PoiProximityEvent {\n /**\n * Vessel IMO number. In the AIS system `ships.id` IS the IMO, and the detector\n * sets this field from `ship.id` \u2014 so this is the real IMO, not an internal id.\n * Used to match shipments directly by `vesselImo` (no MMSI\u2192IMO lookup).\n */\n shipId: number\n\n /** Ship name */\n shipName: string\n\n /** Maritime Mobile Service Identity (9 digits) */\n mmsi: number\n\n /** Latitude of ship position */\n lat: number\n\n /** Longitude of ship position */\n lng: number\n\n /** Actual Time of Arrival (ISO 8601) - present for arrival events */\n ata?: string\n\n /** Actual Time of Departure (ISO 8601) - present for departure events */\n atd?: string\n\n /** Type of proximity event */\n type: ProximityEventType\n\n /** POI code for arrival events (e.g., port/terminal LOCODE or custom code) */\n arrivedPoiCode?: string\n\n /** POI code for departure events */\n departedPoiCode?: string\n\n /** Distance to POI in meters (null if not applicable) */\n distanceToPoiMeters: number | null\n\n /** Sequence order for multiple events in same message batch */\n sequenceOrder: number\n\n /**\n * Human-friendly POI identity for arrival/proximity events at a matched port or terminal.\n * All nullable \u2014 which fields are populated depends on the event type and how rich the\n * matched POI is (e.g. a PORT_ARRIVAL from a port's geometric center may only have `city`).\n * Resolved for display via {@link resolvePoiLocationName}.\n */\n portName?: string | null\n terminalName?: string | null\n city?: string | null\n\n /**\n * Human-friendly region name in Polish (e.g. \"Morze P\u00F3\u0142nocne\"), set for open-water\n * WAYPOINT_REACHED events that have no port/terminal/city. Null for port/terminal events.\n */\n regionNamePl?: string | null\n\n /** Human-friendly region name in English (e.g. \"North Sea\"). Null for port/terminal events \u2014 see {@link regionNamePl}. */\n regionNameEn?: string | null\n}\n\n// \u2500\u2500\u2500 Processed Event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Processed POI event enriched with shipment context.\n */\nexport interface ProcessedPoiEvent {\n /** Original POI event */\n poiEvent: PoiProximityEvent\n\n /** IMO number (resolved from MMSI if needed) */\n vesselImo: string | null\n\n /** Matched shipment IDs */\n shipmentIds: string[]\n\n /** Event timestamp (from ata/atd or current time) */\n eventDateTime: Date\n\n /** POI code (arrived or departed) */\n poiCode: string | null\n\n /** Mapped event code for TrackingEvent */\n eventCode: string\n\n /** Source event ID for deduplication */\n sourceEventId: string\n}\n\n// \u2500\u2500\u2500 Event ID Mapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * POI event type string used in event IDs.\n * Maps ProximityEventType to the underscore-separated format used in event declarations.\n */\nexport type PoiEventTypeString =\n | 'port_arrival'\n | 'port_proximity_arrival'\n | 'port_departure'\n | 'terminal_arrival'\n | 'terminal_proximity_arrival'\n | 'terminal_departure'\n | 'waypoint_reached'\n\n/**\n * Map ProximityEventType to the event type string used in event IDs.\n */\nexport const PROXIMITY_EVENT_TO_STRING: Record<ProximityEventType, PoiEventTypeString> = {\n PORT_ARRIVAL: 'port_arrival',\n PORT_PROXIMITY_ARRIVAL: 'port_proximity_arrival',\n PORT_PROXIMITY_DEPARTURE: 'port_departure',\n TERMINAL_ARRIVAL: 'terminal_arrival',\n TERMINAL_PROXIMITY_ARRIVAL: 'terminal_proximity_arrival',\n TERMINAL_PROXIMITY_DEPARTURE: 'terminal_departure',\n WAYPOINT_REACHED: 'waypoint_reached',\n}\n\n/**\n * Get the per-tracking-job event ID for a POI event type.\n * Used for notifications to avoid duplicates for multi-container bookings.\n *\n * @example\n * getJobPoiEventId('PORT_ARRIVAL') // 'shipment_tracking.tracking_job.poi.port_arrival'\n */\nexport function getJobPoiEventId(eventType: ProximityEventType): string {\n const typeString = PROXIMITY_EVENT_TO_STRING[eventType]\n return `shipment_tracking.tracking_job.poi.${typeString}`\n}\n\n/**\n * Get the per-shipment event ID for a POI event type.\n * Used for webhooks and external integrations.\n *\n * @example\n * getShipmentPoiEventId('PORT_ARRIVAL') // 'shipment_tracking.poi.port_arrival'\n */\nexport function getShipmentPoiEventId(eventType: ProximityEventType): string {\n const typeString = PROXIMITY_EVENT_TO_STRING[eventType]\n return `shipment_tracking.poi.${typeString}`\n}\n"],
|
|
5
|
+
"mappings": "AA0BO,MAAM,wBAA4D;AAAA,EACvE,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,kBAAkB;AACpB;AAOO,MAAM,2BAA+E;AAAA,EAC1F,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,kBAAkB;AACpB;AAoHO,MAAM,4BAA4E;AAAA,EACvF,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,kBAAkB;AACpB;AASO,SAAS,iBAAiB,WAAuC;AACtE,QAAM,aAAa,0BAA0B,SAAS;AACtD,SAAO,sCAAsC,UAAU;AACzD;AASO,SAAS,sBAAsB,WAAuC;AAC3E,QAAM,aAAa,0BAA0B,SAAS;AACtD,SAAO,yBAAyB,UAAU;AAC5C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { logRateLimit } from "./logger.js";
|
|
2
|
+
async function checkRateLimit(cache, tenantId, carrierCode, maxRequests, windowSeconds) {
|
|
3
|
+
const key = `st:ratelimit:${tenantId}:${carrierCode}`;
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
const raw = await cache.get(key);
|
|
6
|
+
let tokens = maxRequests;
|
|
7
|
+
let lastRefill = now;
|
|
8
|
+
if (raw) {
|
|
9
|
+
try {
|
|
10
|
+
const state = JSON.parse(raw);
|
|
11
|
+
const elapsed = (now - state.lastRefill) / 1e3;
|
|
12
|
+
const refilled = Math.floor(elapsed / windowSeconds * maxRequests);
|
|
13
|
+
tokens = Math.min(maxRequests, state.tokens + refilled);
|
|
14
|
+
lastRefill = refilled > 0 ? now : state.lastRefill;
|
|
15
|
+
} catch {
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (tokens <= 0) {
|
|
19
|
+
const retryAfterSeconds = Math.ceil(windowSeconds / maxRequests);
|
|
20
|
+
logRateLimit({ carrierCode, tenantId, allowed: false, retryAfterSeconds });
|
|
21
|
+
return { allowed: false, retryAfterSeconds };
|
|
22
|
+
}
|
|
23
|
+
tokens -= 1;
|
|
24
|
+
await cache.set(key, JSON.stringify({ tokens, lastRefill }), windowSeconds * 2);
|
|
25
|
+
logRateLimit({ carrierCode, tenantId, allowed: true });
|
|
26
|
+
return { allowed: true };
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
checkRateLimit
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/rate-limiter.ts"],
|
|
4
|
+
"sourcesContent": ["import { logRateLimit } from './logger'\n\nexport type RateLimitResult = {\n allowed: boolean\n retryAfterSeconds?: number\n}\n\nexport type CacheService = {\n get(key: string): Promise<string | null>\n set(key: string, value: string, ttlSeconds?: number): Promise<void>\n}\n\n/**\n * Token bucket rate limiter using a cache backend.\n *\n * Key: `st:ratelimit:{tenantId}:{carrier}`\n *\n * Stores the current token count and last refill timestamp in the cache.\n * Refills tokens proportionally based on elapsed time.\n */\nexport async function checkRateLimit(\n cache: CacheService,\n tenantId: string,\n carrierCode: string,\n maxRequests: number,\n windowSeconds: number,\n): Promise<RateLimitResult> {\n const key = `st:ratelimit:${tenantId}:${carrierCode}`\n const now = Date.now()\n\n const raw = await cache.get(key)\n let tokens = maxRequests\n let lastRefill = now\n\n if (raw) {\n try {\n const state = JSON.parse(raw) as { tokens: number; lastRefill: number }\n const elapsed = (now - state.lastRefill) / 1000\n const refilled = Math.floor((elapsed / windowSeconds) * maxRequests)\n tokens = Math.min(maxRequests, state.tokens + refilled)\n lastRefill = refilled > 0 ? now : state.lastRefill\n } catch {\n // Corrupted cache entry, reset\n }\n }\n\n if (tokens <= 0) {\n const retryAfterSeconds = Math.ceil(windowSeconds / maxRequests)\n logRateLimit({ carrierCode, tenantId, allowed: false, retryAfterSeconds })\n return { allowed: false, retryAfterSeconds }\n }\n\n tokens -= 1\n await cache.set(key, JSON.stringify({ tokens, lastRefill }), windowSeconds * 2)\n\n logRateLimit({ carrierCode, tenantId, allowed: true })\n return { allowed: true }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAoB7B,eAAsB,eACpB,OACA,UACA,aACA,aACA,eAC0B;AAC1B,QAAM,MAAM,gBAAgB,QAAQ,IAAI,WAAW;AACnD,QAAM,MAAM,KAAK,IAAI;AAErB,QAAM,MAAM,MAAM,MAAM,IAAI,GAAG;AAC/B,MAAI,SAAS;AACb,MAAI,aAAa;AAEjB,MAAI,KAAK;AACP,QAAI;AACF,YAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,YAAM,WAAW,MAAM,MAAM,cAAc;AAC3C,YAAM,WAAW,KAAK,MAAO,UAAU,gBAAiB,WAAW;AACnE,eAAS,KAAK,IAAI,aAAa,MAAM,SAAS,QAAQ;AACtD,mBAAa,WAAW,IAAI,MAAM,MAAM;AAAA,IAC1C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,UAAU,GAAG;AACf,UAAM,oBAAoB,KAAK,KAAK,gBAAgB,WAAW;AAC/D,iBAAa,EAAE,aAAa,UAAU,SAAS,OAAO,kBAAkB,CAAC;AACzE,WAAO,EAAE,SAAS,OAAO,kBAAkB;AAAA,EAC7C;AAEA,YAAU;AACV,QAAM,MAAM,IAAI,KAAK,KAAK,UAAU,EAAE,QAAQ,WAAW,CAAC,GAAG,gBAAgB,CAAC;AAE9E,eAAa,EAAE,aAAa,UAAU,SAAS,KAAK,CAAC;AACrD,SAAO,EAAE,SAAS,KAAK;AACzB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const ROUTE_DETAIL_EXCLUDED_EVENT_CODES = /* @__PURE__ */ new Set(["WAYR", "TARR", "TPRA", "TPRD"]);
|
|
2
|
+
function earliestIso(a, b) {
|
|
3
|
+
if (!a) return b ?? null;
|
|
4
|
+
if (!b) return a;
|
|
5
|
+
return a < b ? a : b;
|
|
6
|
+
}
|
|
7
|
+
function latestIso(a, b) {
|
|
8
|
+
if (!a) return b ?? null;
|
|
9
|
+
if (!b) return a;
|
|
10
|
+
return a > b ? a : b;
|
|
11
|
+
}
|
|
12
|
+
function extractRouteFromEvents(events, context) {
|
|
13
|
+
if (!events || events.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const sortedEvents = [...events].sort(
|
|
17
|
+
(a, b) => new Date(a.eventDateTime).getTime() - new Date(b.eventDateTime).getTime()
|
|
18
|
+
);
|
|
19
|
+
const locationMap = /* @__PURE__ */ new Map();
|
|
20
|
+
for (const event of sortedEvents) {
|
|
21
|
+
if (ROUTE_DETAIL_EXCLUDED_EVENT_CODES.has(event.eventCode)) continue;
|
|
22
|
+
const loc = event.locationUnlocode || event.locationName;
|
|
23
|
+
if (!loc) continue;
|
|
24
|
+
if (!locationMap.has(loc)) {
|
|
25
|
+
const countryCode = event.locationUnlocode?.length === 5 ? event.locationUnlocode.slice(0, 2) : null;
|
|
26
|
+
locationMap.set(loc, {
|
|
27
|
+
name: event.locationName || loc,
|
|
28
|
+
unlocode: event.locationUnlocode || null,
|
|
29
|
+
countryCode,
|
|
30
|
+
vessels: /* @__PURE__ */ new Map(),
|
|
31
|
+
isTransshipment: false,
|
|
32
|
+
// Initialize facility fields
|
|
33
|
+
facilityCode: null,
|
|
34
|
+
facilityCodeListProvider: null,
|
|
35
|
+
facilityTypeCode: null,
|
|
36
|
+
facilityAddress: null,
|
|
37
|
+
coords: null
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const entry = locationMap.get(loc);
|
|
41
|
+
if (event.facilityCode && !entry.facilityCode) {
|
|
42
|
+
entry.facilityCode = event.facilityCode;
|
|
43
|
+
entry.facilityCodeListProvider = event.facilityCodeListProvider || null;
|
|
44
|
+
}
|
|
45
|
+
if (event.facilityTypeCode && !entry.facilityTypeCode) {
|
|
46
|
+
entry.facilityTypeCode = event.facilityTypeCode;
|
|
47
|
+
}
|
|
48
|
+
if (event.facilityAddress && !entry.facilityAddress) {
|
|
49
|
+
entry.facilityAddress = event.facilityAddress;
|
|
50
|
+
}
|
|
51
|
+
if (event.latitude != null && event.longitude != null && !entry.coords) {
|
|
52
|
+
entry.coords = { latitude: event.latitude, longitude: event.longitude };
|
|
53
|
+
}
|
|
54
|
+
if (event.vesselName) {
|
|
55
|
+
const existingImo = entry.vessels.get(event.vesselName);
|
|
56
|
+
if (!existingImo && event.vesselImo) {
|
|
57
|
+
entry.vessels.set(event.vesselName, event.vesselImo);
|
|
58
|
+
} else if (!entry.vessels.has(event.vesselName)) {
|
|
59
|
+
entry.vessels.set(event.vesselName, event.vesselImo || null);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (event.eventCode === "ARRI" && event.eventClassifierCode === "ACT") {
|
|
63
|
+
entry.ata = event.eventDateTime;
|
|
64
|
+
}
|
|
65
|
+
if (event.eventCode === "GTIN" && event.eventClassifierCode === "ACT" && !entry.ata) {
|
|
66
|
+
if (event.facilityTypeCode === "POTE" || event.facilityTypeCode === "INTE") {
|
|
67
|
+
entry.ata = event.eventDateTime;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (event.eventCode === "DEPA" && event.eventClassifierCode === "ACT") {
|
|
71
|
+
entry.atd = event.eventDateTime;
|
|
72
|
+
}
|
|
73
|
+
if (event.eventCode === "GTOT" && event.eventClassifierCode === "ACT" && !entry.atd) {
|
|
74
|
+
entry.atd = event.eventDateTime;
|
|
75
|
+
}
|
|
76
|
+
if ((event.eventCode === "PARR" || event.eventCode === "PPRA") && event.eventClassifierCode === "ACT" && !entry.ata) {
|
|
77
|
+
entry.ata = event.eventDateTime;
|
|
78
|
+
}
|
|
79
|
+
if (event.eventCode === "PPRD" && event.eventClassifierCode === "ACT" && !entry.atd) {
|
|
80
|
+
entry.atd = event.eventDateTime;
|
|
81
|
+
}
|
|
82
|
+
if (event.eventCode === "ARRI" && (event.eventClassifierCode === "PLN" || event.eventClassifierCode === "EST")) {
|
|
83
|
+
if (!entry.eta) entry.eta = event.eventDateTime;
|
|
84
|
+
}
|
|
85
|
+
if (event.eventCode === "DEPA" && (event.eventClassifierCode === "PLN" || event.eventClassifierCode === "EST")) {
|
|
86
|
+
if (!entry.etd) entry.etd = event.eventDateTime;
|
|
87
|
+
}
|
|
88
|
+
if (event.isTransshipmentMove || (event.eventCode === "DISC" || event.eventCode === "LOAD")) {
|
|
89
|
+
entry.isTransshipment = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const stops = [];
|
|
93
|
+
const locations = Array.from(locationMap.entries());
|
|
94
|
+
for (let i = 0; i < locations.length; i++) {
|
|
95
|
+
const [key, data] = locations[i];
|
|
96
|
+
const isFirst = i === 0;
|
|
97
|
+
const isLast = i === locations.length - 1;
|
|
98
|
+
let type = data.isTransshipment ? "transshipment" : "port_call";
|
|
99
|
+
if (isFirst || key === context?.originUnlocode) {
|
|
100
|
+
type = "origin";
|
|
101
|
+
} else if (isLast || key === context?.destinationUnlocode) {
|
|
102
|
+
type = "destination";
|
|
103
|
+
}
|
|
104
|
+
const vesselEntries = Array.from(data.vessels.entries());
|
|
105
|
+
const lastVessel = vesselEntries.length > 0 ? vesselEntries[vesselEntries.length - 1] : null;
|
|
106
|
+
const vesselName = lastVessel ? lastVessel[0] : null;
|
|
107
|
+
const vesselImo = lastVessel ? lastVessel[1] : null;
|
|
108
|
+
stops.push({
|
|
109
|
+
location: data.name,
|
|
110
|
+
unlocode: data.unlocode,
|
|
111
|
+
type,
|
|
112
|
+
vesselName,
|
|
113
|
+
vesselImo,
|
|
114
|
+
ata: data.ata || null,
|
|
115
|
+
atd: data.atd || null,
|
|
116
|
+
eta: data.eta || null,
|
|
117
|
+
etd: data.etd || null,
|
|
118
|
+
// Facility details
|
|
119
|
+
countryCode: data.countryCode || null,
|
|
120
|
+
facilityCode: data.facilityCode || null,
|
|
121
|
+
facilityCodeListProvider: data.facilityCodeListProvider || null,
|
|
122
|
+
facilityTypeCode: data.facilityTypeCode || null,
|
|
123
|
+
facilityAddress: data.facilityAddress || null,
|
|
124
|
+
coords: data.coords || null,
|
|
125
|
+
operatorName: null
|
|
126
|
+
// Will be populated by BIC enrichment if needed
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const merged = [];
|
|
130
|
+
for (const stop of stops) {
|
|
131
|
+
const prev = merged[merged.length - 1];
|
|
132
|
+
if (prev && prev.location === stop.location) {
|
|
133
|
+
prev.ata = earliestIso(prev.ata, stop.ata);
|
|
134
|
+
prev.atd = latestIso(prev.atd, stop.atd);
|
|
135
|
+
prev.eta = earliestIso(prev.eta, stop.eta);
|
|
136
|
+
prev.etd = latestIso(prev.etd, stop.etd);
|
|
137
|
+
prev.vesselName = prev.vesselName ?? stop.vesselName;
|
|
138
|
+
prev.vesselImo = prev.vesselImo ?? stop.vesselImo;
|
|
139
|
+
if (prev.unlocode?.length !== 5 && stop.unlocode?.length === 5) prev.unlocode = stop.unlocode;
|
|
140
|
+
prev.countryCode = prev.countryCode ?? stop.countryCode;
|
|
141
|
+
prev.facilityCode = prev.facilityCode ?? stop.facilityCode;
|
|
142
|
+
prev.facilityCodeListProvider = prev.facilityCodeListProvider ?? stop.facilityCodeListProvider;
|
|
143
|
+
prev.facilityTypeCode = prev.facilityTypeCode ?? stop.facilityTypeCode;
|
|
144
|
+
prev.facilityAddress = prev.facilityAddress ?? stop.facilityAddress;
|
|
145
|
+
prev.coords = prev.coords ?? stop.coords;
|
|
146
|
+
if (stop.type === "origin") prev.type = "origin";
|
|
147
|
+
else if (stop.type === "destination" && prev.type !== "origin") prev.type = "destination";
|
|
148
|
+
else if (stop.type === "transshipment" && prev.type === "port_call") prev.type = "transshipment";
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
merged.push({ ...stop });
|
|
152
|
+
}
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
155
|
+
function mapTrackingEventToEntry(event) {
|
|
156
|
+
return {
|
|
157
|
+
id: event.id,
|
|
158
|
+
eventType: event.eventType,
|
|
159
|
+
eventCode: event.eventCode,
|
|
160
|
+
eventClassifierCode: event.eventClassifierCode,
|
|
161
|
+
eventDateTime: event.eventDateTime.toISOString(),
|
|
162
|
+
description: event.description || null,
|
|
163
|
+
locationName: event.locationName || null,
|
|
164
|
+
locationUnlocode: event.locationUnlocode || null,
|
|
165
|
+
regionNamePl: event.regionNamePl || null,
|
|
166
|
+
regionNameEn: event.regionNameEn || null,
|
|
167
|
+
vesselName: event.vesselName || null,
|
|
168
|
+
vesselImo: event.vesselImo || null,
|
|
169
|
+
voyageNumber: event.voyageNumber || null,
|
|
170
|
+
isTransshipmentMove: event.isTransshipmentMove ?? null,
|
|
171
|
+
emptyIndicatorCode: event.emptyIndicatorCode ?? null,
|
|
172
|
+
// Facility fields
|
|
173
|
+
facilityCode: event.facilityCode || null,
|
|
174
|
+
facilityCodeListProvider: event.facilityCodeListProvider || null,
|
|
175
|
+
facilityTypeCode: event.facilityTypeCode || null,
|
|
176
|
+
facilityAddress: event.facilityAddress || null,
|
|
177
|
+
latitude: event.latitude ?? null,
|
|
178
|
+
longitude: event.longitude ?? null,
|
|
179
|
+
// Seal fields
|
|
180
|
+
seals: event.seals ?? null
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
extractRouteFromEvents,
|
|
185
|
+
mapTrackingEventToEntry
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=route-extraction.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/route-extraction.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Route extraction utilities for shipment tracking.\n * \n * Extracts route stops from tracking events for display in the shipment details drawer.\n * This logic is shared between:\n * - TrackingService (when populating denormalized data on shipment)\n * - ShipmentDetailsDrawer (as types reference)\n */\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Seal information for a container.\n */\nexport interface SealInfo {\n number: string\n source?: string | null // CAR (carrier), SHI (shipper), TER (terminal), CUS (customs)\n type?: string | null\n}\n\n/**\n * A stop along the shipment's route (origin, transshipment, or destination).\n * Stored as JSONB on the Shipment entity.\n * \n * Includes facility/terminal details from DCSA events for rich location display.\n */\nexport interface RouteStopEntry {\n // Core fields\n location: string // Terminal or port name\n unlocode?: string | null // UN/LOCODE (e.g., \"PLGDN\")\n // 'transshipment' = cargo changes vessels here; 'port_call' = same vessel calls en route\n type: 'origin' | 'transshipment' | 'port_call' | 'destination'\n vesselName?: string | null // Vessel name for this leg\n vesselImo?: string | null // Vessel IMO number for vessel tracking\n \n // Timestamps\n ata?: string | null // Actual arrival - ISO datetime string\n atd?: string | null // Actual departure - ISO datetime string\n eta?: string | null // Estimated arrival - ISO datetime string (for delay calculation)\n etd?: string | null // Estimated departure - ISO datetime string (for delay calculation)\n \n // Facility/terminal details (from DCSA events)\n countryCode?: string | null // ISO 3166-1 alpha-2 (e.g., \"PL\")\n facilityCode?: string | null // SMDG/BIC code (e.g., \"DCT\")\n facilityCodeListProvider?: 'BIC' | 'SMDG' | null\n facilityTypeCode?: string | null // POTE (port terminal), DEPO (depot), etc.\n facilityAddress?: string | null // Full address string from DCSA otherFacility\n coords?: {\n latitude: number\n longitude: number\n } | null\n \n // From BIC enrichment (optional)\n operatorName?: string | null\n}\n\n/**\n * A cargo/tracking event entry for a specific container.\n * Stored as JSONB on the Shipment entity.\n * \n * Includes facility details from DCSA events for route extraction.\n */\nexport interface CargoEventEntry {\n // Core fields\n id: string\n eventType: string\n eventCode: string\n eventClassifierCode?: 'ACT' | 'PLN' | 'EST' | null\n eventDateTime: string // ISO datetime string\n description?: string | null\n \n // Location fields\n locationName?: string | null\n locationUnlocode?: string | null\n\n // Human-friendly region names for AIS POI events (localized at render). Null for carrier events.\n regionNamePl?: string | null\n regionNameEn?: string | null\n\n // Vessel/voyage fields\n vesselName?: string | null\n vesselImo?: string | null\n voyageNumber?: string | null\n isTransshipmentMove?: boolean | null\n \n emptyIndicatorCode?: 'EMPTY' | 'LADEN' | null\n\n // Facility/terminal details (from DCSA events)\n facilityCode?: string | null // SMDG/BIC code (e.g., \"DCT\")\n facilityCodeListProvider?: 'BIC' | 'SMDG' | null\n facilityTypeCode?: string | null // POTE, DEPO, etc.\n facilityAddress?: string | null // Full address string\n latitude?: number | null\n longitude?: number | null\n \n // Seal information (from DCSA events)\n seals?: SealInfo[] | null\n}\n\n/**\n * Context for route extraction - provides origin/destination hints.\n */\nexport interface RouteExtractionContext {\n originUnlocode?: string | null\n destinationUnlocode?: string | null\n}\n\n// \u2500\u2500\u2500 Route Extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * AIS POI event codes that are too fine-grained for Route Details and are\n * skipped during route extraction:\n * - WAYR (waypoint reached) \u2014 open-water grid codes (e.g. \"N051E001-00773\"), no real place\n * - TARR / TPRA / TPRD (terminal arrival / proximity arrival / proximity departure)\n *\n * Port-level AIS events (PARR/PPRA/PPRD) and all carrier/DCSA milestones\n * (ARRI/DEPA/GTIN/GTOT/LOAD/DISC, \u2026) are kept \u2014 they define the route.\n * Excluded events remain in the Journey Timeline (cargoEvents).\n */\nconst ROUTE_DETAIL_EXCLUDED_EVENT_CODES = new Set(['WAYR', 'TARR', 'TPRA', 'TPRD'])\n\n// ISO datetime strings are UTC (toISOString), so lexicographic compare == chronological.\nfunction earliestIso(a?: string | null, b?: string | null): string | null {\n if (!a) return b ?? null\n if (!b) return a\n return a < b ? a : b\n}\nfunction latestIso(a?: string | null, b?: string | null): string | null {\n if (!a) return b ?? null\n if (!b) return a\n return a > b ? a : b\n}\n\n/**\n * Extracts route stops from a list of cargo events.\n * \n * The algorithm:\n * 1. Sorts events chronologically\n * 2. Groups events by location (unlocode or name)\n * 3. Tracks vessels, arrival/departure times, and transshipment indicators\n * 4. Assigns type (origin/transshipment/destination) based on position and context\n * \n * @param events - Array of cargo events (already filtered for specific container)\n * @param context - Optional context with origin/destination hints\n * @returns Array of route stops in chronological order\n */\nexport function extractRouteFromEvents(\n events: CargoEventEntry[],\n context?: RouteExtractionContext\n): RouteStopEntry[] {\n if (!events || events.length === 0) {\n return []\n }\n\n // Sort events by datetime\n const sortedEvents = [...events].sort(\n (a, b) => new Date(a.eventDateTime).getTime() - new Date(b.eventDateTime).getTime()\n )\n\n // Build location map with timestamps and facility data\n // vessels is a Map of vesselName -> vesselImo (to track IMO alongside name)\n const locationMap = new Map<string, {\n name: string\n unlocode?: string | null\n countryCode?: string | null\n vessels: Map<string, string | null> // vesselName -> vesselImo\n ata?: string\n atd?: string\n eta?: string\n etd?: string\n isTransshipment: boolean\n // Facility data from events\n facilityCode?: string | null\n facilityCodeListProvider?: 'BIC' | 'SMDG' | null\n facilityTypeCode?: string | null\n facilityAddress?: string | null\n coords?: { latitude: number; longitude: number } | null\n }>()\n\n for (const event of sortedEvents) {\n // Skip fine-grained AIS POI events (waypoints + terminals) \u2014 Route Details\n // shows port-level stops only. See ROUTE_DETAIL_EXCLUDED_EVENT_CODES.\n if (ROUTE_DETAIL_EXCLUDED_EVENT_CODES.has(event.eventCode)) continue\n\n const loc = event.locationUnlocode || event.locationName\n if (!loc) continue\n\n if (!locationMap.has(loc)) {\n // Extract country code from UN/LOCODE (first 2 chars)\n const countryCode = event.locationUnlocode?.length === 5\n ? event.locationUnlocode.slice(0, 2)\n : null\n\n locationMap.set(loc, {\n name: event.locationName || loc,\n unlocode: event.locationUnlocode || null,\n countryCode,\n vessels: new Map<string, string | null>(),\n isTransshipment: false,\n // Initialize facility fields\n facilityCode: null,\n facilityCodeListProvider: null,\n facilityTypeCode: null,\n facilityAddress: null,\n coords: null,\n })\n }\n\n const entry = locationMap.get(loc)!\n\n // Update facility data if this event has more complete info\n // Prefer events with facility codes over those without\n if (event.facilityCode && !entry.facilityCode) {\n entry.facilityCode = event.facilityCode\n entry.facilityCodeListProvider = event.facilityCodeListProvider || null\n }\n if (event.facilityTypeCode && !entry.facilityTypeCode) {\n entry.facilityTypeCode = event.facilityTypeCode\n }\n if (event.facilityAddress && !entry.facilityAddress) {\n entry.facilityAddress = event.facilityAddress\n }\n if (event.latitude != null && event.longitude != null && !entry.coords) {\n entry.coords = { latitude: event.latitude, longitude: event.longitude }\n }\n\n if (event.vesselName) {\n // Store vessel name with its IMO (may be null)\n // If we already have this vessel, only update if the new event has an IMO\n const existingImo = entry.vessels.get(event.vesselName)\n if (!existingImo && event.vesselImo) {\n entry.vessels.set(event.vesselName, event.vesselImo)\n } else if (!entry.vessels.has(event.vesselName)) {\n entry.vessels.set(event.vesselName, event.vesselImo || null)\n }\n }\n\n // Track actual arrivals/departures\n // ARRI = vessel arrival at port\n // GTIN = gate-in (container enters terminal via truck/rail - indicates arrival for inland segments)\n // For the first segment (depot to port), GTIN at the port indicates the container arrived there\n if (event.eventCode === 'ARRI' && event.eventClassifierCode === 'ACT') {\n entry.ata = event.eventDateTime\n }\n // Use GTIN as arrival indicator for port terminals when no ARRI event exists\n // This handles the depot-to-port segment where container arrives by truck\n if (event.eventCode === 'GTIN' && event.eventClassifierCode === 'ACT' && !entry.ata) {\n // Only use GTIN as arrival if it's at a port terminal (POTE) or intermodal (INTE)\n if (event.facilityTypeCode === 'POTE' || event.facilityTypeCode === 'INTE') {\n entry.ata = event.eventDateTime\n }\n }\n if (event.eventCode === 'DEPA' && event.eventClassifierCode === 'ACT') {\n entry.atd = event.eventDateTime\n }\n // Use GTOT (gate-out) as departure indicator for depots/terminals\n // This handles the first segment where container departs depot by truck\n if (event.eventCode === 'GTOT' && event.eventClassifierCode === 'ACT' && !entry.atd) {\n entry.atd = event.eventDateTime\n }\n\n // AIS port proximity events. Only ports survive into Route Details (waypoints +\n // terminals are filtered out above), so these are the arrival/departure signals for\n // AIS-tracked port calls \u2014 without them an AIS-only stop (e.g. a transshipment port\n // not in the DCSA feed) has no ATA/ATD and its segment bars never turn green.\n // Applied only when no carrier (DCSA) timestamp exists, so DCSA ATA/ATD always win.\n // PARR = port arrival, PPRA = port proximity arrival, PPRD = port proximity departure.\n if (\n (event.eventCode === 'PARR' || event.eventCode === 'PPRA') &&\n event.eventClassifierCode === 'ACT' &&\n !entry.ata\n ) {\n entry.ata = event.eventDateTime\n }\n if (event.eventCode === 'PPRD' && event.eventClassifierCode === 'ACT' && !entry.atd) {\n entry.atd = event.eventDateTime\n }\n\n // Track estimated/planned arrivals/departures (for delay calculation)\n // Only store the first estimate (original plan) to compare against actual\n if (event.eventCode === 'ARRI' && (event.eventClassifierCode === 'PLN' || event.eventClassifierCode === 'EST')) {\n if (!entry.eta) entry.eta = event.eventDateTime\n }\n if (event.eventCode === 'DEPA' && (event.eventClassifierCode === 'PLN' || event.eventClassifierCode === 'EST')) {\n if (!entry.etd) entry.etd = event.eventDateTime\n }\n\n // Mark transshipment if container was discharged and loaded\n if (event.isTransshipmentMove || \n (event.eventCode === 'DISC' || event.eventCode === 'LOAD')) {\n entry.isTransshipment = true\n }\n }\n\n // Convert to route stops\n const stops: RouteStopEntry[] = []\n const locations = Array.from(locationMap.entries())\n\n for (let i = 0; i < locations.length; i++) {\n const [key, data] = locations[i]\n const isFirst = i === 0\n const isLast = i === locations.length - 1\n\n // Determine type based on position and context.\n // An intermediate stop is a real 'transshipment' only when the cargo actually\n // changed vessels here (isTransshipmentMove / DISC+LOAD); otherwise it's a plain\n // 'port_call' \u2014 the same vessel calling en route (e.g. AIS port proximity events).\n let type: 'origin' | 'transshipment' | 'port_call' | 'destination' =\n data.isTransshipment ? 'transshipment' : 'port_call'\n\n if (isFirst || key === context?.originUnlocode) {\n type = 'origin'\n } else if (isLast || key === context?.destinationUnlocode) {\n type = 'destination'\n }\n\n // Get the last vessel entry (most recent vessel at this stop)\n const vesselEntries = Array.from(data.vessels.entries())\n const lastVessel = vesselEntries.length > 0 ? vesselEntries[vesselEntries.length - 1] : null\n const vesselName = lastVessel ? lastVessel[0] : null\n const vesselImo = lastVessel ? lastVessel[1] : null\n\n stops.push({\n location: data.name,\n unlocode: data.unlocode,\n type,\n vesselName,\n vesselImo,\n ata: data.ata || null,\n atd: data.atd || null,\n eta: data.eta || null,\n etd: data.etd || null,\n // Facility details\n countryCode: data.countryCode || null,\n facilityCode: data.facilityCode || null,\n facilityCodeListProvider: data.facilityCodeListProvider || null,\n facilityTypeCode: data.facilityTypeCode || null,\n facilityAddress: data.facilityAddress || null,\n coords: data.coords || null,\n operatorName: null, // Will be populated by BIC enrichment if needed\n })\n }\n\n // Collapse consecutive stops at the same place into one port call. AIS port events for a\n // single city can arrive under several POI codes (proximity vs actual arrival, different\n // berths), which would otherwise render as duplicate rows (e.g. two \"Le Havre\") with split\n // timestamps \u2014 breaking segment colors. Merge by location name, keeping the earliest\n // arrival and latest departure so segment status is computed correctly.\n const merged: RouteStopEntry[] = []\n for (const stop of stops) {\n const prev = merged[merged.length - 1]\n if (prev && prev.location === stop.location) {\n prev.ata = earliestIso(prev.ata, stop.ata)\n prev.atd = latestIso(prev.atd, stop.atd)\n prev.eta = earliestIso(prev.eta, stop.eta)\n prev.etd = latestIso(prev.etd, stop.etd)\n prev.vesselName = prev.vesselName ?? stop.vesselName\n prev.vesselImo = prev.vesselImo ?? stop.vesselImo\n // Prefer a real UN/LOCODE over a raw AIS POI grid code\n if (prev.unlocode?.length !== 5 && stop.unlocode?.length === 5) prev.unlocode = stop.unlocode\n prev.countryCode = prev.countryCode ?? stop.countryCode\n prev.facilityCode = prev.facilityCode ?? stop.facilityCode\n prev.facilityCodeListProvider = prev.facilityCodeListProvider ?? stop.facilityCodeListProvider\n prev.facilityTypeCode = prev.facilityTypeCode ?? stop.facilityTypeCode\n prev.facilityAddress = prev.facilityAddress ?? stop.facilityAddress\n prev.coords = prev.coords ?? stop.coords\n // Promote type if a merged member is an endpoint (origin/destination beats transshipment)\n if (stop.type === 'origin') prev.type = 'origin'\n else if (stop.type === 'destination' && prev.type !== 'origin') prev.type = 'destination'\n else if (stop.type === 'transshipment' && prev.type === 'port_call') prev.type = 'transshipment'\n continue\n }\n merged.push({ ...stop })\n }\n\n return merged\n}\n\n// \u2500\u2500\u2500 Event Mapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Maps a TrackingEvent entity to a CargoEventEntry for storage.\n * \n * @param event - The tracking event entity\n * @returns A simplified event entry suitable for JSONB storage\n */\nexport function mapTrackingEventToEntry(event: {\n id: string\n eventType: string\n eventCode: string\n eventClassifierCode?: string | null\n eventDateTime: Date\n description?: string | null\n locationName?: string | null\n locationUnlocode?: string | null\n regionNamePl?: string | null\n regionNameEn?: string | null\n vesselName?: string | null\n vesselImo?: string | null\n voyageNumber?: string | null\n isTransshipmentMove?: boolean | null\n emptyIndicatorCode?: 'EMPTY' | 'LADEN' | null\n // Facility fields\n facilityCode?: string | null\n facilityCodeListProvider?: 'BIC' | 'SMDG' | null\n facilityTypeCode?: string | null\n facilityAddress?: string | null\n latitude?: number | null\n longitude?: number | null\n // Seal fields\n seals?: SealInfo[] | null\n}): CargoEventEntry {\n return {\n id: event.id,\n eventType: event.eventType,\n eventCode: event.eventCode,\n eventClassifierCode: event.eventClassifierCode as 'ACT' | 'PLN' | 'EST' | null,\n eventDateTime: event.eventDateTime.toISOString(),\n description: event.description || null,\n locationName: event.locationName || null,\n locationUnlocode: event.locationUnlocode || null,\n regionNamePl: event.regionNamePl || null,\n regionNameEn: event.regionNameEn || null,\n vesselName: event.vesselName || null,\n vesselImo: event.vesselImo || null,\n voyageNumber: event.voyageNumber || null,\n isTransshipmentMove: event.isTransshipmentMove ?? null,\n emptyIndicatorCode: event.emptyIndicatorCode ?? null,\n // Facility fields\n facilityCode: event.facilityCode || null,\n facilityCodeListProvider: event.facilityCodeListProvider || null,\n facilityTypeCode: event.facilityTypeCode || null,\n facilityAddress: event.facilityAddress || null,\n latitude: event.latitude ?? null,\n longitude: event.longitude ?? null,\n // Seal fields\n seals: event.seals ?? null,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAuHA,MAAM,oCAAoC,oBAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAGlF,SAAS,YAAY,GAAmB,GAAkC;AACxE,MAAI,CAAC,EAAG,QAAO,KAAK;AACpB,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,IAAI,IAAI,IAAI;AACrB;AACA,SAAS,UAAU,GAAmB,GAAkC;AACtE,MAAI,CAAC,EAAG,QAAO,KAAK;AACpB,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,IAAI,IAAI,IAAI;AACrB;AAeO,SAAS,uBACd,QACA,SACkB;AAClB,MAAI,CAAC,UAAU,OAAO,WAAW,GAAG;AAClC,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,eAAe,CAAC,GAAG,MAAM,EAAE;AAAA,IAC/B,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,aAAa,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,aAAa,EAAE,QAAQ;AAAA,EACpF;AAIA,QAAM,cAAc,oBAAI,IAgBrB;AAEH,aAAW,SAAS,cAAc;AAGhC,QAAI,kCAAkC,IAAI,MAAM,SAAS,EAAG;AAE5D,UAAM,MAAM,MAAM,oBAAoB,MAAM;AAC5C,QAAI,CAAC,IAAK;AAEV,QAAI,CAAC,YAAY,IAAI,GAAG,GAAG;AAEzB,YAAM,cAAc,MAAM,kBAAkB,WAAW,IACnD,MAAM,iBAAiB,MAAM,GAAG,CAAC,IACjC;AAEJ,kBAAY,IAAI,KAAK;AAAA,QACnB,MAAM,MAAM,gBAAgB;AAAA,QAC5B,UAAU,MAAM,oBAAoB;AAAA,QACpC;AAAA,QACA,SAAS,oBAAI,IAA2B;AAAA,QACxC,iBAAiB;AAAA;AAAA,QAEjB,cAAc;AAAA,QACd,0BAA0B;AAAA,QAC1B,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,QACjB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,UAAM,QAAQ,YAAY,IAAI,GAAG;AAIjC,QAAI,MAAM,gBAAgB,CAAC,MAAM,cAAc;AAC7C,YAAM,eAAe,MAAM;AAC3B,YAAM,2BAA2B,MAAM,4BAA4B;AAAA,IACrE;AACA,QAAI,MAAM,oBAAoB,CAAC,MAAM,kBAAkB;AACrD,YAAM,mBAAmB,MAAM;AAAA,IACjC;AACA,QAAI,MAAM,mBAAmB,CAAC,MAAM,iBAAiB;AACnD,YAAM,kBAAkB,MAAM;AAAA,IAChC;AACA,QAAI,MAAM,YAAY,QAAQ,MAAM,aAAa,QAAQ,CAAC,MAAM,QAAQ;AACtE,YAAM,SAAS,EAAE,UAAU,MAAM,UAAU,WAAW,MAAM,UAAU;AAAA,IACxE;AAEA,QAAI,MAAM,YAAY;AAGpB,YAAM,cAAc,MAAM,QAAQ,IAAI,MAAM,UAAU;AACtD,UAAI,CAAC,eAAe,MAAM,WAAW;AACnC,cAAM,QAAQ,IAAI,MAAM,YAAY,MAAM,SAAS;AAAA,MACrD,WAAW,CAAC,MAAM,QAAQ,IAAI,MAAM,UAAU,GAAG;AAC/C,cAAM,QAAQ,IAAI,MAAM,YAAY,MAAM,aAAa,IAAI;AAAA,MAC7D;AAAA,IACF;AAMA,QAAI,MAAM,cAAc,UAAU,MAAM,wBAAwB,OAAO;AACrE,YAAM,MAAM,MAAM;AAAA,IACpB;AAGA,QAAI,MAAM,cAAc,UAAU,MAAM,wBAAwB,SAAS,CAAC,MAAM,KAAK;AAEnF,UAAI,MAAM,qBAAqB,UAAU,MAAM,qBAAqB,QAAQ;AAC1E,cAAM,MAAM,MAAM;AAAA,MACpB;AAAA,IACF;AACA,QAAI,MAAM,cAAc,UAAU,MAAM,wBAAwB,OAAO;AACrE,YAAM,MAAM,MAAM;AAAA,IACpB;AAGA,QAAI,MAAM,cAAc,UAAU,MAAM,wBAAwB,SAAS,CAAC,MAAM,KAAK;AACnF,YAAM,MAAM,MAAM;AAAA,IACpB;AAQA,SACG,MAAM,cAAc,UAAU,MAAM,cAAc,WACnD,MAAM,wBAAwB,SAC9B,CAAC,MAAM,KACP;AACA,YAAM,MAAM,MAAM;AAAA,IACpB;AACA,QAAI,MAAM,cAAc,UAAU,MAAM,wBAAwB,SAAS,CAAC,MAAM,KAAK;AACnF,YAAM,MAAM,MAAM;AAAA,IACpB;AAIA,QAAI,MAAM,cAAc,WAAW,MAAM,wBAAwB,SAAS,MAAM,wBAAwB,QAAQ;AAC9G,UAAI,CAAC,MAAM,IAAK,OAAM,MAAM,MAAM;AAAA,IACpC;AACA,QAAI,MAAM,cAAc,WAAW,MAAM,wBAAwB,SAAS,MAAM,wBAAwB,QAAQ;AAC9G,UAAI,CAAC,MAAM,IAAK,OAAM,MAAM,MAAM;AAAA,IACpC;AAGA,QAAI,MAAM,wBACL,MAAM,cAAc,UAAU,MAAM,cAAc,SAAS;AAC9D,YAAM,kBAAkB;AAAA,IAC1B;AAAA,EACF;AAGA,QAAM,QAA0B,CAAC;AACjC,QAAM,YAAY,MAAM,KAAK,YAAY,QAAQ,CAAC;AAElD,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,UAAM,CAAC,KAAK,IAAI,IAAI,UAAU,CAAC;AAC/B,UAAM,UAAU,MAAM;AACtB,UAAM,SAAS,MAAM,UAAU,SAAS;AAMxC,QAAI,OACF,KAAK,kBAAkB,kBAAkB;AAE3C,QAAI,WAAW,QAAQ,SAAS,gBAAgB;AAC9C,aAAO;AAAA,IACT,WAAW,UAAU,QAAQ,SAAS,qBAAqB;AACzD,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,MAAM,KAAK,KAAK,QAAQ,QAAQ,CAAC;AACvD,UAAM,aAAa,cAAc,SAAS,IAAI,cAAc,cAAc,SAAS,CAAC,IAAI;AACxF,UAAM,aAAa,aAAa,WAAW,CAAC,IAAI;AAChD,UAAM,YAAY,aAAa,WAAW,CAAC,IAAI;AAE/C,UAAM,KAAK;AAAA,MACT,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,KAAK,OAAO;AAAA,MACjB,KAAK,KAAK,OAAO;AAAA,MACjB,KAAK,KAAK,OAAO;AAAA,MACjB,KAAK,KAAK,OAAO;AAAA;AAAA,MAEjB,aAAa,KAAK,eAAe;AAAA,MACjC,cAAc,KAAK,gBAAgB;AAAA,MACnC,0BAA0B,KAAK,4BAA4B;AAAA,MAC3D,kBAAkB,KAAK,oBAAoB;AAAA,MAC3C,iBAAiB,KAAK,mBAAmB;AAAA,MACzC,QAAQ,KAAK,UAAU;AAAA,MACvB,cAAc;AAAA;AAAA,IAChB,CAAC;AAAA,EACH;AAOA,QAAM,SAA2B,CAAC;AAClC,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,QAAQ,KAAK,aAAa,KAAK,UAAU;AAC3C,WAAK,MAAM,YAAY,KAAK,KAAK,KAAK,GAAG;AACzC,WAAK,MAAM,UAAU,KAAK,KAAK,KAAK,GAAG;AACvC,WAAK,MAAM,YAAY,KAAK,KAAK,KAAK,GAAG;AACzC,WAAK,MAAM,UAAU,KAAK,KAAK,KAAK,GAAG;AACvC,WAAK,aAAa,KAAK,cAAc,KAAK;AAC1C,WAAK,YAAY,KAAK,aAAa,KAAK;AAExC,UAAI,KAAK,UAAU,WAAW,KAAK,KAAK,UAAU,WAAW,EAAG,MAAK,WAAW,KAAK;AACrF,WAAK,cAAc,KAAK,eAAe,KAAK;AAC5C,WAAK,eAAe,KAAK,gBAAgB,KAAK;AAC9C,WAAK,2BAA2B,KAAK,4BAA4B,KAAK;AACtE,WAAK,mBAAmB,KAAK,oBAAoB,KAAK;AACtD,WAAK,kBAAkB,KAAK,mBAAmB,KAAK;AACpD,WAAK,SAAS,KAAK,UAAU,KAAK;AAElC,UAAI,KAAK,SAAS,SAAU,MAAK,OAAO;AAAA,eAC/B,KAAK,SAAS,iBAAiB,KAAK,SAAS,SAAU,MAAK,OAAO;AAAA,eACnE,KAAK,SAAS,mBAAmB,KAAK,SAAS,YAAa,MAAK,OAAO;AACjF;AAAA,IACF;AACA,WAAO,KAAK,EAAE,GAAG,KAAK,CAAC;AAAA,EACzB;AAEA,SAAO;AACT;AAUO,SAAS,wBAAwB,OAyBpB;AAClB,SAAO;AAAA,IACL,IAAI,MAAM;AAAA,IACV,WAAW,MAAM;AAAA,IACjB,WAAW,MAAM;AAAA,IACjB,qBAAqB,MAAM;AAAA,IAC3B,eAAe,MAAM,cAAc,YAAY;AAAA,IAC/C,aAAa,MAAM,eAAe;AAAA,IAClC,cAAc,MAAM,gBAAgB;AAAA,IACpC,kBAAkB,MAAM,oBAAoB;AAAA,IAC5C,cAAc,MAAM,gBAAgB;AAAA,IACpC,cAAc,MAAM,gBAAgB;AAAA,IACpC,YAAY,MAAM,cAAc;AAAA,IAChC,WAAW,MAAM,aAAa;AAAA,IAC9B,cAAc,MAAM,gBAAgB;AAAA,IACpC,qBAAqB,MAAM,uBAAuB;AAAA,IAClD,oBAAoB,MAAM,sBAAsB;AAAA;AAAA,IAEhD,cAAc,MAAM,gBAAgB;AAAA,IACpC,0BAA0B,MAAM,4BAA4B;AAAA,IAC5D,kBAAkB,MAAM,oBAAoB;AAAA,IAC5C,iBAAiB,MAAM,mBAAmB;AAAA,IAC1C,UAAU,MAAM,YAAY;AAAA,IAC5B,WAAW,MAAM,aAAa;AAAA;AAAA,IAE9B,OAAO,MAAM,SAAS;AAAA,EACxB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
function isPortOrTerminal(facilityTypeCode) {
|
|
2
|
+
if (!facilityTypeCode) return false;
|
|
3
|
+
return facilityTypeCode === "POTE" || facilityTypeCode === "INTE";
|
|
4
|
+
}
|
|
5
|
+
function isNonPortLocation(facilityTypeCode) {
|
|
6
|
+
if (!facilityTypeCode) return false;
|
|
7
|
+
return facilityTypeCode === "CLOC" || facilityTypeCode === "DEPO";
|
|
8
|
+
}
|
|
9
|
+
function inferRouteFromEvents(events) {
|
|
10
|
+
const result = {
|
|
11
|
+
originUnlocode: null,
|
|
12
|
+
destinationUnlocode: null,
|
|
13
|
+
confidence: {
|
|
14
|
+
origin: null,
|
|
15
|
+
destination: null
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (!events || events.length === 0) {
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
const sortedEvents = [...events].sort(
|
|
22
|
+
(a, b) => a.eventDateTime.getTime() - b.eventDateTime.getTime()
|
|
23
|
+
);
|
|
24
|
+
const plannedArriEvents = sortedEvents.filter(
|
|
25
|
+
(e) => e.eventType === "TRANSPORT" && e.eventCode === "ARRI" && (e.eventClassifierCode === "EST" || e.eventClassifierCode === "PLN") && e.locationUnlocode && !isNonPortLocation(e.facilityTypeCode)
|
|
26
|
+
);
|
|
27
|
+
if (plannedArriEvents.length > 0) {
|
|
28
|
+
const lastPlannedArri = plannedArriEvents[plannedArriEvents.length - 1];
|
|
29
|
+
result.destinationUnlocode = lastPlannedArri.locationUnlocode;
|
|
30
|
+
result.confidence.destination = "high";
|
|
31
|
+
}
|
|
32
|
+
if (!result.destinationUnlocode) {
|
|
33
|
+
const actArriEvents = sortedEvents.filter(
|
|
34
|
+
(e) => e.eventType === "TRANSPORT" && e.eventCode === "ARRI" && e.eventClassifierCode === "ACT" && e.modeOfTransport === "VESSEL" && e.locationUnlocode && !isNonPortLocation(e.facilityTypeCode)
|
|
35
|
+
);
|
|
36
|
+
if (actArriEvents.length > 0) {
|
|
37
|
+
const lastArri = actArriEvents[actArriEvents.length - 1];
|
|
38
|
+
result.destinationUnlocode = lastArri.locationUnlocode;
|
|
39
|
+
result.confidence.destination = "high";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!result.destinationUnlocode) {
|
|
43
|
+
const discEvents = sortedEvents.filter(
|
|
44
|
+
(e) => e.eventType === "EQUIPMENT" && e.eventCode === "DISC" && e.locationUnlocode && !isNonPortLocation(e.facilityTypeCode)
|
|
45
|
+
);
|
|
46
|
+
if (discEvents.length > 0) {
|
|
47
|
+
const lastDisc = discEvents[discEvents.length - 1];
|
|
48
|
+
result.destinationUnlocode = lastDisc.locationUnlocode;
|
|
49
|
+
result.confidence.destination = "medium";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const firstVesselDepa = sortedEvents.find(
|
|
53
|
+
(e) => e.eventType === "TRANSPORT" && e.eventCode === "DEPA" && e.eventClassifierCode === "ACT" && e.modeOfTransport === "VESSEL" && e.carrierExportVoyageNumber && e.locationUnlocode
|
|
54
|
+
);
|
|
55
|
+
if (firstVesselDepa?.locationUnlocode) {
|
|
56
|
+
result.originUnlocode = firstVesselDepa.locationUnlocode;
|
|
57
|
+
result.confidence.origin = "high";
|
|
58
|
+
}
|
|
59
|
+
if (!result.originUnlocode) {
|
|
60
|
+
const firstLoad = sortedEvents.find(
|
|
61
|
+
(e) => e.eventType === "EQUIPMENT" && e.eventCode === "LOAD" && e.eventClassifierCode === "ACT" && e.modeOfTransport === "VESSEL" && e.locationUnlocode
|
|
62
|
+
);
|
|
63
|
+
if (firstLoad?.locationUnlocode) {
|
|
64
|
+
result.originUnlocode = firstLoad.locationUnlocode;
|
|
65
|
+
result.confidence.origin = "high";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!result.originUnlocode) {
|
|
69
|
+
const firstGtinLaden = sortedEvents.find(
|
|
70
|
+
(e) => e.eventType === "EQUIPMENT" && e.eventCode === "GTIN" && e.emptyIndicatorCode === "LADEN" && e.eventClassifierCode === "ACT" && e.locationUnlocode && e.facilityTypeCode === "POTE"
|
|
71
|
+
// Port Terminal only
|
|
72
|
+
);
|
|
73
|
+
if (firstGtinLaden?.locationUnlocode) {
|
|
74
|
+
result.originUnlocode = firstGtinLaden.locationUnlocode;
|
|
75
|
+
result.confidence.origin = "medium";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!result.originUnlocode) {
|
|
79
|
+
const firstPlannedDepa = sortedEvents.find(
|
|
80
|
+
(e) => e.eventType === "TRANSPORT" && e.eventCode === "DEPA" && (e.eventClassifierCode === "EST" || e.eventClassifierCode === "PLN") && e.modeOfTransport === "VESSEL" && e.locationUnlocode
|
|
81
|
+
);
|
|
82
|
+
if (firstPlannedDepa?.locationUnlocode) {
|
|
83
|
+
result.originUnlocode = firstPlannedDepa.locationUnlocode;
|
|
84
|
+
result.confidence.origin = "medium";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!result.originUnlocode) {
|
|
88
|
+
const firstPlannedLoad = sortedEvents.find(
|
|
89
|
+
(e) => e.eventType === "EQUIPMENT" && e.eventCode === "LOAD" && (e.eventClassifierCode === "EST" || e.eventClassifierCode === "PLN") && e.modeOfTransport === "VESSEL" && e.locationUnlocode
|
|
90
|
+
);
|
|
91
|
+
if (firstPlannedLoad?.locationUnlocode) {
|
|
92
|
+
result.originUnlocode = firstPlannedLoad.locationUnlocode;
|
|
93
|
+
result.confidence.origin = "medium";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
function isValidUnlocode(code) {
|
|
99
|
+
if (!code) return false;
|
|
100
|
+
return /^[A-Z]{2}[A-Z0-9]{3}$/.test(code.toUpperCase());
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
inferRouteFromEvents,
|
|
104
|
+
isValidUnlocode
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=route-inference.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/route-inference.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CarrierFetchedEvent } from './carrier-adapter'\n\nexport interface InferredRoute {\n originUnlocode: string | null\n destinationUnlocode: string | null\n confidence: {\n origin: 'high' | 'medium' | 'low' | null\n destination: 'high' | 'medium' | 'low' | null\n }\n}\n\n/**\n * Checks if the facility type represents a port or intermodal terminal.\n * Used to filter out client locations (CLOC) and depots (DEPO) when inferring ports.\n *\n * Valid facility types for port inference:\n * - POTE: Port Terminal\n * - INTE: Intermodal Terminal (rail/truck interchange)\n */\nfunction isPortOrTerminal(facilityTypeCode: string | null | undefined): boolean {\n if (!facilityTypeCode) return false\n return facilityTypeCode === 'POTE' || facilityTypeCode === 'INTE'\n}\n\n/**\n * Checks if the facility type is a known non-port location (client site or depot).\n * Used to filter out these locations when inferring destination ports.\n */\nfunction isNonPortLocation(facilityTypeCode: string | null | undefined): boolean {\n if (!facilityTypeCode) return false\n // CLOC = Client Location, DEPO = Depot - these are inland delivery points\n return facilityTypeCode === 'CLOC' || facilityTypeCode === 'DEPO'\n}\n\n/**\n * Infers origin and destination ports from DCSA tracking events.\n *\n * Strategy for DESTINATION:\n * 1. Last EST/PLN ARRI event at a port terminal (POTE/INTE) \u2192 high confidence\n * 2. Last ACT ARRI on VESSEL at a port terminal \u2192 high confidence\n * 3. Last DISC event at a port terminal \u2192 medium confidence\n * 4. Fallback: Last ARRI/DISC at any location (when no port data available)\n *\n * IMPORTANT: We filter by facilityTypeCode to exclude client locations (CLOC)\n * and depots (DEPO) which are inland delivery points, not ports.\n *\n * Strategy for ORIGIN:\n * 1. First ACT DEPA on VESSEL with exportVoyageNumber \u2192 high confidence (departure from load port)\n * 2. First LOAD event on VESSEL \u2192 high confidence (where cargo was first loaded)\n * 3. First GTIN (LADEN) event \u2192 medium confidence (gate-in at origin)\n *\n * Key insight:\n * - exportVoyageNumber indicates outbound leg (origin side)\n * - importVoyageNumber indicates inbound leg (destination side)\n */\nexport function inferRouteFromEvents(events: CarrierFetchedEvent[]): InferredRoute {\n const result: InferredRoute = {\n originUnlocode: null,\n destinationUnlocode: null,\n confidence: {\n origin: null,\n destination: null,\n },\n }\n\n if (!events || events.length === 0) {\n return result\n }\n\n // Sort events by datetime for chronological processing\n const sortedEvents = [...events].sort(\n (a, b) => a.eventDateTime.getTime() - b.eventDateTime.getTime()\n )\n\n // \u2500\u2500\u2500 DESTINATION INFERENCE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // For transshipment voyages, there are multiple ARRI events (one per port).\n // The LAST arrival event at a PORT TERMINAL is the final destination.\n // We explicitly filter out client locations (CLOC) and depots (DEPO).\n\n // Strategy 1: Look for LAST ARRI event (EST or PLN), excluding known non-port locations\n // PLN (planned) and EST (estimated) both indicate scheduled arrivals.\n // We take the LAST one as the final destination, filtering out client locations/depots.\n // Note: We use \"exclude non-port\" rather than \"include only port\" to handle cases\n // where facilityTypeCode is not provided (common in many carriers' responses).\n const plannedArriEvents = sortedEvents.filter(\n (e) =>\n e.eventType === 'TRANSPORT' &&\n e.eventCode === 'ARRI' &&\n (e.eventClassifierCode === 'EST' || e.eventClassifierCode === 'PLN') &&\n e.locationUnlocode &&\n !isNonPortLocation(e.facilityTypeCode)\n )\n\n if (plannedArriEvents.length > 0) {\n // Take the last planned arrival - this is the final port destination\n const lastPlannedArri = plannedArriEvents[plannedArriEvents.length - 1]\n result.destinationUnlocode = lastPlannedArri.locationUnlocode!\n // High confidence if we have facility type info confirming it's a port, otherwise still high for PLN/EST\n result.confidence.destination = 'high'\n }\n\n // Strategy 2: Look for last ACT ARRI on VESSEL, excluding known non-port locations\n if (!result.destinationUnlocode) {\n const actArriEvents = sortedEvents.filter(\n (e) =>\n e.eventType === 'TRANSPORT' &&\n e.eventCode === 'ARRI' &&\n e.eventClassifierCode === 'ACT' &&\n e.modeOfTransport === 'VESSEL' &&\n e.locationUnlocode &&\n !isNonPortLocation(e.facilityTypeCode)\n )\n\n if (actArriEvents.length > 0) {\n const lastArri = actArriEvents[actArriEvents.length - 1]\n result.destinationUnlocode = lastArri.locationUnlocode!\n result.confidence.destination = 'high'\n }\n }\n\n // Strategy 3: Look for last DISC event, excluding known non-port locations\n if (!result.destinationUnlocode) {\n const discEvents = sortedEvents.filter(\n (e) =>\n e.eventType === 'EQUIPMENT' &&\n e.eventCode === 'DISC' &&\n e.locationUnlocode &&\n !isNonPortLocation(e.facilityTypeCode)\n )\n\n if (discEvents.length > 0) {\n const lastDisc = discEvents[discEvents.length - 1]\n result.destinationUnlocode = lastDisc.locationUnlocode!\n result.confidence.destination = 'medium'\n }\n }\n\n // \u2500\u2500\u2500 ORIGIN INFERENCE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // Strategy 1: First ACT DEPA on VESSEL with exportVoyageNumber\n const firstVesselDepa = sortedEvents.find(\n (e) =>\n e.eventType === 'TRANSPORT' &&\n e.eventCode === 'DEPA' &&\n e.eventClassifierCode === 'ACT' &&\n e.modeOfTransport === 'VESSEL' &&\n e.carrierExportVoyageNumber &&\n e.locationUnlocode\n )\n\n if (firstVesselDepa?.locationUnlocode) {\n result.originUnlocode = firstVesselDepa.locationUnlocode\n result.confidence.origin = 'high'\n }\n\n // Strategy 2: First LOAD event on VESSEL\n if (!result.originUnlocode) {\n const firstLoad = sortedEvents.find(\n (e) =>\n e.eventType === 'EQUIPMENT' &&\n e.eventCode === 'LOAD' &&\n e.eventClassifierCode === 'ACT' &&\n e.modeOfTransport === 'VESSEL' &&\n e.locationUnlocode\n )\n\n if (firstLoad?.locationUnlocode) {\n result.originUnlocode = firstLoad.locationUnlocode\n result.confidence.origin = 'high'\n }\n }\n\n // Strategy 3: First GTIN (LADEN) event at a port terminal (fallback)\n // Only consider events at port terminals (POTE), not inland depots (DEPO)\n if (!result.originUnlocode) {\n const firstGtinLaden = sortedEvents.find(\n (e) =>\n e.eventType === 'EQUIPMENT' &&\n e.eventCode === 'GTIN' &&\n e.emptyIndicatorCode === 'LADEN' &&\n e.eventClassifierCode === 'ACT' &&\n e.locationUnlocode &&\n e.facilityTypeCode === 'POTE' // Port Terminal only\n )\n\n if (firstGtinLaden?.locationUnlocode) {\n result.originUnlocode = firstGtinLaden.locationUnlocode\n result.confidence.origin = 'medium'\n }\n }\n\n // Strategy 4: First EST/PLN DEPA on VESSEL (for pre-departure tracking)\n // When no actual events exist yet, use planned departure to infer origin\n if (!result.originUnlocode) {\n const firstPlannedDepa = sortedEvents.find(\n (e) =>\n e.eventType === 'TRANSPORT' &&\n e.eventCode === 'DEPA' &&\n (e.eventClassifierCode === 'EST' || e.eventClassifierCode === 'PLN') &&\n e.modeOfTransport === 'VESSEL' &&\n e.locationUnlocode\n )\n\n if (firstPlannedDepa?.locationUnlocode) {\n result.originUnlocode = firstPlannedDepa.locationUnlocode\n result.confidence.origin = 'medium' // Lower confidence for planned events\n }\n }\n\n // Strategy 5: First PLN LOAD event on VESSEL (for pre-departure tracking)\n if (!result.originUnlocode) {\n const firstPlannedLoad = sortedEvents.find(\n (e) =>\n e.eventType === 'EQUIPMENT' &&\n e.eventCode === 'LOAD' &&\n (e.eventClassifierCode === 'EST' || e.eventClassifierCode === 'PLN') &&\n e.modeOfTransport === 'VESSEL' &&\n e.locationUnlocode\n )\n\n if (firstPlannedLoad?.locationUnlocode) {\n result.originUnlocode = firstPlannedLoad.locationUnlocode\n result.confidence.origin = 'medium'\n }\n }\n\n return result\n}\n\n/**\n * Validates a UN/LOCODE format.\n * Format: 2 letters (country) + 3 alphanumeric (location)\n * Example: CNYTN (China, Yantian), PLGDN (Poland, Gdansk)\n */\nexport function isValidUnlocode(code: string | null | undefined): boolean {\n if (!code) return false\n return /^[A-Z]{2}[A-Z0-9]{3}$/.test(code.toUpperCase())\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const DEFAULT_WINDOW_DAYS = 120;
|
|
2
|
+
const JITTER_RANGE_HOURS = 4;
|
|
3
|
+
function addDays(date, days) {
|
|
4
|
+
const result = new Date(date);
|
|
5
|
+
result.setDate(result.getDate() + days);
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
function addHours(date, hours) {
|
|
9
|
+
const result = new Date(date);
|
|
10
|
+
result.setTime(result.getTime() + hours * 60 * 60 * 1e3);
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
function randomJitter() {
|
|
14
|
+
return Math.random() * JITTER_RANGE_HOURS - JITTER_RANGE_HOURS / 2;
|
|
15
|
+
}
|
|
16
|
+
function generatePollSchedule(input) {
|
|
17
|
+
const now = /* @__PURE__ */ new Date();
|
|
18
|
+
if (input?.ata) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
let startDate;
|
|
22
|
+
let endDate;
|
|
23
|
+
if (input?.etd) {
|
|
24
|
+
startDate = addDays(input.etd, -7);
|
|
25
|
+
} else if (input?.atd) {
|
|
26
|
+
startDate = input.atd;
|
|
27
|
+
} else {
|
|
28
|
+
startDate = now;
|
|
29
|
+
}
|
|
30
|
+
if (input?.eta) {
|
|
31
|
+
endDate = addDays(input.eta, 14);
|
|
32
|
+
} else {
|
|
33
|
+
endDate = addDays(startDate, DEFAULT_WINDOW_DAYS);
|
|
34
|
+
}
|
|
35
|
+
if (startDate < now) {
|
|
36
|
+
startDate = now;
|
|
37
|
+
}
|
|
38
|
+
const dates = [];
|
|
39
|
+
let current = new Date(startDate);
|
|
40
|
+
while (current <= endDate) {
|
|
41
|
+
const withJitter = addHours(current, randomJitter());
|
|
42
|
+
if (withJitter > now) {
|
|
43
|
+
dates.push(withJitter.toISOString());
|
|
44
|
+
}
|
|
45
|
+
current = addDays(current, 1);
|
|
46
|
+
}
|
|
47
|
+
return dates;
|
|
48
|
+
}
|
|
49
|
+
function getNextPollDate(schedule) {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
for (const dateStr of schedule) {
|
|
52
|
+
const date = new Date(dateStr);
|
|
53
|
+
if (date.getTime() > now) {
|
|
54
|
+
return date;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
generatePollSchedule,
|
|
61
|
+
getNextPollDate
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=schedule-generator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/shipment_tracking/lib/schedule-generator.ts"],
|
|
4
|
+
"sourcesContent": ["const DEFAULT_WINDOW_DAYS = 120 // 4 months\nconst JITTER_RANGE_HOURS = 4\n\nfunction addDays(date: Date, days: number): Date {\n const result = new Date(date)\n result.setDate(result.getDate() + days)\n return result\n}\n\nfunction addHours(date: Date, hours: number): Date {\n const result = new Date(date)\n result.setTime(result.getTime() + hours * 60 * 60 * 1000)\n return result\n}\n\nfunction randomJitter(): number {\n return Math.random() * JITTER_RANGE_HOURS - JITTER_RANGE_HOURS / 2\n}\n\n/**\n * Generates a list of daily poll dates with random time jitter.\n *\n * - If ETD and ETA are provided, generates dates from ETD - 7 days to ETA + 14 days.\n * - If only ETD is provided, generates from ETD - 7 days for the default window.\n * - If nothing is provided, starts from now for the default window.\n * - Jitter is +/- 2 hours to spread API load.\n */\nexport function generatePollSchedule(input?: {\n etd?: Date | null\n eta?: Date | null\n atd?: Date | null\n ata?: Date | null\n}): string[] {\n const now = new Date()\n\n // If already delivered (ATA present), no more polling needed\n if (input?.ata) {\n return []\n }\n\n let startDate: Date\n let endDate: Date\n\n if (input?.etd) {\n startDate = addDays(input.etd, -7)\n } else if (input?.atd) {\n startDate = input.atd\n } else {\n startDate = now\n }\n\n if (input?.eta) {\n endDate = addDays(input.eta, 14)\n } else {\n endDate = addDays(startDate, DEFAULT_WINDOW_DAYS)\n }\n\n // Ensure start is not in the past\n if (startDate < now) {\n startDate = now\n }\n\n const dates: string[] = []\n let current = new Date(startDate)\n\n while (current <= endDate) {\n const withJitter = addHours(current, randomJitter())\n // Only include future dates\n if (withJitter > now) {\n dates.push(withJitter.toISOString())\n }\n current = addDays(current, 1)\n }\n\n return dates\n}\n\n/**\n * Given a schedule (array of ISO date strings), returns the next poll date after now.\n */\nexport function getNextPollDate(schedule: string[]): Date | null {\n const now = Date.now()\n\n for (const dateStr of schedule) {\n const date = new Date(dateStr)\n if (date.getTime() > now) {\n return date\n }\n }\n\n return null\n}\n"],
|
|
5
|
+
"mappings": "AAAA,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAE3B,SAAS,QAAQ,MAAY,MAAoB;AAC/C,QAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,SAAO,QAAQ,OAAO,QAAQ,IAAI,IAAI;AACtC,SAAO;AACT;AAEA,SAAS,SAAS,MAAY,OAAqB;AACjD,QAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,SAAO,QAAQ,OAAO,QAAQ,IAAI,QAAQ,KAAK,KAAK,GAAI;AACxD,SAAO;AACT;AAEA,SAAS,eAAuB;AAC9B,SAAO,KAAK,OAAO,IAAI,qBAAqB,qBAAqB;AACnE;AAUO,SAAS,qBAAqB,OAKxB;AACX,QAAM,MAAM,oBAAI,KAAK;AAGrB,MAAI,OAAO,KAAK;AACd,WAAO,CAAC;AAAA,EACV;AAEA,MAAI;AACJ,MAAI;AAEJ,MAAI,OAAO,KAAK;AACd,gBAAY,QAAQ,MAAM,KAAK,EAAE;AAAA,EACnC,WAAW,OAAO,KAAK;AACrB,gBAAY,MAAM;AAAA,EACpB,OAAO;AACL,gBAAY;AAAA,EACd;AAEA,MAAI,OAAO,KAAK;AACd,cAAU,QAAQ,MAAM,KAAK,EAAE;AAAA,EACjC,OAAO;AACL,cAAU,QAAQ,WAAW,mBAAmB;AAAA,EAClD;AAGA,MAAI,YAAY,KAAK;AACnB,gBAAY;AAAA,EACd;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,UAAU,IAAI,KAAK,SAAS;AAEhC,SAAO,WAAW,SAAS;AACzB,UAAM,aAAa,SAAS,SAAS,aAAa,CAAC;AAEnD,QAAI,aAAa,KAAK;AACpB,YAAM,KAAK,WAAW,YAAY,CAAC;AAAA,IACrC;AACA,cAAU,QAAQ,SAAS,CAAC;AAAA,EAC9B;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,UAAiC;AAC/D,QAAM,MAAM,KAAK,IAAI;AAErB,aAAW,WAAW,UAAU;AAC9B,UAAM,OAAO,IAAI,KAAK,OAAO;AAC7B,QAAI,KAAK,QAAQ,IAAI,KAAK;AACxB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|