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.
- gofannon/base/__init__.py +2 -0
- gofannon/base/adk_mixin.py +291 -0
- gofannon/config.py +2 -0
- gofannon/simpler_grants_gov/__init__.py +0 -0
- gofannon/simpler_grants_gov/base.py +125 -0
- gofannon/simpler_grants_gov/get_opportunity.py +66 -0
- gofannon/simpler_grants_gov/list_agencies.py +83 -0
- gofannon/simpler_grants_gov/query_by_applicant_eligibility.py +114 -0
- gofannon/simpler_grants_gov/query_by_assistance_listing.py +102 -0
- gofannon/simpler_grants_gov/query_by_award_criteria.py +117 -0
- gofannon/simpler_grants_gov/query_by_dates.py +108 -0
- gofannon/simpler_grants_gov/query_by_funding_details.py +115 -0
- gofannon/simpler_grants_gov/query_by_multiple_criteria.py +134 -0
- gofannon/simpler_grants_gov/query_opportunities.py +93 -0
- gofannon/simpler_grants_gov/query_opportunities_by_agency.py +104 -0
- gofannon/simpler_grants_gov/search_agencies.py +102 -0
- gofannon/simpler_grants_gov/search_base.py +167 -0
- gofannon/simpler_grants_gov/search_opportunities.py +360 -0
- {gofannon-0.25.19.dist-info → gofannon-0.25.21.dist-info}/METADATA +2 -2
- {gofannon-0.25.19.dist-info → gofannon-0.25.21.dist-info}/RECORD +22 -6
- {gofannon-0.25.19.dist-info → gofannon-0.25.21.dist-info}/LICENSE +0 -0
- {gofannon-0.25.19.dist-info → gofannon-0.25.21.dist-info}/WHEEL +0 -0
@@ -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)
|