SieveComP
  • Home
  • Installation
  • TOML Schema

Tutorials

  • Python API Quickstart
  • SeisComP SQL Queries Introduction
  • Optimizing Duplicates Search
  • Polygon validation
  • SieveComP: Advanced Usage
  • Generating TOML files with AI
    • Requirements

Interfaces & Apps

  • Command Line Interface (CLI)
  • Streamlit Web Application
  • API Reference
SieveComP
  • Tutorials
  • Generating TOML files with AI
  • Edit on KenethGarcia/SieveComP

This notebook was developed by Keneth Garcia. Source and license info are on GitHub.

In [1]:
Copied!
import os
import numpy as np
import pandas as pd
from shapely.geometry import Polygon
from sievecomp.io.sql import load_sql
from sievecomp.core.runner import Runner
from sievecomp.checks.vectorized_masks import (
    numeric_mask,
    column_column_mask,
    non_numeric_mask,
    polygon_mask,
    temporal_mask,
    combine_masks
)
import os import numpy as np import pandas as pd from shapely.geometry import Polygon from sievecomp.io.sql import load_sql from sievecomp.core.runner import Runner from sievecomp.checks.vectorized_masks import ( numeric_mask, column_column_mask, non_numeric_mask, polygon_mask, temporal_mask, combine_masks )

Generating TOML files with AI¶

In the era of AI, generating configuration files like TOML can be streamlined using language models. This notebook demonstrates how to leverage AI to create TOML files for various applications. Here, we will explore how to use AI to generate TOML configurations for a hypothetical application, and we will use a low level approach to code the same functionality in order to compare the results.

NOTE: To generate AI-based TOML files, you will need access to an AI model capable of understanding and generating text. In this example, we will use Gemini Pro 3.1 as our AI model. You can use any other model that supports text generation.

Requirements¶

In order to improve the quality of the generated TOML files, it is recommended to have a basic understanding of the TOML format and its structure. Additionally, familiarity with the application for which you are generating the configuration files will help in creating more accurate and useful configurations. We strongly recommend to have:

  1. A static SQL that queries the database and returns a dataframe: Avoid using dynamic SQL queries that change based on user input or other factors. This will help ensure that the generated TOML files are consistent and reliable.
  2. A previously generated TOML file: Having a reference TOML file can help the AI model understand the structure and format of the configuration files you want to generate. This can improve the quality and accuracy of the generated files. I recommend to attach a previously generated TOML file to the prompt when generating new files with AI.
  3. Access to the documentation of TOML syntax and structure: I recommend to attach the TOML documentation available at the GitHub repository to the prompt when generating new files with AI. This will help the AI model understand the syntax and structure of TOML files, which can improve the quality and accuracy of the generated files.

All the files used in this notebook (except for the documentation) are available at the AI_files folder. Here is a list of the files used in this notebook:

  • AI_files/seismic_revision_routine.sql: A static SQL file that queries the SeisComP6 database between 2026-03-27 and 2026-08-01 and returns a dataframe with the results.
  • AI_files/input_example.toml: A previously generated TOML file that serves as a reference for the AI model.
  • docs/tutorials/TOML_schema.md: The documentation of TOML syntax and structure available at the GitHub repository.
  • A screenshot of the folder structure of the repo to avoid mistakes when loading the files.

Let's import the func to query data also for checking if the AI-generated TOML files are correct:

In [2]:
Copied!
def fetch_events_by_time_range(
        runner: Runner,
        query_name: str,
        start_time: pd.Timestamp,
        end_time: pd.Timestamp
) -> pd.DataFrame:
    """
    Fetches events from a specific SeisComP database query within a dynamic time window.
    Bypasses the engine's checks and duplicates logic.
    """
    # 1. Retrieve the configured time column used for sorting and filtering
    time_col = runner._cm.get_time_column()

    # 2. Prepare the dynamic time bounds and SQL parameters
    sql_params = {}
    where_clauses = []

    if start_time:
        sql_params["start_time"] = start_time.strftime('%Y-%m-%d %H:%M:%S')
        where_clauses.append(f"base_query.{time_col} >= %(start_time)s")

    if end_time:
        sql_params["end_time"] = end_time.strftime('%Y-%m-%d %H:%M:%S')
        where_clauses.append(f"base_query.{time_col} < %(end_time)s")

    where_sql = " AND ".join(where_clauses)

    # 3. Load the base SQL text strictly as it is defined in the TOML configuration
    query_cfg = runner._cm.select_query(name=query_name)
    base_sql = load_sql(
        query_cfg=query_cfg,
        base_dir=runner._cm.base_dir,
        config_name=runner._cm.config_path.name
    )

    # 4. Wrap the base query as a subquery, applying the new WHERE clauses and sorting
    wrapped_sql = f"""SELECT * FROM ({base_sql}) AS base_query WHERE {where_sql} ORDER BY base_query.{time_col} ASC"""

    # 5. Fetch the events (skipping the check/duplicate evaluation steps for raw data retrieval)
    result = runner.run(
        query_name=query_name,
        multi_query=False,
        perform_duplicates=False,
        perform_checks=False,
        sql_text_override=wrapped_sql,
        sql_params=sql_params
    )

    # 6. Return the raw queried events DataFrame
    return result.total_events
