Source code for compass.extraction.wind.graphs

"""Ordinance Decision Tree Graph setup functions"""

from compass.common import (
    setup_graph_no_nodes,
    llm_response_starts_with_yes,
    llm_response_starts_with_no,
    SYSTEM_SIZE_REMINDER,
)

WES_SYSTEM_SIZE_REMINDER = SYSTEM_SIZE_REMINDER.replace(
    "height or rated capacity", "height, rotor diameter, or rated capacity"
).replace(
    "smaller or clearly non-commercial systems",
    "smaller or clearly non-commercial systems or to meteorological towers",
)


[docs] def setup_graph_wes_types(**kwargs): """Setup graph to get the largest turbine size in the ordinance text Parameters ---------- **kwargs Keyword-value pairs to add to graph. Returns ------- networkx.DiGraph Graph instance that can be used to initialize an `elm.tree.DecisionTree`. """ G = setup_graph_no_nodes( # noqa: N806 d_tree_name="Wind Energy Farm types", **kwargs ) G.add_node( "init", prompt=( "Does the following text distinguish between multiple wind energy " "system sizes? Distinctions are often made as 'small', " "'personal', or 'private' vs 'large', 'commercial', or 'utility'. " "Sometimes the distinction uses actual MW values. " "Please start your response with either 'Yes' or 'No' and briefly " "explain your answer." '\n\n"""\n{text}\n"""' ), ) G.add_edge("init", "get_text", condition=llm_response_starts_with_yes) G.add_node( "get_text", prompt=( "What are the different wind energy system sizes **regulated by " "this ordinance**? List them in order of increasing size. " "Include any relevant numerical qualifiers in the name, if " "appropriate. Only list wind energy system types; do not " "include generic types or other energy system types." ), ) G.add_edge("get_text", "get_largest") G.add_node( "get_largest", prompt=( "Based on your list, what is the **largest** wind energy system " "size **regulated by this ordinance**?" ), ) G.add_edge("get_largest", "check_matches_definition") G.add_node( "check_matches_definition", prompt=( "Does the ordinance explicitly define this system as large, " "commercial, utility-scale, or something akin to that? " "Please start your response with either 'Yes' or 'No' and briefly " "explain your answer." ), ) G.add_edge( "check_matches_definition", "final_large", condition=llm_response_starts_with_yes, ) G.add_edge( "check_matches_definition", "check_scale_reason", condition=llm_response_starts_with_no, ) G.add_node( "check_scale_reason", prompt=( "Would a reasonable person classify this kind of system as a " "**large, commercial, or even utility-scale** wind energy system " "(e.g. with the primary purpose of generating electricity for " "sale, as opposed to small, micro, private, onsite, or other " "kinds of 'small' systems)? " "Please start your response with either 'Yes' or 'No' and briefly " "explain your answer." ), ) G.add_edge( "check_scale_reason", "final_large", condition=llm_response_starts_with_yes, ) G.add_edge( "check_scale_reason", "final_small", condition=llm_response_starts_with_no, ) G.add_node( "final_large", prompt=( "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 " "'largest_wes_type', 'explanation', and 'is_large'. The value of " "the 'largest_wes_type' key should be a string that labels the " "largest wind energy conversion system size **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_large' key should be the boolean value `true`, since " "we determined this is a large-scale system." ), ) G.add_node( "final_small", prompt=( "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 " "'largest_wes_type', 'explanation', and 'is_large'. The value of " "the 'largest_wes_type' key should be a string that labels the " "largest wind energy conversion system size **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_large' key should be the boolean value `false`, since " "we determined this is not a large-scale system." ), ) return G
[docs] def setup_multiplier(**kwargs): """Setup graph to extract a setbacks multiplier values for a feature Parameters ---------- **kwargs Keyword-value pairs to add to graph. Returns ------- networkx.DiGraph Graph instance that can be used to initialize an `elm.tree.DecisionTree`. """ G = setup_graph_no_nodes( # noqa: N806 d_tree_name="Setback distance", **kwargs ) G.add_node( "init", prompt=( "Does the text mention a multiplier that should be applied to a " "turbine dimension (e.g. height, rotor diameter, etc) to compute " "the setback distance from {feature} for {tech}? " "Please consider only {feature}; do not respond based on any text " "related to {ignore_features}. " "Please also only consider setbacks specifically for " f"{WES_SYSTEM_SIZE_REMINDER}" "Remember that 1 is a valid multiplier, and treat any " "mention of 'fall zone' as a system height multiplier of 1. " "Please start your response with either 'Yes' or 'No' and " "briefly explain your answer." ), ) G.add_edge("init", "no_multiplier", condition=llm_response_starts_with_no) G.add_node( "no_multiplier", prompt=( "Does the ordinance give the setback from {feature} as a fixed " "distance value? " "Please consider only {feature}; do not respond based on any text " "related to {ignore_features}. " "Please also only consider setbacks specifically for " f"{WES_SYSTEM_SIZE_REMINDER}" "Please start your response with either 'Yes' or " "'No' and briefly explain your answer." ), ) G.add_edge( "no_multiplier", "units", condition=llm_response_starts_with_yes ) G.add_edge( "no_multiplier", "out_static", condition=llm_response_starts_with_no ) G.add_node( "units", prompt=( "What are the units for the setback from {feature}? " "Ensure that:\n1) You accurately identify the unit value " "associated with the setback.\n2) The unit is " "expressed using standard, conventional unit names (e.g., " "'feet', 'meters', 'miles' etc.)\n3) If multiple " "values are mentioned, return only the units for the most " "restrictive value that directly pertains to the setback.\n\n" "Example Inputs and Outputs:\n" "Text: 'All Solar Farms shall be set back a distance of at least " "one thousand (1000) feet, from any primary structure'\n" "Output: 'feet'\n" ), ) G.add_edge("units", "out_static") G.add_node( "out_static", prompt=( "Please respond based on our entire conversation so far. " "Return your answer in JSON " "format (not markdown). Your JSON file must include exactly " "four keys. The keys are 'value', 'units', 'summary', and " "'section'. The value of the 'value' key should be a " "**numerical** value corresponding to the setback distance value " "from {feature} or `null` if there was no such value. " "The value of the 'units' key should be a string corresponding to " "the (standard) units of the setback distance value from " "{feature} or `null` if there was no such value. " "As before, focus only on setbacks that would apply for " f"{WES_SYSTEM_SIZE_REMINDER}" "{SUMMARY_PROMPT} {SECTION_PROMPT}" ), ) G.add_edge("init", "m_single", condition=llm_response_starts_with_yes) G.add_node( "m_single", prompt=( "Are multiple values given for the multiplier used to compute the " "setback distance value from {feature} for {tech}? " "Remember to ignore any text related to {ignore_features}. " "Please consider only on setbacks specifically for " f"{WES_SYSTEM_SIZE_REMINDER}" "If so, select and state the largest one. Otherwise, " "repeat the single multiplier value that was given in the text. " ), ) G.add_edge("m_single", "m_type") G.add_node( "m_type", prompt=( "What kind of multiplier is stated in the text to " # noqa: S608 "compute the setback distance from {feature}? " "Remember to ignore any text related to {ignore_features}. " "Please consider only setbacks specifically for " f"{WES_SYSTEM_SIZE_REMINDER}" "Select a value from the following list: " "['tip-height-multiplier', 'hub-height-multiplier', " "'rotor-diameter-multiplier]. " "Default to 'tip-height-multiplier' unless the text explicitly " "explains that the multiplier should be applied to the distance " "up to the turbine hub or to the diameter of the rotors. " "Briefly justify your answer." ), ) G.add_edge("m_type", "adder") G.add_node( "adder", prompt=( "Does the ordinance for the setback from {feature} include a " "static distance value that should be added to the result of " "the multiplication? " "Please consider only setbacks specifically for " f"{WES_SYSTEM_SIZE_REMINDER}" "Do not confuse this value with static setback requirements. " "Ignore text with clauses such as " "'no lesser than', 'no greater than', 'the lesser of', or 'the " "greater of'. Please start your response with either 'Yes' or " "'No' and briefly explain your answer, stating the adder value " "if it exists." ), ) G.add_edge("adder", "out_no_adder", condition=llm_response_starts_with_no) G.add_edge("adder", "adder_eq", condition=llm_response_starts_with_yes) G.add_node( "adder_eq", prompt=( "Does the adder value you identified satisfy the following " "equation: `multiplier * height + <adder>`? Please begin your " "response with either 'Yes' or 'No' and briefly explain your " "answer." ), ) G.add_edge( "adder_eq", "out_no_adder", condition=llm_response_starts_with_no ) G.add_edge( "adder_eq", "conversion", condition=llm_response_starts_with_yes, ) G.add_node( "conversion", prompt=( "If the adder value is not given in feet, convert " "it to feet (remember that there are 3.28084 feet in one meter " "and 5280 feet in one mile). Show your work step-by-step " "if you had to perform a conversion." ), ) G.add_edge("conversion", "out_m") G.add_node( "out_m", prompt=( "Please respond based on our entire conversation so far. " "Return your answer as a single dictionary in JSON " "format (not markdown). Your JSON file must include exactly five " "keys. The keys are 'mult_value', 'mult_type', 'adder', " "'summary', and 'section'. The value of the " "'mult_value' key should be a **numerical** value corresponding " "to the multiplier value we determined earlier. The value of the " "'mult_type' key should be a string corresponding to the " "dimension that the multiplier should be applied to, as we " "determined earlier. The value of the 'adder' key should be a " "**numerical** value corresponding to the static value to be " "added to the total setback distance after multiplication, as we " "determined earlier, or `null` if there is no such value. " "{SUMMARY_PROMPT} {SECTION_PROMPT}" ), ) G.add_node( "out_no_adder", prompt=( "Please respond based on our entire conversation so far. " "Return your answer as a single dictionary in JSON " "format (not markdown). Your JSON file must include exactly four " "keys. The keys are 'mult_value', 'mult_type', " "'summary', and 'section'. The value of the " "'mult_value' key should be a **numerical** value corresponding " "to the multiplier value we determined earlier. The value of the " "'mult_type' key should be a string corresponding to the " "dimension that the multiplier should be applied to, as we " "determined earlier. " "{SUMMARY_PROMPT} {SECTION_PROMPT}" ), ) return G
[docs] def setup_conditional_min(**kwargs): """Setup graph to extract min setback values for a feature Min setback values (after application of multiplier) are typically given within the context of 'the greater of' clauses. Parameters ---------- **kwargs Keyword-value pairs to add to graph. Returns ------- networkx.DiGraph Graph instance that can be used to initialize an `elm.tree.DecisionTree`. """ G = setup_graph_no_nodes( # noqa: N806 d_tree_name="Minimum setback distance", **kwargs ) G.add_node( "init", prompt=( "Please consider only setbacks from {feature}; do not base your " "response off of any setbacks that relate to {ignore_features}. " "Please also consider only setbacks that apply to " f"{WES_SYSTEM_SIZE_REMINDER}\n" "Does the setback from {feature} for {tech} define a **minimum** " "setback distance that must be met in all cases, even " "when a multiplier is used for the calculation? This value acts " "like a threshold and is often found within phrases like 'the " "greater of'. " "Please begin your response with either 'Yes' or 'No' and " "briefly explain your answer." ), ) G.add_edge("init", "min_eq", condition=llm_response_starts_with_yes) G.add_node( "min_eq", prompt=( "Does the threshold value you identified satisfy the following " "equation: " "`setback_distance = max(<threshold>, multiplier_setback)`? " "Please begin your response with either 'Yes' or 'No' and " "briefly explain your answer." ), ) G.add_edge( "min_eq", "conversions_min", condition=llm_response_starts_with_yes ) G.add_node( "conversions_min", prompt=( "If the threshold value is not given in feet, convert " "it to feet (remember that there are 3.28084 feet in one meter " "and 5280 feet in one mile). Show your work step-by-step " "if you had to perform a conversion." ), ) G.add_edge("conversions_min", "out") G.add_node( "out", prompt=( "Please respond based on our entire conversation so far. " "Return your answer as a single dictionary in JSON " "format (not markdown). Your JSON file must include exactly two " "keys. The keys are 'min_dist' and 'summary'. The value of the " "'min_dist' key should be a **numerical** value corresponding to " "the minimum setback value from {feature} that we determined " "earlier, or `null` if no such value exists. {SUMMARY_PROMPT}" ), ) return G
[docs] def setup_conditional_max(**kwargs): """Setup graph to extract max setback values for a feature Max setback values (after application of multiplier) are typically given within the context of 'the lesser of' clauses. Parameters ---------- **kwargs Keyword-value pairs to add to graph. Returns ------- networkx.DiGraph Graph instance that can be used to initialize an `elm.tree.DecisionTree`. """ G = setup_graph_no_nodes( # noqa: N806 d_tree_name="Maximum setback distance", **kwargs ) G.add_node( "init", prompt=( "Please consider only setbacks from {feature}; do not base your " "response on any setbacks that relate to {ignore_features}. " "Please also consider only setbacks that apply to" f"{WES_SYSTEM_SIZE_REMINDER}\n" "Does the setback from {feature} for {tech} define a **maximum** " "setback distance that must be observed in all cases, even when " "a multiplier is used for the calculation? This value acts like " "a limit and is often found within phrases like 'the lesser of'. " "Please begin your response with either 'Yes' or 'No' and " "briefly explain your answer." ), ) G.add_edge("init", "max_eq", condition=llm_response_starts_with_yes) G.add_node( "max_eq", prompt=( "Does the limit value you identified satisfy the following " "equation: " "`setback_distance = min(multiplier_setback, <limit>)`? " "Please begin your response with either 'Yes' or 'No' and " "briefly explain your answer." ), ) G.add_edge( "max_eq", "conversions_max", condition=llm_response_starts_with_yes ) G.add_node( "conversions_max", prompt=( "If the limit value is not given in feet, convert " "it to feet (remember that there are 3.28084 feet in one meter " "and 5280 feet in one mile). Show your work step-by-step " "if you had to perform a conversion." ), ) G.add_edge("conversions_max", "out") G.add_node( "out", prompt=( "Please respond based on our entire conversation so far. " "Return your answer as a single dictionary in JSON " "format (not markdown). Your JSON file must include exactly two " "keys. The keys are 'max_dist' and 'summary'. The value of the " "'max_dist' key should be a **numerical** value corresponding to " "the maximum setback value from {feature} that we determined " "earlier, or `null` if no such value exists. {SUMMARY_PROMPT}" ), ) return G