If you've built inventory software for a warehouse, you might assume a construction site is the same problem with more mud. It isn't. Materials arrive in staged deliveries, move between laydown yards and floors, get consumed by installation crews, and sit outdoors in weather. The "warehouse" has no walls, and the shelving changes weekly.
This post covers two things: how to match sensing technology to material type, and how to model the data so the system stays trustworthy once real sites start feeding it.
Start with the question, not the sensor
Before picking hardware, decide what each material needs to answer:
- Discrete items (steel members, MEP equipment, formwork panels): where is it, and has it been delivered and installed?
- Packaged goods (drywall, fittings, pallets): how many are on hand, and in which staging area?
- Bulk materials (aggregate, sand, fill): how much is left?
Each question maps to a different technology.
Comparing the four common options
| Technology | Good for | Main constraints |
|---|---|---|
| Barcode / QR | Delivery receipts, pallet and item check-in | Needs line of sight and a person scanning; accuracy depends on discipline |
| Passive UHF RFID | Batch reads at gates, staging areas, and doorways | Metal and liquids interfere with reads; fixed readers need power |
| BLE beacons / tags | Indoor zone-level location and proximity | Short range; infrastructure density affects accuracy; tags need batteries |
| LoRaWAN sensors | Outdoor and wide-area monitoring, such as bulk level sensors | Low data rate and small payloads; not suited to high-frequency streaming |
None of these wins everywhere. Steel members in a yard, drywall on a fourth floor, and a gravel pile have different physical constraints, so a mixed approach is usually the realistic answer. The rest of this post assumes you'll be ingesting data from more than one source.
The core design problem: heterogeneous, noisy events
Once you combine sensor types, your backend has to reconcile inputs that behave very differently:
- RFID readers can report the same tag dozens of times per second.
- BLE signal strength fluctuates, so zone assignment can flicker.
- Barcode scans are sparse but authoritative.
- LoRaWAN uplinks are infrequent and can arrive late or out of order.
If you write all of this straight into an "inventory" table, you'll get a number nobody trusts. A more robust approach is to store raw observations as immutable events, then derive state from them.
A minimal event model
from dataclasses import dataclass
from datetime import datetime
from typing import Literal, Optional
@dataclass(frozen=True)
class MaterialEvent:
event_id: str # unique, supports idempotent ingestion
source: Literal["rfid", "ble", "barcode", "lorawan"]
asset_id: str # tag ID, SKU, or sensor ID
zone: Optional[str] # laydown yard, floor, gate, etc.
observed_at: datetime # when the sensor saw it
received_at: datetime # when your system got it
value: Optional[float] = None # e.g., fill level for bulk sensors
Keeping observed_at and received_at separate matters. Delayed LoRaWAN uplinks would otherwise make your timeline lie about when something actually happened. It also means you should order and process events by observed_at, not by arrival time.
Debouncing noisy reads
For chatty sources like RFID and BLE, don't let a single read change an asset's zone. Instead, track a candidate zone and only commit to it once the asset has been observed there for a minimum dwell time:
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional
@dataclass
class ZoneState:
zone: Optional[str] = None # committed zone
candidate: Optional[str] = None # zone we might be moving to
candidate_since: Optional[datetime] = None
def update_zone(state: ZoneState, event, min_dwell=timedelta(seconds=30)) -> ZoneState:
if event.zone is None or event.zone == state.zone:
# Reading agrees with current state: drop any pending move.
state.candidate = None
state.candidate_since = None
return state
if event.zone != state.candidate:
# First sighting in a new zone: start the dwell clock.
state.candidate = event.zone
state.candidate_since = event.observed_at
return state
if event.observed_at - state.candidate_since >= min_dwell:
# Seen consistently in the new zone: commit the move.
state.zone = event.zone
state.candidate = None
state.candidate_since = None
return state
This is deliberately simple. In production you'd tune the dwell time per technology and zone type, handle out-of-order events explicitly, and possibly require a minimum number of reads as well as elapsed time. The principle is what matters: don't let a single noisy read move an asset.
Reconcile against purchase orders and schedules
Tracking is only useful if it connects to the plan. Two reconciliations deliver most of the value:
- Delivery receipt vs. purchase order. Compare scanned or read items against what was ordered, and flag shortages or substitutions at the gate rather than weeks later.
- Staged stock vs. installation sequence. If the schedule says a floor's drywall is needed next week, the system should show whether it is actually staged nearby.
This is also where "material staging analytics" becomes concrete: it is a join between your event stream and the project schedule.
Handle failure modes deliberately
Job sites are hostile environments, so design for them:
-
Connectivity gaps. Buffer events at the edge and replay them with their original
observed_attimestamps. -
Duplicate delivery. Use
event_idfor idempotent ingestion, because retries will happen. - Sensor drift and dead batteries. Track last-seen times per device and alert on silence, not just on bad values.
- Human override. Allow manual corrections, but record them as events with an author instead of silently editing state.
Putting it together
A workable architecture looks like this:
- Edge devices and gateways collect RFID, BLE, barcode, and LoRaWAN data.
- An ingestion layer validates, deduplicates, and stores raw events.
- A processing layer debounces, resolves zones, and derives inventory state.
- A reconciliation layer compares state with purchase orders and installation schedules.
- Dashboards and alerts surface exceptions rather than raw data.
For a real-world reference on how a multi-sensor approach to construction material inventory control is described, see Commcon AI, which covers RFID, BLE, barcode, and LoRaWAN sensing. Disclosure: I'm affiliated with Commcon AI.
Key takeaways
- Choose sensing technology per material type and environment, not per project.
- Store raw events immutably and derive inventory state from them.
- Separate observation time from receipt time, and process by observation time.
- Debounce noisy sources before they change state.
- Reconcile with purchase orders and schedules, because that's where the business value is.
Over to you
If you've worked on asset tracking in a harsh environment, such as construction, logistics, or mining, what was your biggest data-quality headache: duplicate reads, connectivity gaps, or reconciling with the plan? I'd like to hear how you handled it.
Top comments (0)