moonwell

    LAS 2.0 well-log integrity, gap-aware resampling and provenance-preserving multi-run assembly

    las
    well-log
    geoscience
    quality-control
    depth-alignment
    Download zip
    Version
    0.3.2
    License
    MIT
    Last updated
    16 days ago
    Downloads
    61

    No dependencies

    #MoonWell

    A pure MoonBit library for LAS 2.0 well-log integrity and provenance-preserving multi-run assembly. MIT licensed, module 2200732518gjy/moonwell at 0.3.2.

    MoonWell serves acquisition QA, research preprocessing, and archival integration. Its central workflow is not just a LAS converter: independent runs are converted to explicit target units, aligned onto a bounded depth grid, and resolved under an explicit conflict policy with per-cell source indexes and interpolation flags.

    #Install and import

    two-step consumer setup:

    moon new consumer cd consumer moon add 2200732518gjy/moonwell@0.3.2

    Add to the consumer package manifest:

    import { "2200732518gjy/moonwell" @well }

    Example inside a raising function or try/catch:

    let log = @well.make_log(
    [{name:"DEPT",unit:"M",api_code:"",description:"depth"},
    {name:"GR",unit:"GAPI",api_code:"45 310 01 00",description:"gamma ray"}],
    [[Some(0.0),Some(10.0)], [Some(2.0),Some(30.0)]],
    )
    let table = @well.resample(log, @well.depth_grid(0.0,2.0,0.5), 2.0)
    let assembled = @well.assemble([log,table], ["original","resampled"],
    [0.0,1.0,2.0], 2.0, Reject)
    let report = @well.assembly_report(assembled)

    The complete executable in examples/main.mbt exercises three input→processing→output workflows using public interfaces only. pkg.generated.mbti is the generated public API.

    #Capabilities

    • Strict finite decimal grammar, bounded LAS section state machine, required VERS/WRAP/STRT/STOP/STEP/NULL metadata, rectangular ASCII records, wrapped and unwrapped decoding, sentinel→optional samples.
    • Snapshot-owned rectangular tables with non-null, unique, monotonic depth in either direction; case-insensitive curve mnemonics; ascending/window/select transforms.
    • Canonical LAS writer recomputes grid headers and refuses sentinel collisions; numerical CSV has quoted headers and blank missing cells.
    • Binary-search adjacent-endpoint interpolation, explicit maximum gap, no extrapolation or null-endpoint bridging; bounded grids and missing-span fill.
    • Explicit dimensional conversions: length, voltage, density, temperature and slowness. Unknown units may pass identically, never guessed.
    • Null-aware moments and type-7 quantiles; clipped trapezoidal interval integrals with explicit coverage; deterministic range, gradient, constant-run, missing and depth-gap findings.
    • Gap-stopping median filter, gradients, masks, affine transforms and validated derived curves.
    • Multi-run union of curve names, unit-aware depth alignment, reject/first/last conflict policies, absolute tolerance, complete cell lineage and conflict reports.
    • Versioned JSON QA/interval/assembly reports and a strict JSON command protocol implemented in MoonBit.

    #IO-only Node CLI

    Node 24 is used for file IO only; it does not parse LAS, compute statistics or decide conflicts.

    moon build --target js --release --deny-warn node tools/moonwell.mjs fixtures/qa.json node tools/moonwell.mjs fixtures/assemble.json result.json node tools/test-cli.mjs

    One JSON request file produces one envelope: {"ok":true,"result":...} or {"ok":false,"error":"..."}. Output files use exclusive creation and never overwrite. Exit 0: successful envelope; 1: domain error envelope; 2: adapter/IO failure. The adapter returns error messages, not stack traces.

    Commands: qa, normalize, csv, resample, interval, assemble, validate-las2, archive-las2, strict-normalize. See docs/protocol.md for exact fields. The library, not the file adapter, is the published integration surface.

    #Verify

    moon info moon fmt moon fmt --check moon check --target wasm-gc --deny-warn moon test --target wasm-gc --deny-warn moon build --target wasm-gc --release --deny-warn moon run examples --target wasm-gc --deny-warn python tools/audit.py

    Repeat target checks/tests/build/examples with wasm, js and native. Native requires a system C compiler (gcc/clang). Original v0.1.0 local Windows evidence: 40/40 tests per wasm-gc, wasm and js, strict builds and examples, CLI acceptance, 32/32 synthetic numerical differential cases against lasio 0.32. Local native strict check passed; execution requires the missing system C compiler, so the four-target Linux CI is the native execution gate. CI pins the official development compiler bundle 0.10.14+7d59c7ec9 (the archive uses the compiler version, not the standalone moon CLI version) and records its actual toolchain. No local native execution is claimed.

    Optional independent oracle:

    python -m pip install lasio==0.32 numpy==2.3.5 python tools/differential.py

    The original v0.1.0 production implementation contains 2042 nonblank, noncomment MoonBit lines before any later repairs; tests, generated interfaces, examples and bridge excluded. tools/audit.py recomputes the count and commit threshold; nonempty commits are additionally reviewed for coherent engineering changes, not artificially split history.

    #Scope and cautions

    LAS2 conformance is now an explicit path, frozen to CWLS January 2014, not a latest-edition or third-party certification claim. Use read_las_document + check_las2 for structured standard/resource diagnostics and document_text for exact original text (including comments, duplicate names and layout). Use parse_strict / document_to_log for validated numerical conversion and write_las_strict for revalidated standard output. Legal documents that cannot fit the unique-column finite-Double model raise model: instead of being declared illegal or silently losing columns. Strict writing expands exponents, uses exact decimal grid checks, and never fabricates missing well metadata or rounds observations. See the implemented rules, budgets, examples and conformance gate.

    The original parse / write_las are compatibility APIs, not conformance assertions. Canonical writing is not an exact archive. Empty tables are valid in memory but cannot be written to LAS. LAS3, DLIS/LIS, comma decimals and proprietary dialects are outside this release. Arbitrary-precision source decimals are preserved in the document but numerical analysis remains finite Double. No curve alias inference, depth-shift optimization, reservoir interpretation, drilling advice, safety decisions, geological inference or thermodynamic model is provided. QA thresholds and units remain the caller's responsibility. Equal-within-tolerance observations retain the earlier source even under PreferLast. Conflict records identify the previous/incoming observations before policy application. Generated depth cells deliberately have no run owner.

    Arrays are readable and mutable; constructors snapshot inputs, transformations rebuild validated copies, and report writers revalidate their input. Do not mutate objects concurrently. Use numerical CSV in data pipelines, not as a safe spreadsheet-formula format for untrusted run names.

    See architecture, security, references and AI disclosure, bounded duplication review, and changelog. Application/registration materials are deliberately absent from the source package.

    #0.2.0 migration and standards cases

    Curve record literals now require api_code: "" when the optional LAS code is absent. This intentional source incompatibility is why the release is 0.2.0 rather than a replacement of 0.1.0. Metadata descriptions must not contain colons: LAS uses the last colon, so embedded colons belong in Header.value (or opaque curve data), not in the description. Existing 0.1.0 code using first-colon semantics must migrate explicitly; no silent repair is applied.

    Log.version_headers contains only additional version headers. Log.extra_sections stores uninterpreted customer sections; transformations copy their contents, but do not interpret or update embedded vendor depth references. Assembly retains the first run's well/parameter/customer metadata and merges curve API definitions: a later nonempty code fills an empty one; two different nonempty codes are rejected, independent of sample conflict policy. API codes are not a registry lookup or evidence of measurement correctness.

    moon run examples/compliance --target wasm-gc --deny-warn node tools/moonwell.mjs fixtures/las2-api-codes.json python tools/verify-las2.py # optional lasio 0.32 metadata oracle

    #0.3.2 strict document workflows

    let document = @well.read_las_document(source)
    let diagnostics = @well.check_las2(document)
    // Empty diagnostics means the declared 2014 baseline passes within budgets.
    let original = @well.document_text(document) // exact source String, never regenerated
    let log = @well.document_to_log(document) // can raise model: for valid documents
    let output = @well.write_las_strict(log, wrapped=true)

    The complete strict public example covers validation, both WRAP outputs, duplicate-name archival/model rejection and structured missing-header diagnostics. A source capture alone never implies validity. Numeric-model output may not preserve arbitrary decimal spelling: retain the document as the authoritative archive.

    moon run examples/strict --target wasm-gc --deny-warn moon build --target js --release --deny-warn python tools/make-conformance-corpus.py python tools/verify-conformance.py

    The deterministic 105-case corpus has 27 positives and 78 negatives. Independent Python structure/Decimal and MoonBit verdicts agree; all 105 exact archives match; 22 numerical-model positives survive strict normalization and independent revalidation; 5 standard-valid model-limited cases explicitly reject conversion. This cross-check is not CWLS Certify certification. CI runs it alongside the 32 numerical and 6 metadata lasio oracle cases, tests/builds and all public examples on wasm-gc/wasm/js/native. Windows has no local native C compiler, so native execution is verified in Linux CI, not claimed locally.

    Local 0.3.2 measurements: 2988 nonblank/noncomment production MoonBit lines (tests, examples, bridge, generated interfaces excluded); 79/79 tests on each of wasm-gc/wasm/js, strict checks, release builds, public workflows and real CLI acceptance passed. Native strict check passed locally; actual native execution is a CI requirement.

    WellError

    pub(all) suberror WellError {
    WellError(String)
    }

    Invalid input, unsupported format or unsafe operation. Messages include context.

    Assembly

    pub struct Assembly {
    log : Log
    sources : Array[Array[CellSource?]]
    conflicts : Array[Conflict]
    run_names : Array[String]
    }

    CellSource

    pub struct CellSource {
    run : Int
    interpolated : Bool
    }

    Conflict

    pub struct Conflict {
    row : Int
    curve : String
    depth : Double
    kept_run : Int
    incoming_run : Int
    kept_value : Double
    incoming_value : Double
    }

    ConflictPolicy

    pub(all) enum ConflictPolicy {
    Reject
    PreferFirst
    PreferLast
    }

    Curve

    pub(all) struct Curve {
    api_code : String
    name : String
    unit : String
    description : String
    }

    DataLine

    pub struct DataLine {
    number : Int
    text : String
    }

    pub(all) struct Header {
    name : String
    unit : String
    value : String
    description : String
    }

    IntervalSummary

    pub struct IntervalSummary {
    start : Double
    stop : Double
    covered : Double
    coverage : Double
    integral : Double
    weighted_mean : Double?
    segments : Int
    }

    Issue

    pub struct Issue {
    code : String
    curve : String
    row : Int
    depth : Double
    message : String
    }

    LasDiagnostic

    pub(all) struct LasDiagnostic {
    kind : String
    clause : String
    section : String
    line : Int
    field : String
    message : String
    }

    LasDocument

    pub struct LasDocument {
    source : String
    }

    Immutable source archive. Retains exact line endings, comments, duplicates, section order and numeric spelling; construction is not a conformance claim.

    LasEntry

    pub(all) struct LasEntry {
    section : String
    line : Int
    header : Header
    }

    LasHeader

    pub struct LasHeader {
    curves : Array[Curve]
    well : Array[Header]
    parameters : Array[Header]
    other : Array[String]
    version_headers : Array[Header]
    extra_sections : Array[LasSection]
    data : Array[DataLine]
    wrapped : Bool
    null_value : Double
    }

    LasSection

    pub(all) struct LasSection {
    title : String
    lines : Array[String]
    }

    An uninterpreted customer section. Title includes the leading tilde.

    Log

    pub struct Log {
    curves : Array[Curve]
    rows : Array[Array[Double?]]
    headers : Array[Header]
    parameters : Array[Header]
    other : Array[String]
    version_headers : Array[Header]
    extra_sections : Array[LasSection]
    null_value : Double
    }

    A rectangular table; column zero is non-null strictly monotonic depth. Arrays are owned snapshots at construction. Call validate after external mutation.

    MissingSpan

    pub struct MissingSpan {
    first_row : Int
    last_row : Int
    start_depth : Double
    stop_depth : Double
    count : Int
    bounded : Bool
    }

    QcRule

    pub(all) struct QcRule {
    curve : String
    minimum : Double?
    maximum : Double?
    max_gradient : Double?
    constant_run : Int
    }

    Summary

    pub struct Summary {
    present : Int
    missing : Int
    coverage : Double
    minimum : Double?
    maximum : Double?
    mean : Double?
    variance : Double?
    median : Double?
    }

    affine_curve

    fn affine_curve(log : Log, name : String, scale : Double, offset : Double) -> Log raise WellError

    Affine value transform; depth uses dedicated dimensional conversion instead.

    append_curve

    fn append_curve(log : Log, curve : Curve, values : Array[Double?]) -> Log raise WellError

    Add an explicitly described derived column, validating all input and output.

    ascending

    fn ascending(log : Log) -> Log raise WellError

    Return an ascending owned copy, never sorting arbitrary invalid depth reversals.

    assemble

    fn assemble(runs : Array[Log], names : Array[String], grid : Array[Double], max_gap : Double, policy : ConflictPolicy, tolerance? : Double) -> Assembly raise WellError

    Assemble named runs on an explicit common grid. The first run sets target units. Case-insensitive curve identity, no alias guessing, source retained per non-null cell.

    assembly_report

    fn assembly_report(assembly : Assembly) -> Json raise WellError

    Contains complete per-cell sources, including explicit null and synthetic depth cells.

    check_las2

    fn check_las2(document : LasDocument) -> Array[LasDiagnostic]

    CWLS January 2014 structural and exact-decimal validator, independent of Log. An empty diagnostic array means this baseline passes within stated budgets, not latest-edition certification or correctness of geological content.

    convert_curve

    fn convert_curve(log : Log, name : String, unit : String) -> Log raise WellError

    convert_value

    fn convert_value(value : Double, from : String, to : String) -> Double raise WellError

    Unknown units may pass through identically, but are never guessed or relabelled.

    curve_index

    fn curve_index(log : Log, name : String) -> Int raise WellError

    depth_grid

    fn depth_grid(start : Double, stop : Double, step : Double, limit? : Int) -> Array[Double] raise WellError

    Construct a finite ascending uniform grid, explicitly bounded before allocation.

    depths

    fn depths(log : Log) -> Array[Double] raise WellError

    dispatch

    fn dispatch(request : Json) -> Json raise WellError

    Stateless JSON protocol. All domain work is implemented here, not in the IO adapter.

    document_entries

    fn document_entries(document : LasDocument) -> Array[LasEntry] raise WellError

    Ordered header entries, including duplicate names. Use check_las2 first if conformance matters; an unparseable header is an error, never silently dropped.

    document_text

    fn document_text(document : LasDocument) -> String

    Exact original text, never a regenerated interpretation of the source.

    document_to_log

    fn document_to_log(document : LasDocument) -> Log raise WellError

    Strict standard validation is separate from the unique-column finite-Double analysis model. Legal duplicates/extreme numbers remain available in source.

    execute

    fn execute(text : String) -> String

    A request yields exactly one JSON envelope, including recoverable input errors.

    fill_constant

    fn fill_constant(log : Log, name : String, value : Double) -> Log raise WellError

    Explicit constant fill, never applying to the depth index.

    fill_linear

    fn fill_linear(log : Log, name : String, max_gap : Double) -> Log raise WellError

    Fill only bounded null runs whose supporting endpoint distance is within max_gap. Does not interpolate already-present values, extend edges, or bridge large gaps.

    gradient_curve

    fn gradient_curve(log : Log, name : String, output_name : String, max_gap : Double) -> Log raise WellError

    Backward adjacent difference / signed depth increment. First row is missing. New curve unit is an explicit ratio; no domain interpretation is attached.

    interval_report

    fn interval_report(summary : IntervalSummary) -> Json

    las2_report

    fn las2_report(document : LasDocument) -> Json

    Returns structured diagnostics; compatibility parse() never implies this check.

    las_decimal_text

    fn las_decimal_text(value : Double) -> String raise WellError

    Expand the shortest round-trippable Double spelling without exponent notation. Does not round to a chosen precision or change observed values.

    make_log

    fn make_log(curves : Array[Curve], rows : Array[Array[Double?]], headers? : Array[Header], parameters? : Array[Header], other? : Array[String], null_value? : Double, version_headers? : Array[Header], extra_sections? : Array[LasSection]) -> Log raise WellError

    mask_range

    fn mask_range(log : Log, name : String, minimum : Double, maximum : Double) -> Log raise WellError

    Null samples outside inclusive bounds rather than silently clamping outliers.

    median_filter

    fn median_filter(log : Log, name : String, radius : Int, max_gap : Double) -> Log raise WellError

    Median neighborhood in row space, stopping at depth gaps and null samples. Missing centers remain missing; edge neighborhoods are clipped, not padded.

    missing_spans

    fn missing_spans(log : Log, name : String) -> Array[MissingSpan] raise WellError

    Contiguous missing samples in acquisition order, including leading/trailing spans.

    parse

    fn parse(text : String) -> Log raise WellError

    Decode LAS2 ASCII. Wrapped records must begin with a depth-only line.

    parse_header

    fn parse_header(line : String) -> Header raise WellError

    Parse mnemonic.unit value : description. The last colon separates description (CWLS 2014 section 5.2).

    parse_number

    fn parse_number(token : String) -> Double raise WellError

    Strict decimal numbers with optional E exponent; no NaN/Infinity/hex/underscores.

    parse_strict

    fn parse_strict(text : String) -> Log raise WellError

    provenance_csv

    fn provenance_csv(assembly : Assembly) -> String raise WellError

    Long-form provenance CSV; null cells omitted and synthetic depth has no run owner.

    quality_check

    fn quality_check(log : Log, rules : Array[QcRule], max_depth_gap : Double) -> Array[Issue] raise WellError

    Rules run in caller order, rows in acquisition order. No interpretations inferred.

    quality_report

    fn quality_report(log : Log, rules : Array[QcRule], max_depth_gap : Double) -> Json raise WellError

    Versioned machine report. Statistics remain distinct from rule-based QC findings.

    quantile

    fn quantile(log : Log, name : String, probability : Double) -> Double? raise WellError

    Type-7 linear quantile, ignoring missing samples; rejects invalid probabilities.

    read_header

    fn read_header(text : String) -> LasHeader raise WellError

    Parse the bounded section state machine. No data is decoded at this stage.

    read_las_document

    fn read_las_document(text : String) -> LasDocument raise WellError

    Bounded capture, including invalid documents for diagnostics and repair.

    read_report

    fn read_report(text : String) -> Json raise WellError

    Bounded report decoder for downstream integrations.

    rename_curve

    fn rename_curve(log : Log, name : String, replacement : String) -> Log raise WellError

    Rename metadata without permitting ambiguity or changing numerical values.

    report_text

    fn report_text(report : Json) -> String

    resample

    fn resample(log : Log, grid : Array[Double], max_gap : Double) -> Log raise WellError

    Output grid must be finite, unique and ascending; preserves explicit null spans.

    sample

    fn sample(log : Log, name : String, depth : Double, max_gap : Double) -> Double? raise WellError

    Interpolate only between adjacent valid endpoints within max_gap, never extrapolate.

    select

    fn select(log : Log, names : Array[String]) -> Log raise WellError

    Select curves in requested order; depth remains mandatory and first.

    summarize

    fn summarize(log : Log, name : String) -> Summary raise WellError

    Population variance (not sample variance), with scaled Welford accumulation.

    summarize_interval

    fn summarize_interval(log : Log, name : String, start : Double, stop : Double, max_gap : Double) -> IntervalSummary raise WellError

    Integrate clipped adjacent segments with valid endpoints, no bridging nulls/gaps.

    validate

    fn validate(log : Log) -> Unit raise WellError

    validate_assembly

    fn validate_assembly(assembly : Assembly) -> Unit raise WellError

    Guard reports against caller mutation of the exposed arrays.

    window

    fn window(log : Log, low : Double, high : Double) -> Log raise WellError

    Inclusive depth window, preserving acquisition order and all curve metadata.

    write_csv

    fn write_csv(log : Log, include_units? : Bool) -> String raise WellError

    CSV with quoted mnemonic/unit header, CRLF records and empty missing cells. Intended for numerical pipelines, not spreadsheet formulas or LAS roundtrips.

    write_las

    fn write_las(log : Log, wrapped? : Bool) -> String raise WellError

    Canonical numerical LAS2 output with opaque curve API codes. Not a full certifier.

    write_las_strict

    fn write_las_strict(log : Log, wrapped? : Bool) -> String raise WellError

    Refuses absent well metadata, off-grid decimal coordinates and other standard violations. Never fabricates company/location/identifier or rounds observations.