> ## Documentation Index
> Fetch the complete documentation index at: https://docs-terra.withunify.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Eligibility Screener

> Multi-step questionnaire that matches users to benefit programs

# Eligibility Screener

> The screener is a guided questionnaire that collects household information and progressively matches users to benefit programs.

***

## Design Philosophy

The screener follows three principles:

1. **Start with location**: Geography determines which programs are available
2. **Show progress immediately**: Update match counts after each step
3. **Allow incompleteness**: Users can skip questions and still get useful results

```mermaid theme={null}
flowchart LR
    subgraph Steps
        S1[Location]
        S2[Household]
        S3[Income]
        S4[Demographics]
        S5[Current Benefits]
        S6[Results]
    end

    subgraph Feedback
        Count[Match Count]
        Progress[Progress Bar]
    end

    S1 --> S2 --> S3 --> S4 --> S5 --> S6
    S1 -.-> Count
    S2 -.-> Count
    S3 -.-> Count
    S4 -.-> Count
    S5 -.-> Count
```

***

## Step-by-Step Breakdown

### Step 1: Location

**Purpose**: Filter to geographically available programs

```typescript theme={null}
interface LocationStep {
  zipCode: string;
  // Derived from geocoding:
  state: string;
  countyFips: string;
  city?: string;
  coordinates: { lat: number; lng: number };
}
```

**UI Component**: ZIP code input with auto-geocoding

```tsx theme={null}
<LocationStep
  value={screenerData.zipCode}
  onChange={(location) => {
    updateScreener({
      zipCode: location.zipCode,
      state: location.state,
      countyFips: location.fips,
    });
  }}
/>
```

**What happens**:

1. User enters ZIP code
2. Geocoding resolves to coordinates
3. Reverse geocoding determines county FIPS
4. Programs filtered to federal + state + county scope

***

### Step 2: Household Composition

**Purpose**: Determine household size for FPL calculations and family-based programs

```typescript theme={null}
interface HouseholdStep {
  householdSize: number;
  adults: number;
  children: number;
  childrenAges?: number[];  // For age-specific programs
}
```

**UI Component**: Numeric inputs with validation

```tsx theme={null}
<HouseholdStep
  onComplete={(data) => {
    updateScreener({
      householdSize: data.adults + data.children,
      adults: data.adults,
      children: data.children,
      childrenAges: data.childrenAges,
    });
  }}
/>
```

**Programs affected**:

* WIC (children under 5, pregnant women)
* Head Start (children 3-5)
* CHIP (children under 19)
* Free/Reduced Lunch (school-age children)

***

### Step 3: Income

**Purpose**: Apply income-based eligibility rules

```typescript theme={null}
interface IncomeStep {
  hasIncome: boolean;
  grossMonthlyIncome?: number;
  netMonthlyIncome?: number;
  incomeTypes?: IncomeType[];
}
```

**UI Component**: Income calculator with breakdowns

```tsx theme={null}
<IncomeStep
  householdSize={screenerData.householdSize}
  onComplete={(income) => {
    updateScreener({
      hasIncome: income.hasIncome,
      grossMonthlyIncome: income.gross,
      netMonthlyIncome: income.net,
    });
  }}
/>
```

**Income types tracked**:

* Wages and salaries
* Self-employment
* Social Security (retirement)
* SSI/SSDI (disability)
* Unemployment benefits
* Child support/alimony
* Pension/retirement
* Investment income
* Rental income

**FPL Display**:

```tsx theme={null}
<FPLIndicator
  monthlyIncome={3000}
  householdSize={4}
/>
// Shows: "115% of Federal Poverty Level"
```

***

### Step 4: Demographics

**Purpose**: Apply categorical eligibility rules

```typescript theme={null}
interface DemographicsStep {
  citizenshipStatus: CitizenshipStatus;
  hasElderly: boolean;      // Anyone 60+ in household
  hasDisabled: boolean;     // Anyone with disability
  isPregnant: boolean;
  isVeteran: boolean;
  isStudent: boolean;
}
```

