Deck records
ps.testing.records(path_or_text, *, includes="refuse") reads a native
deck, or deck text, as the ordered keyword occurrences it states and returns
a tuple of keyword records. A str holding a line break is deck text; any
other str or path names a file. Both are read as ps.load reads them,
and a deck it refuses is refused here with the same
ps.DeckImportError. It is a reader for consumers that check a file
against what they meant to write, the investigator’s effective-readback
among them. It is not an authoring door: no verb on the authoring surface
accepts a record, and a law keeps it that way.
The record
Section titled “The record”| field | what it holds |
|---|---|
name | the keyword as the deck spells it, upper case |
section | the section the occurrence stands in (RUNSPEC .. SCHEDULE), or None before the first section keyword |
records | the data records in deck order, each a tuple of int, float, str or None |
at | (file, line) of the keyword line, the included file’s own name when expansion was asked for |
lines | the verbatim body of a keyword the reader keeps as text (TITLE, UDQ, ACTIONX); such a record has no records |
The contract of a record
Section titled “The contract of a record”These are the scalar semantics the reader has always given the importer, named here as the contract so that a change to any of them is a declared change:
- a
n*valuerepeat is expanded tonvalues (2*0.2is(0.2, 0.2)); - a defaulted slot is
None, one per slot (1*is(None,);5*before a BHP is fiveNones); - a quoted word is the word without its quotes; a number is
intwhen the deck wrote an integer andfloatotherwise; - the trailing empty record that closes a record list (
WELSPECS,COMPDAT,WCONPRODand their kind) is not a record; - every occurrence is kept, duplicates included, in deck order: a keyword the deck states twice is two records, never one map entry that lost the first.
Content the deck skips (SKIP/ENDSKIP, anything after END) is not read.
ps.load stays the door for what a deck means; this door says only what it
states.
Includes
Section titled “Includes”includes="refuse", the default, refuses a deck that includes another file
with ps.DeckImportError whose code is deck.include.present,
naming the INCLUDE line; what comes back is therefore one self-contained
file. includes="expand" reads the included files in place, and each
record’s at names the file it came from. Text has no folder to include
from, so an INCLUDE in text is refused either way, and each record’s at
names the text <rendered deck>.
ps.io.deck_files(path) names the files themselves: the deck and every file
its INCLUDE stanzas read, in the order they are opened, each as the deck’s
text names it from the deck’s folder, with the sha256 of its bytes.
import petroscript as ps
records = ps.testing.records("opm-tests/spe1/SPE1CASE1.DATA")welspecs = [record for record in records if record.name == "WELSPECS"]assert [record.section for record in welspecs] == ["SCHEDULE"]assert welspecs[0].records[0][:4] == ("PROD", "G1", 10, 10)assert welspecs[0].at[1] == 384PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt