Files
LAS-stream-viewer/src/main/java/com/oiusa/las/service/ChannelRoles.java

154 lines
8.5 KiB
Java

package com.oiusa.las.service;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.oiusa.las.model.Curve;
import com.oiusa.las.model.ResolvedRole;
/**
* Maps the raw LAS channel mnemonics to standard drilling "roles" (ROP, WOB, total gas, stick-slip,
* …) so the UI can build a proper multi-track log plot instead of a wall of numbers.
*
* <p>Resolution is by exact mnemonic first (Pason names are stable), then a description-keyword
* fallback for robustness across exports. Whatever auto-resolves is just the default — the UI lets
* the engineer reassign any track to any of the file's curves.
*/
public final class ChannelRoles {
public record RoleDef(String key, String label, String group, String unit,
double defMin, double defMax, List<String> aliases) {}
/** group keys, in display order */
public static final List<String> GROUPS = List.of("index", "mechanics", "hydraulics", "gas", "directional");
// The role table. unit is expected unit (resolved curve's own unit wins for display); defMin/defMax
// are sensible physical scales so a single garbage spike can't flatten the real trace.
public static final List<RoleDef> ROLES = List.of(
// ---- index / state ----
new RoleDef("holeDepth", "Hole Depth", "index", "ft", 0, 25000, List.of("DEPT")),
new RoleDef("bitDepth", "Bit Depth", "index", "ft", 0, 25000, List.of("BDEP")),
new RoleDef("time", "Time", "index", "s", 0, 0, List.of("TIME")),
new RoleDef("tvd", "TVD", "index", "ft", 0, 15000, List.of("TVDHD", "TVDBD")),
new RoleDef("onBottom", "On Bottom", "index", "", 0, 1, List.of("ONBTM")),
// ---- drilling mechanics ----
new RoleDef("rop", "ROP", "mechanics", "ft/hr", 0, 300, List.of("ROP", "IROP", "OBR", "OROP")),
new RoleDef("wob", "WOB", "mechanics", "klbs", 0, 80, List.of("WOB", "ADWOB")),
new RoleDef("rpm", "Rotary RPM", "mechanics", "RPM", 0, 250, List.of("RPM", "TDROT")),
new RoleDef("bitRpm", "Bit RPM", "mechanics", "RPM", 0, 300, List.of("BR", "MTRPM")),
new RoleDef("torque", "Torque", "mechanics", "kft-lbf", 0, 50, List.of("TDTOR", "TOR", "BITOR")),
new RoleDef("mse", "MSE", "mechanics", "kpsi", 0, 60, List.of("MSED")),
new RoleDef("hookload", "Hook Load", "mechanics", "klbs", 0, 500, List.of("HL", "CSW", "STRWT")),
new RoleDef("blockHeight", "Block Height", "mechanics", "ft", 0, 140, List.of("BHT", "ADBLP")),
new RoleDef("diffPress", "Diff Press", "mechanics", "psi", 0, 2000, List.of("DIFP")),
new RoleDef("doc", "Depth of Cut", "mechanics", "in", 0, 1, List.of("DOC")),
new RoleDef("overpull", "Over Pull", "mechanics", "klbs", 0, 100, List.of("OVRP")),
// ---- hydraulics / well control ----
new RoleDef("spp", "Standpipe Press", "hydraulics", "psi", 0, 5000, List.of("SPP", "UFSPP")),
new RoleDef("flow", "Flow", "hydraulics", "%", 0, 100, List.of("FLOW", "FEST")),
new RoleDef("spm1", "Pump 1 SPM", "hydraulics", "SPM", 0, 150, List.of("SPM1")),
new RoleDef("spm2", "Pump 2 SPM", "hydraulics", "SPM", 0, 150, List.of("SPM2")),
new RoleDef("spmTotal", "Total SPM", "hydraulics", "SPM", 0, 400, List.of("SKTtl")),
new RoleDef("pumpOutput", "Pump Output", "hydraulics", "gpm", 0, 1200, List.of("TPO")),
new RoleDef("casingPress", "Casing Press", "hydraulics", "psi", 0, 3000, List.of("PCAS")),
new RoleDef("mudVolume", "Total Mud Vol", "hydraulics", "bbl", 0, 1500, List.of("MV", "SIMUD")),
new RoleDef("gainLoss", "Pit Gain/Loss", "hydraulics", "bbl", -50, 50, List.of("VTGL", "GLA1")),
new RoleDef("tripTank", "Trip Tank", "hydraulics", "bbl", 0, 200, List.of("MVTT", "MVTT1", "TTACC")),
// ---- mud gas / formation ----
new RoleDef("totalGas", "Total Gas", "gas", "%", 0, 100, List.of("PGAS", "3GAS", "WGASP")),
new RoleDef("c1", "C1 Methane", "gas", "ppm", 0, 50000, List.of("C1M")),
new RoleDef("c2", "C2 Ethane", "gas", "ppm", 0, 10000, List.of("C2M")),
new RoleDef("c3", "C3 Propane", "gas", "ppm", 0, 5000, List.of("C3M")),
new RoleDef("ic4", "iC4", "gas", "ppm", 0, 2000, List.of("IC4")),
new RoleDef("nc4", "nC4", "gas", "ppm", 0, 2000, List.of("NC4")),
new RoleDef("ic5", "iC5", "gas", "ppm", 0, 1000, List.of("IC5")),
new RoleDef("nc5", "nC5", "gas", "ppm", 0, 1000, List.of("NC5")),
new RoleDef("gamma", "Gamma", "gas", "gAPI", 0, 150, List.of("GAM", "GAMB")),
new RoleDef("h2s", "H2S", "gas", "ppm", 0, 100, List.of("H2S")),
// ---- directional & drilling dynamics ----
new RoleDef("incl", "Inclination", "directional", "deg", 0, 110, List.of("INCL", "DYNIN")),
new RoleDef("azi", "Azimuth", "directional", "deg", 0, 360, List.of("AZ", "DYNAZ")),
new RoleDef("toolface", "Tool Face", "directional", "deg", 0, 360, List.of("TF", "GTF", "MTF", "ATFAV")),
new RoleDef("stickSlip", "Stick-Slip", "directional", "%", 0, 100, List.of("SSSI", "DTSEA")),
new RoleDef("vibeAxial", "Axial Vibe", "directional", "g", 0, 10, List.of("DAVAM")),
new RoleDef("vibeLateral", "Lateral Vibe", "directional", "g", 0, 10, List.of("DAVLM")),
new RoleDef("vibeHfto", "HFTO Vibe", "directional", "g", 0, 10, List.of("DAVHM")),
new RoleDef("slideRotate", "Slide/Rotate", "directional", "", 0, 1, List.of("ASR"))
);
/** Physical default [min,max] display scale for a role key (0,0 if unknown / index role). */
public static double[] defaultScale(String key) {
for (RoleDef r : ROLES) if (r.key().equals(key)) return new double[]{ r.defMin(), r.defMax() };
return new double[]{ 0, 0 };
}
private ChannelRoles() {}
/** Resolve every role against the file's curves; missing roles are simply omitted. */
public static Map<String, ResolvedRole> resolve(List<Curve> curves) {
// index curves by upper mnemonic
Map<String, Curve> byMnem = new LinkedHashMap<>();
for (Curve c : curves) byMnem.put(c.mnemonic().toUpperCase(Locale.ROOT), c);
Map<String, ResolvedRole> out = new LinkedHashMap<>();
for (RoleDef r : ROLES) {
Curve hit = null;
for (String alias : r.aliases()) {
Curve c = byMnem.get(alias.toUpperCase(Locale.ROOT));
if (c != null) { hit = c; break; }
}
if (hit == null) hit = byDescription(curves, r);
if (hit != null) {
out.put(r.key(), new ResolvedRole(r.key(), r.label(), r.group(),
hit.mnemonic(), hit.unit(), hit.description(), hit.column()));
}
}
return out;
}
private static Curve byDescription(List<Curve> curves, RoleDef r) {
// very light keyword fallback derived from the role label
String kw = r.label().toLowerCase(Locale.ROOT);
for (Curve c : curves) {
String d = c.description() == null ? "" : c.description().toLowerCase(Locale.ROOT);
if (!d.isEmpty() && d.contains(kw)) return c;
}
return null;
}
/** Distinct data columns that a set of resolved roles needs (sorted ascending). */
public static int[] neededColumns(Map<String, ResolvedRole> roles, int... extra) {
java.util.TreeSet<Integer> set = new java.util.TreeSet<>();
for (ResolvedRole r : roles.values()) if (r.column() >= 0) set.add(r.column());
for (int e : extra) if (e >= 0) set.add(e);
int[] cols = new int[set.size()];
int i = 0;
for (int v : set) cols[i++] = v;
return cols;
}
/** Default track layout (ordered) used by the UI as the starting point. */
public static List<List<String>> defaultTracks() {
List<List<String>> t = new ArrayList<>();
t.add(List.of("gamma"));
t.add(List.of("rop"));
t.add(List.of("wob", "rpm"));
t.add(List.of("torque", "mse"));
t.add(List.of("spp", "flow"));
t.add(List.of("spmTotal", "pumpOutput"));
t.add(List.of("totalGas", "c1", "c2", "c3"));
t.add(List.of("gainLoss", "tripTank"));
t.add(List.of("incl", "azi"));
t.add(List.of("toolface"));
t.add(List.of("stickSlip", "vibeLateral", "vibeAxial"));
return t;
}
}