Skip to content
58 changes: 54 additions & 4 deletions compass/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
"and `null` otherwise."
)
_SUMMARY_PROMPT = (
"The value of the 'summary' key should be a short summary "
"of the ordinance, using direct text excerpts as much as possible."
"The value of the 'summary' key should be a short summary of the relevant "
"ordinance, **using direct text excerpts as much as possible.** "
"If you had to make a selection when reporting the ordinance, be sure to "
"list out all the other options and their conditions in the summary."
)
_UNITS_IN_SUMMARY_PROMPT = (
"Include any clarifications about the units in the summary."
"Also include any clarifications about the units in the summary."
)
EXTRACT_ORIGINAL_SETBACK_TEXT_PROMPT = (
"Extract all portions of the text (with original formatting) "
Expand Down Expand Up @@ -189,7 +191,7 @@ def setup_base_setback_graph(**kwargs):
"{feature_clarifications}" # expected to end in space
"Please consider only setbacks from {feature}. "
"Please also only consider setbacks that would apply for "
"{system_size_reminder}"
"{system_size_reminder}" # expected to end in space
"Don't forget to pay extra attention to clarifying text found "
"in parentheses and footnotes. "
"Please start your response with either 'Yes' or 'No' and briefly "
Expand Down Expand Up @@ -465,6 +467,54 @@ def setup_graph_extra_restriction(is_numerical=True, **kwargs):
),
)

G.add_edge("init", "enr", condition=llm_response_starts_with_no)
G.add_node(
"enr",
prompt=(
"Does the legal text **directly** mention not regulating "
"{restriction} for {tech}? "
"As before, focus only on {restriction} specifically for "
"{system_size_reminder}"
"Please start your response with either 'Yes' or 'No' and "
"briefly explain your answer."
),
)
G.add_edge("enr", "enr_text", condition=llm_response_starts_with_yes)
G.add_node(
"enr_text",
prompt=(
"What is the **exact text excerpt** that explicitly states there "
"are no regulations around {restriction} for {tech}? "
),
)
G.add_edge("enr_text", "enr_validate")
G.add_node(
"enr_validate",
prompt=(
"Based on your response, are you still confident that the text "
"**directly** states that there are no regulations around "
"{restriction} for {tech}? "
"Please start your response with either 'Yes' or 'No' and "
"briefly explain your answer."
),
)
G.add_edge(
"enr_validate", "final_enr", condition=llm_response_starts_with_yes
)
G.add_node(
"final_enr",
prompt=(
"Please respond based on our entire conversation so far. "
"Return your answer as a dictionary in JSON format (not "
"markdown). Your JSON file must include exactly three keys. "
"The keys are 'value', 'summary', and 'section'. The value of "
"the 'value' key should be the string 'ENR' if the text "
"clearly states that the jurisdiction does not regulate "
"{restriction} for {tech} or `null` otherwise. "
"{SUMMARY_PROMPT} {SECTION_PROMPT}"
),
)

return G


