Skip to content

Cruits Platform — Context Handshake: Payload Formation Rules

Note: This story belongs to the Cruits platform, not the Housing Module. It defines how Cruits constructs and sends the session initiation payload to the Housing Module each time a Flexworker opens the Housing section. The receiving side — validation, session storage, and constraint enforcement — is specified in Housing Module Story 2.1.


User Story

As the Cruits platform, when a Flexworker opens the Housing section in the Cruits app, I need to construct a session context payload from the worker's active and future placements and job offers, and from the global Housing configuration, and send it to the Housing Module, so that the Housing Module has the correct worker identity, job location, permitted booking period, maximum daily cost, and payment options for this session.


Global Housing Configuration

These settings are managed by a Cruits Platform Admin via Django Admin. They apply uniformly to all workers for MVP.

Out of scope for MVP: per-employer, per-placement, and per-offer overrides of any setting below. All workers receive values derived from the global configuration.

Setting Label Type Default Notes
direct_payment_enabled Direct payment Yes/No When Yes: workers may pay for housing directly. Sent as-is to the Housing Module.
indirect_payment_enabled Indirect payment Yes/No When Yes: Cruits acts as payer and covers accommodation costs within constraints.
payer_id Housing Module Payer ID Text Required The identifier of the Cruits Payer record in the Housing Module. Configured once after Cruits is registered as a Payer.
payer_name Payer display name Text Required Shown to the worker in the WGV confirmation notice. Typically "Cruits" or the Cruits legal entity name.
permitted_period_start_offset Booking start offset Number (days) How many days before the earliest work start date the worker may begin booking housing.
permitted_period_end_offset Booking end offset Number (days) How many days after the latest work end date the worker may continue booking housing.
max_daily_cost_salary_percent Max daily cost — fallback (% of salary) Decimal 25 Percentage of total daily salary used to calculate the fallback max_daily_cost. Applied when no PKS cost table is configured or when a listing has no PKS value.
default_billing_period_unit Default billing period unit Enum Sent as billing_period_unit in the payload. Values: day, week, month.
pks_cost_table PKS cost table Table Empty Optional. When configured, enables quality-based cost limits. See PKS Cost Table section. When empty, only the fallback max daily cost applies.

PKS Cost Table (Nice to have)

The PKS cost table allows different maximum daily cost thresholds based on a listing's Housing Quality Score (PKS — set by the Provider on the listing; see Housing Module Story 1.12). Each row defines a PKS range and the corresponding salary percentage.

Column Type Notes
pks_from Decimal ≥ 0 Lower bound of the PKS range (inclusive). The first row must have pks_from = 0.
pks_to Decimal or blank Upper bound (exclusive). Blank = open-ended (no upper limit). The last row must have pks_to blank.
salary_percent Decimal Maximum daily cost as a percentage of total daily salary for listings in this PKS range.

Validation rules: - Ranges must not overlap. - Ranges must be contiguous with no gaps — each row's pks_from must equal the previous row's pks_to. - The first row's pks_from must be 0. - The last row's pks_to must be blank (open-ended).

Example:

pks_from pks_to salary_percent
0 150 20
150 300 25
300 (open) 30

Qualifying Records

All payload fields that depend on the worker's placements and job offers use the following definition consistently.

A placement or job offer is qualifying if it meets any of the following conditions: - start_date is today or in the future (upcoming contract), or - start_date is in the past and end_date is today or in the future (currently active), or - start_date is in the past or today and end_date is not set (indefinite active contract).

Records with an end_date strictly in the past are excluded from all calculations.


Payload Field Formation Rules

Worker identity

Payload field Source Rule
worker_id Cruits worker record The worker's unique Cruits identifier.
source_client Hardcoded Always cruits.
full_name Cruits worker record Worker's full name.
email Cruits worker record Worker's email address. Omitted if not recorded.
phone Cruits worker record Worker's phone number in E.164 format. Omitted if not recorded.

Job location

The Housing tab can be opened from five entry points in the Cruits app. Four entry points carry a specific job context (e.g., Job Details, Offer Notification); one entry point has no job context.

Entry point with job context (4 of 5): The location of that specific job is used for job_location_lat, job_location_lng, and job_location_display_name. The display name is constructed from the employer name and the job site city or location label (format: "Employer name, Location display name", e.g., "Intertoys, Amsterdam"). These values pre-select the matching item in the job location dropdown on the Housing listing screen. The worker may change the selected location via the listing filter panel; doing so updates only the Housing Module filter — no new session initiation is triggered.

Entry point without job context (1 of 5): job_location_lat, job_location_lng, and job_location_display_name are omitted from the payload. The job location dropdown opens empty and the distance filter starts inactive.

Session permissions

Payload field Rule
reservation_permitted Yes if the worker has at least one active offer or placement. No otherwise.
direct_payment_enabled Copied directly from global setting.
indirect_payment_enabled Copied directly from global setting.

Permitted period (sent only when indirect_payment_enabled = Yes)

permitted_period_start

  1. Take all active offers or placements
  2. Find the earliest start_date.
  3. Subtract permitted_period_start_offset days.
  4. Send the result as permitted_period_start. The result may be in the past — this is acceptable; the Housing Module accepts past start dates.

permitted_period_end

  1. Take all active offers or placements
  2. For each record, determine its effective end date:
  3. Record has an end_date → effective end date = end_date.
  4. Record has no end_date (indefinite contract) → effective end date = start_date + 6 months.
  5. Find the latest effective end date across all qualifying records.
  6. Add permitted_period_end_offset days.
  7. Send the result as permitted_period_end.

