gofannon 0.25.19__py3-none-any.whl → 0.25.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByApplicantEligibility(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on applicant types and/or cost-sharing requirements.
14
+ """
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_applicant_eligibility"):
16
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
17
+ self.name = name
18
+
19
+ @property
20
+ def definition(self):
21
+ base_params = self._get_common_parameter_definitions()
22
+ specific_params = {
23
+ "applicant_types": {
24
+ "type": "array",
25
+ "items": {"type": "string"},
26
+ "description": "Optional. List of applicant types (e.g., ['state_governments', 'nonprofits_with_501c3']). Valid values: "
27
+ "city_or_township_governments, county_governments, federal_government_agencies_fed_recognized_tribes_excluded, "
28
+ "independent_school_districts, individuals, native_american_tribal_governments_federally_recognized, "
29
+ "native_american_tribal_organizations_other_than_federally_recognized_tribal_governments, "
30
+ "nonprofits_having_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education, "
31
+ "nonprofits_that_do_not_have_a_501c3_status_with_the_irs_other_than_institutions_of_higher_education, "
32
+ "private_institutions_of_higher_education, public_and_state_controlled_institutions_of_higher_education, "
33
+ "public_housing_authorities_or_indian_housing_authorities, small_businesses, special_district_governments, state_governments, other."
34
+ },
35
+ "requires_cost_sharing": { # Parameter name is more direct for LLM
36
+ "type": "boolean",
37
+ "description": "Optional. Filter by cost-sharing requirement. True for opportunities requiring cost sharing, False for those not requiring it. Omit to not filter by this."
38
+ },
39
+ "query_text": {
40
+ "type": "string",
41
+ "description": "Optional. Text to search for within the results filtered by eligibility."
42
+ },
43
+ "query_operator": {
44
+ "type": "string",
45
+ "enum": ["AND", "OR"],
46
+ "description": "Operator for 'query_text' if provided (default: AND).",
47
+ "default": "AND"
48
+ }
49
+ }
50
+ all_properties = {**specific_params, **base_params}
51
+
52
+ return {
53
+ "type": "function",
54
+ "function": {
55
+ "name": self.name,
56
+ "description": (
57
+ "Search for grant opportunities by applicant types and/or cost-sharing requirements. "
58
+ "At least one of 'applicant_types' or 'requires_cost_sharing' must be specified if used for filtering. "
59
+ f"{self._pagination_description}"
60
+ f"{self._status_filter_description}"
61
+ f"{self._common_search_description_suffix}"
62
+ ),
63
+ "parameters": {
64
+ "type": "object",
65
+ "properties": all_properties,
66
+ "required": [] # Logic in fn enforces condition
67
+ }
68
+ }
69
+ }
70
+
71
+ def fn(self,
72
+ applicant_types: Optional[List[str]] = None,
73
+ requires_cost_sharing: Optional[bool] = None,
74
+ query_text: Optional[str] = None,
75
+ query_operator: str = "AND",
76
+ # Common params
77
+ items_per_page: int = 5,
78
+ page_number: int = 1,
79
+ order_by: str = "relevancy",
80
+ sort_direction: str = "descending",
81
+ show_posted: bool = True,
82
+ show_forecasted: bool = False,
83
+ show_closed: bool = False,
84
+ show_archived: bool = False) -> str:
85
+
86
+ self.logger.info(f"Querying by applicant eligibility: types={applicant_types}, cost_sharing={requires_cost_sharing}, query='{query_text}'")
87
+
88
+ if applicant_types is None and requires_cost_sharing is None and not query_text: # if no specific filters or query, it's too broad
89
+ return json.dumps({"error": "Please provide 'applicant_types', 'requires_cost_sharing', or 'query_text' to refine the search.", "success": False})
90
+
91
+ specific_filters: Dict[str, Any] = {}
92
+ if applicant_types:
93
+ specific_filters["applicant_type"] = {"one_of": applicant_types}
94
+ if requires_cost_sharing is not None: # API expects list for boolean filters
95
+ specific_filters["is_cost_sharing"] = {"one_of": [requires_cost_sharing]}
96
+
97
+ specific_query_params: Optional[Dict[str, Any]] = None
98
+ if query_text:
99
+ specific_query_params = {"query": query_text}
100
+
101
+ payload = self._build_api_payload(
102
+ specific_query_params=specific_query_params,
103
+ specific_filters=specific_filters,
104
+ items_per_page=items_per_page,
105
+ page_number=page_number,
106
+ order_by=order_by,
107
+ sort_direction=sort_direction,
108
+ show_posted=show_posted,
109
+ show_forecasted=show_forecasted,
110
+ show_closed=show_closed,
111
+ show_archived=show_archived,
112
+ query_operator=query_operator
113
+ )
114
+ return self._execute_search(payload)
@@ -0,0 +1,102 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByAssistanceListing(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities by Assistance Listing Numbers (formerly CFDA numbers).
14
+ Numbers should be in the format '##.###' or '##.##'.
15
+ """
16
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_assistance_listing"):
17
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
18
+ self.name = name
19
+
20
+ @property
21
+ def definition(self):
22
+ base_params = self._get_common_parameter_definitions()
23
+ specific_params = {
24
+ "assistance_listing_numbers": {
25
+ "type": "array",
26
+ "items": {"type": "string"},
27
+ "description": "List of Assistance Listing Numbers (e.g., ['10.001', '93.123']). Must match pattern ##.### or ##.##."
28
+ },
29
+ "query_text": {
30
+ "type": "string",
31
+ "description": "Optional. Text to search for within the results filtered by assistance listing."
32
+ },
33
+ "query_operator": {
34
+ "type": "string",
35
+ "enum": ["AND", "OR"],
36
+ "description": "Operator for 'query_text' if provided (default: AND).",
37
+ "default": "AND"
38
+ }
39
+ }
40
+ all_properties = {**specific_params, **base_params}
41
+
42
+ return {
43
+ "type": "function",
44
+ "function": {
45
+ "name": self.name,
46
+ "description": (
47
+ "Search for grant opportunities by one or more Assistance Listing Numbers. "
48
+ f"{self._pagination_description}"
49
+ f"{self._status_filter_description}"
50
+ f"{self._common_search_description_suffix}"
51
+ ),
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": all_properties,
55
+ "required": ["assistance_listing_numbers"]
56
+ }
57
+ }
58
+ }
59
+
60
+ def fn(self,
61
+ assistance_listing_numbers: List[str],
62
+ query_text: Optional[str] = None,
63
+ query_operator: str = "AND",
64
+ # Common params
65
+ items_per_page: int = 5,
66
+ page_number: int = 1,
67
+ order_by: str = "relevancy",
68
+ sort_direction: str = "descending",
69
+ show_posted: bool = True,
70
+ show_forecasted: bool = False,
71
+ show_closed: bool = False,
72
+ show_archived: bool = False) -> str:
73
+
74
+ self.logger.info(f"Querying by Assistance Listing Numbers: {assistance_listing_numbers}, query='{query_text}'")
75
+
76
+ if not assistance_listing_numbers:
77
+ return json.dumps({"error": "assistance_listing_numbers list cannot be empty.", "success": False})
78
+ # Basic pattern validation could be added here for each number if desired,
79
+ # but the API will ultimately validate.
80
+
81
+ specific_filters: Dict[str, Any] = {
82
+ "assistance_listing_number": {"one_of": assistance_listing_numbers}
83
+ }
84
+
85
+ specific_query_params: Optional[Dict[str, Any]] = None
86
+ if query_text:
87
+ specific_query_params = {"query": query_text}
88
+
89
+ payload = self._build_api_payload(
90
+ specific_query_params=specific_query_params,
91
+ specific_filters=specific_filters,
92
+ items_per_page=items_per_page,
93
+ page_number=page_number,
94
+ order_by=order_by,
95
+ sort_direction=sort_direction,
96
+ show_posted=show_posted,
97
+ show_forecasted=show_forecasted,
98
+ show_closed=show_closed,
99
+ show_archived=show_archived,
100
+ query_operator=query_operator
101
+ )
102
+ return self._execute_search(payload)
@@ -0,0 +1,117 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByAwardCriteria(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on award amount criteria.
14
+ """
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_award_criteria"):
16
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
17
+ self.name = name
18
+
19
+ @property
20
+ def definition(self):
21
+ base_params = self._get_common_parameter_definitions()
22
+ specific_params = {
23
+ "min_award_floor": {"type": "integer", "description": "Optional. Minimum award floor amount."},
24
+ "max_award_ceiling": {"type": "integer", "description": "Optional. Maximum award ceiling amount."},
25
+ "min_expected_awards": {"type": "integer", "description": "Optional. Minimum number of expected awards."},
26
+ "max_expected_awards": {"type": "integer", "description": "Optional. Maximum number of expected awards."},
27
+ "min_total_funding": {"type": "integer", "description": "Optional. Minimum estimated total program funding."},
28
+ "max_total_funding": {"type": "integer", "description": "Optional. Maximum estimated total program funding."},
29
+ "query_text": {
30
+ "type": "string",
31
+ "description": "Optional. Text to search for within the results filtered by award criteria."
32
+ },
33
+ "query_operator": {
34
+ "type": "string",
35
+ "enum": ["AND", "OR"],
36
+ "description": "Operator for 'query_text' if provided (default: AND).",
37
+ "default": "AND"
38
+ }
39
+ }
40
+ all_properties = {**specific_params, **base_params}
41
+
42
+ return {
43
+ "type": "function",
44
+ "function": {
45
+ "name": self.name,
46
+ "description": (
47
+ "Search for grant opportunities by award criteria (floor, ceiling, number of awards, total funding). "
48
+ "At least one award criterion must be specified. "
49
+ f"{self._pagination_description}"
50
+ f"{self._status_filter_description}"
51
+ f"{self._common_search_description_suffix}"
52
+ ),
53
+ "parameters": {
54
+ "type": "object",
55
+ "properties": all_properties,
56
+ "required": [] # Logic in fn enforces one award criteria
57
+ }
58
+ }
59
+ }
60
+
61
+ def fn(self,
62
+ min_award_floor: Optional[int] = None, max_award_ceiling: Optional[int] = None,
63
+ min_expected_awards: Optional[int] = None, max_expected_awards: Optional[int] = None,
64
+ min_total_funding: Optional[int] = None, max_total_funding: Optional[int] = None,
65
+ query_text: Optional[str] = None, query_operator: str = "AND",
66
+ # Common params
67
+ items_per_page: int = 5, page_number: int = 1, order_by: str = "relevancy",
68
+ sort_direction: str = "descending", show_posted: bool = True, show_forecasted: bool = False,
69
+ show_closed: bool = False, show_archived: bool = False) -> str:
70
+
71
+ self.logger.info(f"Querying by award criteria: floor={min_award_floor}, ceiling={max_award_ceiling}, ... query='{query_text}'")
72
+
73
+ specific_filters: Dict[str, Any] = {}
74
+ award_criteria_provided = False
75
+
76
+ if min_award_floor is not None:
77
+ specific_filters.setdefault("award_floor", {})["min"] = min_award_floor
78
+ award_criteria_provided = True
79
+ if max_award_ceiling is not None:
80
+ specific_filters.setdefault("award_ceiling", {})["max"] = max_award_ceiling
81
+ award_criteria_provided = True
82
+
83
+ expected_awards_filter = {}
84
+ if min_expected_awards is not None:
85
+ expected_awards_filter["min"] = min_expected_awards
86
+ award_criteria_provided = True
87
+ if max_expected_awards is not None:
88
+ expected_awards_filter["max"] = max_expected_awards
89
+ award_criteria_provided = True
90
+ if expected_awards_filter:
91
+ specific_filters["expected_number_of_awards"] = expected_awards_filter
92
+
93
+ total_funding_filter = {}
94
+ if min_total_funding is not None:
95
+ total_funding_filter["min"] = min_total_funding
96
+ award_criteria_provided = True
97
+ if max_total_funding is not None:
98
+ total_funding_filter["max"] = max_total_funding
99
+ award_criteria_provided = True
100
+ if total_funding_filter:
101
+ specific_filters["estimated_total_program_funding"] = total_funding_filter
102
+
103
+ if not award_criteria_provided and not query_text:
104
+ return json.dumps({"error": "At least one award criterion or query_text must be specified.", "success": False})
105
+
106
+ specific_query_params: Optional[Dict[str, Any]] = None
107
+ if query_text:
108
+ specific_query_params = {"query": query_text}
109
+
110
+ payload = self._build_api_payload(
111
+ specific_query_params=specific_query_params,
112
+ specific_filters=specific_filters if specific_filters else None,
113
+ items_per_page=items_per_page, page_number=page_number, order_by=order_by,
114
+ sort_direction=sort_direction, show_posted=show_posted, show_forecasted=show_forecasted,
115
+ show_closed=show_closed, show_archived=show_archived, query_operator=query_operator
116
+ )
117
+ return self._execute_search(payload)
@@ -0,0 +1,108 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByDates(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on post dates and/or close dates.
14
+ Dates should be in YYYY-MM-DD format.
15
+ """
16
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_dates"):
17
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
18
+ self.name = name
19
+
20
+ @property
21
+ def definition(self):
22
+ base_params = self._get_common_parameter_definitions()
23
+ specific_params = {
24
+ "post_start_date": {"type": "string", "description": "Optional. Start of post date range (YYYY-MM-DD)."},
25
+ "post_end_date": {"type": "string", "description": "Optional. End of post date range (YYYY-MM-DD)."},
26
+ "close_start_date": {"type": "string", "description": "Optional. Start of close date range (YYYY-MM-DD)."},
27
+ "close_end_date": {"type": "string", "description": "Optional. End of close date range (YYYY-MM-DD)."},
28
+ "query_text": {
29
+ "type": "string",
30
+ "description": "Optional. Text to search for within the results filtered by dates."
31
+ },
32
+ "query_operator": {
33
+ "type": "string",
34
+ "enum": ["AND", "OR"],
35
+ "description": "Operator for 'query_text' if provided (default: AND).",
36
+ "default": "AND"
37
+ }
38
+ }
39
+ all_properties = {**specific_params, **base_params}
40
+
41
+ return {
42
+ "type": "function",
43
+ "function": {
44
+ "name": self.name,
45
+ "description": (
46
+ "Search for grant opportunities by post and/or close date ranges. "
47
+ "At least one date parameter must be specified. Dates must be YYYY-MM-DD. "
48
+ f"{self._pagination_description}"
49
+ f"{self._status_filter_description}"
50
+ f"{self._common_search_description_suffix}"
51
+ ),
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": all_properties,
55
+ "required": [] # Logic in fn enforces one date
56
+ }
57
+ }
58
+ }
59
+
60
+ def fn(self,
61
+ post_start_date: Optional[str] = None, post_end_date: Optional[str] = None,
62
+ close_start_date: Optional[str] = None, close_end_date: Optional[str] = None,
63
+ query_text: Optional[str] = None, query_operator: str = "AND",
64
+ # Common params
65
+ items_per_page: int = 5, page_number: int = 1, order_by: str = "relevancy",
66
+ sort_direction: str = "descending", show_posted: bool = True, show_forecasted: bool = False,
67
+ show_closed: bool = False, show_archived: bool = False) -> str:
68
+
69
+ self.logger.info(f"Querying by dates: post={post_start_date}-{post_end_date}, close={close_start_date}-{close_end_date}, query='{query_text}'")
70
+
71
+ specific_filters: Dict[str, Any] = {}
72
+ date_criteria_provided = False
73
+
74
+ post_date_filter = {}
75
+ if post_start_date:
76
+ post_date_filter["start_date"] = post_start_date
77
+ date_criteria_provided = True
78
+ if post_end_date:
79
+ post_date_filter["end_date"] = post_end_date
80
+ date_criteria_provided = True
81
+ if post_date_filter:
82
+ specific_filters["post_date"] = post_date_filter
83
+
84
+ close_date_filter = {}
85
+ if close_start_date:
86
+ close_date_filter["start_date"] = close_start_date
87
+ date_criteria_provided = True
88
+ if close_end_date:
89
+ close_date_filter["end_date"] = close_end_date
90
+ date_criteria_provided = True
91
+ if close_date_filter:
92
+ specific_filters["close_date"] = close_date_filter
93
+
94
+ if not date_criteria_provided and not query_text:
95
+ return json.dumps({"error": "At least one date parameter or query_text must be specified.", "success": False})
96
+
97
+ specific_query_params: Optional[Dict[str, Any]] = None
98
+ if query_text:
99
+ specific_query_params = {"query": query_text}
100
+
101
+ payload = self._build_api_payload(
102
+ specific_query_params=specific_query_params,
103
+ specific_filters=specific_filters if specific_filters else None,
104
+ items_per_page=items_per_page, page_number=page_number, order_by=order_by,
105
+ sort_direction=sort_direction, show_posted=show_posted, show_forecasted=show_forecasted,
106
+ show_closed=show_closed, show_archived=show_archived, query_operator=query_operator
107
+ )
108
+ return self._execute_search(payload)
@@ -0,0 +1,115 @@
1
+ import logging
2
+ import json
3
+ from typing import Optional, Dict, Any, List
4
+
5
+ from .search_base import SearchOpportunitiesBase
6
+ from ..config import FunctionRegistry
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ @FunctionRegistry.register
11
+ class QueryByFundingDetails(SearchOpportunitiesBase):
12
+ """
13
+ Tool to search for grant opportunities based on funding instruments and/or categories.
14
+ """
15
+ def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_funding_details"):
16
+ super().__init__(api_key=api_key, base_url=base_url, name=name)
17
+ self.name = name
18
+
19
+ @property
20
+ def definition(self):
21
+ base_params = self._get_common_parameter_definitions()
22
+ specific_params = {
23
+ "funding_instruments": {
24
+ "type": "array",
25
+ "items": {"type": "string"},
26
+ "description": "Optional. List of funding instruments (e.g., ['grant', 'cooperative_agreement']). Valid values: "
27
+ "grant, cooperative_agreement, direct_payment_for_specified_use, direct_payment_with_unrestricted_use, "
28
+ "direct_loan, guaranteed_or_insured_loan, insurance, other, procurement_contract."
29
+ },
30
+ "funding_categories": {
31
+ "type": "array",
32
+ "items": {"type": "string"},
33
+ "description": "Optional. List of funding categories (e.g., ['health', 'education']). Valid values: "
34
+ "agriculture, arts, business_and_commerce, community_development, consumer_protection, disaster_prevention_and_relief, "
35
+ "education, employment_labor_and_training, energy, environment, food_and_nutrition, health, housing, "
36
+ "humanities, information_and_statistics, infrastructure, income_security_and_social_services, "
37
+ "international_affairs, law_justice_and_legal_services, natural_resources, opportunity_zone_benefits, "
38
+ "recovery_act, regional_development, science_and_technology, transportation, other."
39
+ },
40
+ "query_text": {
41
+ "type": "string",
42
+ "description": "Optional. Text to search for within the results filtered by funding details."
43
+ },
44
+ "query_operator": {
45
+ "type": "string",
46
+ "enum": ["AND", "OR"],
47
+ "description": "Operator for 'query_text' if provided (default: AND).",
48
+ "default": "AND"
49
+ }
50
+ }
51
+ all_properties = {**specific_params, **base_params}
52
+
53
+ return {
54
+ "type": "function",
55
+ "function": {
56
+ "name": self.name,
57
+ "description": (
58
+ "Search for grant opportunities by funding instruments and/or categories. "
59
+ "At least one of 'funding_instruments' or 'funding_categories' must be provided. "
60
+ f"{self._pagination_description}"
61
+ f"{self._status_filter_description}"
62
+ f"{self._common_search_description_suffix}"
63
+ ),
64
+ "parameters": {
65
+ "type": "object",
66
+ "properties": all_properties,
67
+ "required": [] # No single field is required, but logic in fn enforces at least one funding filter
68
+ }
69
+ }
70
+ }
71
+
72
+ def fn(self,
73
+ funding_instruments: Optional[List[str]] = None,
74
+ funding_categories: Optional[List[str]] = None,
75
+ query_text: Optional[str] = None,
76
+ query_operator: str = "AND",
77
+ # Common params
78
+ items_per_page: int = 5,
79
+ page_number: int = 1,
80
+ order_by: str = "relevancy",
81
+ sort_direction: str = "descending",
82
+ show_posted: bool = True,
83
+ show_forecasted: bool = False,
84
+ show_closed: bool = False,
85
+ show_archived: bool = False) -> str:
86
+
87
+ self.logger.info(f"Querying by funding details: instruments={funding_instruments}, categories={funding_categories}, query='{query_text}'")
88
+
89
+ if not funding_instruments and not funding_categories:
90
+ return json.dumps({"error": "At least one of 'funding_instruments' or 'funding_categories' must be provided.", "success": False})
91
+
92
+ specific_filters: Dict[str, Any] = {}
93
+ if funding_instruments:
94
+ specific_filters["funding_instrument"] = {"one_of": funding_instruments}
95
+ if funding_categories:
96
+ specific_filters["funding_category"] = {"one_of": funding_categories}
97
+
98
+ specific_query_params: Optional[Dict[str, Any]] = None
99
+ if query_text:
100
+ specific_query_params = {"query": query_text}
101
+
102
+ payload = self._build_api_payload(
103
+ specific_query_params=specific_query_params,
104
+ specific_filters=specific_filters,
105
+ items_per_page=items_per_page,
106
+ page_number=page_number,
107
+ order_by=order_by,
108
+ sort_direction=sort_direction,
109
+ show_posted=show_posted,
110
+ show_forecasted=show_forecasted,
111
+ show_closed=show_closed,
112
+ show_archived=show_archived,
113
+ query_operator=query_operator
114
+ )
115
+ return self._execute_search(payload)