Expand Down
11 changes: 11 additions & 0 deletions compass/extraction/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class SetbackFeatures:
"transmission lines",
],
"water": ["lakes", "reservoirs", "streams", "rivers", "wetlands"],
"public conservation lands": [
"public conservation lands",
"natural resource protection areas",
"preservation areas",
],
}
"""Aliases for mutually-exclusive setback features"""
FEATURES_AS_IGNORE = {
Expand All @@ -41,6 +46,7 @@ class SetbackFeatures:
"railroads": "railroads",
"transmission": "transmission lines",
"water": "wetlands",
"public conservation lands": "public conservation lands",
}
"""Features as they should appear in ignore phrases"""
FEATURE_CLARIFICATIONS = {
Expand All @@ -50,6 +56,11 @@ class SetbackFeatures:
"parcel boundaries unless the text **explicitly** makes that "
"connection. "
),
"water": (
"Public conservation lands (or similar) **are not equivalent** to "
"wetlands (or similar) unless the text **explicitly** makes that "
"connection. "
),
"roads": "Roads may also be labeled as rights-of-way. ",
}
"""Clarifications to add to feature prompts"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
"""Accessory wind ordinance extraction utilities"""
"""Small wind ordinance extraction utilities"""

from .ordinance import (
AccessoryWindHeuristic,
AccessoryWindOrdinanceTextCollector,
AccessoryWindOrdinanceTextExtractor,
AccessoryWindPermittedUseDistrictsTextCollector,
AccessoryWindPermittedUseDistrictsTextExtractor,
SmallWindHeuristic,
SmallWindOrdinanceTextCollector,
SmallWindOrdinanceTextExtractor,
SmallWindPermittedUseDistrictsTextCollector,
SmallWindPermittedUseDistrictsTextExtractor,
)
from .parse import (
StructuredAccessoryWindOrdinanceParser,
StructuredAccessoryWindPermittedUseDistrictsParser,
StructuredSmallWindOrdinanceParser,
StructuredSmallWindPermittedUseDistrictsParser,
)


ACCESSORY_WIND_QUESTION_TEMPLATES = [
SMALL_WIND_QUESTION_TEMPLATES = [
"filetype:pdf {jurisdiction} wind energy conversion system ordinances",
"wind energy conversion system ordinances {jurisdiction}",
"{jurisdiction} wind WECS ordinance",
"Where can I find the legal text for accessory wind energy "
"Where can I find the legal text for small wind energy "
"turbine zoning ordinances in {jurisdiction}?",
"What is the specific legal information regarding zoning "
"ordinances for accessory wind turbines in {jurisdiction}?",
"ordinances for small wind turbines in {jurisdiction}?",
]

BEST_ACCESSORY_WIND_ORDINANCE_WEBSITE_URL_KEYWORDS = {
BEST_SMALL_WIND_ORDINANCE_WEBSITE_URL_KEYWORDS = {
"pdf": 92160,
"wecs": 46080,
"wind": 23040,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


def setup_graph_wes_types(**kwargs):
"""Setup graph to get the accessory turbine size in the text
"""Setup graph to get the small turbine size in the text

Parameters
----------
Expand Down Expand Up @@ -49,23 +49,25 @@ def setup_graph_wes_types(**kwargs):
"include generic types or other energy system types."
),
)
G.add_edge("get_text", "get_accessory")
G.add_edge("get_text", "get_small_label")
G.add_node(
"get_accessory",
"get_small_label",
prompt=(
"Based on your list, what is the wind energy system size that is "
"**closest to the definition of accessory wind energy systems** "
"that is also **regulated by this ordinance**?"
"**closest to the definition of small, medium, or non-commercial "
"wind energy systems** that is also **regulated by this "
"ordinance**?"
),
)

G.add_edge("get_accessory", "check_matches_definition")
G.add_edge("get_small_label", "check_matches_definition")
G.add_node(
"check_matches_definition",
prompt=(
"Does the ordinance explicitly define this system as small, "
"medium, private, accessory, or something akin to that (i.e. "
"**not** large, commercial, or utility-scale)? "
"medium, non-commercial, or something akin to that (i.e. "
"**not** private, micro, building-mounted and **not** large, "
"commercial, or utility-scale)? "
"Please start your response with either 'Yes' or 'No' and briefly "
"explain your answer."
),
Expand All @@ -87,7 +89,7 @@ def setup_graph_wes_types(**kwargs):
"check_scale_reason",
prompt=(
"Would a reasonable person classify this kind of system as a "
"**small, private, medium, accessory, or even residential** wind "
"**small, medium, or non-commercial** wind "
"energy generation system (e.g. with the primary purpose of "
"generating electricity for use on-site, as opposed to large, "
"commercial, utility-scale, or other kinds of 'large' systems)? "
Expand All @@ -112,14 +114,14 @@ def setup_graph_wes_types(**kwargs):
"Respond based on our entire conversation so far. Return your "
"answer as a dictionary in JSON format (not markdown). Your JSON "
"file must include exactly three keys. The keys are "
"'wes_type', 'explanation', and 'is_accessory'. The value of "
"'wes_type', 'explanation', and 'is_small'. The value of "
"the 'wes_type' key should be a string that labels the system "
"size **closest to the definition of accessory wind energy "
"systems** that is also **regulated by this ordinance**. "
"The value of the 'explanation' key should be a "
"size **closest to the definition of small, medium, or "
"non-commercial wind energy systems** that is also **regulated by "
"this ordinance**. The value of the 'explanation' key should be a "
"string containing a short explanation for your choice. The value "
"of the 'is_accessory' key should be the boolean value `true`, "
"since we determined this is an accessory system."
"of the 'is_small' key should be the boolean value `true`, "
"since we determined this is a small or non-commercial system."
),
)
G.add_node(
Expand All @@ -128,14 +130,14 @@ def setup_graph_wes_types(**kwargs):
"Respond based on our entire conversation so far. Return your "
"answer as a dictionary in JSON format (not markdown). Your JSON "
"file must include exactly three keys. The keys are "
"'wes_type', 'explanation', and 'is_accessory'. The value of "
"'wes_type', 'explanation', and 'is_small'. The value of "
"the 'wes_type' key should be a string that labels the system "
"size **closest to the definition of accessory wind energy "
"systems** that is also **regulated by this ordinance**. "
"The value of the 'explanation' key should be a "
"size **closest to the definition of small, medium, or "
"non-commercial wind energy systems** that is also **regulated by "
"this ordinance**. The value of the 'explanation' key should be a "
"string containing a short explanation for your choice. The value "
"of the 'is_accessory' key should be the boolean value `false`, "
"since we determined this is not an accessory system."
"of the 'is_small' key should be the boolean value `false`, "
"since we determined this is not a small or non-commercial system."
),
)
return G
Expand Down
Loading