Hello! Welcome to the first lesson of our course on building a resilient and intelligent logistics system for the Chittagong Port.
In this lesson, we'll start by creating the fundamental data building blocks for our entire system: digital twins. You'll learn how to develop a Python class to represent physical port entities like vehicles and cargo, ensuring these representations can be cleanly serialized to JSON. This is a crucial first step, as these digital twin objects will be the data packets that flow through the distributed services we'll build in later modules. We will explore Python's built-in dataclasses and then advance to using Pydantic, a powerful library that provides the robustness needed for enterprise-grade systems.
What is a Digital Twin?
At its core, a digital twin is a virtual model of a physical object or system. This virtual model is a living entity, continuously updated with data from its physical counterpart. It allows us to simulate, analyze, and control the physical object without directly interacting with it.

For our port logistics system, a Vehicle in the yard or a Cargo container will each have a corresponding digital twin. These software objects will hold their state (e.g., location, status, contents) and will be the primary actors in the workflows we design.
Approach 1: Python's Built-in dataclasses
When modeling data in modern Python, we need structures that are more robust than simple dictionaries but less verbose than traditional classes. Python 3.7+ offers the dataclasses module for this purpose. A dataclass automatically generates special methods like __init__(), __repr__(), and __eq__() from type-hinted class attributes, making your code cleaner and less repetitive.
To get a solid grasp of how dataclasses work, please review the following material.
How to Use Dataclasses for Clean Data Models - OneUptime
This article from the OneUptime blog provides a thorough introduction to dataclasses. It will give you the foundational knowledge for creating structured data classes in Python.
Please read the following parts: Getting Started with Dataclasses: Start from the beginning and read the sections on basic definitions and field ordering. This will show you how to declare a simple dataclass. Field Customization: Continue to the section on field() customization. Focus on the explanation and the code example under complex defaults. Pay attention to default_factory, which is essential for mutable default values like lists. Post-Initialization: Finally, read the section on post-init processing. The __post_init__ method is very useful for validation or computing derived fields after an object is created.
With this foundation, let's consider how to represent a piece of cargo. Using a dataclass, it might look like this:
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import json
class CargoStatus(str, Enum):
IN_YARD = "in_yard"
ON_VEHICLE = "on_vehicle"
CUSTOMS_CLEARED = "customs_cleared"
GATE_OUT = "gate_out"
@dataclass
class CargoTwin:
container_id: str
cargo_type: str
weight_kg: float
destination: str
status: CargoStatus = CargoStatus.IN_YARD
last_updated: datetime = field(default_factory=datetime.now)
history: list[str] = field(default_factory=list)
def __post_init__(self):
# Basic validation
if self.weight_kg <= 0:
raise ValueError("Weight must be a positive number.")
self.history.append(f"Created at {self.last_updated.isoformat()} with status {self.status}")
def to_json(self) -> str:
"""Serializes the object to a JSON string."""
# Custom default function to handle datetime and Enum objects
def json_encoder(obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, Enum):
return obj.value
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
from dataclasses import asdict
return json.dumps(asdict(self), default=json_encoder, indent=2)
# Example usage
try:
cargo = CargoTwin(
container_id="CMAU1234567",
cargo_type="Electronics",
weight_kg=15000.5,
destination="Dhaka"
)
print(cargo.to_json())
except ValueError as e:
print(f"Error creating cargo twin: {e}")
This CargoTwin dataclass is a good start. It has typed attributes, default values, and a __post_init__ hook for simple validation. However, the serialization part (to_json) is manual. We have to handle types like datetime and Enum ourselves. For a large, enterprise-grade system, this can become cumbersome and error-prone.
Approach 2: Pydantic for Enterprise-Grade Data Modeling
For systems that demand high reliability, data integrity, and seamless integration with APIs, Pydantic is the industry standard. It enforces type hints at runtime, providing robust data validation and parsing, not just after an object is created, but during its creation. It also offers superior serialization capabilities out of the box.
Given your background in designing secure, audit-friendly systems, you'll find Pydantic's approach to data validation particularly powerful. It helps ensure that malformed data never enters your system's logic.
This video provides an excellent introduction to Pydantic's philosophy and core features.
Pydantic Tutorial • Solving Python's Biggest Problem
This tutorial from the pixegami channel clearly explains why Pydantic is so valuable and demonstrates its main features: modeling, validation, and serialization.
Please watch the following segments: Defining Models: This part shows how to create a basic Pydantic model by inheriting from BaseModel. Data Validation: Watch this section to see how Pydantic performs validation automatically, including for special types like emails and through custom validators. JSON Serialization: Finally, see how easily Pydantic handles JSON in this segment. Note: The video uses Pydantic V1 syntax (e.g., .json(), .dict()). In our code, we will use the modern V2 syntax (e.g., model_dump_json(), model_dump()), but the underlying concepts are identical.
Now, let's refactor our CargoTwin using Pydantic. Notice how much cleaner and more powerful it becomes.
from pydantic import BaseModel, Field, ValidationError
from datetime import datetime
from enum import Enum
# Enums remain the same
class CargoStatus(str, Enum):
IN_YARD = "in_yard"
ON_VEHICLE = "on_vehicle"
CUSTOMS_CLEARED = "customs_cleared"
GATE_OUT = "gate_out"
class VehicleStatus(str, Enum):
IDLE = "idle"
EN_ROUTE = "en_route"
LOADING = "loading"
UNLOADING = "unloading"
# A nested model for location
class Location(BaseModel):
lat: float = Field(..., ge=-90, le=90)
lon: float = Field(..., ge=-180, le=180)
# The Pydantic version of our Digital Twins
class CargoTwin(BaseModel):
container_id: str = Field(..., pattern=r"^[A-Z]{4}\d{7}$") # Regex validation
cargo_type: str
weight_kg: float = Field(..., gt=0) # Value must be greater than 0
destination: str
status: CargoStatus = CargoStatus.IN_YARD
last_updated: datetime = Field(default_factory=datetime.utcnow)
class VehicleTwin(BaseModel):
vehicle_id: str
location: Location
status: VehicleStatus = VehicleStatus.IDLE
current_cargo_id: str | None = None # Optional field
last_updated: datetime = Field(default_factory=datetime.utcnow)
# --- Example Usage ---
# 1. Create instances with valid data
try:
vehicle_location = Location(lat=22.25, lon=91.82)
vehicle = VehicleTwin(
vehicle_id="DH-GA-1234",
location=vehicle_location
)
cargo = CargoTwin(
container_id="MSKU1812345",
cargo_type="Garments",
weight_kg=18500.0,
destination="Chittagong EPZ"
)
# 2. Serialize to JSON effortlessly
# Pydantic handles datetime and enums automatically
vehicle_json = vehicle.model_dump_json(indent=2)
cargo_json = cargo.model_dump_json(indent=2)
print("--- VehicleTwin ---")
print(vehicle_json)
print("\n--- CargoTwin ---")
print(cargo_json)
# 3. Deserialize from JSON
restored_vehicle = VehicleTwin.model_validate_json(vehicle_json)
print("\n--- Restored Vehicle ID ---")
print(restored_vehicle.vehicle_id)
except ValidationError as e:
# Pydantic raises a detailed error if validation fails
print(f"\n--- Validation Error ---")
print(e)
In this Pydantic version, notice the improvements:
- Built-in Validation: We use
Fieldto add validation rules directly to the attributes (e.g.,gt=0for weight, a regex pattern forcontainer_id). This is far more declarative and powerful than manual checks in__post_init__. - Nested Models: Pydantic seamlessly handles nested data structures, like the
Locationmodel insideVehicleTwin. - Effortless Serialization: The
.model_dump_json()method handles all type conversions (likedatetimeandEnum) automatically. The reverse,model_validate_json(), is just as simple. - Clarity and Intent: The code is more focused on what the data is, leaving the validation and serialization mechanics to the framework.
This final code block is a practical, hands-on implementation that directly achieves our learning outcome.

Conclusion
In this lesson, you have laid the very first stone in our architecture. We explored the concept of a digital twin and implemented it in Python. We started with the standard library's dataclasses to appreciate the basics of structured data modeling, then graduated to Pydantic to meet the demands of an enterprise-grade system requiring robust validation and simple, reliable serialization. The VehicleTwin and CargoTwin classes we've built are now ready to be used as the core data entities in the logistics workflows we will design next.
In the next lesson, we will shift our focus from data structures to business processes. You will learn how to model a key logistics workflow, such as a container's gate-in process, using BPMN 2.0 with the Camunda Modeler. This visual blueprint will define how our digital twins interact within the port ecosystem.