Get started with a coding agent
You can use a coding agent to quickly get up and running with RelationalAI (RAI). This tutorial introduces a repeatable workflow using RAI’s agent skills. Whether you’re a data analyst or an application developer, you can use this workflow to turn raw Snowflake data into a semantic model that you can reason over, question, and improve.
You’ll learn how to:
- Set up a coding agent and install the RAI skills.
- Build a starter semantic model from data in Snowflake.
- Explore the model with natural-language queries.
- Push the model toward real decision support by leveraging RAI’s advanced reasoning capabilities.
-
You have access to a Snowflake account with the RelationalAI (RAI) Native App installed.
How do I know if the RAI Native App is installed?
To check if the RAI Native App is installed in your Snowflake account:- Log in to your Snowflake account.
- Run the following SQL command in a worksheet:
SHOW APPLICATIONS;
- Check the output for an application named
RELATIONALAI.- If you see it listed, the RAI Native App is installed.
- If not, you need to install it.
How do I install the RAI Native App?
See Install the RAI Native App for Snowflake for instructions. -
You have a Snowflake user with the
RAI_DEVELOPERdatabase role.How do I check my roles?
To check your roles in Snowflake:- Log in to your Snowflake account.
- Run the following SQL command in a worksheet:
SHOW GRANTS TO USER <your_username>;
- Check the output in the
namecolumn for theRAI_DEVELOPERrole.- If you see it listed, you have the required role.
- If not, contact your Snowflake account administrator to have the role assigned to you. See Set Up User Access for the RAI Native App for more information.
-
You can create tables in a Snowflake database and schema for the tutorial data.
How do I create a sample database and schema for this tutorial?
Any Snowflake database and schema will work for this tutorial, as long as you have permission to create tables in that schema. You can use an existing database and schema, or create a new one specifically for this tutorial.If you do not already have a writable database and schema for this tutorial, ask a Snowflake administrator to run the following SQL as
ACCOUNTADMINor another role with permission to create databases, create schemas, and grant privileges:CREATE DATABASE IF NOT EXISTS RAI_TUTORIAL;CREATE SCHEMA IF NOT EXISTS RAI_TUTORIAL.ENERGY_GRID;GRANT USAGE ON DATABASE RAI_TUTORIAL TO ROLE rai_developer;GRANT USAGE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;GRANT CREATE TABLE ON SCHEMA RAI_TUTORIAL.ENERGY_GRID TO ROLE rai_developer;After that, use
RAI_TUTORIAL.ENERGY_GRIDwhen the tutorial asks for your Snowflake schema.
Install the RAI skills
Section titled “Install the RAI skills”Choose the tab for your coding agent to see the installation instructions. For more details and other installation options, see Install RAI agent skills.
In Claude Code, add the RelationalAI marketplace and install the rai plugin:
/plugin marketplace add RelationalAI/rai-agent-skills/plugin install rai@RelationalAIIn your terminal, execute the following commands to add the RelationalAI marketplace and install the rai plugin:
-
Add the RelationalAI marketplace:
Terminal window codex plugin marketplace add RelationalAI/rai-agent-skills -
Start Codex:
Terminal window codex -
Open the plugin browser and install the
raiplugin from therelationalaimarketplace:Terminal window /plugins
-
In VS Code, add the RelationalAI marketplace to your settings:
"chat.plugins.marketplaces": ["RelationalAI/rai-agent-skills"] -
Then open the Extensions view, search for
@agentPlugins, and install theRaiplugin.
Execute the following command in your terminal and accept all the default settings to install the RAI skills into an .agents/ directory in your project:
npx skills add RelationalAI/rai-agent-skills --skill '*'See the Vercel Skills CLI documentation for more information.
Download the starter project
Section titled “Download the starter project”To get started, download the RelationalAI starter project. This project contains a simple PyRel project structure:
Directorydata/ Sample CSV files
- …
Directorymodel Starter PyRel semantic model package
__init__.py- schema.py Defines the model’s schema
- sources.py Maps source data to the schema
- load_data.py Creates Snowflake tables from sample data
- pyproject.toml Define project metadata and dependencies
- queries.py Contains example queries
- raiconfig.yaml Stores the RAI settings for this project
Don’t worry about the contents of the starter project just yet. You’ll explore them later in this tutorial as you build a PyRel semantic model for the sample data.
Follow these steps to download the starter project and set up your project environment.
-
Download and extract the ZIP file.
Copy and paste the following prompt into your coding agent to download the starter project and place it in a new project directory:
Create a new directory called rai-agent-skills-tutorial.Download the starter project from https://docs.relational.ai/rai-starter-project.zip into that directory, extract it there, and then remove the downloaded zip file.In your terminal, run the following commands to create a new project directory, download the starter project as a zip file, and extract it:
-
Create a new directory and navigate into it.
Terminal window mkdir rai-agent-skills-tutorialcd rai-agent-skills-tutorial -
Download and extract the starter project zip file.
Terminal window curl -L -o rai-starter-project.zip https://docs.relational.ai/rai-starter-project.zipunzip rai-starter-project.zip -
Remove the downloaded zip file.
Terminal window rm rai-starter-project.zip
-
-
Set up the project environment.
Use
uvto create the project environment and install the dependencies pinned inpyproject.toml.Copy and paste the following prompt into your coding agent to set up the starter project environment:
Check whether uv is installed in this project. If it is missing, install it first. Then run uv sync in the current project directory to create the virtual environment and install the dependencies from pyproject.toml.-
Install
uvif needed.Run the following in your terminal to check if you already have
uvinstalled:Terminal window uv --versionIf you see a version number, you’re ready for the next step. If you get a “command not found” error, install
uvfirst.Select the instructions for your operating system below to install
uv:Use the standalone installer:
Terminal window curl -LsSf https://astral.sh/uv/install.sh | shIn PowerShell, run:
Terminal window powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -
Run
uv sync.In the starter project directory, run:
Terminal window uv syncThis creates the project environment and installs the dependencies from
pyproject.toml.
-
Configure your Snowflake connection
Section titled “Configure your Snowflake connection”The starter project assumes you use a programmatic access token (PAT) to authenticate with Snowflake. We recommend using a PAT for most use cases, but you can use any Snowflake authentication method.
Follow the steps below to add your Snowflake PAT credentials, or use the /rai-setup skill to help you set up a different authentication method.
-
Gather your Snowflake connection details.
You will need:
Detail Where to find it Account Log in to the Snowflake account that has the RAI Native App installed. Execute the following SQL command in a worksheet to find your account name: SELECT CURRENT_ORGANIZATION_NAME() || '-' || CURRENT_ACCOUNT_NAME() AS account_identifier;User The Snowflake username you will use to connect. This user must have the RAI_DEVELOPER role. Token The programmatic access token (PAT) you use to authenticate with Snowflake. If you don’t have one, ask a Snowflake admin to follow the steps in Configure programmatic access token authentication to create one. Warehouse The name of the Snowflake warehouse you want to use to execute SQL queries. Can be any existing X-SMALLor larger warehouse. -
Edit the
raiconfig.yamlfile.Open
raiconfig.yamland replace the placeholders foraccount,warehouse,user, andtokenwith your Snowflake connection details and PAT credentials. Therolefield is already set toRAI_DEVELOPER, so you can leave that as is unless you know you need to use a different role. -
Validate the connection.
After you have added your Snowflake credentials to
raiconfig.yaml, run the following command in your terminal to validate the connection:Terminal window uv run rai connectIf the connection is successful, you will see a message indicating that the connection to Snowflake was established successfully.
-
Choose a different authentication method.
Use the
/rai-setupskill to have your agent walk you through setting up a different authentication method for your Snowflake account:/rai-setup Explain the supported authentication methods. Ask me which one I want to use, and then help me edit my `raiconfig.yaml` to set up that method with my Snowflake account details and credentials. -
Verify the connection.
After you have set up your Snowflake authentication method, run the following command in your terminal to validate the connection:
Terminal window uv run rai connectIf the connection is successful, you will see a message indicating that the connection to Snowflake was established successfully.
Load the sample data
Section titled “Load the sample data”The starter project includes a load_data.py script that creates tutorial tables in the Snowflake namespace you specify and loads the sample CSV files into them.
What is in the sample data?
The sample data represents a dataset representing the Texas electric grid’s substations, generators, load zones, and transmission lines.
It also includes data for requests to interconnect new data centers to the grid, as well as current load readings, demand forecasts, maintenance windows, and grid upgrades.
Choose a tab below to see the instructions for loading the sample data into Snowflake.
Copy and paste the following prompt into your coding agent to load the sample data into Snowflake and clean up the local starter files afterward:
Ask me for the Snowflake database and schema name in `DATABASE.SCHEMA` form. Then run `uv run load_data.py <DATABASE.SCHEMA>` in the current project directory to load the sample data into Snowflake. After the data is loaded successfully, remove the local `data/` directory and `load_data.py`.-
Run the
load_data.pyscript.Execute the following command in your terminal. Replace
<MY_DB>.<MY_SCHEMA>with your own database and schema name, such asRAI_TUTORIAL.ENERGY_GRIDif you followed the instructions in the prerequisites section:Terminal window uv run load_data.py <MY_DB>.<MY_SCHEMA> -
Remove the CSV files and the
load_data.pyscript.After the data is loaded, you can remove the CSV files and the
load_data.pyscript from your project directory to keep it clean. This step is important because your agent may try to use these CSV files later instead of the data in Snowflake.Execute the following command in your terminal:
Terminal window rm -rf data load_data.py
Start building with /rai-build-starter-ontology
Section titled “Start building with /rai-build-starter-ontology”Follow these steps to have your agent build a starter PyRel semantic model based on the sample data you just loaded into Snowflake.
-
Invoke the
/rai-build-starter-ontologyskill.Use the following prompt to have your agent build a PyRel semantic model based on your Snowflake data:
/rai-build-starter-ontology Electric grid and datacenter capacity planning, with data from my Snowflake account. Model substations, load zones, generators, transmission lines, demand, forecasts, upgrades, maintenance, customers, and datacenter requests.Do the following:- Edit the existing model/ directory structure in this project with the new semantic model.- Update the `queries.py` file with one query that counts the number of data center requests in the model.Before you start, ask me for the Snowflake schema (in the format `DATABASE.SCHEMA`) to point at for the source data.- Inspects the Snowflake tables to understand the sample data.
- Defines concepts, properties, and relationships in
model/schema.pywith a PyRelModelobject. - Maps Snowflake tables to the model in
model/sources.pywith PyRelTableobjects and definitions. - Adds a query to
queries.pythat counts data center requests.
-
Enter your Snowflake schema.
When prompted, enter the Snowflake schema you used when loading the sample data in the previous step.
-
Choose one-shot mode.
When prompted by your agent, choose one-shot mode for this tutorial so that the agent can complete the model-building process in a single pass.
-
Review the generated model.
After the agent finishes building the model, review the generated files in the
model/directory and the updatedqueries.pyfile to ensure they meet your expectations. Here are some samples of outputs you can expect to see in the generated files:Click to expand the sample output from
/rai-build-starter-ontology"""Semantic model for electric-grid and data-center capacity planning."""from relationalai.semantics import Date, Float, Integer, Model, Stringmodel = Model("EnergyGridCapacityPlanning")LoadZone = model.Concept("LoadZone", identify_by={"id": String})LoadZone.name = model.Property(f"{LoadZone} has {String:name}")LoadZone.peak_demand_mw = model.Property(f"{LoadZone} has {Float:peak_demand_mw}")LoadZone.base_demand_mw = model.Property(f"{LoadZone} has {Float:base_demand_mw}")Customer = model.Concept("Customer", identify_by={"id": String})Customer.name = model.Property(f"{Customer} has {String:name}")Customer.load_zone = model.Property(f"{Customer} is served by {LoadZone:load_zone}")Customer.contracted_demand_mw = model.Property(f"{Customer} has {Float:contracted_demand_mw}")Customer.flexibility_pct = model.Property(f"{Customer} has {Float:flexibility_pct}")Customer.curtailment_cost_per_mwh = model.Property(f"{Customer} has {Float:curtailment_cost_per_mwh}")Substation = model.Concept("Substation", identify_by={"id": String})Substation.name = model.Property(f"{Substation} has {String:name}")Substation.voltage_kv = model.Property(f"{Substation} has {Integer:voltage_kv}")Substation.max_capacity_mw = model.Property(f"{Substation} has {Float:max_capacity_mw}")Substation.current_load_mw = model.Property(f"{Substation} has {Float:current_load_mw}")DataCenterRequest = model.Concept("DataCenterRequest", identify_by={"id": String})DataCenterRequest.name = model.Property(f"{DataCenterRequest} has {String:name}")DataCenterRequest.hyperscaler = model.Property(f"{DataCenterRequest} has {String:hyperscaler}")DataCenterRequest.substation = model.Property(f"{DataCenterRequest} interconnects at {Substation:substation}")DataCenterRequest.requested_mw = model.Property(f"{DataCenterRequest} has {Float:requested_mw}")DataCenterRequest.annual_revenue_per_mw = model.Property(f"{DataCenterRequest} has {Float:annual_revenue_per_mw}")DataCenterRequest.pue = model.Property(f"{DataCenterRequest} has {Float:pue}")DataCenterRequest.low_carbon_requirement_pct = model.Property(f"{DataCenterRequest} has {Float:low_carbon_requirement_pct}")DataCenterRequest.queue_position = model.Property(f"{DataCenterRequest} has {Integer:queue_position}")DataCenterRequest.status = model.Property(f"{DataCenterRequest} has {String:status}")DataCenterRequest.is_ai_workload = model.Relationship(f"{DataCenterRequest} is AI workload")DemandPeriod = model.Concept("DemandPeriod", identify_by={"id": String})DemandPeriod.load_zone = model.Property(f"{DemandPeriod} is for {LoadZone:load_zone}")DemandPeriod.period = model.Property(f"{DemandPeriod} has {Integer:period}")DemandPeriod.demand_mw = model.Property(f"{DemandPeriod} has {Float:demand_mw}")DemandPeriod.price_per_mwh = model.Property(f"{DemandPeriod} has {Float:price_per_mwh}")DemandForecast = model.Concept("DemandForecast", identify_by={"id": String})DemandForecast.substation = model.Property(f"{DemandForecast} is for {Substation:substation}")DemandForecast.forecast_period = model.Property(f"{DemandForecast} has {Integer:forecast_period}")DemandForecast.predicted_load_mw = model.Property(f"{DemandForecast} has {Float:predicted_load_mw}")DemandForecast.confidence = model.Property(f"{DemandForecast} has {Float:confidence}")DemandForecast.includes_dc_growth = model.Relationship(f"{DemandForecast} includes data center growth")Generator = model.Concept("Generator", identify_by={"id": String})Generator.name = model.Property(f"{Generator} has {String:name}")Generator.gen_type = model.Property(f"{Generator} has {String:gen_type}")Generator.substation = model.Property(f"{Generator} connects at {Substation:substation}")Generator.capacity_mw = model.Property(f"{Generator} has {Float:capacity_mw}")Generator.marginal_cost = model.Property(f"{Generator} has {Float:marginal_cost}")Generator.is_renewable = model.Relationship(f"{Generator} is renewable")LoadReading = model.Concept("LoadReading", identify_by={"reading_id": Integer})LoadReading.substation = model.Property(f"{LoadReading} measures {Substation:substation}")LoadReading.reading_date = model.Property(f"{LoadReading} has {Date:reading_date}")LoadReading.load_mw = model.Property(f"{LoadReading} has {Float:load_mw}")LoadReading.temperature_f = model.Property(f"{LoadReading} has {Float:temperature_f}")LoadReading.is_peak_season = model.Relationship(f"{LoadReading} is in peak season")RenewableProfile = model.Concept("RenewableProfile", identify_by={"id": String})RenewableProfile.generator = model.Property(f"{RenewableProfile} is for {Generator:generator}")RenewableProfile.period = model.Property(f"{RenewableProfile} has {Integer:period}")RenewableProfile.capacity_factor = model.Property(f"{RenewableProfile} has {Float:capacity_factor}")SubstationUpgrade = model.Concept("SubstationUpgrade", identify_by={"id": String})SubstationUpgrade.substation = model.Property(f"{SubstationUpgrade} expands {Substation:substation}")SubstationUpgrade.capacity_increase_mw = model.Property(f"{SubstationUpgrade} has {Float:capacity_increase_mw}")SubstationUpgrade.cost_million = model.Property(f"{SubstationUpgrade} has {Float:cost_million}")SubstationUpgrade.lead_time_months = model.Property(f"{SubstationUpgrade} has {Integer:lead_time_months}")SubstationUpgrade.enables_low_carbon = model.Relationship(f"{SubstationUpgrade} enables low carbon service")TransmissionLine = model.Concept("TransmissionLine", identify_by={"id": String})TransmissionLine.from_substation = model.Property(f"{TransmissionLine} originates at {Substation:from_substation}")TransmissionLine.to_substation = model.Property(f"{TransmissionLine} terminates at {Substation:to_substation}")TransmissionLine.capacity_mw = model.Property(f"{TransmissionLine} has {Float:capacity_mw}")TransmissionLine.maintenance_priority = model.Property(f"{TransmissionLine} has {String:maintenance_priority}")TransmissionLine.is_active = model.Relationship(f"{TransmissionLine} is active")MaintenanceWindow = model.Concept("MaintenanceWindow", identify_by={"id": String})MaintenanceWindow.asset_type = model.Property(f"{MaintenanceWindow} has {String:asset_type}")MaintenanceWindow.start_period = model.Property(f"{MaintenanceWindow} has {Integer:start_period}")MaintenanceWindow.end_period = model.Property(f"{MaintenanceWindow} has {Integer:end_period}")MaintenanceWindow.generator = model.Property(f"{MaintenanceWindow} affects {Generator:generator}")MaintenanceWindow.transmission_line = model.Property(f"{MaintenanceWindow} affects {TransmissionLine:transmission_line}")MaintenanceWindow.is_planned = model.Relationship(f"{MaintenanceWindow} is planned")"""Snowflake source bindings for the energy-grid starter ontology."""from .schema import (Customer,DataCenterRequest,DemandForecast,DemandPeriod,Generator,LoadReading,LoadZone,MaintenanceWindow,RenewableProfile,Substation,SubstationUpgrade,TransmissionLine,model,)SNOWFLAKE_NAMESPACE = "TUTORIALS.ENERGY_GRID"class Sources:customers = model.Table(f"{SNOWFLAKE_NAMESPACE}.CUSTOMERS")data_center_requests = model.Table(f"{SNOWFLAKE_NAMESPACE}.DATA_CENTER_REQUESTS")demand_forecasts = model.Table(f"{SNOWFLAKE_NAMESPACE}.DEMAND_FORECASTS")demand_periods = model.Table(f"{SNOWFLAKE_NAMESPACE}.DEMAND_PERIODS")generators = model.Table(f"{SNOWFLAKE_NAMESPACE}.GENERATORS")load_history = model.Table(f"{SNOWFLAKE_NAMESPACE}.LOAD_HISTORY")load_zones = model.Table(f"{SNOWFLAKE_NAMESPACE}.LOAD_ZONES")maintenance_windows = model.Table(f"{SNOWFLAKE_NAMESPACE}.MAINTENANCE_WINDOWS")renewable_profiles = model.Table(f"{SNOWFLAKE_NAMESPACE}.RENEWABLE_PROFILES")substations = model.Table(f"{SNOWFLAKE_NAMESPACE}.SUBSTATIONS")substation_upgrades = model.Table(f"{SNOWFLAKE_NAMESPACE}.SUBSTATION_UPGRADES")transmission_lines = model.Table(f"{SNOWFLAKE_NAMESPACE}.TRANSMISSION_LINES")src = Sourcesmodel.define(LoadZone.new(id=src.load_zones.ID,name=src.load_zones.NAME,peak_demand_mw=src.load_zones.PEAK_DEMAND_MW,base_demand_mw=src.load_zones.BASE_DEMAND_MW,))model.define(Customer.new(id=src.customers.ID,name=src.customers.NAME,load_zone=LoadZone.filter_by(id=src.customers.LOAD_ZONE_ID),contracted_demand_mw=src.customers.CONTRACTED_DEMAND_MW,flexibility_pct=src.customers.FLEXIBILITY_PCT,curtailment_cost_per_mwh=src.customers.CURTAILMENT_COST_PER_MWH,))model.define(Substation.new(id=src.substations.ID,name=src.substations.NAME,voltage_kv=src.substations.VOLTAGE_KV,max_capacity_mw=src.substations.MAX_CAPACITY_MW,current_load_mw=src.substations.CURRENT_LOAD_MW,))model.define(DataCenterRequest.new(id=src.data_center_requests.ID,name=src.data_center_requests.NAME,hyperscaler=src.data_center_requests.HYPERSCALER,substation=Substation.filter_by(id=src.data_center_requests.SUBSTATION_ID),requested_mw=src.data_center_requests.REQUESTED_MW,annual_revenue_per_mw=src.data_center_requests.ANNUAL_REVENUE_PER_MW,pue=src.data_center_requests.PUE,low_carbon_requirement_pct=src.data_center_requests.LOW_CARBON_REQUIREMENT_PCT,queue_position=src.data_center_requests.QUEUE_POSITION,status=src.data_center_requests.STATUS,))model.define(DataCenterRequest.is_ai_workload(DataCenterRequest.filter_by(id=src.data_center_requests.ID))).where(src.data_center_requests.IS_AI_WORKLOAD)model.define(DemandPeriod.new(id=src.demand_periods.ID,load_zone=LoadZone.filter_by(id=src.demand_periods.LOAD_ZONE_ID),period=src.demand_periods.PERIOD,demand_mw=src.demand_periods.DEMAND_MW,price_per_mwh=src.demand_periods.PRICE_PER_MWH,))model.define(DemandForecast.new(id=src.demand_forecasts.ID,substation=Substation.filter_by(id=src.demand_forecasts.SUBSTATION_ID),forecast_period=src.demand_forecasts.FORECAST_PERIOD,predicted_load_mw=src.demand_forecasts.PREDICTED_LOAD_MW,confidence=src.demand_forecasts.CONFIDENCE,))model.define(DemandForecast.includes_dc_growth(DemandForecast.filter_by(id=src.demand_forecasts.ID))).where(src.demand_forecasts.INCLUDES_DC_GROWTH)model.define(Generator.new(id=src.generators.ID,name=src.generators.NAME,gen_type=src.generators.GEN_TYPE,substation=Substation.filter_by(id=src.generators.SUBSTATION_ID),capacity_mw=src.generators.CAPACITY_MW,marginal_cost=src.generators.MARGINAL_COST,))model.define(Generator.is_renewable(Generator.filter_by(id=src.generators.ID))).where(src.generators.IS_RENEWABLE)model.define(LoadReading.new(reading_id=src.load_history.READING_ID,substation=Substation.filter_by(id=src.load_history.SUBSTATION_ID),reading_date=src.load_history.READING_DATE,load_mw=src.load_history.LOAD_MW,temperature_f=src.load_history.TEMPERATURE_F,))model.define(LoadReading.is_peak_season(LoadReading.filter_by(reading_id=src.load_history.READING_ID))).where(src.load_history.IS_PEAK_SEASON)model.define(RenewableProfile.new(id=src.renewable_profiles.ID,generator=Generator.filter_by(id=src.renewable_profiles.GENERATOR_ID),period=src.renewable_profiles.PERIOD,capacity_factor=src.renewable_profiles.CAPACITY_FACTOR,))model.define(SubstationUpgrade.new(id=src.substation_upgrades.ID,substation=Substation.filter_by(id=src.substation_upgrades.SUBSTATION_ID),capacity_increase_mw=src.substation_upgrades.CAPACITY_INCREASE_MW,cost_million=src.substation_upgrades.COST_MILLION,lead_time_months=src.substation_upgrades.LEAD_TIME_MONTHS,))model.define(SubstationUpgrade.enables_low_carbon(SubstationUpgrade.filter_by(id=src.substation_upgrades.ID))).where(src.substation_upgrades.ENABLES_LOW_CARBON)model.define(TransmissionLine.new(id=src.transmission_lines.ID,from_substation=Substation.filter_by(id=src.transmission_lines.FROM_SUBSTATION_ID),to_substation=Substation.filter_by(id=src.transmission_lines.TO_SUBSTATION_ID),capacity_mw=src.transmission_lines.CAPACITY_MW,maintenance_priority=src.transmission_lines.MAINTENANCE_PRIORITY,))model.define(TransmissionLine.is_active(TransmissionLine.filter_by(id=src.transmission_lines.ID))).where(src.transmission_lines.IS_ACTIVE)model.define(MaintenanceWindow.new(id=src.maintenance_windows.ID,asset_type=src.maintenance_windows.ASSET_TYPE,start_period=src.maintenance_windows.START_PERIOD,end_period=src.maintenance_windows.END_PERIOD,))model.define(MaintenanceWindow.generator(Generator.filter_by(id=src.maintenance_windows.ASSET_ID))).where(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID),src.maintenance_windows.ASSET_TYPE == "generator",)model.define(MaintenanceWindow.transmission_line(TransmissionLine.filter_by(id=src.maintenance_windows.ASSET_ID))).where(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID),src.maintenance_windows.ASSET_TYPE == "transmission_line",)model.define(MaintenanceWindow.is_planned(MaintenanceWindow.filter_by(id=src.maintenance_windows.ID))).where(src.maintenance_windows.IS_PLANNED)"""Queries against the energy-grid semantic model."""from relationalai.semantics.std import aggregates as aggsfrom model import DataCenterRequest, modeldef count_data_center_requests() -> int:"""Return the number of data center requests loaded in the model."""dataframe = model.select(aggs.count(DataCenterRequest).alias("data_center_request_count")).to_df()if dataframe.empty:return 0return int(dataframe.iloc[0]["data_center_request_count"])if __name__ == "__main__":print(count_data_center_requests())
Answer questions with /rai-querying
Section titled “Answer questions with /rai-querying”Use the /rai-querying skill to have your agent answer basic questions about the semantic model.
Anything you could ask in a SQL query can be expressed in natural language, and your agent will translate it into a PyRel query and execute it against the model.
-
Invoke the
/rai-queryingskill.Copy and paste the following prompt into your coding agent to have it answer a basic question about the semantic model:
/rai-querying What substation is currently most constrained in terms of load capacity? What percentage of its maximum capacity is currently being used? What customers would be impacted if the substation went offline? -
Review the agent’s response.
The agent will generate a PyRel query to answer your question and execute it against the semantic model. It may or may not edit
queries.pyas part of the process.Click to expand the sample response from
/rai-queryingThe most constrained substation in the current model is queries.py:
SUB-001, with1250.0 MWof current load against1800.0 MWmax capacity. That puts it at69.44%utilization. I added the focused report in queries.py and validated it by runningpython queries.py.The customer-impact part is not answerable from the current ontology. Customers are linked to load zones, but there is no modeled path from load zones to substations, so the report now returns that limitation explicitly in queries.py.
- If you want the impacted-customer query next, add a source-backed load-zone-to-substation mapping and I can wire it into the ontology.
- If that mapping already exists in Snowflake but is just not modeled yet, point me to the table or columns and I’ll implement the full query.
Use /rai-discovery to unlock advanced reasoning capabilities
Section titled “Use /rai-discovery to unlock advanced reasoning capabilities”RelationalAI’s advanced reasoning capabilities allow you to model complex ideas and find answers to challenging business questions.
Follow these steps to have your agent explain how to use advanced reasoning to answer a complex business question.
-
Explore an open-ended question.
Use the
/rai-discoveryskill with an open-ended question to have your agent explain how to use advanced reasoning to answer it:/rai-discovery I need to determine which datacenter requests to approve and which upgrades to fund at what budget level.Your agent will:
- Investigate how the semantic model can be used to answer this question
- Determine what parts of the question are feasible and what parts are not
- Explain how RAI’s reasoning capabilities can be used to answer the question.
Click to expand the sample response from
Your question is squarely prescriptive — a coupled binary-selection problem (approve/rai-discoveryDataCenterRequest× fundSubstationUpgrade) with budget as a scenario parameter. The ontology supports it directly: READY.Primary suggestion — approve & fund
Statement. Choose which data center requests to approve and which substation upgrades to fund so that approved load fits within (upgraded) substation capacity, total upgrade spend stays under the budget, and total annual revenue is maximized — repeated across budget levels to see the value curve.
Field Value Reasoner prescriptiveFeasibility READY Decision scope DataCenterRequest.approved(binary),SubstationUpgrade.funded(binary)Forcing requirement Every approved request’s requested_mwmust be served by its targetSubstation’s available headroom (=max_capacity_mw − current_load_mw + Σ funded upgrades' capacity_increase_mw at that substation)Objective property maximize Σ DataCenterRequest.requested_mw × annual_revenue_per_mwover approved requestsScenario parameter total upgrade budget ( Σ funded SubstationUpgrade.cost_million ≤ B), swept across levels (e.g.100M / 500M / unbounded) Competing objectives Optional second pass: revenue vs. low-carbon shortfall (sum of low_carbon_requirement_pct × requested_mwshortfall vs. renewable supply at substation, derivable fromGenerator.is_renewable+Generator.substation)Why this maps cleanly. Each
DataCenterRequest.substationand eachSubstationUpgrade.substationalready resolve to the sameSubstation, so the capacity coupling between the two decisions is a straightforward grouped sum. Budget is a single global constraint overSubstationUpgrade.cost_million. Revenue is a linear function over approved requests.Adjacent suggestions worth keeping on the table
-
Queue-fairness variant (rules + prescriptive). Use
DataCenterRequest.queue_positionandstatusto add a fairness rule — e.g. don’t approve a request if an earlier-queued request at the same substation is denied. Sharper realism, slightly tighter feasible set. Status: READY. -
Low-carbon-aware variant (prescriptive, multi-objective). Add a constraint or soft penalty: for each approved request, the renewable share of its substation’s supply (renewable generator capacity at that substation ÷ total generator capacity at that substation) must meet
low_carbon_requirement_pct. Funded upgrades thatenables_low_carbonraise the supply side. Reveals the revenue ↔ low-carbon trade-off. Status: READY. -
Forecast-driven upgrade selection (predictive → prescriptive chain). Use
DemandForecast.predicted_load_mwper substation/period to pick upgrades that relieve substations projected to be saturated regardless of DC approvals — gives a “no-regret” upgrade subset before the joint optimization. Status: READY (forecasts already in model).
Recommended next step
Proceed with the primary suggestion. To run it I’ll need to load
rai-prescriptive-problem-formulation→rai-prescriptive-solver-management→rai-prescriptive-results-interpretation(plusrai-querying/rai-pyrel-coding).Before I do — two quick choices:
- Budget levels to sweep? (e.g.
[50, 100, 200, 500]$M, plus unbounded) — or a single budget you have in mind. - Low-carbon: hard constraint, soft penalty, or skip for v1? Skipping keeps v1 a clean single-objective MILP; adding it turns it into a Pareto / scenario question.
-
Provide more details to get a more actionable answer.
Use a more detailed prompt to get a plan that is:
- More likely to be complete and aligned with your business goals.
- Auditable and explainable. An expert can review the plan and modify it as needed before you run it, which is important for high-stakes business decisions.
For example, the following prompt asks the agent to explore solutions that involve chains of reasoning, recommend the best solution, and break it down into actionable steps complete with copy-pastable prompts that invoke the appropriate skills for each step:
/rai-discovery I need to determine which datacenter requests to approve and which upgrades to fund at what budget level.Give me the best option for answering this question that makes use of all three reasoning types in a reasoning chain: rules-based, graph, and prescriptive reasoning.Break the solution into three copy-pastable prompts that make use of the appropriate skills for each step in the reasoning chain:- Use the smallest number of skills required for each prompt.- Keep each step as simple as possible while preserving real business value.- Keep prompts fully self self-contained.- Include step-by-step instructions.- Explicitly state which files to add/edit. All model additions should be made to files in the `model/` directory. Verification and summary reports should be done in `queries.py` or in a new file in the project root with a descriptive name.- Instruct the agent to summarize what it did and explain results in plain English.Use the following template for your output. Write explanation, benefits, and tradeoffs in clear, concise, plain English.--- TEMPLATE ---<Describe the option in a few sentences. Explain how each step reinforces the next.>**Benefits:**- Benefit 1- Benefit 2- ...**Trade-offs:**- Trade-off 1- Trade-off 2- ...**Step 1:**```text/skill1 [+ /skill2 ...] <PROMPT>```**Step 2:**```text[/skill1 [+ /skill2 ...]] <PROMPT>```**Step 3:**```text[/skill1 [+ /skill2 ...]] <PROMPT>```- Asks the agent to combine rules, graph, and prescriptive reasoning in a single chain instead of picking just one.
- Produces one prompt per step so you can work through the solution incrementally.
- Keeps prompts self-contained and detailed enough to be copy-pasted into your agent without additional context.
- Generates detailed, step-by-step prompts that are auditable and explainable, and help keep agents on track.
Click to expand the sample response from
This chain answers your question by first filtering for what’s eligible, then identifying what matters most structurally in the grid, then solving which to approve and fund under a budget. Rules trim the candidate set and pre-compute available headroom so the optimizer has clean inputs. Graph centrality assigns each substation a structural-importance score so upgrades at network hubs (which unlock capacity for many downstream nodes) are favored. The prescriptive solver picks the binary approve/fund decisions that maximize centrality-weighted revenue under a budget, and re-runs at several budget levels so you can see the marginal return on each extra dollar./rai-discoveryBenefits:
- Cleanly separates eligibility (rules), network value (graph), and decision-making (optimization) — each stage is independently inspectable.
- Centrality weighting captures the intuition that an upgrade at a hub substation has more value than one at a leaf, even when MW and cost look identical.
- The budget-sweep scenario directly answers “at what budget level?” by showing approvals and revenue at each budget point.
- All three stages enrich the same ontology, so downstream queries can introspect intermediate results.
Trade-offs:
- Centrality is a proxy for network value, not a load-flow simulation — true grid impact would require power-flow modeling.
- The optimizer assumes a single planning horizon (no upgrade lead-time scheduling);
lead_time_monthsis reported but not enforced. - Eligibility rules are heuristic thresholds; tightening or loosening them shifts the candidate pool and can change the answer.
Step 1:
/rai-rules-authoringProject context:- RAI tutorial project at the workspace root with model files in `model/`.- `model/schema.py` already defines: Substation, LoadZone, Generator, TransmissionLine, DemandPeriod, DemandForecast, SubstationUpgrade, MaintenanceWindow, Customer, DataCenterRequest.- `model/sources.py` binds them to Snowflake tables in TUTORIALS.ENERGY_GRID.- `model/__init__.py` already does `from .rules import *`, so create a `model/rules.py` file.Task: Author derived-property rules that prepare clean inputs for a later optimization stage. Add the following to a new file `model/rules.py`:1. On Substation, derive:- `available_headroom_mw` = max_capacity_mw - current_load_mw (clamped at 0 minimum).- `is_constrained` flag: True when current_load_mw / max_capacity_mw >= 0.85.2. On DataCenterRequest, derive:- `is_eligible` flag: True when status == "pending" AND requested_mw > 0 AND annual_revenue_per_mw > 0. Reject everything else from consideration.- `expected_annual_revenue` = requested_mw * annual_revenue_per_mw.- `requires_low_carbon` flag: True when low_carbon_requirement_pct >= 50.0.3. On SubstationUpgrade, derive:- `mw_per_million` = capacity_increase_mw / cost_million (efficiency score; guard against zero cost).- `is_high_value` flag: True when mw_per_million is in the top quartile across all upgrades OR enables_low_carbon is True.Then, in `queries.py`, add and run a `rules_summary()` function that prints:- Count of eligible DataCenterRequests and their total requested_mw and total expected_annual_revenue.- Count of constrained substations and their average utilization.- Count of high-value upgrades and their total cost_million.- A small sample (top 5 by expected_annual_revenue) of eligible requests with their substation_id and available_headroom_mw at that substation.After running, summarize in plain English: how many requests passed eligibility, which substations are most squeezed, and which upgrades look most cost-efficient. Do not modify schema.py or sources.py.Step 2:
/rai-graph-analysisProject context:- RAI tutorial project at workspace root. `model/schema.py` defines Substation and TransmissionLine; TransmissionLine has from_substation, to_substation, capacity_mw, and an is_active flag.- Rules from Step 1 have been added in `model/rules.py` (Substation.available_headroom_mw / is_constrained, DataCenterRequest eligibility flags, SubstationUpgrade.is_high_value).Task: Build the grid transmission graph and compute per-substation structural importance, then write the score back onto the ontology so Step 3 can use it.1. Create a new file `model/graph.py` that:- Constructs an undirected, weighted graph: nodes = Substation, edges = active TransmissionLine pairs (use `is_active`), edge weight = capacity_mw.- Runs eigenvector centrality on the graph.- Defines a new property `Substation.centrality_score` (Float) and binds the centrality result to it via `model.define()` so it is queryable like any other property.- Also defines `Substation.network_weight` = 1.0 + centrality_score (so leaf nodes still get a non-zero multiplier in optimization).2. In `queries.py`, add and run a `centrality_report()` function that prints the top 10 substations by centrality_score along with their available_headroom_mw and whether they are targeted by any pending DataCenterRequest.After running, summarize in plain English: which substations are the structural hubs of the network, whether the hubs are also the constrained ones, and whether high-revenue datacenter requests are clustering at hubs or at peripheral substations. Do not modify schema.py, sources.py, or rules.py.Step 3:
/rai-prescriptive-problem-formulation + /rai-prescriptive-solver-management + /rai-prescriptive-results-interpretationProject context:- RAI tutorial project at workspace root. `model/schema.py` + `model/sources.py` define the base ontology.- `model/rules.py` provides DataCenterRequest.is_eligible / .expected_annual_revenue, Substation.available_headroom_mw, SubstationUpgrade.is_high_value, etc.- `model/graph.py` provides Substation.centrality_score and Substation.network_weight.Task: Formulate, solve, and interpret a portfolio decision that picks which eligible DataCenterRequests to approve and which SubstationUpgrades to fund under a budget.1. Create `model/optimization.py` with the prescriptive formulation:- Binary decision: `DataCenterRequest.approve` on each eligible request (filter by is_eligible).- Binary decision: `SubstationUpgrade.fund` on every upgrade.- Capacity constraint per Substation: sum(approve * requested_mw of requests targeting that substation) <= available_headroom_mw + sum(fund * capacity_increase_mw of upgrades at that substation).- Budget constraint (global): sum(fund * cost_million) <= BUDGET_MILLION (parameter — see below).- Objective: maximize sum over approved requests of (expected_annual_revenue * network_weight of target substation). Centrality-weighting biases approvals toward hubs.- Expose BUDGET_MILLION as a module-level parameter that can be set before solving.2. Create a new project-root file `portfolio_decision.py` that:- Solves the problem at three budget levels: $50M, $150M, $300M.- For each budget, extracts: list of approved requests (id, name, requested_mw, expected_annual_revenue, target substation, centrality), list of funded upgrades (id, substation, capacity_increase_mw, cost_million), total expected annual revenue, total upgrade spend, number of constrained substations relieved.- Prints a side-by-side comparison table across the three budgets.3. After running, write a plain-English executive summary covering:- Which datacenter requests should be approved at each budget level and why.- Which upgrades should be funded at each budget level and why (cost-efficiency vs. network position).- The marginal return: how much extra revenue each additional $100M of budget unlocks.- The recommended budget level and the reasoning behind it.Only add files as specified; do not modify schema.py, sources.py, rules.py, or graph.py.
Encode business rules with /rai-rules-authoring
Section titled “Encode business rules with /rai-rules-authoring”The /rai-rules-authoring skill enables your agent to add complex rules-based reasoning to your semantic model.
Rules-based reasoning uses if-then logic to express business rules, such as “if a substation reaches 85% of its maximum capacity, mark it as constrained.”
Follow these steps to have your agent add rules to your model.
-
Invoke the
/rai-rules-authoringskill.Use a detailed prompt that explains what you want to accomplish and any important decision points or constraints.
For example, the following prompt is copied from the sample output in the previous section and pre-screens datacenter requests to help identify any that are clearly infeasible or require upgrades to be approved:
/rai-rules-authoringProject context:- RAI tutorial project at the workspace root with model files in `model/`.- `model/schema.py` already defines: Substation, LoadZone, Generator, TransmissionLine, DemandPeriod, DemandForecast, SubstationUpgrade, MaintenanceWindow, Customer, DataCenterRequest.- `model/sources.py` binds them to Snowflake tables in TUTORIALS.ENERGY_GRID.- `model/__init__.py` already does `from .rules import *`, so create a `model/rules.py` file.Task: Author derived-property rules that prepare clean inputs for a later optimization stage. Add the following to a new file `model/rules.py`:1. On Substation, derive:- `available_headroom_mw` = max_capacity_mw - current_load_mw (clamped at 0 minimum).- `is_constrained` flag: True when current_load_mw / max_capacity_mw >= 0.85.2. On DataCenterRequest, derive:- `is_eligible` flag: True when status == "pending" AND requested_mw > 0 AND annual_revenue_per_mw > 0. Reject everything else from consideration.- `expected_annual_revenue` = requested_mw * annual_revenue_per_mw.- `requires_low_carbon` flag: True when low_carbon_requirement_pct >= 50.0.3. On SubstationUpgrade, derive:- `mw_per_million` = capacity_increase_mw / cost_million (efficiency score; guard against zero cost).- `is_high_value` flag: True when mw_per_million is in the top quartile across all upgrades OR enables_low_carbon is True.Then, in `queries.py`, add and run a `rules_summary()` function that prints:- Count of eligible DataCenterRequests and their total requested_mw and total expected_annual_revenue.- Count of constrained substations and their average utilization.- Count of high-value upgrades and their total cost_million.- A small sample (top 5 by expected_annual_revenue) of eligible requests with their substation_id and available_headroom_mw at that substation.After running, summarize in plain English: how many requests passed eligibility, which substations are most squeezed, and which upgrades look most cost-efficient. Do not modify schema.py or sources.py.- Creates a new
model/rules.pyfile for the derived properties used in later steps. - Calculates substation headroom and flags substations that are already constrained.
- Filters datacenter requests to eligible requests and calculates their expected annual revenue.
- Scores upgrades by capacity per dollar and flags high-value upgrades.
- Adds a
rules_summary()query that explains the eligible requests, constrained substations, and cost-efficient upgrades in plain English.
- Creates a new
-
Review the agent’s response.
Your agent will generate PyRel code to implement the rules and execute queries against the model. You should see:
- A new
model/rules.pyfile that implements the rules described in the prompt. - An updated
queries.pyfile that includes therules_summary()function. - An in chat summary describing what was implemented and the results of running the
rules_summary()query.
Click to expand the sample response from
/rai-rules-authoringImplemented the derived rules in
model/rules.pyand addedrules_summary()inqueries.py. I leftschema.pyandsources.pyuntouched.Ran
source .venv/bin/activate && python queries.pysuccessfully. Results:- 10 data center requests passed eligibility, totaling
2930.0 MWand$528,200,000expected annual revenue. - 0 substations crossed the
>= 85%constrained threshold. The most squeezed by utilization areSUB-001Houston Ship Channel at69.44%,SUB-004Austin Energy at68.89%, andSUB-002Dallas-Fort Worth at68.75%. - 7 upgrades are high-value by top-quartile efficiency or low-carbon enablement, totaling
$460.0M. - Most cost-efficient upgrades are
UPG-008andUPG-009, tied at5.0 MW per $M, followed byUPG-001,UPG-005, andUPG-007.
The top eligible request sample is led by
DC-004xAI Colossus Texas at$105Mexpected annual revenue, targetingSUB-002with500 MWavailable headroom.The agent should create a new file
model/rules.pywith the content similar to the following:"""Derived rules for optimization-ready electric grid inputs."""from relationalai.semantics import Floatfrom relationalai.semantics.std import aggregates as aggsfrom relationalai.semantics.std import mathfrom relationalai.semantics.std.aggregates import desc, rankfrom .schema import DataCenterRequest, Substation, SubstationUpgrade, modelSubstation.available_headroom_mw = model.Property(f"{Substation} has {Float:available_headroom_mw}")Substation.is_constrained = model.Relationship(f"{Substation} is constrained")model.define(Substation.available_headroom_mw(math.maximum(Substation.max_capacity_mw - Substation.current_load_mw, 0.0)))model.where(Substation.max_capacity_mw > 0.0,Substation.current_load_mw / Substation.max_capacity_mw >= 0.85,).define(Substation.is_constrained())DataCenterRequest.is_eligible = model.Relationship(f"{DataCenterRequest} is eligible")DataCenterRequest.expected_annual_revenue = model.Property(f"{DataCenterRequest} has {Float:expected_annual_revenue}")DataCenterRequest.requires_low_carbon = model.Relationship(f"{DataCenterRequest} requires low carbon")model.where(DataCenterRequest.status == "pending",DataCenterRequest.requested_mw > 0.0,DataCenterRequest.annual_revenue_per_mw > 0.0,).define(DataCenterRequest.is_eligible())model.define(DataCenterRequest.expected_annual_revenue(DataCenterRequest.requested_mw * DataCenterRequest.annual_revenue_per_mw))model.where(DataCenterRequest.low_carbon_requirement_pct >= 50.0).define(DataCenterRequest.requires_low_carbon())SubstationUpgrade.mw_per_million = model.Property(f"{SubstationUpgrade} has {Float:mw_per_million}")SubstationUpgrade.is_high_value = model.Relationship(f"{SubstationUpgrade} is high value")model.where(SubstationUpgrade.cost_million > 0.0).define(SubstationUpgrade.mw_per_million(SubstationUpgrade.capacity_increase_mw / SubstationUpgrade.cost_million))valid_upgrade_count = aggs.count(SubstationUpgrade).where(SubstationUpgrade.mw_per_million)top_quartile_cutoff = math.ceil(valid_upgrade_count * 0.25)model.where(SubstationUpgrade.mw_per_million,rank(desc(SubstationUpgrade.mw_per_million)) <= top_quartile_cutoff,).define(SubstationUpgrade.is_high_value())model.where(SubstationUpgrade.enables_low_carbon()).define(SubstationUpgrade.is_high_value())The agent should update the
queries.pyfile to include something similar to the following:"""Queries against the electric grid capacity planning model."""import pandas as pdfrom relationalai.semantics.std import aggregates as aggsfrom model import DataCenterRequest, Substation, SubstationUpgrade, modeldef count_data_center_requests():"""Count the data center requests loaded into the model."""return model.select(aggs.count(DataCenterRequest).alias("data_center_request_count")).to_df()def most_constrained_substation():"""Return the substation with the highest current utilization."""utilization_df = model.where(Substation.max_capacity_mw > 0.0).select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),Substation.current_load_mw.alias("current_load_mw"),Substation.max_capacity_mw.alias("max_capacity_mw"),((Substation.current_load_mw * 100.0) / Substation.max_capacity_mw).alias("utilization_pct"),).to_df()return utilization_df.sort_values("utilization_pct", ascending=False, kind="stable").head(1).reset_index(drop=True)def constrained_substation_report():"""Summarize the most constrained substation and customer impact limits."""constrained_df = most_constrained_substation()if constrained_df.empty:return {"summary": "No substation capacity data is available.","impacted_customers": None,}row = constrained_df.iloc[0]return {"substation_id": row["substation_id"],"substation_name": row["substation_name"],"current_load_mw": row["current_load_mw"],"max_capacity_mw": row["max_capacity_mw"],"utilization_pct": row["utilization_pct"],"impacted_customers": None,"impact_note": ("The current ontology does not model a path from Customer to ""Substation. Customers are linked only to LoadZone, so impacted ""customers cannot be identified without adding a load-zone-to-""substation mapping."),}def rules_summary():"""Print a summary of derived rules used by later optimization stages."""eligible_df = model.where(DataCenterRequest.is_eligible()).select(aggs.count(DataCenterRequest).alias("eligible_request_count"),aggs.sum(DataCenterRequest.requested_mw).alias("total_requested_mw"),aggs.sum(DataCenterRequest.expected_annual_revenue).alias("total_expected_annual_revenue"),).to_df()constrained_df = model.where(Substation.is_constrained()).select(aggs.count(Substation).alias("constrained_substation_count"),aggs.avg(Substation.current_load_mw / Substation.max_capacity_mw).alias("average_utilization"),).to_df()if constrained_df.empty:constrained_df = pd.DataFrame([{"constrained_substation_count": 0, "average_utilization": None}])high_value_upgrade_df = model.where(SubstationUpgrade.is_high_value()).select(aggs.count(SubstationUpgrade).alias("high_value_upgrade_count"),aggs.sum(SubstationUpgrade.cost_million).alias("total_cost_million"),).to_df()eligible_sample_df = model.where(DataCenterRequest.is_eligible(),DataCenterRequest.substation(Substation),).select(DataCenterRequest.id.alias("request_id"),DataCenterRequest.name.alias("request_name"),DataCenterRequest.requested_mw.alias("requested_mw"),DataCenterRequest.expected_annual_revenue.alias("expected_annual_revenue"),Substation.id.alias("substation_id"),Substation.available_headroom_mw.alias("available_headroom_mw"),).to_df()constrained_detail_df = model.where(Substation.max_capacity_mw > 0.0).select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),(Substation.current_load_mw / Substation.max_capacity_mw).alias("utilization"),).to_df()upgrade_detail_df = model.where(SubstationUpgrade.mw_per_million).select(SubstationUpgrade.id.alias("upgrade_id"),SubstationUpgrade.capacity_increase_mw.alias("capacity_increase_mw"),SubstationUpgrade.cost_million.alias("cost_million"),SubstationUpgrade.mw_per_million.alias("mw_per_million"),).to_df()eligible_sample_df = eligible_sample_df.sort_values("expected_annual_revenue", ascending=False, kind="stable").head(5).reset_index(drop=True)constrained_detail_df = constrained_detail_df.sort_values("utilization", ascending=False, kind="stable").head(5).reset_index(drop=True)upgrade_detail_df = upgrade_detail_df.sort_values("mw_per_million", ascending=False, kind="stable").head(5).reset_index(drop=True)print("Eligible data center requests")print(eligible_df.to_string(index=False))print("\nConstrained substations")print(constrained_df.to_string(index=False))print("\nHigh-value upgrades")print(high_value_upgrade_df.to_string(index=False))print("\nTop eligible request sample")print(eligible_sample_df.to_string(index=False))print("\nMost utilized substations")print(constrained_detail_df.to_string(index=False))print("\nMost cost-efficient upgrades")print(upgrade_detail_df.to_string(index=False))return {"eligible_requests": eligible_df,"constrained_substations": constrained_df,"high_value_upgrades": high_value_upgrade_df,"eligible_sample": eligible_sample_df,"most_utilized_substations": constrained_detail_df,"most_cost_efficient_upgrades": upgrade_detail_df,}if __name__ == "__main__":rules_summary() - A new
Reveal hidden structure with /rai-graph-analysis
Section titled “Reveal hidden structure with /rai-graph-analysis”The /rai-graph-analysis skill enables your agent to use graph reasoning to better understand the relationships between entities in your model.
For instance, you can use graph reasoning to identify critical nodes in the energy grid network that would have a disproportionate impact on the system if they were to fail.
Follow these steps to have your agent analyze the graph structure of your model.
-
Invoke the
/rai-graph-analysisskill.Use a detailed prompt that explains which graph to build, which graph metrics to calculate, and how the results should be saved back to the model.
For example, the following prompt is copied from the sample output in the
/rai-discoverysection and scores substations by their structural importance in the transmission network:/rai-graph-analysisProject context:- RAI tutorial project at workspace root. `model/schema.py` defines Substation and TransmissionLine; TransmissionLine has from_substation, to_substation, capacity_mw, and an is_active flag.- Rules from Step 1 have been added in `model/rules.py` (Substation.available_headroom_mw / is_constrained, DataCenterRequest eligibility flags, SubstationUpgrade.is_high_value).Task: Build the grid transmission graph and compute per-substation structural importance, then write the score back onto the ontology so Step 3 can use it.1. Create a new file `model/graph.py` that:- Constructs an undirected, weighted graph: nodes = Substation, edges = active TransmissionLine pairs (use `is_active`), edge weight = capacity_mw.- Runs eigenvector centrality on the graph.- Defines a new property `Substation.centrality_score` (Float) and binds the centrality result to it via `model.define()` so it is queryable like any other property.- Also defines `Substation.network_weight` = 1.0 + centrality_score (so leaf nodes still get a non-zero multiplier in optimization).2. In `queries.py`, add and run a `centrality_report()` function that prints the top 10 substations by centrality_score along with their available_headroom_mw and whether they are targeted by any pending DataCenterRequest.After running, summarize in plain English: which substations are the structural hubs of the network, whether the hubs are also the constrained ones, and whether high-revenue datacenter requests are clustering at hubs or at peripheral substations. Do not modify schema.py, sources.py, or rules.py.- Builds a graph from active transmission lines between substations.
- Scores substations by how important they are to the grid structure.
- Adds graph results to the model, including
Substation.centrality_score,Substation.component_id, and strategic scores for requests and upgrades. - Generates queries that identify critical substations and explain how those scores affect upgrade decisions.
-
Review the agent’s response.
Your agent should:
- Create a new
model/graph.pyfile that builds a graph of the energy grid transmission network and computes centrality scores for each substation. - Update
queries.pyto include acentrality_report()function that summarizes the centrality scores and their implications for pending datacenter requests. - Provide a chat summary explaining the changes made, validation steps, and a plain-English summary of the results.
Click to expand the sample response from
/rai-graph-analysisChanges made:
- Added graph.py with an undirected weighted
GraphoverSubstation, using activeTransmissionLineedges weighted bycapacity_mw. - Added
Substation.centrality_scoreandSubstation.network_weight = 1.0 + centrality_score. - Imported the graph extension from
__init__.pyso the graph-derived properties load with the model. - Added
centrality_report()in queries.py, and made the script run that report directly.
Validation:
python queries.pycompleted successfully.- I did not modify schema.py, sources.py, or rules.py.
Plain-English Summary
The structural hubs are Dallas-Fort Worth, Houston Ship Channel, and San Antonio Metro, followed by Waco Gateway and Austin Energy. Dallas-Fort Worth is the strongest hub with centrality
0.5245, then Houston at0.4776, and San Antonio at0.3854.Those hubs are not currently the constrained substations in the report. The top centrality substations all showed
is_constrained = False, with meaningful available headroom: Dallas-Fort Worth has500 MW, Houston has550 MW, and San Antonio has420 MW.High-revenue data center demand is clustering mostly at the hubs. Dallas-Fort Worth alone has
3pending requests totaling1100 MWand$213Mexpected annual revenue. Houston adds$112.5M, and San Antonio adds$91.35M. The main peripheral pending requests are smaller: Midland-Permian at$28.8Mand Corpus Christi Coast at$21.75M.The agent should create a new file
model/graph.pywith the content similar to the following:"""Graph-derived properties for electric grid transmission structure."""from relationalai.semantics import Floatfrom relationalai.semantics.reasoners.graph import Graphfrom .schema import Substation, TransmissionLine, modeltransmission_graph = Graph(model,directed=False,weighted=True,node_concept=Substation,aggregator="sum",)line = TransmissionLine.ref()source_substation, target_substation = Substation.ref(), Substation.ref()model.where(line.is_active(),line.from_substation(source_substation),line.to_substation(target_substation),).define(transmission_graph.Edge.new(src=source_substation,dst=target_substation,weight=line.capacity_mw,))transmission_graph.Node.graph_centrality_score = (transmission_graph.eigenvector_centrality())Substation.centrality_score = model.Property(f"{Substation} has {Float:centrality_score}")Substation.network_weight = model.Property(f"{Substation} has {Float:network_weight}")model.where(transmission_graph.Node == Substation).define(Substation.centrality_score(transmission_graph.Node.graph_centrality_score))model.define(Substation.network_weight(1.0 + Substation.centrality_score))The agent should update the
queries.pyfile to include acentrality_report()function similar to the following:"""Queries against the electric grid capacity planning model."""import pandas as pdfrom relationalai.semantics.std import aggregates as aggsfrom model import DataCenterRequest, Substation, SubstationUpgrade, modeldef centrality_report():"""Print substations ranked by transmission-network centrality."""centrality_df = model.select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),Substation.centrality_score.alias("centrality_score"),Substation.available_headroom_mw.alias("available_headroom_mw"),Substation.current_load_mw.alias("current_load_mw"),Substation.max_capacity_mw.alias("max_capacity_mw"),).to_df()pending_request_df = model.where(DataCenterRequest.status == "pending",DataCenterRequest.substation(Substation),).select(DataCenterRequest.id.alias("request_id"),DataCenterRequest.name.alias("request_name"),DataCenterRequest.requested_mw.alias("requested_mw"),DataCenterRequest.expected_annual_revenue.alias("expected_annual_revenue"),Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),Substation.centrality_score.alias("centrality_score"),).to_df()if pending_request_df.empty:pending_by_substation_df = pd.DataFrame(columns=["substation_id","pending_request_count","pending_requested_mw","pending_expected_annual_revenue",])else:pending_by_substation_df = (pending_request_df.groupby("substation_id", as_index=False).agg(pending_request_count=("request_id", "count"),pending_requested_mw=("requested_mw", "sum"),pending_expected_annual_revenue=("expected_annual_revenue","sum",),))report_df = centrality_df.merge(pending_by_substation_df,on="substation_id",how="left",)fill_values = {"pending_request_count": 0,"pending_requested_mw": 0.0,"pending_expected_annual_revenue": 0.0,}report_df = report_df.fillna(fill_values)report_df["pending_request_count"] = report_df["pending_request_count"].astype(int)report_df["targeted_by_pending_request"] = (report_df["pending_request_count"] > 0)report_df["utilization_pct"] = (report_df["current_load_mw"] * 100.0 / report_df["max_capacity_mw"])report_df["is_constrained"] = report_df["utilization_pct"] >= 85.0report_df = report_df.sort_values("centrality_score", ascending=False, kind="stable").reset_index(drop=True)top_substations_df = report_df.head(10).copy()display_df = top_substations_df[["substation_id","substation_name","centrality_score","available_headroom_mw","is_constrained","targeted_by_pending_request","pending_request_count","pending_requested_mw","pending_expected_annual_revenue",]]print("Top substations by transmission centrality")print(display_df.to_string(index=False))return {"top_substations": top_substations_df,"all_substations": report_df,"pending_requests": pending_request_df.sort_values("expected_annual_revenue", ascending=False, kind="stable").reset_index(drop=True),}def count_data_center_requests():"""Count the data center requests loaded into the model."""return model.select(aggs.count(DataCenterRequest).alias("data_center_request_count")).to_df()def most_constrained_substation():"""Return the substation with the highest current utilization."""utilization_df = model.where(Substation.max_capacity_mw > 0.0).select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),Substation.current_load_mw.alias("current_load_mw"),Substation.max_capacity_mw.alias("max_capacity_mw"),((Substation.current_load_mw * 100.0) / Substation.max_capacity_mw).alias("utilization_pct"),).to_df()return utilization_df.sort_values("utilization_pct", ascending=False, kind="stable").head(1).reset_index(drop=True)def constrained_substation_report():"""Summarize the most constrained substation and customer impact limits."""constrained_df = most_constrained_substation()if constrained_df.empty:return {"summary": "No substation capacity data is available.","impacted_customers": None,}row = constrained_df.iloc[0]return {"substation_id": row["substation_id"],"substation_name": row["substation_name"],"current_load_mw": row["current_load_mw"],"max_capacity_mw": row["max_capacity_mw"],"utilization_pct": row["utilization_pct"],"impacted_customers": None,"impact_note": ("The current ontology does not model a path from Customer to ""Substation. Customers are linked only to LoadZone, so impacted ""customers cannot be identified without adding a load-zone-to-""substation mapping."),}def rules_summary():"""Print a summary of derived rules used by later optimization stages."""eligible_df = model.where(DataCenterRequest.is_eligible()).select(aggs.count(DataCenterRequest).alias("eligible_request_count"),aggs.sum(DataCenterRequest.requested_mw).alias("total_requested_mw"),aggs.sum(DataCenterRequest.expected_annual_revenue).alias("total_expected_annual_revenue"),).to_df()constrained_df = model.where(Substation.is_constrained()).select(aggs.count(Substation).alias("constrained_substation_count"),aggs.avg(Substation.current_load_mw / Substation.max_capacity_mw).alias("average_utilization"),).to_df()if constrained_df.empty:constrained_df = pd.DataFrame([{"constrained_substation_count": 0, "average_utilization": None}])high_value_upgrade_df = model.where(SubstationUpgrade.is_high_value()).select(aggs.count(SubstationUpgrade).alias("high_value_upgrade_count"),aggs.sum(SubstationUpgrade.cost_million).alias("total_cost_million"),).to_df()eligible_sample_df = model.where(DataCenterRequest.is_eligible(),DataCenterRequest.substation(Substation),).select(DataCenterRequest.id.alias("request_id"),DataCenterRequest.name.alias("request_name"),DataCenterRequest.requested_mw.alias("requested_mw"),DataCenterRequest.expected_annual_revenue.alias("expected_annual_revenue"),Substation.id.alias("substation_id"),Substation.available_headroom_mw.alias("available_headroom_mw"),).to_df()constrained_detail_df = model.where(Substation.max_capacity_mw > 0.0).select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),(Substation.current_load_mw / Substation.max_capacity_mw).alias("utilization"),).to_df()upgrade_detail_df = model.where(SubstationUpgrade.mw_per_million).select(SubstationUpgrade.id.alias("upgrade_id"),SubstationUpgrade.capacity_increase_mw.alias("capacity_increase_mw"),SubstationUpgrade.cost_million.alias("cost_million"),SubstationUpgrade.mw_per_million.alias("mw_per_million"),).to_df()eligible_sample_df = eligible_sample_df.sort_values("expected_annual_revenue", ascending=False, kind="stable").head(5).reset_index(drop=True)constrained_detail_df = constrained_detail_df.sort_values("utilization", ascending=False, kind="stable").head(5).reset_index(drop=True)upgrade_detail_df = upgrade_detail_df.sort_values("mw_per_million", ascending=False, kind="stable").head(5).reset_index(drop=True)print("Eligible data center requests")print(eligible_df.to_string(index=False))print("\nConstrained substations")print(constrained_df.to_string(index=False))print("\nHigh-value upgrades")print(high_value_upgrade_df.to_string(index=False))print("\nTop eligible request sample")print(eligible_sample_df.to_string(index=False))print("\nMost utilized substations")print(constrained_detail_df.to_string(index=False))print("\nMost cost-efficient upgrades")print(upgrade_detail_df.to_string(index=False))return {"eligible_requests": eligible_df,"constrained_substations": constrained_df,"high_value_upgrades": high_value_upgrade_df,"eligible_sample": eligible_sample_df,"most_utilized_substations": constrained_detail_df,"most_cost_efficient_upgrades": upgrade_detail_df,}if __name__ == "__main__":centrality_report() - Create a new
Find optimal actions with prescriptive reasoning
Section titled “Find optimal actions with prescriptive reasoning”The prescriptive reasoning skills enable your agent to formulate an optimization problem, run a solver, and explain the results. Use them when you need to choose the best set of actions under constraints, such as which datacenter requests to approve and which upgrades to fund under a fixed budget.
Follow these steps to have your agent formulate and solve a prescriptive optimization problem.
-
Invoke the prescriptive reasoning skills.
Use a detailed prompt that defines the candidate set, decision variables, constraints, objective, scenario sweep, and expected outputs.
For example, the following prompt is copied from the sample output in the
/rai-discoverysection and uses the rules and graph scores from the prior steps to solve an approval and upgrade optimization:/rai-prescriptive-problem-formulation + /rai-prescriptive-solver-management + /rai-prescriptive-results-interpretationProject context:- RAI tutorial project at workspace root. `model/schema.py` + `model/sources.py` define the base ontology.- `model/rules.py` provides DataCenterRequest.is_eligible / .expected_annual_revenue, Substation.available_headroom_mw, SubstationUpgrade.is_high_value, etc.- `model/graph.py` provides Substation.centrality_score and Substation.network_weight.Task: Formulate, solve, and interpret a portfolio decision that picks which eligible DataCenterRequests to approve and which SubstationUpgrades to fund under a budget.1. Create `model/optimization.py` with the prescriptive formulation:- Binary decision: `DataCenterRequest.approve` on each eligible request (filter by is_eligible).- Binary decision: `SubstationUpgrade.fund` on every upgrade.- Capacity constraint per Substation: sum(approve * requested_mw of requests targeting that substation) <= available_headroom_mw + sum(fund * capacity_increase_mw of upgrades at that substation).- Budget constraint (global): sum(fund * cost_million) <= BUDGET_MILLION (parameter — see below).- Objective: maximize sum over approved requests of (expected_annual_revenue * network_weight of target substation). Centrality-weighting biases approvals toward hubs.- Expose BUDGET_MILLION as a module-level parameter that can be set before solving.2. Create a new project-root file `portfolio_decision.py` that:- Solves the problem at three budget levels: $50M, $150M, $300M.- For each budget, extracts: list of approved requests (id, name, requested_mw, expected_annual_revenue, target substation, centrality), list of funded upgrades (id, substation, capacity_increase_mw, cost_million), total expected annual revenue, total upgrade spend, number of constrained substations relieved.- Prints a side-by-side comparison table across the three budgets.3. After running, write a plain-English executive summary covering:- Which datacenter requests should be approved at each budget level and why.- Which upgrades should be funded at each budget level and why (cost-efficiency vs. network position).- The marginal return: how much extra revenue each additional $100M of budget unlocks.- The recommended budget level and the reasoning behind it.Only add files as specified; do not modify schema.py, sources.py, rules.py, or graph.py.- Creates
model/optimization.pyto define the approval and funding decisions, constraints, and objective. - Uses the rules and graph properties from the earlier steps as inputs to the optimization.
- Creates
portfolio_decision.pyto solve the model at three budget levels and compare the results side by side. - Produces a plain-English summary of which requests and upgrades are recommended at each budget level and why.
- Creates
-
Review the agent’s response.
Your agent will formulate the optimization problem, run the solver, and summarize how the recommended approvals and upgrades change across budget levels. It may add solver code, output CSVs, or helper queries to the project.
Click to expand the sample response from the prescriptive reasoning skills
All three scenarios solved to
OPTIMAL.Key results:
Budget Approved requests Funded upgrades Revenue Spend Relieved substations $50MDC-004,DC-001,DC-006,DC-007,DC-010,DC-009UPG-006$309.9M$45.0M1$150MDC-004,DC-001,DC-002,DC-006,DC-008,DC-010,DC-009UPG-002,UPG-007$376.4M$120.0M2$300MDC-004,DC-003,DC-001,DC-002,DC-006,DC-007,DC-008,DC-010,DC-009UPG-003,UPG-002,UPG-006,UPG-007$498.2M$275.0M4Executive readout:
$300Mis the recommended tested budget level. It unlocks the highest expected annual revenue, funds the network-critical Dallas/Fort Worth and Houston upgrades plus strong capacity-per-dollar upgrades, and the second budget step has the stronger marginal return:$81.2Mper extra$100Mversus$66.5Mfrom$50Mto$150M.Your agent should create a new file
model/optimization.pywith the content similar to the following:"""Prescriptive portfolio optimization for data center approvals and upgrades."""from dataclasses import dataclassfrom typing import Anyimport pandas as pdfrom relationalai.semantics import Floatfrom relationalai.semantics.reasoners.prescriptive import Problemfrom relationalai.semantics.std import aggregates as aggsfrom . import DataCenterRequest, Substation, SubstationUpgrade, modelBUDGET_MILLION = 150.0SOLVER_NAME = "highs"SOLVE_TIME_LIMIT_SEC = 120DataCenterRequest.approve = model.Property(f"{DataCenterRequest} has {Float:approve}")SubstationUpgrade.fund = model.Property(f"{SubstationUpgrade} has {Float:fund}")@dataclass(frozen=True)class PortfolioProblem:"""Container for the registered optimization problem and solution handles."""problem: Problemapprove_variable: Anyfund_variable: Anycapacity_constraint: Anybudget_constraint: Anyobjective: Anybudget_million: floatdef build_portfolio_problem(budget_million: float | None = None) -> PortfolioProblem:"""Build the data center portfolio MILP for the active budget."""active_budget = float(BUDGET_MILLION if budget_million is None else budget_million)problem = Problem(model, Float)approve_variable = problem.solve_for(DataCenterRequest.approve,where=[DataCenterRequest.is_eligible()],populate=False,name=["approve", DataCenterRequest.id],type="bin",lower=0.0,upper=1.0,)fund_variable = problem.solve_for(SubstationUpgrade.fund,populate=False,name=["fund", SubstationUpgrade.id],type="bin",lower=0.0,upper=1.0,)approved_request_mw = aggs.sum(DataCenterRequest.approve * DataCenterRequest.requested_mw).per(Substation).where(DataCenterRequest.substation(Substation))funded_capacity_mw = aggs.sum(SubstationUpgrade.fund * SubstationUpgrade.capacity_increase_mw).per(Substation).where(SubstationUpgrade.substation(Substation))capacity_requirement = model.require(approved_request_mw <= Substation.available_headroom_mw + funded_capacity_mw)budget_requirement = model.require(aggs.sum(SubstationUpgrade.fund * SubstationUpgrade.cost_million)<= active_budget)capacity_constraint = problem.satisfy(capacity_requirement,name=["substation_capacity", Substation.id],)budget_constraint = problem.satisfy(budget_requirement, name=["upgrade_budget"])objective = problem.maximize(aggs.sum(DataCenterRequest.approve* DataCenterRequest.expected_annual_revenue* Substation.network_weight).where(DataCenterRequest.substation(Substation)),name=["weighted_expected_annual_revenue"],)return PortfolioProblem(problem=problem,approve_variable=approve_variable,fund_variable=fund_variable,capacity_constraint=capacity_constraint,budget_constraint=budget_constraint,objective=objective,budget_million=active_budget,)def solve_portfolio(budget_million: float | None = None,*,solver: str = SOLVER_NAME,time_limit_sec: int = SOLVE_TIME_LIMIT_SEC,) -> PortfolioProblem:"""Build and solve the portfolio problem for the given budget."""portfolio_problem = build_portfolio_problem(budget_million)problem = portfolio_problem.problemproblem.solve(solver, time_limit_sec=time_limit_sec)solve_info = problem.solve_info()if solve_info.termination_status not in {"OPTIMAL", "LOCALLY_SOLVED"}:raise RuntimeError("Portfolio solve did not produce an optimal solution: "f"{solve_info.termination_status}; error={solve_info.error}")return portfolio_problemdef approved_requests_df(portfolio_problem: PortfolioProblem) -> pd.DataFrame:"""Return approved data center requests for a solved portfolio problem."""value = Float.ref()return model.where(portfolio_problem.approve_variable.datacenterrequest(DataCenterRequest),portfolio_problem.approve_variable.values(0, value),value > 0.5,DataCenterRequest.substation(Substation),).select(DataCenterRequest.id.alias("request_id"),DataCenterRequest.name.alias("request_name"),DataCenterRequest.requested_mw.alias("requested_mw"),DataCenterRequest.expected_annual_revenue.alias("expected_annual_revenue"),Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),Substation.available_headroom_mw.alias("available_headroom_mw"),Substation.centrality_score.alias("centrality"),Substation.network_weight.alias("network_weight"),(DataCenterRequest.expected_annual_revenue * Substation.network_weight).alias("weighted_expected_value"),value.alias("approved"),).to_df()def funded_upgrades_df(portfolio_problem: PortfolioProblem) -> pd.DataFrame:"""Return funded substation upgrades for a solved portfolio problem."""value = Float.ref()return model.where(portfolio_problem.fund_variable.substationupgrade(SubstationUpgrade),portfolio_problem.fund_variable.values(0, value),value > 0.5,SubstationUpgrade.substation(Substation),).select(SubstationUpgrade.id.alias("upgrade_id"),Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),SubstationUpgrade.capacity_increase_mw.alias("capacity_increase_mw"),SubstationUpgrade.cost_million.alias("cost_million"),SubstationUpgrade.mw_per_million.alias("mw_per_million"),Substation.available_headroom_mw.alias("available_headroom_mw"),Substation.centrality_score.alias("centrality"),Substation.network_weight.alias("network_weight"),value.alias("funded"),).to_df()def constrained_substations_df() -> pd.DataFrame:"""Return substations marked constrained before portfolio decisions."""return model.where(Substation.is_constrained()).select(Substation.id.alias("substation_id"),Substation.name.alias("substation_name"),).to_df()def summarize_solution(portfolio_problem: PortfolioProblem) -> dict[str, Any]:"""Extract business-facing metrics from a solved portfolio problem."""approved_df = approved_requests_df(portfolio_problem)funded_df = funded_upgrades_df(portfolio_problem)total_expected_revenue = 0.0if not approved_df.empty:total_expected_revenue = float(approved_df["expected_annual_revenue"].astype(float).sum())total_upgrade_spend = 0.0if not funded_df.empty:total_upgrade_spend = float(funded_df["cost_million"].astype(float).sum())relieved_count = 0if not approved_df.empty and not funded_df.empty:approved_mw_by_substation = approved_df.groupby(["substation_id", "available_headroom_mw"],as_index=False,).agg(approved_mw=("requested_mw", "sum"))funded_substations = set(funded_df["substation_id"].tolist())relieved_count = int(approved_mw_by_substation.loc[approved_mw_by_substation["substation_id"].isin(funded_substations)& (approved_mw_by_substation["approved_mw"]> approved_mw_by_substation["available_headroom_mw"]),"substation_id",].nunique())return {"budget_million": portfolio_problem.budget_million,"solve_status": portfolio_problem.problem.solve_info().termination_status,"objective_value": portfolio_problem.problem.solve_info().objective_value,"approved_requests": approved_df.sort_values(["network_weight", "expected_annual_revenue", "request_id"],ascending=[False, False, True],kind="stable",).reset_index(drop=True),"funded_upgrades": funded_df.sort_values(["network_weight", "mw_per_million", "upgrade_id"],ascending=[False, False, True],kind="stable",).reset_index(drop=True),"total_expected_annual_revenue": total_expected_revenue,"total_upgrade_spend": total_upgrade_spend,"constrained_substations_relieved": relieved_count,}Your agent should create a new file
portfolio_decision.pywith the content similar to the following:"""Run data center approval and substation upgrade portfolio scenarios."""from __future__ import annotationsimport argparseimport jsonimport subprocessimport sysfrom typing import Any, castimport pandas as pdimport model.optimization as optimizationBUDGET_LEVELS_MILLION = [50.0, 150.0, 300.0]JSON_START = "===PORTFOLIO_JSON_START==="JSON_END = "===PORTFOLIO_JSON_END==="APPROVED_COLUMNS = ["request_id","request_name","requested_mw","expected_annual_revenue","substation_id","substation_name","available_headroom_mw","centrality","network_weight","weighted_expected_value","approved",]FUNDED_COLUMNS = ["upgrade_id","substation_id","substation_name","capacity_increase_mw","cost_million","mw_per_million","available_headroom_mw","centrality","network_weight","funded",]def _format_millions(value: float) -> str:return f"${value:,.1f}M"def _format_revenue(value: float) -> str:return _format_millions(value / 1_000_000.0)def _format_list(values: pd.Series) -> str:if values.empty:return "None"return ", ".join(str(value) for value in values.tolist())def _frame_from_records(records: list[dict[str, Any]], columns: list[str]) -> pd.DataFrame:if not records:return pd.DataFrame(columns=columns)return pd.DataFrame(records, columns=columns)def _json_ready_summary(summary: dict[str, Any]) -> dict[str, Any]:return {"budget_million": float(summary["budget_million"]),"solve_status": summary["solve_status"],"objective_value": float(summary["objective_value"]),"approved_requests": json.loads(summary["approved_requests"].to_json(orient="records")),"funded_upgrades": json.loads(summary["funded_upgrades"].to_json(orient="records")),"total_expected_annual_revenue": float(summary["total_expected_annual_revenue"]),"total_upgrade_spend": float(summary["total_upgrade_spend"]),"constrained_substations_relieved": int(summary["constrained_substations_relieved"]),}def _summary_from_payload(payload: dict[str, Any]) -> dict[str, Any]:return {"budget_million": float(payload["budget_million"]),"solve_status": payload["solve_status"],"objective_value": float(payload["objective_value"]),"approved_requests": _frame_from_records(cast(list[dict[str, Any]], payload["approved_requests"]),APPROVED_COLUMNS,),"funded_upgrades": _frame_from_records(cast(list[dict[str, Any]], payload["funded_upgrades"]),FUNDED_COLUMNS,),"total_expected_annual_revenue": float(payload["total_expected_annual_revenue"]),"total_upgrade_spend": float(payload["total_upgrade_spend"]),"constrained_substations_relieved": int(payload["constrained_substations_relieved"]),}def _extract_payload(output: str) -> dict[str, Any]:start_index = output.rfind(JSON_START)end_index = output.rfind(JSON_END)if start_index == -1 or end_index == -1 or end_index <= start_index:raise RuntimeError("Scenario child process did not return a JSON payload.")json_text = output[start_index + len(JSON_START) : end_index].strip()return json.loads(json_text)def _solve_single_budget(budget: float) -> dict[str, Any]:optimization.BUDGET_MILLION = budgetportfolio_problem = optimization.solve_portfolio()return optimization.summarize_solution(portfolio_problem)def _solve_single_budget_child(budget: float) -> None:summary = _json_ready_summary(_solve_single_budget(budget))print(JSON_START)print(json.dumps(summary, sort_keys=True))print(JSON_END)def _solve_single_budget_isolated(budget: float) -> dict[str, Any]:completed = subprocess.run([sys.executable, __file__, "--json-budget", str(budget)],check=False,capture_output=True,text=True,timeout=240,)if completed.returncode != 0:raise RuntimeError("Scenario child process failed for budget "f"${budget:,.1f}M.\nSTDOUT:\n{completed.stdout}\nSTDERR:\n{completed.stderr}")return _summary_from_payload(_extract_payload(completed.stdout))def solve_budget_scenarios() -> list[dict[str, Any]]:"""Solve the portfolio problem at each configured budget level."""return [_solve_single_budget_isolated(budget) for budget in BUDGET_LEVELS_MILLION]def comparison_table(summaries: list[dict[str, Any]]) -> pd.DataFrame:"""Create the side-by-side budget comparison table."""rows = []for summary in summaries:approved_df = summary["approved_requests"]funded_df = summary["funded_upgrades"]rows.append({"Budget": _format_millions(summary["budget_million"]),"Status": summary["solve_status"],"Approved requests": _format_list(approved_df["request_id"]),"Funded upgrades": _format_list(funded_df["upgrade_id"]),"Expected annual revenue": _format_revenue(summary["total_expected_annual_revenue"]),"Upgrade spend": _format_millions(summary["total_upgrade_spend"]),"Constrained substations relieved": summary["constrained_substations_relieved"],"Weighted objective": round(float(summary["objective_value"]), 2),})return pd.DataFrame(rows).set_index("Budget").Tdef print_solution_details(summaries: list[dict[str, Any]]) -> None:"""Print detailed approved-request and funded-upgrade lists."""for summary in summaries:budget_label = _format_millions(summary["budget_million"])approved_df = summary["approved_requests"]funded_df = summary["funded_upgrades"]print(f"\n=== {budget_label} budget ===")print("Approved requests")if approved_df.empty:print("None")else:print(approved_df[["request_id","request_name","requested_mw","expected_annual_revenue","substation_name","available_headroom_mw","centrality",]].to_string(index=False))print("\nFunded upgrades")if funded_df.empty:print("None")else:print(funded_df[["upgrade_id","substation_name","capacity_increase_mw","cost_million","mw_per_million","centrality",]].to_string(index=False))def executive_summary(summaries: list[dict[str, Any]]) -> str:"""Return a plain-English summary of the scenario results."""ordered = sorted(summaries, key=lambda item: item["budget_million"])lines = ["Executive summary", ""]for summary in ordered:budget = summary["budget_million"]approved_df = summary["approved_requests"]funded_df = summary["funded_upgrades"]approved_names = _format_list(approved_df["request_name"])funded_names = _format_list(funded_df["upgrade_id"])revenue = summary["total_expected_annual_revenue"]spend = summary["total_upgrade_spend"]top_request = Noneif not approved_df.empty:top_request = approved_df.sort_values(["weighted_expected_value", "expected_annual_revenue"],ascending=[False, False],kind="stable",).iloc[0]highest_weight = Nonebest_efficiency = Noneif not funded_df.empty:highest_weight = funded_df.sort_values(["network_weight", "mw_per_million"],ascending=[False, False],kind="stable",).iloc[0]best_efficiency = funded_df.sort_values(["mw_per_million", "network_weight"],ascending=[False, False],kind="stable",).iloc[0]upgrade_reason = "No upgrade funding is needed at this budget level."if highest_weight is not None and best_efficiency is not None:upgrade_reason = (f"The upgrade mix is led by {highest_weight['upgrade_id']} on "f"network position and {best_efficiency['upgrade_id']} on ""capacity per dollar.")request_reason = ""if top_request is not None:request_reason = (f" The strongest approval driver is {top_request['request_name']} "f"at {top_request['substation_name']}, combining "f"{_format_revenue(top_request['expected_annual_revenue'])} "f"of expected annual revenue with centrality "f"{top_request['centrality']:.3f}.")lines.append(f"At ${budget:,.0f}M, approve {approved_names}. "f"Fund upgrades {funded_names}, spending {_format_millions(spend)} "f"to unlock {_format_revenue(revenue)} in expected annual revenue. "f"{upgrade_reason}{request_reason}")lines.append("")lines.append("Marginal return")previous = ordered[0]for current in ordered[1:]:budget_delta = current["budget_million"] - previous["budget_million"]revenue_delta = (current["total_expected_annual_revenue"]- previous["total_expected_annual_revenue"])return_per_100m = (revenue_delta / 1_000_000.0) * 100.0 / budget_deltalines.append(f"Moving from ${previous['budget_million']:,.0f}M to "f"${current['budget_million']:,.0f}M unlocks "f"{_format_revenue(revenue_delta)} more expected annual revenue, "f"or ${return_per_100m:,.1f}M per additional $100M of budget.")previous = currentbest_summary = max(ordered,key=lambda item: (item["total_expected_annual_revenue"],-item["total_upgrade_spend"],),)lines.append("")lines.append(f"Recommended budget: ${best_summary['budget_million']:,.0f}M. ""This level delivers the highest expected annual revenue in the tested ""portfolio, and the second budget step produces the stronger marginal ""return per $100M while preserving feasibility at every targeted ""substation.")return "\n".join(lines)def main() -> None:parser = argparse.ArgumentParser()parser.add_argument("--json-budget", type=float, default=None)args = parser.parse_args()if args.json_budget is not None:_solve_single_budget_child(args.json_budget)returnsummaries = solve_budget_scenarios()table = comparison_table(summaries)print("Portfolio decision comparison")print(table.to_string())print_solution_details(summaries)print("\n" + executive_summary(summaries))if __name__ == "__main__":main()
Next steps
Section titled “Next steps”Explore more examples
Section titled “Explore more examples”Our project templates provide more examples of semantic models and reasoning in action.
Here’s a selection of good follow-up examples to explore:
Dive deeper with documentation
Section titled “Dive deeper with documentation”To learn more about PyRel and all of RelationalAI’s features, continue reading with the following resources: