Skip to content

Formation tops in another application

This recipe reads formation tops and writes them as CSV for another application (a spreadsheet, a petrophysics package, your own tool). client is an ophiolite.Client for your project — see Build on Ophiolite for a key and the SDK.

tops_asset = next(a for a in client.assets() if a["name"] == "Formation tops")
tops = client.read_data(tops_asset["asset_id"], tops_asset["revision"])
tops.to_frame().to_csv("tops.csv", index=False)
print(open("tops.csv").read())

tops.csv has one row per top: name, md (measured depth), tvd (true vertical depth, empty when the source did not give it) and source. Depth units and the depth reference are the source’s declarations (tops.context); nothing is converted. Keep tops_asset["revision"] with the file if the other application needs to know exactly which version it received.

Most other applications also want the wellbore (or the well), the depth unit and the depth basis on every row. They are not columns of the file: the unit and basis are declarations in tops.context, and the wellbore or well is the one this version of the file is associated with. Add them (from 2026.10.22, client.associations() names the well or wellbore of every file at once). Name one only when there is exactly one; never pick the first of several:

frame = tops.to_frame()
mine = [i for i in client.associations(assets=[tops_asset["asset_id"]]) if i["revision"] == tops_asset["revision"]]
def only(kind):
found = {i["entity"]["entity_id"]: i["entity"]["name"] for i in mine if i["entity"]["kind"] == kind}
return next(iter(found.values())) if len(found) == 1 else None, len(found)
bore, bores = only("wellbore")
well, wells = only("well")
frame["wellbore"] = bore # the one wellbore this version names, else empty
frame["well"] = well if bores == 0 else None # the one well, when no wellbore is named
frame["association"] = ("wellbore" if bore else "well" if frame["well"].notna().all()
else "several" if bores + wells > 1 else "none")
frame["depth_unit"] = tops.context["depth_unit"] # as declared, e.g. "M"
frame["depth_basis"] = tops.context["depth_basis"] # "same-as-log", "other" or "unknown", as declared
frame["revision"] = tops_asset["revision"]
frame.to_csv("tops.csv", index=False)

same-as-log is what the file’s provider declared: the depths are measured from the same reference as that wellbore’s (or well’s) logs. Ophiolite does not link the file to a particular log and converts nothing. If the other application needs the reference point itself (a kelly bushing elevation, say), ask the file’s provider which log or datum they meant before using one; do not assume a log’s header applies.