Maximum daily cost (sent only when indirect_payment_enabled = Yes)

Total daily salary base:

For each active offer or placement:

salary_per_day = hourly_rate × expected_hours_per_week / 7

Sum across all

total_daily_salary = Σ salary_per_day

max_daily_cost (fallback — always sent):

max_daily_cost = total_daily_salary × max_daily_cost_salary_percent / 100
Rounded to 2 decimal places.

pks_cost_table (sent only when PKS cost table is configured in settings):

For each row in the configured PKS cost table, Cruits pre-calculates the maximum daily cost amount:

row.max_daily_cost = total_daily_salary × row.salary_percent / 100
Rounded to 2 decimal places.

The payload includes pks_cost_table as a list of {pks_from, pks_to, max_daily_cost} entries — one per configured row, with monetary amounts already calculated. The Housing Module receives pre-calculated Euro amounts, not percentages.

max_daily_cost (fallback) is always included even when pks_cost_table is present. It is used by the Housing Module for listings without a PKS value.

Billing and payer fields (sent only when indirect_payment_enabled = Yes)

Payload field Source Rule
billing_period_unit Global setting Copied directly from default_billing_period_unit.
payer_id Global setting Copied directly from payer_id.
payer_name Global setting Copied directly from payer_name.

Acceptance Criteria

  1. Global configuration completeness check
  2. Before constructing the payload, Cruits checks that all required settings have a value. Required settings: payer_id, payer_name, default_billing_period_unit.
  3. If any required setting is missing, the Housing section cannot be opened and the worker sees a generic error. A system alert is logged for the Cruits Platform Admin.
  4. direct_payment_enabled and indirect_payment_enabled must both be set to Yes or No. If both are No, the payload is still sent (browse-only session); this is not treated as a configuration error by Cruits.

  5. Qualifying records applied consistently

  6. All payload fields that depend on placements or job offers use the qualifying record definition above. Records with end_date in the past are excluded from all calculations in this payload.

  7. reservation_permitted determination

  8. Yes if there is at least one qualifying record for the worker. No if there are none.

  9. Job location — entry point with job context

  10. When the worker opens Housing from an entry point that carries a specific job context, job_location_lat, job_location_lng, and job_location_display_name are taken from that job's location and sent in the payload.
  11. The Housing Module uses job_location_display_name to pre-select the matching item in the location dropdown. The worker may change the selected location via the listing filter panel without triggering a new session initiation.

  12. Job location — entry point without job context

  13. When the worker opens Housing from the entry point with no job context, job_location_lat, job_location_lng, and job_location_display_name are omitted from the payload. The location dropdown opens empty and the distance filter starts inactive.

  14. permitted_period_start calculation

  15. Takes the earliest start_date across all qualifying records, subtracts permitted_period_start_offset days. The result may be in the past.

  16. permitted_period_end calculation

  17. For qualifying records without an end_date, the effective end date is start_date + 6 months.
  18. Takes the latest effective end date across all qualifying records, adds permitted_period_end_offset days.

  19. max_daily_cost (fallback) calculation

  20. Sums hourly_rate × expected_hours_per_week / 7 across all qualifying records, multiplied by max_daily_cost_salary_percent / 100. Rounded to 2 decimal places.

  21. pks_cost_table — when configured (Nice to have)

  22. When the global PKS cost table is configured, the payload includes a pks_cost_table list.
  23. Each entry contains pks_from, pks_to (absent for the open-ended last range), and max_daily_cost — pre-calculated as total_daily_salary × row.salary_percent / 100, rounded to 2 decimal places.
  24. max_daily_cost (fallback) is always present alongside the table.
  25. When the PKS cost table is not configured (empty), pks_cost_table is not included in the payload.

  26. Indirect payment fields omitted when disabled

  27. When indirect_payment_enabled = No, the following fields are not included in the payload: permitted_period_start, permitted_period_end, max_daily_cost, pks_cost_table, billing_period_unit, payer_id, payer_name.

  28. Fresh payload on every session open

  29. Cruits always recalculates the full payload at the moment the worker opens the Housing section. No cached context from a previous session is reused.
  30. When the worker changes the selected job location within the Housing listing screen, this is handled by the Housing Module filter directly and does not trigger a new payload from Cruits.
  31. If the Housing Module rejects the session initiation (e.g., inactive Payer, expired permitted period), the Cruits app displays an error and does not open the listing screen.

Dependencies

  • Cruits Placement and Job Offer data models — source for start_date, end_date, hourly_rate, expected_hours_per_week, and job site location coordinates.
  • Cruits Worker record — source for worker_id, full_name, email, phone.
  • Global Housing Configuration (Django Admin) — source for all setting-derived fields.
  • Housing Module Story 2.1 — specifies the receiving-side payload structure, validation, session storage, and constraint enforcement.
  • Housing Module Story 1.12 — specifies the PKS field on listings; the Housing Module uses pks_cost_table from this payload to determine the effective cost threshold per listing.

Out of Scope for MVP

  • Per-employer, per-placement, and per-offer overrides of any global setting.
  • Pro-rated max_daily_cost calculation for days when work periods and booking periods do not overlap.
  • Currency conversion across placements in different currencies (assumption: all qualifying records use Euro).
  • Session expiry, re-initiation, or refresh logic beyond "always send fresh payload on Housing section open."
  • Tracking or reacting to job location changes made by the worker within the Housing Module filter panel.
  • PKS cost table validation UI beyond Django Admin built-in form validation.

Comments