Integration

How eligibility rules flow from policy to code, and how to integrate structured rulesets into your systems.

How Eligibility Determination Works

User Attributes

A person's circumstances expressed as key/value pairs: income, age, household size, residency, etc.

{
  "household_income": 2400,
  "household_size": 3,
  "age": 34,
  "residency": "CA"
}

Determination

A clear, consistent, calculable result: eligible or not, with detailed validation messages.

{
  "eligible": true,
  "passed": ["income", "age"],
  "failed": [],
  "messages": [...]
}
Rules Engine
validate(ruleset, attributes)

The rules engine is a pure function: given a person's attributes and a program's ruleset, it returns a determination. The same inputs always produce the same outputs. This makes eligibility calculable, testable, and auditable.

Each rule in a ruleset maps to a specific attribute key. The engine evaluates each condition and returns which rules passed, which failed, and which were skipped (due to missing data or conditional logic).

From Policy to Code

Eligibility rules originate in legislation, regulation, and policy documents. The path to implementation often looks like this:

1

Legislation & Policy

Laws and regulations define who qualifies for a program. These documents contain the authoritative eligibility criteria, often in legal language.

"...households with gross income at or below 130 percent of the poverty line..."
2

Agency Interpretation

Agencies publish guidance, handbooks, and FAQs that clarify how rules apply. This is where edge cases and exceptions get documented.

3

Structured Encoding

Rules are distilled into machine-readable YAML with clear operators, values, and source citations. Each condition becomes testable.

operator: less_than_or_equal
value: 130
unit: percent_fpl
4

Software Integration

Applications consume the ruleset via API or direct import. The rules engine evaluates user data against the current ruleset version.

5

Version & Update

When policy changes, the ruleset is updated via pull request. Git history provides full audit trail. Downstream systems pull the new version.

For Agency Staff & Policy Teams

Encoding Existing Programs

Your program's eligibility rules likely already exist in handbooks, policy documents, or legacy systems. These can be reliably distilled into structured rulesets.

Start with your program's eligibility page or handbook. Identify each discrete condition (income limits, age ranges, residency requirements). Map each to a rule with a clear operator and value.

Adding New Rules

When policy changes, add or update conditions in the ruleset. Each change is a clear, versioned edit that can be reviewed, tested, and deployed.

New rules should include: a unique ID, the attribute key being checked, the operator and value, a human-readable description, and a source URL citing the authoritative policy.

Distillation Prompt

Use this prompt with an LLM to extract eligibility conditions from a program's documentation into a structured ruleset:

Prompt Template
You are an eligibility rules encoder. Given a program's documentation, extract the eligibility conditions into a structured YAML ruleset.

For each eligibility condition, create a rule with:
- id: A unique snake_case identifier (e.g., "income_gross_limit")
- type: The category (income_fpl, age, residency, citizenship, household, employment, disability, enrollment, assets)
- key: The attribute key to check (e.g., "household_income_fpl_percent")
- operator: One of: equals, not_equals, in, not_in, less_than, less_than_or_equal, greater_than, greater_than_or_equal, between, exists, matches
- value: The threshold or allowed values
- unit: If applicable (percent, dollars, months, years)
- description: A clear, human-readable explanation
- source_url: The URL where this rule is documented

Use these conventions:
- Income as FPL percentage: key ends in "_fpl_percent", value is the percentage (e.g., 130 for 130% FPL)
- Age ranges: use "between" operator with [min, max] array
- Categorical requirements: use "in" operator with array of valid values
- Boolean conditions: use "exists" operator

Output only valid YAML. Include all conditions you can identify, even if some details require assumptions (note assumptions in the description).

---

Program URL: [URL]

Program Documentation:
[PASTE CONTENT HERE]

Technical Integration

Direct Import

Clone the repository and import YAML files directly into your application. Parse with any YAML library.

data/jurisdictions/federal/services/snap.yaml

Raw GitHub URLs

Fetch rulesets directly from GitHub's raw content URLs for always-current data.

raw.githubusercontent.com/civicstudio/eligibility-rules/main/data/...

Rules Engine

Implement the validation function in your language. The engine evaluates user attributes against ruleset conditions.

validate(ruleset, attributes) → result

Code Examples

JS JavaScript / Node.js
import yaml from 'js-yaml';

// Fetch and parse ruleset
const response = await fetch(
  'https://raw.githubusercontent.com/civicstudio/eligibility-rules/main/data/jurisdictions/federal/services/snap.yaml'
);
const ruleset = yaml.load(await response.text());

// User attributes to validate
const attributes = {
  household_income_fpl_percent: 125,
  household_size: 3,
  residency_state: 'CA',
  citizenship_status: 'us_citizen'
};

// Simple validation function
function validate(ruleset, attributes) {
  const results = { passed: [], failed: [], skipped: [] };

  for (const rule of ruleset.ruleset.rules) {
    const value = attributes[rule.key];
    if (value === undefined) {
      results.skipped.push(rule.id);
      continue;
    }

    const passed = evaluateRule(rule, value);
    (passed ? results.passed : results.failed).push(rule.id);
  }

  results.eligible = results.failed.length === 0;
  return results;
}

const result = validate(ruleset, attributes);
console.log(result); // { eligible: true, passed: [...], failed: [], skipped: [...] }
PY Python
import yaml
import requests

# Fetch and parse ruleset
url = "https://raw.githubusercontent.com/civicstudio/eligibility-rules/main/data/jurisdictions/federal/services/snap.yaml"
response = requests.get(url)
ruleset = yaml.safe_load(response.text)

# User attributes to validate
attributes = {
    "household_income_fpl_percent": 125,
    "household_size": 3,
    "residency_state": "CA",
    "citizenship_status": "us_citizen"
}

def validate(ruleset: dict, attributes: dict) -> dict:
    results = {"passed": [], "failed": [], "skipped": []}

    for rule in ruleset["ruleset"]["rules"]:
        value = attributes.get(rule["key"])
        if value is None:
            results["skipped"].append(rule["id"])
            continue

        passed = evaluate_rule(rule, value)
        (results["passed"] if passed else results["failed"]).append(rule["id"])

    results["eligible"] = len(results["failed"]) == 0
    return results

result = validate(ruleset, attributes)
print(result)  # {'eligible': True, 'passed': [...], 'failed': [], 'skipped': [...]}
RB Ruby
require 'yaml'
require 'net/http'

# Fetch and parse ruleset
uri = URI("https://raw.githubusercontent.com/civicstudio/eligibility-rules/main/data/jurisdictions/federal/services/snap.yaml")
response = Net::HTTP.get(uri)
ruleset = YAML.safe_load(response)

# User attributes to validate
attributes = {
  "household_income_fpl_percent" => 125,
  "household_size" => 3,
  "residency_state" => "CA",
  "citizenship_status" => "us_citizen"
}

def validate(ruleset, attributes)
  results = { passed: [], failed: [], skipped: [] }

  ruleset["ruleset"]["rules"].each do |rule|
    value = attributes[rule["key"]]
    if value.nil?
      results[:skipped] << rule["id"]
      next
    end

    passed = evaluate_rule(rule, value)
    (passed ? results[:passed] : results[:failed]) << rule["id"]
  end

  results[:eligible] = results[:failed].empty?
  results
end

result = validate(ruleset, attributes)
puts result # {:eligible=>true, :passed=>[...], :failed=>[], :skipped=>[...]}

Ready to integrate?

Browse existing rulesets, request an encoding for your program, or contribute directly via GitHub.