def fetch_events_by_time_range( runner: Runner, query_name: str, start_time: pd.Timestamp, end_time: pd.Timestamp ) -> pd.DataFrame: """ Fetches events from a specific SeisComP database query within a dynamic time window. Bypasses the engine's checks and duplicates logic. """ # 1. Retrieve the configured time column used for sorting and filtering time_col = runner._cm.get_time_column() # 2. Prepare the dynamic time bounds and SQL parameters sql_params = {} where_clauses = [] if start_time: sql_params["start_time"] = start_time.strftime('%Y-%m-%d %H:%M:%S') where_clauses.append(f"base_query.{time_col} >= %(start_time)s") if end_time: sql_params["end_time"] = end_time.strftime('%Y-%m-%d %H:%M:%S') where_clauses.append(f"base_query.{time_col} < %(end_time)s") where_sql = " AND ".join(where_clauses) # 3. Load the base SQL text strictly as it is defined in the TOML configuration query_cfg = runner._cm.select_query(name=query_name) base_sql = load_sql( query_cfg=query_cfg, base_dir=runner._cm.base_dir, config_name=runner._cm.config_path.name ) # 4. Wrap the base query as a subquery, applying the new WHERE clauses and sorting wrapped_sql = f"""SELECT * FROM ({base_sql}) AS base_query WHERE {where_sql} ORDER BY base_query.{time_col} ASC""" # 5. Fetch the events (skipping the check/duplicate evaluation steps for raw data retrieval) result = runner.run( query_name=query_name, multi_query=False, perform_duplicates=False, perform_checks=False, sql_text_override=wrapped_sql, sql_params=sql_params ) # 6. Return the raw queried events DataFrame return result.total_events
In [3]:
Copied!
# Initialize the runner
runner = Runner(config_path='../../examples/data/configs/seismic_revision_routine.toml')

start = pd.to_datetime("2026-03-17 00:00:00", utc=True)
end = pd.to_datetime("2026-08-01 00:00:00", utc=True)

# Fetch just the events for the specified window
events_df = fetch_events_by_time_range(
    runner=runner,
    query_name="SeisComP6",
    start_time=start,
    end_time=end
)

print("Number of events by event_type:")
print(events_df['event_type'].value_counts())
# Initialize the runner runner = Runner(config_path='../../examples/data/configs/seismic_revision_routine.toml') start = pd.to_datetime("2026-03-17 00:00:00", utc=True) end = pd.to_datetime("2026-08-01 00:00:00", utc=True) # Fetch just the events for the specified window events_df = fetch_events_by_time_range( runner=runner, query_name="SeisComP6", start_time=start, end_time=end ) print("Number of events by event_type:") print(events_df['event_type'].value_counts())
                                           
Number of events by event_type:
event_type
not locatable                  20064
earthquake                     10362
outside of network interest      715
explosion                        518
not existing                     111
volcanic eruption                 12
Name: count, dtype: int64

Example 1: Angel request -->> Check how many earthquakes and volcanic eruptions are inside volcanic zones (all saved in examples/data/polygons/SGC/volcanological_observatories folder)¶

Let's consider the following case: We want to check how many earthquakes and volcanic eruptions are inside the volcanic zones defined in the volcanological_observatories folder. The volcanological_observatories folder contains 9 different files (labeled obspas{i}.txt) for Pasto, Nariño. Also, one single file for Manizales (obsman.txt) and two main (obspop.txt and obspopvnh.txt) for Popayán, Cauca. The idea is to check two conditions for all events:

  • If the event type is 'earthquake' or 'volcanic eruption'
  • If the event is inside any of the volcanic zones defined in the volcanological_observatories folder. This can be done by creating a boolean mask for each polygon and then combining them with an 'or' condition. However, we need to consider that the number of polygons can be large, so we need to use a loop to iterate over the files in the folder and create the masks

