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 emptyframe["well"] = well if bores == 0 else None # the one well, when no wellbore is namedframe["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 declaredframe["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.
