# AggregateWorkedDays

## Overview

Counts a worker's worked days over a date range, for evaluating leave grant conditions (labour-law worked days, ADR-026 decision B1). A worked day is a distinct `workDate` on which any of the supplied Assignments has at least one `CalculatedTimeBlock`. Pure producer query: the caller resolves worker→assignments and supplies `assignmentIds` plus a date range; this query does not depend on workforce.

## Business Rules

- A worked day = a distinct `workDate` within `[startDate, endDate]` (inclusive) on which at least one `CalculatedTimeBlock` exists for any of the supplied `assignmentIds`
- A day worked under any of the supplied Assignments counts once — the count is the union of workDates across `assignmentIds`, never double-counted
- Multiple CalculatedTimeBlocks on the same workDate (e.g. regular + overtime) still count as one worked day
- An empty `assignmentIds` list is not an error; it returns `workedDays: 0`
- `startDate` and `endDate` must be valid dates and `endDate` must not be before `startDate`
- Only current CalculatedTimeBlocks are counted (CalculatedTimeBlock is a re-derivable projection; discarded/superseded calculations do not exist as rows)

## Process Flow

```mermaid
flowchart TD
    A[Caller supplies assignmentIds + startDate + endDate] --> B{startDate/endDate valid and endDate >= startDate?}
    B -- No --> C[Return INVALID_SEARCH_FILTER]
    B -- Yes --> D{assignmentIds empty?}
    D -- Yes --> E[Return workedDays 0]
    D -- No --> F[Fetch workDates of CalculatedTimeBlocks for assignmentIds within the range]
    F --> G[Dedupe distinct workDates across all assignments]
    G --> H[Return workedDays = distinct workDate count]
```

## External Dependencies

- [time-tracking::CalculatedTimeBlock](../model/CalculatedTimeBlock.md) model — entity whose workDates are aggregated

## Error Scenarios

- **INVALID_SEARCH_FILTER**: a supplied filter value is malformed or unrecognized

## Test Cases

- counts each distinct workDate once even when a day has multiple blocks
- counts a day worked under any of the supplied assignmentIds once (union across assignments)
- returns workedDays 0 when assignmentIds is empty
- includes boundary-day blocks when startDate/endDate carry a time-of-day component
- returns INVALID_SEARCH_FILTER when endDate is before startDate
- returns INVALID_SEARCH_FILTER when startDate or endDate is an invalid date