IA TOML solution¶

To generate the TOML file for this case, we will use the following prompt:

Based on the attached TOML documentation, please generate a TOML file that checks how many earthquakes and volcanic eruptions are inside the volcanic zones defined in the `volcanological_observatories` folder. The `volcanological_observatories` folder contains 9 different files (labeled obspas{i}.txt) for Pasto, Nariño. Also, one single file for Manizales (obsman.txt) and two main (obspop.txt and obspopvnh.txt) for Popayán, Cauca. The idea is to check two conditions for all events:
- If the event type is 'earthquake' or 'volcanic eruption'
- If the event is inside any of the volcanic zones defined in the volcanological_observatories folder.

Use the provided input_example.toml file as a guideline, and make sure to include the necessary parameters and configurations for the checks. The output should be a valid TOML file that can be used in the SieveComP application. Moreover, take into account the attached screenshot of the folder structure of the repo to avoid mistakes when loading the files (located at `examples/data`).

The TOML file will be saved at the same location as the input_example.toml file (`docs/tutorials/IA_files`), and it will be named `check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml`.

After sending the prompt to the AI model, we will receive a TOML file that can be used in the SieveComP application. The generated TOML file will be saved at the same location as the input_example.toml file, and it will be named check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml.

The answer provided by the AI follows:

# ============================================================
# check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml
# ============================================================

# ── Settings ──────────────────────────────────────────────────
[settings]
event_type_column = "event_type"
time_column = "time_value"
author_column = "creationInfo_author"

# ── Returned table columns ────────────────────────────────────
[output]
columns = ["time_value",
    "publicID",
    "text",
    "depth_value",
    "magnitude_value",
    "magnitude_type",
    "quality_standardError",
    "depth_uncertainty",
    "latitude_uncertainty",
    "longitude_uncertainty",
    "quality_associatedPhaseCount",
    "creationInfo_author",
    "event_type",
    "creationInfo_agencyID"]

# ── Database connection ───────────────────────────────────────
[[database]]
name       = "SeisComP6"
env_file   = "../../../examples/data/credentials/.env"
env_prefix = "SERVER_SC6"

# ── SQL query ────────────────────────────────────────────────
[[queries]]
name       = "SeisComP6"
sql_file   = "seismic_revision_routine.sql"
description= "Query to retrieve events from SeisComP6 database"
database   = "SeisComP6"

# ── Polygon files ────────────────────────────────────────────
[[polygons]]
name        = "ovsman"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obsman.txt"
description = "Observatorio Sismológico de Manizales - OVSM region"

[[polygons]]
name        = "ovspop"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspop.txt"
description = "Observatorio Sismológico de Popayán - OVSPOP region"

[[polygons]]
name        = "ovspopvnh"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspopvnh.txt"
description = "Observatorio Sismológico de Popayán - VNH region"

[[polygons]]
name        = "ovsp1"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas1.txt"
description = "Observatorio Sismológico de Pasto - OVS1 region"

[[polygons]]
name        = "ovsp2"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas2.txt"
description = "Observatorio Sismológico de Pasto - OVS2 region"

[[polygons]]
name        = "ovsp3"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas3.txt"
description = "Observatorio Sismológico de Pasto - OVS3 region"

[[polygons]]
name        = "ovsp4"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas4.txt"
description = "Observatorio Sismológico de Pasto - OVS4 region"

[[polygons]]
name        = "ovsp5"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas5.txt"
description = "Observatorio Sismológico de Pasto - OVS5 region"

[[polygons]]
name        = "ovsp6"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas6.txt"
description = "Observatorio Sismológico de Pasto - OVS6 region"

[[polygons]]
name        = "ovsp7"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas7.txt"
description = "Observatorio Sismológico de Pasto - OVS7 region"

[[polygons]]
name        = "ovsp8"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas8.txt"
description = "Observatorio Sismológico de Pasto - OVS8 region"

[[polygons]]
name        = "ovsp9"
path        = "../../../examples/data/polygons/SGC/volcanological_observatories/obspas9.txt"
description = "Observatorio Sismológico de Pasto - OVS9 region"

