gofannon 0.25.18__py3-none-any.whl → 0.25.20__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/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.18.dist-info → gofannon-0.25.20.dist-info}/METADATA +1 -1
- {gofannon-0.25.18.dist-info → gofannon-0.25.20.dist-info}/RECORD +20 -5
- {gofannon-0.25.18.dist-info → gofannon-0.25.20.dist-info}/WHEEL +1 -1
- {gofannon-0.25.18.dist-info → gofannon-0.25.20.dist-info}/LICENSE +0 -0
@@ -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)
|
@@ -0,0 +1,134 @@
|
|
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 QueryByMultipleCriteria(SearchOpportunitiesBase):
|
12
|
+
"""
|
13
|
+
Tool to search for grant opportunities by combining multiple filter criteria.
|
14
|
+
Use this for more complex queries not covered by specialized tools.
|
15
|
+
All filter parameters are optional, but at least one filter or query_text should be provided.
|
16
|
+
"""
|
17
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_multiple_criteria"):
|
18
|
+
super().__init__(api_key=api_key, base_url=base_url, name=name)
|
19
|
+
self.name = name
|
20
|
+
|
21
|
+
@property
|
22
|
+
def definition(self):
|
23
|
+
base_params = self._get_common_parameter_definitions()
|
24
|
+
# Parameters from other specific tools, all optional here
|
25
|
+
specific_params = {
|
26
|
+
"agency_codes": {
|
27
|
+
"type": "array", "items": {"type": "string"},
|
28
|
+
"description": "Optional. List of agency codes (e.g., ['USAID', 'DOC'])."
|
29
|
+
},
|
30
|
+
"funding_instruments": {
|
31
|
+
"type": "array", "items": {"type": "string"},
|
32
|
+
"description": "Optional. List of funding instruments."
|
33
|
+
},
|
34
|
+
"funding_categories": {
|
35
|
+
"type": "array", "items": {"type": "string"},
|
36
|
+
"description": "Optional. List of funding categories."
|
37
|
+
},
|
38
|
+
"applicant_types": {
|
39
|
+
"type": "array", "items": {"type": "string"},
|
40
|
+
"description": "Optional. List of applicant types."
|
41
|
+
},
|
42
|
+
"assistance_listing_numbers": {
|
43
|
+
"type": "array", "items": {"type": "string"},
|
44
|
+
"description": "Optional. List of Assistance Listing Numbers."
|
45
|
+
},
|
46
|
+
"requires_cost_sharing": {
|
47
|
+
"type": "boolean",
|
48
|
+
"description": "Optional. Filter by cost-sharing requirement."
|
49
|
+
},
|
50
|
+
# For simplicity, award criteria and dates are not individual params here.
|
51
|
+
# If needed, they could be added, or a user could use a more specific tool.
|
52
|
+
"query_text": {
|
53
|
+
"type": "string",
|
54
|
+
"description": "Optional. Text to search for within filtered results."
|
55
|
+
},
|
56
|
+
"query_operator": {
|
57
|
+
"type": "string", "enum": ["AND", "OR"],
|
58
|
+
"description": "Operator for 'query_text' if provided (default: AND).", "default": "AND"
|
59
|
+
}
|
60
|
+
}
|
61
|
+
all_properties = {**specific_params, **base_params}
|
62
|
+
|
63
|
+
return {
|
64
|
+
"type": "function",
|
65
|
+
"function": {
|
66
|
+
"name": self.name,
|
67
|
+
"description": (
|
68
|
+
"Search grant opportunities by combining various filters like agency, funding types, applicant types, etc. "
|
69
|
+
"Useful for complex queries. At least one filter criterion or query_text should be provided. "
|
70
|
+
f"{self._pagination_description}"
|
71
|
+
f"{self._status_filter_description}"
|
72
|
+
f"{self._common_search_description_suffix}"
|
73
|
+
),
|
74
|
+
"parameters": {
|
75
|
+
"type": "object",
|
76
|
+
"properties": all_properties,
|
77
|
+
"required": [] # Logic in fn
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
def fn(self,
|
83
|
+
agency_codes: Optional[List[str]] = None,
|
84
|
+
funding_instruments: Optional[List[str]] = None,
|
85
|
+
funding_categories: Optional[List[str]] = None,
|
86
|
+
applicant_types: Optional[List[str]] = None,
|
87
|
+
assistance_listing_numbers: Optional[List[str]] = None,
|
88
|
+
requires_cost_sharing: Optional[bool] = None,
|
89
|
+
query_text: Optional[str] = None,
|
90
|
+
query_operator: str = "AND",
|
91
|
+
# Common params
|
92
|
+
items_per_page: int = 5, page_number: int = 1, order_by: str = "relevancy",
|
93
|
+
sort_direction: str = "descending", show_posted: bool = True, show_forecasted: bool = False,
|
94
|
+
show_closed: bool = False, show_archived: bool = False) -> str:
|
95
|
+
|
96
|
+
self.logger.info(f"Querying by multiple criteria, query='{query_text}'")
|
97
|
+
|
98
|
+
specific_filters: Dict[str, Any] = {}
|
99
|
+
any_filter_provided = False
|
100
|
+
|
101
|
+
if agency_codes:
|
102
|
+
specific_filters["agency"] = {"one_of": agency_codes}
|
103
|
+
any_filter_provided = True
|
104
|
+
if funding_instruments:
|
105
|
+
specific_filters["funding_instrument"] = {"one_of": funding_instruments}
|
106
|
+
any_filter_provided = True
|
107
|
+
if funding_categories:
|
108
|
+
specific_filters["funding_category"] = {"one_of": funding_categories}
|
109
|
+
any_filter_provided = True
|
110
|
+
if applicant_types:
|
111
|
+
specific_filters["applicant_type"] = {"one_of": applicant_types}
|
112
|
+
any_filter_provided = True
|
113
|
+
if assistance_listing_numbers:
|
114
|
+
specific_filters["assistance_listing_number"] = {"one_of": assistance_listing_numbers}
|
115
|
+
any_filter_provided = True
|
116
|
+
if requires_cost_sharing is not None:
|
117
|
+
specific_filters["is_cost_sharing"] = {"one_of": [requires_cost_sharing]}
|
118
|
+
any_filter_provided = True
|
119
|
+
|
120
|
+
if not any_filter_provided and not query_text:
|
121
|
+
return json.dumps({"error": "At least one filter criterion or query_text must be specified for this tool.", "success": False})
|
122
|
+
|
123
|
+
specific_query_params: Optional[Dict[str, Any]] = None
|
124
|
+
if query_text:
|
125
|
+
specific_query_params = {"query": query_text}
|
126
|
+
|
127
|
+
payload = self._build_api_payload(
|
128
|
+
specific_query_params=specific_query_params,
|
129
|
+
specific_filters=specific_filters if specific_filters else None,
|
130
|
+
items_per_page=items_per_page, page_number=page_number, order_by=order_by,
|
131
|
+
sort_direction=sort_direction, show_posted=show_posted, show_forecasted=show_forecasted,
|
132
|
+
show_closed=show_closed, show_archived=show_archived, query_operator=query_operator
|
133
|
+
)
|
134
|
+
return self._execute_search(payload)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional, Dict, Any
|
3
|
+
|
4
|
+
from .search_base import SearchOpportunitiesBase
|
5
|
+
from ..config import FunctionRegistry
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
@FunctionRegistry.register
|
10
|
+
class QueryOpportunities(SearchOpportunitiesBase):
|
11
|
+
"""
|
12
|
+
Tool to search for grant opportunities using a general text query.
|
13
|
+
"""
|
14
|
+
def __init__(self, api_key: Optional[str] = None,
|
15
|
+
base_url: Optional[str] = None,
|
16
|
+
name: str = "query_opportunities"):
|
17
|
+
super().__init__(api_key=api_key, base_url=base_url)
|
18
|
+
self.name = name
|
19
|
+
|
20
|
+
@property
|
21
|
+
def definition(self):
|
22
|
+
base_params = self._get_common_parameter_definitions()
|
23
|
+
specific_params = {
|
24
|
+
"query_text": {
|
25
|
+
"type": "string",
|
26
|
+
"description": "The text to search for in opportunity titles, descriptions, etc."
|
27
|
+
},
|
28
|
+
"query_operator": {
|
29
|
+
"type": "string",
|
30
|
+
"enum": ["AND", "OR"],
|
31
|
+
"description": "Operator for combining terms in 'query_text' (default: AND).",
|
32
|
+
"default": "AND"
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
# Combine and order parameters as desired for the definition
|
37
|
+
# Typically, specific parameters first, then common ones.
|
38
|
+
all_properties = {**specific_params, **base_params}
|
39
|
+
|
40
|
+
return {
|
41
|
+
"type": "function",
|
42
|
+
"function": {
|
43
|
+
"name": self.name,
|
44
|
+
"description": (
|
45
|
+
"Search for grant opportunities using a text query. "
|
46
|
+
f"{self._pagination_description}"
|
47
|
+
f"{self._status_filter_description}"
|
48
|
+
f"{self._common_search_description_suffix}"
|
49
|
+
),
|
50
|
+
"parameters": {
|
51
|
+
"type": "object",
|
52
|
+
"properties": all_properties,
|
53
|
+
"required": ["query_text"] # Only query_text is strictly required for this tool
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
def fn(self,
|
59
|
+
query_text: str,
|
60
|
+
query_operator: str = "AND",
|
61
|
+
# Common params from base, with defaults
|
62
|
+
items_per_page: int = 5,
|
63
|
+
page_number: int = 1,
|
64
|
+
order_by: str = "relevancy",
|
65
|
+
sort_direction: str = "descending",
|
66
|
+
show_posted: bool = True,
|
67
|
+
show_forecasted: bool = False,
|
68
|
+
show_closed: bool = False,
|
69
|
+
show_archived: bool = False) -> str:
|
70
|
+
"""
|
71
|
+
Executes the general opportunity search.
|
72
|
+
"""
|
73
|
+
self.logger.info(f"Executing general opportunity query: '{query_text}'")
|
74
|
+
|
75
|
+
specific_query_params = {
|
76
|
+
"query": query_text # API expects "query" not "query_text"
|
77
|
+
}
|
78
|
+
|
79
|
+
payload = self._build_api_payload(
|
80
|
+
specific_query_params=specific_query_params,
|
81
|
+
specific_filters=None, # No other specific filters for this tool
|
82
|
+
items_per_page=items_per_page,
|
83
|
+
page_number=page_number,
|
84
|
+
order_by=order_by,
|
85
|
+
sort_direction=sort_direction,
|
86
|
+
show_posted=show_posted,
|
87
|
+
show_forecasted=show_forecasted,
|
88
|
+
show_closed=show_closed,
|
89
|
+
show_archived=show_archived,
|
90
|
+
query_operator=query_operator
|
91
|
+
)
|
92
|
+
|
93
|
+
return self._execute_search(payload)
|
@@ -0,0 +1,104 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional, Dict, Any, List
|
3
|
+
|
4
|
+
from .search_base import SearchOpportunitiesBase
|
5
|
+
from ..config import FunctionRegistry
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
@FunctionRegistry.register
|
10
|
+
class QueryOpportunitiesByAgencyCode(SearchOpportunitiesBase):
|
11
|
+
"""
|
12
|
+
Tool to search for grant opportunities filtered by agency code(s).
|
13
|
+
Optionally, a text query can also be provided.
|
14
|
+
"""
|
15
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "query_opportunities_by_agency"):
|
16
|
+
super().__init__(api_key=api_key, base_url=base_url)
|
17
|
+
self.name = name
|
18
|
+
|
19
|
+
@property
|
20
|
+
def definition(self):
|
21
|
+
base_params = self._get_common_parameter_definitions()
|
22
|
+
specific_params = {
|
23
|
+
"agency_codes": {
|
24
|
+
"type": "array",
|
25
|
+
"items": {"type": "string"},
|
26
|
+
"description": "A list of agency codes to filter by (e.g., ['USAID', 'DOC'])."
|
27
|
+
},
|
28
|
+
"query_text": {
|
29
|
+
"type": "string",
|
30
|
+
"description": "Optional. Text to search for within the results filtered by agency."
|
31
|
+
},
|
32
|
+
"query_operator": {
|
33
|
+
"type": "string",
|
34
|
+
"enum": ["AND", "OR"],
|
35
|
+
"description": "Operator for combining terms in '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 filtered by one or more agency codes. "
|
47
|
+
"An optional text query can further refine results. "
|
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": ["agency_codes"]
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
def fn(self,
|
61
|
+
agency_codes: List[str],
|
62
|
+
query_text: Optional[str] = None,
|
63
|
+
query_operator: str = "AND",
|
64
|
+
# Common params from base
|
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
|
+
Executes the opportunity search filtered by agency codes.
|
75
|
+
"""
|
76
|
+
self.logger.info(f"Executing opportunity query by agency codes: {agency_codes}, query_text: '{query_text}'")
|
77
|
+
|
78
|
+
if not agency_codes:
|
79
|
+
logger.error("agency_codes list cannot be empty.")
|
80
|
+
return json.dumps({"error": "agency_codes list cannot be empty.", "success": False})
|
81
|
+
|
82
|
+
specific_filters: Dict[str, Any] = {
|
83
|
+
"agency": {"one_of": agency_codes}
|
84
|
+
}
|
85
|
+
|
86
|
+
specific_query_params: Optional[Dict[str, Any]] = None
|
87
|
+
if query_text:
|
88
|
+
specific_query_params = {"query": query_text}
|
89
|
+
|
90
|
+
payload = self._build_api_payload(
|
91
|
+
specific_query_params=specific_query_params,
|
92
|
+
specific_filters=specific_filters,
|
93
|
+
items_per_page=items_per_page,
|
94
|
+
page_number=page_number,
|
95
|
+
order_by=order_by,
|
96
|
+
sort_direction=sort_direction,
|
97
|
+
show_posted=show_posted,
|
98
|
+
show_forecasted=show_forecasted,
|
99
|
+
show_closed=show_closed,
|
100
|
+
show_archived=show_archived,
|
101
|
+
query_operator=query_operator
|
102
|
+
)
|
103
|
+
|
104
|
+
return self._execute_search(payload)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional, Dict, Any
|
3
|
+
import json
|
4
|
+
|
5
|
+
from .base import SimplerGrantsGovBase
|
6
|
+
from ..config import FunctionRegistry
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
@FunctionRegistry.register
|
11
|
+
class SearchAgencies(SimplerGrantsGovBase):
|
12
|
+
"""
|
13
|
+
Tool to search for agencies based on query text and filters.
|
14
|
+
Corresponds to the POST /v1/agencies/search endpoint.
|
15
|
+
"""
|
16
|
+
def __init__(self, api_key: Optional[str] = None, base_url: Optional[str] = None, name: str = "search_agencies"):
|
17
|
+
super().__init__(api_key=api_key, base_url=base_url)
|
18
|
+
self.name = name
|
19
|
+
|
20
|
+
@property
|
21
|
+
def definition(self):
|
22
|
+
# Based on AgencySearchRequestSchema
|
23
|
+
return {
|
24
|
+
"type": "function",
|
25
|
+
"function": {
|
26
|
+
"name": self.name,
|
27
|
+
"description": "Search for agencies using a query string and structured filters like 'has_active_opportunity' or 'is_test_agency'.",
|
28
|
+
"parameters": {
|
29
|
+
"type": "object",
|
30
|
+
"properties": {
|
31
|
+
"query": {
|
32
|
+
"type": "string",
|
33
|
+
"description": "Optional. Query string which searches against agency text fields."
|
34
|
+
},
|
35
|
+
"query_operator": {
|
36
|
+
"type": "string",
|
37
|
+
"enum": ["AND", "OR"],
|
38
|
+
"description": "Optional. Operator for combining query conditions (default: OR).",
|
39
|
+
"default": "OR"
|
40
|
+
},
|
41
|
+
"filters": {
|
42
|
+
"type": "object",
|
43
|
+
"description": "Optional. A JSON object for filtering. Keys can be 'has_active_opportunity' or 'is_test_agency'. Each key holds an object like {'one_of': [true/false]} specifying the filter.",
|
44
|
+
"properties": {
|
45
|
+
"has_active_opportunity": {
|
46
|
+
"type": "object",
|
47
|
+
"properties": {"one_of": {"type": "array", "items": {"type": "boolean"}}}
|
48
|
+
},
|
49
|
+
"is_test_agency": {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {"one_of": {"type": "array", "items": {"type": "boolean"}}}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
},
|
55
|
+
"pagination": {
|
56
|
+
"type": "object",
|
57
|
+
"description": "Required. A JSON object for pagination. Must include 'page_offset', 'page_size', and 'sort_order' (array of objects with 'order_by': ['agency_code', 'agency_name'] and 'sort_direction': ['ascending', 'descending']).",
|
58
|
+
"properties": {
|
59
|
+
"page_offset": {"type": "integer", "description": "Page number (starts at 1)."},
|
60
|
+
"page_size": {"type": "integer", "description": "Results per page."},
|
61
|
+
"sort_order": {
|
62
|
+
"type": "array",
|
63
|
+
"items": {
|
64
|
+
"type": "object",
|
65
|
+
"properties": {
|
66
|
+
"order_by": {"type": "string", "enum": ["agency_code", "agency_name"]},
|
67
|
+
"sort_direction": {"type": "string", "enum": ["ascending", "descending"]}
|
68
|
+
},
|
69
|
+
"required": ["order_by", "sort_direction"]
|
70
|
+
}
|
71
|
+
}
|
72
|
+
},
|
73
|
+
"required": ["page_offset", "page_size", "sort_order"]
|
74
|
+
}
|
75
|
+
},
|
76
|
+
"required": ["pagination"]
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
def fn(self, pagination: Dict[str, Any], query: Optional[str] = None, filters: Optional[Dict[str, Any]] = None, query_operator: str = "OR") -> str:
|
82
|
+
"""
|
83
|
+
Executes the search agencies request.
|
84
|
+
"""
|
85
|
+
self.logger.info("Executing Simpler Grants Gov search agencies tool")
|
86
|
+
payload: Dict[str, Any] = {
|
87
|
+
"pagination": pagination,
|
88
|
+
"query_operator": query_operator
|
89
|
+
}
|
90
|
+
if query:
|
91
|
+
payload["query"] = query
|
92
|
+
if filters:
|
93
|
+
payload["filters"] = filters
|
94
|
+
|
95
|
+
endpoint = "/v1/agencies/search"
|
96
|
+
try:
|
97
|
+
result = self._make_request("POST", endpoint, json_payload=payload)
|
98
|
+
self.logger.debug(f"Search agencies successful. Response length: {len(result)}")
|
99
|
+
return result
|
100
|
+
except Exception as e:
|
101
|
+
self.logger.error(f"Search agencies failed: {e}", exc_info=True)
|
102
|
+
return json.dumps({"error": f"Search agencies failed: {str(e)}", "success": False})
|