**UI Component**: Multiple choice questions

```tsx theme={null}
<DemographicsStep
  questions={[
    {
      id: "citizenship",
      label: "What is your citizenship status?",
      options: [
        { value: "citizen", label: "U.S. Citizen" },
        { value: "permanent_resident", label: "Permanent Resident (Green Card)" },
        { value: "qualified_alien", label: "Qualified Immigrant" },
        { value: "refugee", label: "Refugee or Asylee" },
        { value: "other", label: "Other / Prefer not to say" },
      ],
    },
    // ...
  ]}
/>
```

**Programs affected by demographics**:

| Demographic   | Programs                                   |
| ------------- | ------------------------------------------ |
| Elderly (60+) | Medicare, Senior SNAP, Meals on Wheels     |
| Disabled      | SSI, SSDI, Medicaid, Vocational Rehab      |
| Pregnant      | WIC, Medicaid, TANF                        |
| Veteran       | VA Healthcare, GI Bill, VA Pension         |
| Student       | Pell Grant, Work-Study, SNAP student rules |

***

### Step 5: Current Benefits (Optional)

**Purpose**: Apply categorical eligibility and avoid duplicate suggestions

```typescript theme={null}
interface CurrentBenefitsStep {
  receivingBenefits: boolean;
  currentBenefits: string[];
}
```

**UI Component**: Checkbox list

```tsx theme={null}
<CurrentBenefitsStep
  options={[
    { value: "snap", label: "SNAP / Food Stamps" },
    { value: "medicaid", label: "Medicaid" },
    { value: "tanf", label: "TANF / Cash Assistance" },
    { value: "ssi", label: "SSI (Supplemental Security Income)" },
    { value: "ssdi", label: "SSDI (Social Security Disability)" },
    { value: "section8", label: "Section 8 / Housing Voucher" },
    { value: "wic", label: "WIC" },
    { value: "liheap", label: "LIHEAP / Utility Assistance" },
  ]}
/>
```

**Categorical eligibility triggers**:

* SNAP → Free school meals, Lifeline, WIC
* TANF → SNAP, Free meals, Medicaid
* SSI → SNAP, Medicaid, Lifeline
* Medicaid → WIC, Lifeline

***

### Step 6: Results

**Purpose**: Display matched programs with actions

```tsx theme={null}
<ScreenerResults
  results={matchResults}
  onSaveProgram={(programId) => saveToUser(programId)}
  onStartApplication={(programId) => startApplication(programId)}
/>
```

**Result grouping**:

```tsx theme={null}
<ResultsDisplay>
  <ResultGroup
    title="Likely Eligible"
    description="Based on your answers, you appear to qualify"
    programs={results.filter(r => r.eligibility === "likely")}
    variant="success"
  />

  <ResultGroup
    title="Possibly Eligible"
    description="You may qualify - check program details"
    programs={results.filter(r => r.eligibility === "possible")}
    variant="info"
  />

  <ResultGroup
    title="Worth Exploring"
    description="Programs in your area you might qualify for later"
    programs={results.filter(r => r.eligibility === "unlikely")}
    variant="muted"
    collapsed
  />
</ResultsDisplay>
```

***

## State Management

Screener state persists across sessions using localStorage and database sync.

```typescript theme={null}
interface ScreenerState {
  // Current progress
  currentStep: number;
  completedSteps: number[];

  // Collected data
  data: Partial<HouseholdData>;

  // Match results (cached)
  matchResults?: MatchResult[];
  lastMatchedAt?: Date;

  // Metadata
  startedAt: Date;
  lastUpdatedAt: Date;
}

// Hook for screener state
function useScreener() {
  const [state, setState] = useState<ScreenerState>(() => {
    // Load from localStorage on mount
    const saved = localStorage.getItem("pathfinder_screener");
    return saved ? JSON.parse(saved) : initialState;
  });

  // Auto-save on changes
  useEffect(() => {
    localStorage.setItem("pathfinder_screener", JSON.stringify(state));
  }, [state]);

  // Sync to database when logged in
  useEffect(() => {
    if (user) {
      syncScreenerToProfile(user.id, state.data);
    }
  }, [user, state.data]);

  return { state, updateScreener, resetScreener };
}
```