# ──────────────────────────────────────────────────────────────
# Checks configuration
# ──────────────────────────────────────────────────────────────

[[checks]]
name = "Earthquakes and volcanic eruptions inside volcanic zones"
event_type = ["earthquake", "volcanic eruption"]

    [[checks.conditions]]
    rule_type = "polygon"
    lat_col = "latitude_value"
    lon_col = "longitude_value"
    mode = "inside"
    polygon = ["ovsman", "ovspop", "ovspopvnh", "ovsp1", "ovsp2", "ovsp3", "ovsp4", "ovsp5", "ovsp6", "ovsp7", "ovsp8", "ovsp9"]

And it mentions: I have created check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml with the specified configuration. Here is a breakdown of the implementation:

  1. Polygons: I defined 12 distinct polygon entries matching the requested files in the volcanological_observatories folder. The relative paths correctly point to ../../../examples/data/polygons/SGC/volcanological_observatories/ based on the file's target location (docs/tutorials/IA_files) and the repository structure shown in the screenshot.
  2. Event Type Filter: The check utilizes the event_type key directly in the root node set to ["earthquake", "volcanic eruption"], efficiently filtering the dataset before executing any spatial checks.
  3. Spatial Condition: The actual condition uses a polygon rule type with mode = "inside". It passes an array containing all 12 defined polygon names to the polygon key, which perfectly satisfies the "inside ANY of the listed polygons" requirement.

Remarks¶

As you can see from the generated TOML file, the AI model has successfully created a configuration that checks for earthquakes and volcanic eruptions inside the specified volcanic zones. The polygons are correctly defined, and the event type filter is applied as requested. So CONGRATS! The AI model has successfully generated a TOML file that meets the requirements of the request.

So let's try to run the generated TOML file (with the SeisComP6 database) and check if it works as expected. We will use the Runner class to execute the checks defined in the TOML file and then analyze the results.

In [4]:
Copied!
runner_ai_1 = Runner(config_path='IA_files/check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml')
result_ai_1 = runner_ai_1.run()
runner_ai_1 = Runner(config_path='IA_files/check_earthquakes_volcanic_eruptions_in_volcanic_zones.toml') result_ai_1 = runner_ai_1.run()
                                           

Now let's analyze the results of the total events, duplicates, and checks. We will print the number of events fetched, potential duplicates found, and events satisfying quality checks.

In [5]:
Copied!
print(f"Potential duplicates found: {len(result_ai_1.duplicates)}")
print(f"Events satisfying quality checks: {len(result_ai_1.checks)}")
print(f"Total events fetched: {len(result_ai_1.total_events)}")
print(f"Distribution of total events by event_type:\n{result_ai_1.total_events['event_type'].value_counts()}")
print(f"Potential duplicates found: {len(result_ai_1.duplicates)}") print(f"Events satisfying quality checks: {len(result_ai_1.checks)}") print(f"Total events fetched: {len(result_ai_1.total_events)}") print(f"Distribution of total events by event_type:\n{result_ai_1.total_events['event_type'].value_counts()}")
Potential duplicates found: 0
Events satisfying quality checks: 102
Total events fetched: 31782
Distribution of total events by event_type:
event_type
not locatable                  20064
earthquake                     10362
outside of network interest      715
explosion                        518
not existing                     111
volcanic eruption                 12
Name: count, dtype: int64

All seems to be working fine. Now let's compare the results of the AI-generated TOML file with a low-level solution that we will implement in the next section. We will check if the number of events satisfying the quality checks is the same in both cases.

Low level solution¶

This is a simple check that can be done by creating a boolean mask for each polygon and then combining them with an 'or' condition. However, we need to consider that the number of polygons can be large, so we need to use a loop to iterate over the files in the folder and create the masks.

In [6]:
Copied!
# Filter events for year 2026
events_2026 = events_df[events_df['time_value'].dt.year == 2026]

# Filter events for 'earthquake' or 'volcanic eruption'
events_eq_ve = events_2026[events_2026['event_type'].isin(['earthquake', 'volcanic eruption'])]

# Create a boolean mask for events inside volcanic zones
volcanic_mask = np.zeros(len(events_eq_ve), dtype=bool)

# Iterate over the files in the bna_volcanic_files folder
volcanic_dir= "../../examples/data/polygons/SGC/volcanological_observatories/"

for file in os.listdir(volcanic_dir):
    if file.endswith(".txt"):
        # Load the polygon from the file
        polygon = Polygon(np.loadtxt(volcanic_dir + file, delimiter=",", skiprows=1))

        # Create a boolean mask for events inside the polygon
        inside_polygon_mask = polygon_mask(
            events=events_eq_ve,
            lon_col='longitude_value',
            lat_col='latitude_value',
            polygon=polygon,
            mode='inside'
        )

        # Combine the mask with the volcanic_mask using 'or' condition
        volcanic_mask = combine_masks([volcanic_mask, inside_polygon_mask], logic="or")

# Filter events for those inside volcanic zones
events_inside_volcanic_zones = events_eq_ve[volcanic_mask]

# Print number of events inside volcanic zones
print(f"Number of earthquakes and volcanic eruptions inside volcanic zones in 2026: {len(events_inside_volcanic_zones)}")
# Filter events for year 2026 events_2026 = events_df[events_df['time_value'].dt.year == 2026] # Filter events for 'earthquake' or 'volcanic eruption' events_eq_ve = events_2026[events_2026['event_type'].isin(['earthquake', 'volcanic eruption'])] # Create a boolean mask for events inside volcanic zones volcanic_mask = np.zeros(len(events_eq_ve), dtype=bool) # Iterate over the files in the bna_volcanic_files folder volcanic_dir= "../../examples/data/polygons/SGC/volcanological_observatories/" for file in os.listdir(volcanic_dir): if file.endswith(".txt"): # Load the polygon from the file polygon = Polygon(np.loadtxt(volcanic_dir + file, delimiter=",", skiprows=1)) # Create a boolean mask for events inside the polygon inside_polygon_mask = polygon_mask( events=events_eq_ve, lon_col='longitude_value', lat_col='latitude_value', polygon=polygon, mode='inside' ) # Combine the mask with the volcanic_mask using 'or' condition volcanic_mask = combine_masks([volcanic_mask, inside_polygon_mask], logic="or") # Filter events for those inside volcanic zones events_inside_volcanic_zones = events_eq_ve[volcanic_mask] # Print number of events inside volcanic zones print(f"Number of earthquakes and volcanic eruptions inside volcanic zones in 2026: {len(events_inside_volcanic_zones)}")
Number of earthquakes and volcanic eruptions inside volcanic zones in 2026: 102

And we can see that the number of events satisfying the quality checks is the same in both cases. This means that the AI-generated TOML file is working as expected and is equivalent to the low-level solution. To just be sure, let compare the entire dataframes of the events satisfying the quality checks in both cases. We will use the equals() method to check if the two dataframes are equal.

In [9]:
Copied!
# Compare the dataframes of events by checking the publicID column
df_ai_1 = result_ai_1.checks[['publicID']].sort_values(by='publicID').reset_index(drop=True)
df_low_level = events_inside_volcanic_zones[['publicID']].sort_values(by='publicID').reset_index(drop=True)
print(f"Are the dataframes of events satisfying the quality checks equal? {df_ai_1.equals(df_low_level)}")
# Compare the dataframes of events by checking the publicID column df_ai_1 = result_ai_1.checks[['publicID']].sort_values(by='publicID').reset_index(drop=True) df_low_level = events_inside_volcanic_zones[['publicID']].sort_values(by='publicID').reset_index(drop=True) print(f"Are the dataframes of events satisfying the quality checks equal? {df_ai_1.equals(df_low_level)}")
Are the dataframes of events satisfying the quality checks equal? True

Example 2: Freddy request --> Check how many events have been detected inside the Bucaramanga nest¶

The request here is simple: Consider events that are earthquakes, have been occurred since 2026-03-17 and are between two lat-lon limits (called lat_min, lat_max, lon_min, lon_max and with values 6.0, 8.0, -74.0, -72.0). Moreover, it must have depths inside 120 km and 180 km.

IA TOML solution¶

With the context of the previous example, we can generate a TOML file for this case using the following simpler prompt:

