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
- Take all active offers or placements
- Find the earliest
start_date. - Subtract
permitted_period_start_offsetdays. - 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
- Take all active offers or placements
- For each record, determine its effective end date:
- Record has an
end_date→ effective end date =end_date. - Record has no
end_date(indefinite contract) → effective end date =start_date + 6 months. - Find the latest effective end date across all qualifying records.
- Add
permitted_period_end_offsetdays. - 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:
Sum across all
max_daily_cost (fallback — always sent):
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:
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 whenpks_cost_tableis 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¶
- Global configuration completeness check
- Before constructing the payload, Cruits checks that all required settings have a value. Required settings:
payer_id,payer_name,default_billing_period_unit. - 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.
-
direct_payment_enabledandindirect_payment_enabledmust 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. -
Qualifying records applied consistently
-
All payload fields that depend on placements or job offers use the qualifying record definition above. Records with
end_datein the past are excluded from all calculations in this payload. -
reservation_permitteddetermination -
Yesif there is at least one qualifying record for the worker.Noif there are none. -
Job location — entry point with job context
- When the worker opens Housing from an entry point that carries a specific job context,
job_location_lat,job_location_lng, andjob_location_display_nameare taken from that job's location and sent in the payload. -
The Housing Module uses
job_location_display_nameto 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. -
Job location — entry point without job context
-
When the worker opens Housing from the entry point with no job context,
job_location_lat,job_location_lng, andjob_location_display_nameare omitted from the payload. The location dropdown opens empty and the distance filter starts inactive. -
permitted_period_startcalculation -
Takes the earliest
start_dateacross all qualifying records, subtractspermitted_period_start_offsetdays. The result may be in the past. -
permitted_period_endcalculation - For qualifying records without an
end_date, the effective end date isstart_date + 6 months. -
Takes the latest effective end date across all qualifying records, adds
permitted_period_end_offsetdays. -
max_daily_cost(fallback) calculation -
Sums
hourly_rate × expected_hours_per_week / 7across all qualifying records, multiplied bymax_daily_cost_salary_percent / 100. Rounded to 2 decimal places. -
pks_cost_table— when configured (Nice to have) - When the global PKS cost table is configured, the payload includes a
pks_cost_tablelist. - Each entry contains
pks_from,pks_to(absent for the open-ended last range), andmax_daily_cost— pre-calculated astotal_daily_salary × row.salary_percent / 100, rounded to 2 decimal places. max_daily_cost(fallback) is always present alongside the table.-
When the PKS cost table is not configured (empty),
pks_cost_tableis not included in the payload. -
Indirect payment fields omitted when disabled
-
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. -
Fresh payload on every session open
- Cruits always recalculates the full payload at the moment the worker opens the Housing section. No cached context from a previous session is reused.
- 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.
- 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_tablefrom 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_costcalculation 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.