Spaces:
Runtime error
Runtime error
| # BPS Eligible School Tool | |
| ## Overview | |
| This tool lets an LLM or backend service find **eligible Boston Public Schools** for a student based on: | |
| - grade level | |
| - home address | |
| - ZIP code | |
| - city/state | |
| - home language | |
| It uses the Boston Explore / Avela eligibility API to get the list of **ineligible** schools, then joins that result with our local school catalog and returns the remaining **eligible** BPS schools. | |
| In other words: | |
| ```text | |
| eligible schools = candidate BPS schools for the grade - ineligible schools returned by Avela | |
| ``` | |
| --- | |
| ## Why this tool exists | |
| The original `check_eligibility.py` script successfully called the Avela API, but it returned only the API's `ineligibleSchools` list. | |
| That was not enough for the chatbot pipeline, because the chatbot needs a list of schools the student **can actually apply to**. | |
| The new tool solves that by: | |
| 1. calling the eligibility API | |
| 2. loading the local school data | |
| 3. filtering to Boston Public Schools only | |
| 4. filtering to schools that serve the requested grade | |
| 5. subtracting ineligible schools | |
| 6. returning the final eligible school list | |
| --- | |
| ## Main file | |
| The current tool implementation lives in: | |
| ```text | |
| check_eligibility_tool.py | |
| ``` | |
| --- | |
| ## Main function | |
| ```python | |
| find_eligible_schools( | |
| grade_level: str, | |
| street_address: str, | |
| zip_code: str, | |
| city: str = "Boston", | |
| state: str = "MA", | |
| street_address_line2: str = "", | |
| home_language: str = "English", | |
| dataset_path: str = "raw_data/choice_tool_raw.json", | |
| include_ineligible: bool = False, | |
| ) -> dict[str, Any] | |
| ``` | |
| ### What it returns | |
| A dictionary with fields like: | |
| ```json | |
| { | |
| "enrollment_period_name": "2026-2027 School Year", | |
| "eligible_schools": [...], | |
| "eligible_count": 16, | |
| "candidate_school_count": 73, | |
| "ineligible_count": 913, | |
| "matched_ineligible_count": 57, | |
| "error": null | |
| } | |
| ``` | |
| ### Important return fields | |
| - `eligible_schools`: the final list the chatbot should use | |
| - `eligible_count`: number of eligible schools returned | |
| - `candidate_school_count`: number of BPS schools in the local dataset that serve the requested grade | |
| - `ineligible_count`: total number of ineligible providers returned by Avela | |
| - `matched_ineligible_count`: number of those ineligible providers that matched candidate BPS schools in our local dataset | |
| - `error`: error message if something failed | |
| --- | |
| ## Tool definition for LLM function calling | |
| The file exposes: | |
| ```python | |
| TOOL_DEFINITION | |
| ``` | |
| This is the function schema the LLM can use to call the tool. | |
| Tool name: | |
| ```text | |
| find_eligible_schools | |
| ``` | |
| Expected user-facing parameters: | |
| - `grade_level` | |
| - `street_address` | |
| - `zip_code` | |
| - `city` | |
| - `state` | |
| - `street_address_line2` | |
| - `home_language` | |
| Note: `dataset_path` should stay internal and should **not** be exposed to the model in production. | |
| --- | |
| ## Minimal dispatcher | |
| The file also exposes: | |
| ```python | |
| handle_tool_call(function_name: str, args: dict[str, Any]) | |
| ``` | |
| This makes it easy to plug into a future chatbot tool handler. | |
| Example: | |
| ```python | |
| args = { | |
| "grade_level": "K2", | |
| "street_address": "2300 Washington St", | |
| "zip_code": "02119", | |
| "city": "Boston", | |
| "state": "MA", | |
| "home_language": "English", | |
| } | |
| result = handle_tool_call("find_eligible_schools", args) | |
| ``` | |
| --- | |
| ## Data sources used | |
| ### 1. Avela API | |
| Used to determine which schools are **ineligible** for the student. | |
| Endpoints used: | |
| - `GET /enrollmentPeriods` | |
| - `POST /formTemplates/{FORM_TEMPLATE_ID}/findEligibility` | |
| ### 2. Local dataset | |
| Used to map API references back to school metadata and produce the final eligible list. | |
| Current catalog path: | |
| ```text | |
| raw_data/choice_tool_raw.json | |
| ``` | |
| This dataset includes more than just BPS schools, so the tool filters to: | |
| ```python | |
| provider_type == "Boston Public School" | |
| ``` | |
| --- | |
| ## Eligibility logic | |
| The tool works in this order: | |
| 1. validate grade and language inputs | |
| 2. fetch enrollment periods | |
| 3. build the eligibility API payload | |
| 4. call Avela's `findEligibility` | |
| 5. collect `referenceId` values from `ineligibleSchools` | |
| 6. load the local catalog | |
| 7. filter the catalog to BPS schools that serve the requested grade | |
| 8. subtract matching ineligible school IDs | |
| 9. return the remaining schools as eligible | |
| ### Set logic | |
| ```text | |
| candidate_ids = all BPS school IDs for the requested grade | |
| matched_ineligible_ids = candidate_ids ∩ Avela_ineligible_ids | |
| eligible_ids = candidate_ids - matched_ineligible_ids | |
| ``` | |
| --- | |
| ## Testing | |
| ### Direct script test | |
| Run: | |
| ```bash | |
| python3 check_eligibility_tool.py | |
| ``` | |
| This runs an example address through the tool and prints the result. | |
| ### Manual tool-call test | |
| Use `test_tool_call.py`: | |
| ```python | |
| import json | |
| import check_eligibility_tool | |
| from check_eligibility_tool import TOOL_DEFINITION, handle_tool_call, get_enrollment_periods | |
| print("Imported from:", check_eligibility_tool.__file__) | |
| print("Tool name:", TOOL_DEFINITION["name"]) | |
| periods, periods_error = get_enrollment_periods() | |
| print("Enrollment periods error:", periods_error) | |
| print("Enrollment periods count:", len(periods)) | |
| args = { | |
| "grade_level": "K2", | |
| "street_address": "2300 Washington St", | |
| "zip_code": "02119", | |
| "city": "Boston", | |
| "state": "MA", | |
| "home_language": "English", | |
| "dataset_path": "raw_data/choice_tool_raw.json", | |
| } | |
| result = handle_tool_call("find_eligible_schools", args) | |
| print(json.dumps(result, indent=2)) | |
| ``` | |
| Run: | |
| ```bash | |
| python3 test_tool_call.py | |
| ``` | |
| --- | |
| ## Validation that the tool is correct | |
| The tool was validated in a few ways: | |
| ### 1. Count math | |
| For the tested input: | |
| - `candidate_school_count = 73` | |
| - `matched_ineligible_count = 57` | |
| - `eligible_count = 16` | |
| Check: | |
| ```text | |
| 73 - 57 = 16 | |
| ``` | |
| ### 2. Website comparison | |
| The eligible school results matched the Boston Explore website for the tested address. | |
| ### 3. Join logic sanity | |
| The tool only subtracts schools whose local `id` matches an Avela `referenceId` after normalization. | |
| --- | |
| ## Example tested input | |
| ```python | |
| result = find_eligible_schools( | |
| grade_level="K2", | |
| street_address="2300 Washington St", | |
| zip_code="02119", | |
| city="Boston", | |
| state="MA", | |
| home_language="English", | |
| dataset_path="raw_data/choice_tool_raw.json", | |
| include_ineligible=False, | |
| ) | |
| ``` | |
| Observed result summary: | |
| - enrollment period fetched successfully | |
| - 73 candidate BPS schools for K2 | |
| - 57 matched as ineligible | |
| - 16 eligible schools returned | |
| --- | |
| ## Known implementation details | |
| ### Enrollment periods response format | |
| The Avela `enrollmentPeriods` endpoint may return a dictionary instead of a raw list. | |
| The tool handles both of these cases: | |
| - list response | |
| - dict response containing `enrollment_period` | |
| ### UUID mappings | |
| The grade and language option UUIDs must match the values from the original working `check_eligibility.py` implementation. | |
| These mappings were preserved because the original script successfully returned working eligibility results. | |
| --- | |
| ## Recommended production usage | |
| In production, the chatbot pipeline should work like this: | |
| ```text | |
| User question | |
| -> LLM extracts structured args | |
| -> find_eligible_schools tool call | |
| -> return eligible_schools | |
| -> ranking/filtering/retrieval over eligible_schools | |
| -> final chatbot answer | |
| ``` | |
| The chatbot should use: | |
| ```python | |
| result["eligible_schools"] | |
| ``` | |
| as the input to ranking and school recommendation logic. | |
| --- | |
| ## Suggested follow-up improvements | |
| 1. Hide `dataset_path` from the LLM-facing tool interface. | |
| 2. Add optional debug mode for returning matched ineligible IDs. | |
| 3. Add unit tests for: | |
| - grade filtering | |
| - ID normalization | |
| - candidate minus ineligible set subtraction | |
| 4. Add ranking layer on top of `eligible_schools`. | |
| 5. Wrap the tool in the actual chatbot tool handler. | |
| --- | |
| ## Summary | |
| This new tool converts the old “return ineligible schools” flow into a chatbot-ready “return eligible Boston Public Schools” flow. | |
| It is now verified to: | |
| - call the API correctly | |
| - join against local school data correctly | |
| - return eligible BPS schools correctly | |
| - support LLM tool-calling via `TOOL_DEFINITION` | |
| - support backend execution via `handle_tool_call` | |