Now create a TOML file that checks how many earthquakes have been detected inside the Bucaramanga nest. The Bucaramanga nest is defined by the following lat-lon limits: lat_min = 6.0, lat_max = 8.0, lon_min = -74.0, lon_max = -72.0. Moreover, the events must have depths between 120 km and 180 km and must have occurred since 2026-03-17.
Use the previously provided input_example.toml file as a guideline, and make sure to include the necessary parameters and configurations for the checks. The output should be a valid TOML file that can be used in the SieveComP application. The TOML file will be saved at the same location as the input_example.toml file (`docs/tutorials/IA_files`), and it will be named `check_earthquakes_in_bucaramanga_nest.toml`.

The answer provided by the AI follows:

I will now generate the TOML configuration file to filter earthquakes located within the Bucaramanga nest.

This configuration uses the numeric rule type with the between mode to elegantly define the spatial bounding box (latitude and longitude) and the depth limits. It also includes a temporal rule type to filter events from the specified start date.

[settings]
event_type_column = "event_type"
time_column = "time_value"
author_column = "creationInfo_author"


[output]
columns = [
"time_value",
"publicID",
"text",
"depth_value",
"magnitude_value",
"magnitude_type",
"quality_standardError",
"depth_uncertainty",
"latitude_uncertainty",
"longitude_uncertainty",
"quality_associatedPhaseCount",
"creationInfo_author",
"event_type",
"creationInfo_agencyID"
]


[[database]]
name       = "SeisComP6"
env_file   = "../../../examples/data/credentials/.env"
env_prefix = "SERVER_SC6"


[[queries]]
name       = "SeisComP6"
sql_file   = "seismic_revision_routine.sql"
description= "Query to retrieve events from SeisComP6 database"
database   = "SeisComP6"


[[checks]]
name = "Earthquakes inside Bucaramanga nest"
logic = "and"
event_type = "earthquake"

# 1. Latitude condition
[[checks.conditions]]
rule_type = "numeric"
column = "latitude_value"
mode = "between"
lower = 6.0
upper = 8.0

# 2. Longitude condition
[[checks.conditions]]
rule_type = "numeric"
column = "longitude_value"
mode = "between"
lower = -74.0
upper = -72.0

# 3. Depth condition
[[checks.conditions]]
rule_type = "numeric"
column = "depth_value"
mode = "between"
lower = 120.0
upper = 180.0

# 4. Temporal condition (Events since 2026-03-17)
[[checks.conditions]]
rule_type = "temporal"
column = "time_value"
mode = "ge"
threshold = "2026-03-17T00:00:00Z"

The file successfully combines all four rules using an and logic block. Since we don't need any polygons for this bounding box approach, the [[polygons]] section is safely omitted! Let me know if you want to add any other filters to this.

Remarks¶

Another successful TOML file generated by the AI model. The configuration uses the numeric rule type with the between mode to define the spatial bounding box (latitude and longitude) and the depth limits. It also includes a temporal rule type to filter events from the specified start date. The logic is set to "and" to ensure that all conditions must be met for an event to be included in the results. Pretty nice results, right? Now let's try to run the generated TOML file (with the SeisComP6 database) and check if it works as expected. We will use the Runner class to execute the checks defined in the TOML file and then analyze the results.

In [11]:
Copied!
runner_ai_2 = Runner(config_path='IA_files/check_earthquakes_in_bucaramanga_nest.toml')
result_ai_2 = runner_ai_2.run()
runner_ai_2 = Runner(config_path='IA_files/check_earthquakes_in_bucaramanga_nest.toml') result_ai_2 = runner_ai_2.run()
/tmp/ipykernel_12145/4113671035.py:2: UserWarning: No [[polygons]] entries found in the configuration. Spatial checks will not be available.
  result_ai_2 = runner_ai_2.run()
                                           
In [12]:
Copied!
print(f"Number of earthquakes inside Bucaramanga nest: {len(result_ai_2.checks)}")
print(f"Number of earthquakes inside Bucaramanga nest: {len(result_ai_2.checks)}")
Number of earthquakes inside Bucaramanga nest: 4778

Low level solution¶

The idea is to create a boolean mask for each condition and then combine them with an 'and' condition:

In [7]:
Copied!
# Filter events
events_2024 = events_df[events_df['time_value'] >= '2026-03-17']

# Filter events for 'earthquake'
events_eq_ve = events_2024[events_2024['event_type'].isin(['earthquake'])]

# Define limits
lat_min, lat_max, lon_min, lon_max = 6.0, 8.0, -74.0, -72.0

# Create a boolean mask for events inside the Bucaramanga nest
lat_mask = numeric_mask(
    events=events_eq_ve,
    column='latitude_value',
    mode='between',
    lower=lat_min,
    upper=lat_max
)

