# Rai Pyrel Coding

> Covers PyRel v1 language syntax — imports, type system, concepts, properties, relationships, data loading, references, and code structure. Use when writing or reviewing general PyRel code — not query construction (see rai-querying), business-rule authoring via derived properties (see rai-rules-authoring), or optimization formulation with decision variables, constraints, and objectives (see rai-pr…

- **Type:** Skill
- **Install:** `agentstack add skill-relationalai-rai-agent-skills-rai-pyrel-coding`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [RelationalAI](https://agentstack.voostack.com/s/relationalai)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [RelationalAI](https://github.com/RelationalAI)
- **Source:** https://github.com/RelationalAI/rai-agent-skills/tree/main/plugins/rai/skills/rai-pyrel-coding
- **Website:** https://relational.ai

## Install

```sh
agentstack add skill-relationalai-rai-agent-skills-rai-pyrel-coding
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# PyRel Coding

## Summary

**What:** PyRel v1 language syntax — imports, types, concepts, properties, expressions, data loading, and standard library.

**When to use:**
- Writing or reviewing PyRel model code
- Need the correct import path, type syntax, or property declaration pattern
- Debugging a syntax error, `UninitializedPropertyException`, or empty/unexpected query results
- Checking what standard library functions are available (math, strings, dates)
- Looking up data loading patterns (CSV, Snowflake, FK resolution)
- Understanding expression rules (`.where()` targets, `.per()` grouping, operators)

**When NOT to use:** This is general-syntax support. Any **reasoner-specific** task routes to its reasoner skill — when the task is to query, classify, optimize, analyze a graph, or train a model, load that skill (it owns the patterns and pitfalls), not this one.

- Query construction (select, aggregation, filtering, joins) — see `rai-querying`
- Business-rule authoring via derived properties (validation, classification, segmentation, alerting) — see `rai-rules-authoring`
- Optimization formulation (decision variables, constraints, objectives) — see `rai-prescriptive-problem-formulation`
- Graph analysis (centrality, community, reachability, paths, impact/"what's affected if X fails") — see `rai-graph-analysis`
- GNN / predictive modeling and training (features, edges, training, evaluation) — see `rai-predictive-modeling`, `rai-predictive-training`
- RAI domain modeling decisions (when to create a concept vs property, gap classification) — see `rai-ontology-design`
- Connection and config setup — see `rai-setup`

**Overview:** Reference skill. Key lookup areas: Imports, Model Patterns, Type System, Concepts/Properties/Relationships, Data Loading, References and Aliasing, Standard Library (math/strings/dates), Expression Rules.

---

## Quick Reference

```python
# Imports
from relationalai.semantics import (
    Model, Float, Integer, String, Date, DateTime,
    count, sum, min, max, avg, product, stddev_samp, per, distinct,
)
from relationalai.semantics import Number  # Always use Number.size(p,s) — bare Number causes inference issues

# Model + concepts
model = Model("my_model")
Product = model.Concept("Product", identify_by={"id": Integer})

# Properties (concept → value) and Relationships (concept → concept)
Product.cost = model.Property(f"{Product} has {Float:cost}")
Product.supplier = model.Property(f"{Product} supplied by {Supplier:supplier}")

# Data loading
model.define(Product.new(model.data(df).to_schema()))

# Query — prefer model.where/model.select for multi-model safety
result = model.where(Product.cost > 10).select(Product.id, Product.cost).to_df()

# Debugging — print() shows readable PyRel structure, .inspect() materializes data
print(Product.cost)        # → Product.cost
print(Product.cost > 10)   # → Product.cost > 10
Product.cost.inspect()     # → executes query, prints DataFrame to stdout
```

For solver formulation (Problem, solve_for, satisfy, minimize/maximize), see `rai-prescriptive-problem-formulation`. For CSP-style formulation specifically (`Problem(model, Integer)` + `solver="minizinc"`, all-discrete decisions and data, globals, multi-solution enumeration), see `rai-prescriptive-problem-formulation/references/csp-formulation.md`.

---

## Imports

When you need imports beyond the common ones shown in Quick Reference (reasoners, `std` library, non-obvious aliases, or avoiding builtin shadowing), see [imports.md](references/imports.md).

---

## Model Patterns

```python
model = Model("my_model")
```

**Accepted parameters:** `name` (str), `config` (Config), `exclude_core` (bool), `is_library` (bool). Without `config`, auto-discovers `raiconfig.yaml`. Use `time_ns()` suffix only for throwaway models in automated pipelines. Shorthand: `Concept, Property = model.Concept, model.Property`.

**Always use `model.define()`/`model.where()`/`model.select()`** — standalone `define()`, `where()`, `select()` fail when multiple Models exist (`"Multiple Models have been defined."`). Generated code should always use the model method form.

---

## Type System

Types are imported objects, not strings. Used in Property f-strings and as type arguments to reasoner APIs.

| Type | Usage in Property | Python equivalent |
|------|-------------------|-------------------|
| `Float` | `{Float:cost}` | `float` |
| `Integer` | `{Integer:count}` | `int` |
| `String` | `{String:name}` | `str` |
| `Number.size(p,s)` | `{Number.size(38,4):price}` | `decimal(p,s)` |
| `Date` | `{Date:due_date}` | date |
| `DateTime` | `{DateTime:timestamp}` | datetime |
| `Boolean` | `{Boolean:active}` | `bool` |

**`Number.size(precision, scale)`** — always use with explicit parameters. `Number` alone (unparameterized) causes type inference issues and should be avoided. Always specify `Number.size(p, s)` (e.g., `Number.size(38, 4)` for Snowflake NUMBER columns). Do NOT call `Number(38, 4)` directly (that's concept invocation, not type construction).

**Deprecated alias:** `decimal` is a deprecated alias for `Number` — do not use it. Use `Number.size(p, s)` instead.

**Snowflake type alignment:** When loading from Snowflake tables, the property type must match the column's actual data type. Verify with `DESCRIBE TABLE` or `INFORMATION_SCHEMA.COLUMNS`. See [rai-ontology-design](../rai-ontology-design/SKILL.md) for the full mapping table and [rai-build-starter-ontology](../rai-build-starter-ontology/SKILL.md) for the validation workflow.

---

## Properties and Relationships — When to Use Which

The choice is about **multiplicity** — whether the association is many-to-one or many-to-many. Both support any arity (unary, binary, ternary+).

**Property** — functional dependency (uniqueness constraint). Inputs uniquely determine the output (at most one value). Use for **many-to-one**. Provides performance benefits and enforces the constraint.

**Relationship** — no uniqueness constraint (zero, one, or many outputs per input). Use for **many-to-many**. More flexible when cardinality is uncertain, but prefer establishing multiplicity.

| Pattern | Use Property | Use Relationship |
|---------|-------------|-----------------|
| Single-valued attribute | `Food` has one `Float:cost` | — |
| Concept → Concept (functional, e.g. FK) | `Order` placed by exactly one `Customer` | — |
| Concept → Concept (multi-valued) | — | `Parent` has many `Child` |
| Availability/membership (many-to-many) | — | `Worker` available for `Shift` |
| Unary flag (functional) | `Order` is rush order | — |
| Ternary with FD | `Food` contains `Nutrient` in `Float:qty` | — |
| Ternary without FD | — | `Food` contains `Nutrient` (many-to-many) |
| Association where cardinality is uncertain | — | Use Relationship — but prefer establishing multiplicity |

---

## Concepts

```python
# Identified concept (typed key — PREFERRED, use whenever possible)
Food = model.Concept("Food", identify_by={"name": String})
Stock = model.Concept("Stock", identify_by={"index": Integer})
Edge = model.Concept("Edge", identify_by={"i": Integer, "j": Integer})

# Basic concept (no identify_by — avoid unless extending primitives)
Product = model.Concept("Product")

# Subtype (inherits parent properties)
ActiveOrder = model.Concept("ActiveOrder", extends=[Order])
# Populate subtype membership with model.define():
model.define(ActiveOrder(Order)).where(Order.total > 75)
# Query through parent properties:
model.select(ActiveOrder.id, ActiveOrder.total)

# Extending a primitive type (no identify_by needed — identity comes from the primitive)
PositiveInt = model.Concept("PositiveInt", extends=[Integer])
```

- **Always include `identify_by` whenever possible.** It defines the natural key — entities with the same key values are the same instance. This prevents duplicate entities and makes identity explicit.
- Without `identify_by`, ALL parameters passed to `.new()` are used for the identity hash. This is fragile — adding or removing a `.new()` parameter changes entity identity.
- Composite keys use multiple entries: `identify_by={"i": Integer, "j": Integer}`.
- **`identify_by` auto-creates properties.** `Concept("Customer", identify_by={"customer_id": Integer})` automatically creates `Customer.customer_id` as a `Property(Integer)`. Do not declare a separate `model.Property()` for identity fields — it will create a duplicate.
- **`identify_by` supports concept types** — use for composite keys involving other concepts: `OrderItem = model.Concept("OrderItem", identify_by={"order": Order, "item": Item})`. This is standard for association/junction concepts.
- **Exception where `identify_by` is not used:** Extending primitive types (`extends=[Integer]`) — identity comes from the primitive value.
- **Introspection:** Prefer `inspect.schema(model)` from `relationalai.semantics.inspect` (v1.0.14+) — returns a frozen `ModelSchema` with concepts, inherited properties, types, and data sources. Use `inspect.to_concept(obj)` for reusable helpers that must accept any DSL handle (Chain, Ref, FieldRef, Expression). Lower-level `model.concepts` / `model.concept_index["Name"]` remain available as a fallback. See `rai-querying/references/inspect-module.md`.

---

## Properties

Properties use f-strings with type references. The type comes BEFORE the field name: `{Type:field}`. Use Property for **many-to-one (functional) associations** — scalar attributes, functional concept-to-concept FKs, and N-ary associations with a functional dependency.

```python
# Scalar value properties (concept → primitive)
Food.cost = model.Property(f"{Food} has {Float:cost}")
Worker.name = model.Property(f"{Worker} has {String:name}")

# Functional FK property (concept → concept, many-to-one)
Order.customer = model.Property(f"{Order} placed by {Customer:customer}")

# Multiarity property with value output (inputs uniquely determine the Float output)
Food.contains = model.Property(f"{Food} contains {Nutrient} in {Float:qty}")
```

**Canonical syntax (v1):** `model.Property(f"{Food} has {Float:cost}")` — f-string with type objects interpolated. Always use this form.

**Property name vs f-string verb:** The property name is the f-string field name (e.g., `qty` in `{Float:qty}`), not the verb (`contains`, `in`).

**Multi-argument (multiarity) properties:** Field names required when the same type appears multiple times to disambiguate inputs.

```python
Stock.covar = model.Property(f"{Stock:stock1} and {Stock:stock2} have {Float:covar}")       # Binary (same-type disambiguation)
ResourceGroup.inv = model.Property(f"{ResourceGroup} on day {Integer:t} has {Float:inv}")    # Time-indexed
Worker.assignment = model.Property(f"{Worker} has {Shift} if {Integer:assigned}")             # Multi-concept
```

**Scalar / standalone properties** (primitives only, no user-defined concepts): `bin_fast = model.Property(f"departure day {Integer:t} has {Float:bin_fast}")`

**Don't pass `short_name=` at all** — PyRel derives it from the LHS attribute name. If you do pass one that doesn't match (e.g. legacy prefixed names copied from older example files), declaration fails immediately with `RAIException: [Invalid short name] ... has a mismatching short_name "x". Either omit short_name or set it to ""`. The silent-call hazard remains too: invoking via an alternate name (`Concept.cf_foo(...)` instead of `Concept.foo(...)`) creates a parallel implicit Property, producing NaN-everywhere reads on queries and `UnresolvedType` / `[TyperError]` on joins. Default to never passing `short_name=`; it's needed only for `model.relationship_index[...]` lookup and same-type-slot disambiguation. See [common-pitfalls.md](references/common-pitfalls.md).

**Avoid reserved attribute names** — Property/Relationship names that collide with reserved attributes on `Concept` (`ref`, `new`, `select`, `where`, `define`, `filter_by`, `identify_by`, …) raise `[Reserved relationship name]` at declaration. Common bites: `ref` (git refs, FK ref columns), `new` (versioning flag). Rename the Concept-side attribute (`deploy_ref` instead of `ref`); the source column doesn't have to change. Full reserved list in [common-pitfalls.md](references/common-pitfalls.md) — defer to the actual PyRel error message for the authoritative set, which may shift across releases.

**Dynamic Property names need `setattr`.** Declaring via `model.Property(f"{Concept} has {Float:%s}" % attr)` registers the Property with that field name, but `getattr(Concept, attr)` may surface as `value type Any` in `solve_for(...)`. Bind the typed attribute explicitly: `setattr(Concept, attr, prop)`. Required for scenario sweeps and any pattern that programmatically generates Property names.

---

## Relationships

Relationships are **multi-valued** associations — zero, one, or many outputs per input. No uniqueness constraint. Use when an input can have many outputs, or the association has multiple fields.

```python
# Multi-valued concept-to-concept (one parent → many children)
Parent.has_child = model.Relationship(f"{Parent} has {Child}")

# Availability / membership (many-to-many)
Worker.available_for = model.Relationship(f"{Worker} is available for {Shift}")

# Functional concept-to-concept FKs use Property, NOT Relationship —
# see Properties section above. Example: Order.customer = model.Property(
#   f"{Order} placed by {Customer:customer}") because each Order has exactly one Customer.
```

**Scalar variables** (standalone floats/ints without a parent concept). Use for optimization variables not attached to any concept (e.g., NLP problems with just a few free variables):

```python
x = model.Relationship(f"{Float:x}")
y = model.Relationship(f"{Float:y}")
```

For how to bind these to a solver, see `rai-prescriptive-problem-formulation`.

**Global counts/values:** Assign to a Python variable (`NODE_COUNT = count(Node)`). Use a model Relationship only when the value must be available in downstream `define()` rules or solver expressions: `model.define(node_count(count(Node)))`.

**Bracket access:** Use `rel["field"]` to access named roles. See References and Aliasing section below.

**`Chain.ref()` and `.alt()`:** For independent chain traversals and inverse relationships, see [expression-rules.md](references/expression-rules.md#chainref-and-alt).

---

## Definitions

Definitions are the core of PyRel coding. They let you bake business logic and domain knowledge into the model so that every query and solver formulation can leverage it. Prefer putting logic in definitions over writing complex queries.

**`model.define()`** — declare facts, computed properties, derived relationships, and entity creation rules:

```python
# Computed property — derived from existing data
model.define(Order.total(Order.quantity * Order.unit_price))

# Boolean flag as unary relationship
model.define(Shipment.is_delayed()).where(Shipment.delay_days > 0)

# Derived relationship — named subset
# NOTE: aggregation needs an explicit join through the relationship
high_value = model.Relationship(f"High Value: {Customer}")
model.define(high_value(Customer)).where(
    aggs.sum(Order.total).where(Order.customer(Customer)).per(Customer) > 10000
)

# Entity creation from relationships
model.define(
    OrderItem.new(order=Order, item=Item)
).where(Order.contains(Item))

# Conditional / multi-branch definitions
model.define(Order.priority_label("high")).where(Order.total > 1000)
model.define(Order.priority_label("low")).where(Order.total =1.12`, as constants in every value position: kwargs (`Account.new(status=Status.ACTIVE)`, `Account.lookup(status=...)`), enum-typed property values, and prescriptive `solve_for()`/`satisfy()` expressions. Use instead of bare string comparisons when values are a fixed, closed vocabulary — and only where members are a clear readability or usability win; leave open-ended, data-driven string fields (entity names, free-form codes) as `String`.

```python
# Class-style:
class Priority(model.Enum):
    LOW = "low"
    MEDIUM

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [RelationalAI](https://github.com/RelationalAI)
- **Source:** [RelationalAI/rai-agent-skills](https://github.com/RelationalAI/rai-agent-skills)
- **License:** Apache-2.0
- **Homepage:** https://relational.ai

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-relationalai-rai-agent-skills-rai-pyrel-coding
- Seller: https://agentstack.voostack.com/s/relationalai
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