***

## Real-Time Match Updates

Matches update after each step with debouncing.

```typescript theme={null}
function useMatchUpdates(screenerData: Partial<HouseholdData>) {
  const [matches, setMatches] = useState<MatchResult[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  // Debounced match calculation
  const debouncedMatch = useMemo(
    () =>
      debounce(async (data: Partial<HouseholdData>) => {
        setIsLoading(true);
        const results = await matchPrograms(data);
        setMatches(results);
        setIsLoading(false);
      }, 300),
    []
  );

  useEffect(() => {
    debouncedMatch(screenerData);
  }, [screenerData]);

  return { matches, isLoading };
}
```

***

## Benefits Calculator

Shows estimated annual value of matched programs.

```tsx theme={null}
function BenefitsCalculator({ matches }: { matches: MatchResult[] }) {
  const likelyMatches = matches.filter(m => m.eligibility === "likely");

  const monthlyTotal = likelyMatches.reduce((sum, match) => {
    const monthly = match.program.benefitValueCents
      ? match.program.benefit_frequency === "annual"
        ? match.program.benefitValueCents / 12
        : match.program.benefitValueCents
      : 0;
    return sum + monthly;
  }, 0);

  const annualTotal = monthlyTotal * 12;

  return (
    <Card>
      <CardHeader>
        <CardTitle>Estimated Benefits</CardTitle>
      </CardHeader>
      <CardContent>
        <div className="text-3xl font-bold text-emerald-600">
          ${(annualTotal / 100).toLocaleString()}/year
        </div>
        <p className="text-sm text-stone-500">
          Based on {likelyMatches.length} programs you likely qualify for
        </p>

        <div className="mt-4 space-y-2">
          {likelyMatches.slice(0, 5).map(match => (
            <div key={match.programId} className="flex justify-between">
              <span>{match.program.shortName || match.program.name}</span>
              <span className="text-emerald-600">
                ${(match.program.benefitValueCents / 100).toLocaleString()}
                /{match.program.benefit_frequency}
              </span>
            </div>
          ))}
        </div>
      </CardContent>
    </Card>
  );
}
```

***

## Accessibility

The screener follows WCAG 2.1 AA guidelines:

* **Keyboard navigation**: All steps navigable via Tab/Enter
* **Screen reader support**: ARIA labels on all inputs
* **Focus management**: Focus moves to first input on step change
* **Error handling**: Inline validation with clear error messages
* **Progress indication**: Step progress announced to screen readers

```tsx theme={null}
<form
  aria-label="Benefits eligibility screener"
  aria-describedby="screener-description"
>
  <div id="screener-description" className="sr-only">
    Answer questions about your household to find benefits you may qualify for.
    Step {currentStep} of {totalSteps}.
  </div>

  {/* Step content */}
</form>
```

***

## Analytics Events

Track screener usage for optimization.

```typescript theme={null}
const SCREENER_EVENTS = {
  STARTED: "screener_started",
  STEP_COMPLETED: "screener_step_completed",
  STEP_SKIPPED: "screener_step_skipped",
  COMPLETED: "screener_completed",
  ABANDONED: "screener_abandoned",
  PROGRAM_SAVED: "screener_program_saved",
};

// Track step completion
analytics.track(SCREENER_EVENTS.STEP_COMPLETED, {
  step: currentStep,
  stepName: STEP_NAMES[currentStep],
  timeOnStep: Date.now() - stepStartTime,
  matchCount: matches.length,
});
```

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Eligibility Engine" icon="gears" href="/pathfinder/core-systems/eligibility-engine">
    How matching works under the hood
  </Card>

  <Card title="Programs Database" icon="database" href="/pathfinder/core-systems/programs-database">
    Schema for benefit programs
  </Card>
</CardGroup>
