Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
23cfbcb
Update docstrings
ppinchuk Oct 22, 2025
08069d5
Add distributed wind ordinance file
ppinchuk Oct 22, 2025
5333682
Add `__init__.py`
ppinchuk Oct 22, 2025
a91582e
Add first pass of graphs for d wind
ppinchuk Oct 22, 2025
9482f25
Move system size reminder to be technology-specific
ppinchuk Oct 22, 2025
0fbb313
Rename variable
ppinchuk Oct 22, 2025
4b63a56
Update name
ppinchuk Oct 22, 2025
d699fbb
Add initial version of parsing for dwind
ppinchuk Oct 22, 2025
2c53e3b
Remove unused keys
ppinchuk Oct 22, 2025
6021694
Fix system size prompt
ppinchuk Oct 22, 2025
62ee376
ANother fix for system size
ppinchuk Oct 22, 2025
cb30ef0
Fix import
ppinchuk Oct 22, 2025
82fbfa1
Update example
ppinchuk Oct 22, 2025
100d508
System size fix
ppinchuk Oct 22, 2025
1067ff3
No need for primary or special use districts for dwind
ppinchuk Oct 22, 2025
583c6c3
Add max turbine height clarifications
ppinchuk Oct 27, 2025
ac94034
Add clarification about participating owners
ppinchuk Oct 27, 2025
de61b45
Add clarification about costs
ppinchuk Oct 27, 2025
e93ae1a
Add tower density for DWind
ppinchuk Oct 27, 2025
6a231d6
Rename classes
ppinchuk Oct 27, 2025
c754840
Rename module
ppinchuk Oct 27, 2025
3f22105
Add "accessory wind" as tech
ppinchuk Oct 27, 2025
503daff
Merge remote-tracking branch 'origin/main' into pp/dwind
ppinchuk Oct 27, 2025
4e577c2
Try to fix tests
ppinchuk Oct 27, 2025
e3c5a1a
Cache only on main branch
ppinchuk Oct 27, 2025
a2afdf6
Don't cache on branch
ppinchuk Oct 27, 2025
9ba5bec
Update lockfile
ppinchuk Oct 27, 2025
2f0c07b
Guard Python in the rdev env
ppinchuk Oct 27, 2025
d174315
Run locked tests specifically
ppinchuk Oct 27, 2025
5bc1fcb
Add release instructions
ppinchuk Oct 27, 2025
401ec1e
Merge remote-tracking branch 'origin/main' into pp/dwind
ppinchuk Oct 28, 2025
524e3f9
Add install command to GHA
ppinchuk Oct 28, 2025
a0a930b
Bump elm dep
ppinchuk Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ jobs:
args: "format --check"
src: "./compass"

locked-tests:
needs: lint
name: Pixi-Locked Unit Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 8

steps:
- name: Checkout Repo
uses: actions/checkout@v5
with:
fetch-tags: true

- uses: prefix-dev/setup-pixi@v0.9.2
with:
pixi-version: v0.50.2
locked: true
cache: true
cache-write: ${{ github.ref == 'refs/heads/main' }}
environments: pdev

- run: |
pixi install -e pdev --locked
pixi run -e pdev --locked tests-u

unit-tests:
needs: lint
name: Python Unit Tests (Pixi)
Expand All @@ -47,7 +73,6 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v5
with:
fetch-depth: 0
fetch-tags: true

- uses: prefix-dev/setup-pixi@v0.9.2
Expand All @@ -59,7 +84,8 @@ jobs:
environments: pdev

- run: |
pixi run -e pdev --locked tests-u
pixi install -e pdev
pixi run -e pdev tests-u

integration-tests:
needs: lint
Expand All @@ -75,7 +101,6 @@ jobs:
- name: Checkout Repo
uses: actions/checkout@v5
with:
fetch-depth: 0
fetch-tags: true

- uses: prefix-dev/setup-pixi@v0.9.2
Expand All @@ -87,7 +112,8 @@ jobs:
environments: pdev

- run: |
pixi run -e pdev --locked tests-i
pixi install -e pdev
pixi run -e pdev tests-i

tox-tests:
needs: lint
Expand All @@ -106,12 +132,19 @@ jobs:
fetch-depth: 0
fetch-tags: true

- name: Set up Python ${{ matrix.python-version }}
- name: Set up Python ${{ matrix.python-version }} (with cache)
if: github.ref == 'refs/heads/main'
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Set up Python ${{ matrix.python-version }} (no cache)
if: github.ref != 'refs/heads/main'
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install Poppler Reqs (Linux)
if: ${{ runner.os == 'Linux' }}
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
shared-key: "gha"
save-if: ${{ github.ref == 'refs/heads/main' }}

- run: pixi run -e rdev --locked tests-r
- run: pixi run -e rdev tests-r

publish-dry:
name: Rust - Publish (dry-run)
Expand Down
3 changes: 1 addition & 2 deletions compass/common/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""COMPASS common extraction utilities and graphs"""

from .base import (
EXTRACT_ORIGINAL_TEXT_PROMPT,
SYSTEM_SIZE_REMINDER,
EXTRACT_ORIGINAL_SETBACK_TEXT_PROMPT,
BaseTextExtractor,
empty_output,
llm_response_starts_with_no,
Expand Down
69 changes: 52 additions & 17 deletions compass/common/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,7 @@
_UNITS_IN_SUMMARY_PROMPT = (
"Include any clarifications about the units in the summary."
)
SYSTEM_SIZE_REMINDER = (
"systems that would typically be defined as {tech} based on the text "
"itself — for example, systems intended for offsite electricity "
"generation or sale, or those above thresholds such as height or rated "
"capacity (often 1MW+). Do not consider any text that applies **only** "
"to smaller or clearly non-commercial systems. "
)
EXTRACT_ORIGINAL_TEXT_PROMPT = (
EXTRACT_ORIGINAL_SETBACK_TEXT_PROMPT = (
"Extract all portions of the text (with original formatting) "
"that state how close I can site {tech} to {feature}. "
"{feature_clarifications}"
Expand All @@ -46,7 +39,7 @@
"The extracted text will be used for structured data extraction, so it "
"must be both **comprehensive** (retaining all relevant details) and "
"**focused** (excluding unrelated content). Ensure that all retained "
f"information is **directly applicable** to {SYSTEM_SIZE_REMINDER}"
"information is **directly applicable** to {system_size_reminder}"
)


Expand Down Expand Up @@ -196,7 +189,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 "
f"{SYSTEM_SIZE_REMINDER}"
"{system_size_reminder}"
"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 @@ -241,7 +234,7 @@ def setup_base_setback_graph(**kwargs):
"verify_feature", "get_text", condition=llm_response_starts_with_no
)

G.add_node("get_text", prompt=EXTRACT_ORIGINAL_TEXT_PROMPT)
G.add_node("get_text", prompt=EXTRACT_ORIGINAL_SETBACK_TEXT_PROMPT)

return G

Expand Down Expand Up @@ -269,11 +262,14 @@ def setup_participating_owner(**kwargs):
prompt=(
"Does the ordinance for {feature} setbacks explicitly distinguish "
"between **participating** and **non-participating** {owned_type} "
"owners? {feature_clarifications} We are only interested in "
"owners? Participating may, for example, be able to sign a waiver "
"or enter some sort of agreement to reduce or completely "
"eliminate the setback requirement. "
"{feature_clarifications} We are only interested in "
"setbacks from {feature}; do not base your response on any text "
"related to {ignore_features}. "
"Please only consider setbacks that would apply for "
f"{SYSTEM_SIZE_REMINDER}"
"{system_size_reminder}"
"Please start your response with either 'Yes' or 'No' "
"and briefly explain your answer."
),
Expand Down Expand Up @@ -401,7 +397,7 @@ def setup_graph_extra_restriction(is_numerical=True, **kwargs):
"2) If the text only provides a definition of what {restriction} "
"are without providing specifics, please respond with 'No'.\n"
"3) Please focus only on {restriction} that would apply for "
f"{SYSTEM_SIZE_REMINDER}\n"
"{system_size_reminder}\n"
"4) Pay close attention to clarifying details in parentheses, "
"footnotes, or additional explanatory text.\n"
"5) Please start your response with either 'Yes' or 'No' and "
Expand All @@ -423,6 +419,8 @@ def setup_graph_extra_restriction(is_numerical=True, **kwargs):
_add_maximum_lot_size_clarification_nodes(G)
elif "maximum project size" in feature_id:
_add_maximum_project_size_clarification_nodes(G)
elif "maximum turbine height" in feature_id:
_add_maximum_turbine_height_clarification_nodes(G)
else:
G.add_edge("init", "value", condition=llm_response_starts_with_yes)

Expand All @@ -446,7 +444,7 @@ def setup_graph_extra_restriction(is_numerical=True, **kwargs):
"below, or `null` if the text does not mention such a "
"restriction. "
"As before, focus only on {restriction} specifically for "
f"{SYSTEM_SIZE_REMINDER}"
"{system_size_reminder}"
"{SUMMARY_PROMPT} {UNITS_IN_SUMMARY_PROMPT} {SECTION_PROMPT}"
),
)
Expand Down Expand Up @@ -601,13 +599,50 @@ def _add_maximum_project_size_clarification_nodes(G): # noqa: N803
return G


def _add_maximum_turbine_height_clarification_nodes(G): # noqa: N803
"""Add nodes and edges to clarify max turbine height extraction"""
G.add_edge("init", "has_relative", condition=llm_response_starts_with_yes)
G.add_node(
"has_relative",
prompt=(
"Are any of the turbine height restrictions measured from a point "
"other than the ground (e.g. a building height or an airspace "
"level, etc.)? "
"Please start your response with either 'Yes' or 'No' and "
"briefly explain your answer."
),
)
G.add_edge(
"has_relative",
"has_non_relative",
condition=llm_response_starts_with_yes,
)
G.add_edge("has_relative", "value", condition=llm_response_starts_with_no)

G.add_node(
"has_non_relative",
prompt=(
"We are not interested in restrictions that are relative to some "
"level. Does the legal text enact any turbine height restrictions "
"measured from the ground? "
"Please start your response with either 'Yes' or 'No' and "
"briefly explain your answer."
),
)

G.add_edge(
"has_non_relative", "value", condition=llm_response_starts_with_yes
)
return G


def _add_value_and_units_clarification_nodes(G): # noqa: N803
"""Add nodes and edges to clarify value and units extraction"""

G.add_node(
"value",
prompt=(
"What is the **numerical** value given for the " # noqa: S608
"What is the **numerical** value given for the "
"{restriction} for {tech}? Follow these guidelines:\n"
"1) Extract only the explicit numerical value provided for "
"the restriction. Do not infer values from related "
Expand All @@ -616,7 +651,7 @@ def _add_value_and_units_clarification_nodes(G): # noqa: N803
"one (i.e., the smallest allowable limit, the lowest maximum, "
"etc.).\n"
"3) Please focus only on {restriction} that would apply for "
f"{SYSTEM_SIZE_REMINDER}\n"
"{system_size_reminder}\n"
"4) Pay close attention to clarifying details in parentheses, "
"footnotes, or additional explanatory text.\n\n"
"Example Inputs and Outputs:\n"
Expand Down
49 changes: 49 additions & 0 deletions compass/extraction/accessory_wind/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""Accessory wind ordinance extraction utilities"""

from .ordinance import (
AccessoryWindHeuristic,
AccessoryWindOrdinanceTextCollector,
AccessoryWindOrdinanceTextExtractor,
AccessoryWindPermittedUseDistrictsTextCollector,
AccessoryWindPermittedUseDistrictsTextExtractor,
)
from .parse import (
StructuredAccessoryWindOrdinanceParser,
StructuredAccessoryWindPermittedUseDistrictsParser,
)


ACCESSORY_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 "
"turbine zoning ordinances in {jurisdiction}?",
"What is the specific legal information regarding zoning "
"ordinances for accessory wind turbines in {jurisdiction}?",
]

BEST_ACCESSORY_WIND_ORDINANCE_WEBSITE_URL_KEYWORDS = {
"pdf": 92160,
"wecs": 46080,
"wind": 23040,
"zoning": 11520,
"ordinance": 5760,
r"renewable%20energy": 1440,
r"renewable+energy": 1440,
"renewable energy": 1440,
"planning": 720,
"plan": 360,
"government": 180,
"code": 60,
"area": 60,
r"land%20development": 15,
r"land+development": 15,
"land development": 15,
"land": 3,
"environment": 3,
"energy": 3,
"renewable": 3,
"municipal": 1,
"department": 1,
}
Loading