lon_mask = numeric_mask(
    events=events_eq_ve,
    column='longitude_value',
    mode='between',
    lower=lon_min,
    upper=lon_max
)

depth_mask = numeric_mask(
    events=events_eq_ve,
    column='depth_value',
    mode='between',
    lower=120.0,
    upper=180.0
)

combined_mask = combine_masks([lat_mask, lon_mask, depth_mask], logic="and")
bucaramanga_nest_events = events_eq_ve[combined_mask]

print(f'Total earthquakes inside Bucaramanga nest: {len(bucaramanga_nest_events)}')
# Filter events events_2024 = events_df[events_df['time_value'] >= '2026-03-17'] # Filter events for 'earthquake' events_eq_ve = events_2024[events_2024['event_type'].isin(['earthquake'])] # Define limits lat_min, lat_max, lon_min, lon_max = 6.0, 8.0, -74.0, -72.0 # Create a boolean mask for events inside the Bucaramanga nest lat_mask = numeric_mask( events=events_eq_ve, column='latitude_value', mode='between', lower=lat_min, upper=lat_max ) lon_mask = numeric_mask( events=events_eq_ve, column='longitude_value', mode='between', lower=lon_min, upper=lon_max ) depth_mask = numeric_mask( events=events_eq_ve, column='depth_value', mode='between', lower=120.0, upper=180.0 ) combined_mask = combine_masks([lat_mask, lon_mask, depth_mask], logic="and") bucaramanga_nest_events = events_eq_ve[combined_mask] print(f'Total earthquakes inside Bucaramanga nest: {len(bucaramanga_nest_events)}')
Total earthquakes inside Bucaramanga nest: 4778

As you can notice, the number of earthquakes inside the Bucaramanga nest is the same in both cases. This means that the AI-generated TOML file is working as expected and is equivalent to the low-level solution. To just be sure, let compare the entire dataframes of the events satisfying the quality checks in both cases. We will use the equals() method to check if the two dataframes are equal.

In [13]:
Copied!
# Compare the dataframes of events by checking the publicID column
df_ai_2 = result_ai_2.checks[['publicID']].sort_values(by='publicID').reset_index(drop=True)
df_low_level_2 = bucaramanga_nest_events[['publicID']].sort_values(by='publicID').reset_index(drop=True)
print(f"Are the dataframes of events satisfying the quality checks equal? {df_ai_2.equals(df_low_level_2)}")
# Compare the dataframes of events by checking the publicID column df_ai_2 = result_ai_2.checks[['publicID']].sort_values(by='publicID').reset_index(drop=True) df_low_level_2 = bucaramanga_nest_events[['publicID']].sort_values(by='publicID').reset_index(drop=True) print(f"Are the dataframes of events satisfying the quality checks equal? {df_ai_2.equals(df_low_level_2)}")
Are the dataframes of events satisfying the quality checks equal? True

Conclusions¶

Mind. Blown. We just witnessed a fundamental shift in how we interact with seismic data pipelines. By simply feeding our plain-English requirements into a language model, we generated deeply nested, syntactically perfect TOML configurations that executed lightning-fast quality checks—and the results matched our meticulously hand-crafted NumPy boolean masks event for event.

The implications here are absolutely wild. You not necessary need to memorize the SieveComP TOML schema.

Forget spending hours debugging whether your spatial condition requires a nested [[checks.groups]] node, or if your depth filter should use gt instead of ge. You don't have to painstakingly map out logic = "xor" bracket trees or manually type out twelve different volcanic observatory polygon paths. We just outsourced the steepest part of the learning curve directly to the AI.

SieveComP's architecture already gives you the raw, vectorized execution speed of a low-level framework, but combining it with AI-driven TOML generation gives you the ultimate superpower: coding at the speed of thought. Whether you are a hardened seismologist trying to filter out Bucaramanga nest events or a junior analyst looking for anomalous deep Caribbean quakes, you can now build, test, and deploy enterprise-grade data filters without writing a single line of boilerplate logic.

The underlying engine handles the math, the TOML provides the structure, and the AI acts as your personal translator. The barrier to entry for advanced seismic catalog validation just dropped to zero. Buckle up.

Previous Next

Built with MkDocs using a theme provided by Read the Docs.
KenethGarcia/SieveComP « Previous Next »