@welldot/core 0.4.0-next.20261014.26249fd → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +14 -4
  2. package/dist/index.cjs +51 -2
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +20 -1
  5. package/dist/index.d.ts +20 -1
  6. package/dist/index.js +50 -2
  7. package/dist/index.js.map +1 -1
  8. package/examples/README.md +83 -0
  9. package/examples/belem-pa-condominio-outorga.well +961 -0
  10. package/examples/belem-pa-monitoramento-posto.well +1141 -0
  11. package/examples/belem-pa-pirabas-abastecimento.well +1751 -0
  12. package/examples/belem-pa-poco-colmatado.well +496 -0
  13. package/examples/belem-pa-poco-raso-abandonado.well +388 -0
  14. package/examples/belem-pa-qualidade-agua-acida.well +895 -0
  15. package/examples/colatina-es-gnaisse-rural.well +1047 -0
  16. package/examples/coluna-mg-cristalino-abastecimento.well +1437 -0
  17. package/examples/estrela-doeste-sp-bauru-serra-geral.well +919 -0
  18. package/examples/extremoz-rn-barreiras-raso.well +623 -0
  19. package/examples/gastao-vidigal-sp-dados-inconsistentes.well +222 -0
  20. package/examples/index.json +452 -0
  21. package/examples/irece-ba-carste-bambui.well +861 -0
  22. package/examples/lagoa-santa-mg-carste-abastecimento.well +1087 -0
  23. package/examples/minas-novas-mg-cadastro-incompleto.well +157 -0
  24. package/examples/quixeramobim-ce-cristalino-salobro.well +989 -0
  25. package/examples/rinopolis-sp-estratigrafico-guarani.well +287 -0
  26. package/examples/santarem-pa-jorrante-alter-do-chao.well +554 -0
  27. package/examples/sao-gabriel-rs-cacimba.well +360 -0
  28. package/examples/sete-lagoas-mg-carste-industrial.well +410 -0
  29. package/examples/thumbs/belem-pa-condominio-outorga.svg +1 -0
  30. package/examples/thumbs/belem-pa-monitoramento-posto.svg +1 -0
  31. package/examples/thumbs/belem-pa-pirabas-abastecimento.svg +1 -0
  32. package/examples/thumbs/belem-pa-poco-colmatado.svg +1 -0
  33. package/examples/thumbs/belem-pa-poco-raso-abandonado.svg +1 -0
  34. package/examples/thumbs/belem-pa-qualidade-agua-acida.svg +1 -0
  35. package/examples/thumbs/colatina-es-gnaisse-rural.svg +1 -0
  36. package/examples/thumbs/coluna-mg-cristalino-abastecimento.svg +1 -0
  37. package/examples/thumbs/estrela-doeste-sp-bauru-serra-geral.svg +1 -0
  38. package/examples/thumbs/extremoz-rn-barreiras-raso.svg +1 -0
  39. package/examples/thumbs/gastao-vidigal-sp-dados-inconsistentes.svg +1 -0
  40. package/examples/thumbs/irece-ba-carste-bambui.svg +1 -0
  41. package/examples/thumbs/lagoa-santa-mg-carste-abastecimento.svg +1 -0
  42. package/examples/thumbs/minas-novas-mg-cadastro-incompleto.svg +1 -0
  43. package/examples/thumbs/quixeramobim-ce-cristalino-salobro.svg +1 -0
  44. package/examples/thumbs/rinopolis-sp-estratigrafico-guarani.svg +1 -0
  45. package/examples/thumbs/santarem-pa-jorrante-alter-do-chao.svg +1 -0
  46. package/examples/thumbs/sao-gabriel-rs-cacimba.svg +1 -0
  47. package/examples/thumbs/sete-lagoas-mg-carste-industrial.svg +1 -0
  48. package/package.json +5 -3
package/README.md CHANGED
@@ -26,6 +26,10 @@ The format is designed for three use cases: visualization of technical well prof
26
26
 
27
27
  See the [v2 format specification](./docs/spec/v2/overview.md) (current revision: v2.3) for the complete schema reference and design rationale. The [v1 spec](./docs/spec/v1/well-format.md) remains available for reference.
28
28
 
29
+ ### Example wells
30
+
31
+ The package ships 14 complete, valid `.well` files in [`examples/`](./examples/README.md). They are built on real wells from the Brazilian SIAGAS registry, with illustrative names, permits, analyses and operating data. Together they cover crystalline, karst, sedimentary, deep, flowing, hand-dug and monitoring wells, plus incomplete and inconsistent records. Import them as `@welldot/core/examples/<file>`, or download them from `https://welldot.org/examples/<file>`.
32
+
29
33
  ## Installation
30
34
 
31
35
  ```bash
@@ -50,6 +54,7 @@ import {
50
54
  parseWell,
51
55
  serializeWell,
52
56
  deserializeWell,
57
+ hasWellData,
53
58
  isWellEmpty,
54
59
  } from '@welldot/core';
55
60
 
@@ -65,8 +70,9 @@ const jsonString = serializeWell(well);
65
70
  // Deserialize — handles versioned format and legacy formats
66
71
  const restored = deserializeWell(jsonString);
67
72
 
68
- // Check if a well contains any data
73
+ // Check if a well has a profile to draw, or holds any data at all
69
74
  const empty = isWellEmpty(well);
75
+ const hasData = hasWellData(well);
70
76
  ```
71
77
 
72
78
  ## API Reference
@@ -219,11 +225,11 @@ Each schema validates its corresponding type at runtime. All schemas are Zod obj
219
225
 
220
226
  #### `parseWell(json: string): Well`
221
227
 
222
- Parses a raw JSON string and validates it against `WellSchema`. Throws a `ZodError` on validation failure. Does not handle legacy formats — use `deserializeWell` for that.
228
+ Parses a raw JSON string and validates it against `WellSchema`. Throws a `ZodError` on validation failure. Unknown members (`x-` fields and blocks) are kept at every level, including inside array items. Does not handle legacy formats — use `deserializeWell` for that.
223
229
 
224
230
  #### `serializeWell(well: Well): string`
225
231
 
226
- Serializes a `Well` object to a versioned `.well` JSON string (`"version": 2`). Emits `location` when set; does not emit deprecated flat `lat`/`lng`/`elevation`. Omits `undefined` optional fields.
232
+ Serializes a `Well` object to a versioned `.well` JSON string (`"version": 2`). Emits `location` when set; does not emit deprecated flat `lat`/`lng`/`elevation` or other legacy v1 keys. Omits `undefined` optional fields. Root members the spec does not define (`x-` fields and blocks, foreign members) are written unchanged after the spec blocks, as the spec requires.
227
233
 
228
234
  #### `deserializeWell(jsonString: string): Well | null`
229
235
 
@@ -241,13 +247,17 @@ Returns `null` if the parsed data is empty. Throws on unrecognized version numbe
241
247
 
242
248
  Returns `true` when all constructive and geologic arrays are empty. Accepts `null` and `undefined` (both treated as empty).
243
249
 
250
+ #### `hasWellData(well: Well | null | undefined): boolean`
251
+
252
+ Returns `true` when the well holds any data: a profile, or any other filled root member (identification, location, events, samples, permits, `x-` members, …). A registry record without construction or geology has data although `isWellEmpty` says it has nothing to draw. Blank strings, zeros and objects holding only those do not count.
253
+
244
254
  #### `calculatedWellDepth(well: Well): number`
245
255
 
246
256
  Deepest `to` across the constructive arrays (bore hole, casings, screens, reductions, hole fill), ignoring geology. `0` without constructive data. Used as the fallback for a missing `well_depth`.
247
257
 
248
258
  #### `redactWell(well: Well, visibility: WellVisibility): Well`
249
259
 
250
- Copy of `well` with hidden parts emptied (arrays become `[]`, optional fields are removed), so the result stays schema-valid. `visibility` keys are the 7 `SECTION_KEYS` and/or the fields of `VISIBILITY_TREE` (e.g. `meters`, `permits`, `location`); `false` hides, missing keys are visible, and a hidden section hides all its fields. `VISIBILITY_LEAF_KEYS` lists the finest toggles. References from visible records to hidden ones are left as-is.
260
+ Copy of `well` with hidden parts emptied (arrays become `[]`, optional fields are removed), so the result stays schema-valid. `visibility` keys are the 7 `SECTION_KEYS` and/or the fields of `VISIBILITY_TREE` (e.g. `meters`, `permits`, `location`); `false` hides, missing keys are visible, and a hidden section hides all its fields. `VISIBILITY_LEAF_KEYS` lists the finest toggles. References from visible records to hidden ones are left as-is. Unknown root members (`x-` fields and blocks) are kept only when nothing is hidden; nested ones follow their record.
251
261
 
252
262
  ```typescript
253
263
  redactWell(well, { operation: false }); // whole section
package/dist/index.cjs CHANGED
@@ -161,6 +161,7 @@ __export(index_exports, {
161
161
  getParameterLabel: () => getParameterLabel,
162
162
  getVocabEntry: () => getVocabEntry,
163
163
  getVocabLabel: () => getVocabLabel,
164
+ hasWellData: () => hasWellData,
164
165
  hoursToMinutes: () => hoursToMinutes,
165
166
  hpToKilowatts: () => hpToKilowatts,
166
167
  inchesToMm: () => inchesToMm,
@@ -209,6 +210,16 @@ var mergeWell = (0, import_defu.createDefu)((obj, key, value) => {
209
210
  return true;
210
211
  }
211
212
  });
213
+ var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
214
+ var mergeParsed = (0, import_defu.createDefu)((obj, key, value) => {
215
+ if (Array.isArray(value)) {
216
+ const raw = obj[key];
217
+ obj[key] = Array.isArray(raw) && raw.length === value.length ? value.map(
218
+ (item, i) => isPlainObject(item) && isPlainObject(raw[i]) ? mergeParsed(item, raw[i]) : item
219
+ ) : value;
220
+ return true;
221
+ }
222
+ });
212
223
  var RFC3339_WITH_OFFSET = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
213
224
  var rfc3339 = () => import_zod.z.string().regex(RFC3339_WITH_OFFSET, "datetime must be RFC 3339 with UTC offset");
214
225
  var CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
@@ -840,7 +851,7 @@ function parseWell(json) {
840
851
  }
841
852
  if (version === 1) {
842
853
  const normalized = normalizeV1Fields(raw);
843
- return mergeWell(
854
+ return mergeParsed(
844
855
  WellSchema.parse(normalized),
845
856
  normalized
846
857
  );
@@ -850,12 +861,20 @@ function parseWell(json) {
850
861
  } else {
851
862
  raw.version = 2;
852
863
  }
853
- return mergeWell(WellSchema.parse(raw), raw);
864
+ return mergeParsed(WellSchema.parse(raw), raw);
854
865
  }
855
866
 
856
867
  // src/utils/well.utils.ts
857
868
  var WELL_FORMAT_VERSION = 2;
858
869
  var INCHES_TO_MM = 25.4;
870
+ var LEGACY_ROOT_KEYS = /* @__PURE__ */ new Set([
871
+ "lat",
872
+ "lng",
873
+ "elevation",
874
+ "bole_hole",
875
+ "constructive",
876
+ "geologic"
877
+ ]);
859
878
  var SECTION_KEYS = [
860
879
  "general",
861
880
  "constructive",
@@ -1100,6 +1119,11 @@ function normalizeGeologic(raw) {
1100
1119
  caves: raw.caves ?? []
1101
1120
  };
1102
1121
  }
1122
+ function unknownRootKeys(well) {
1123
+ return Object.keys(well).filter(
1124
+ (key) => !(key in WellSchema.shape) && !LEGACY_ROOT_KEYS.has(key)
1125
+ );
1126
+ }
1103
1127
  function serializeWell(well) {
1104
1128
  const payload = {
1105
1129
  version: WELL_FORMAT_VERSION,
@@ -1152,6 +1176,10 @@ function serializeWell(well) {
1152
1176
  water_samples: well.water_samples
1153
1177
  }
1154
1178
  };
1179
+ for (const key of unknownRootKeys(well)) {
1180
+ const value = well[key];
1181
+ if (value !== void 0) payload[key] = value;
1182
+ }
1155
1183
  return JSON.stringify(payload);
1156
1184
  }
1157
1185
  function isWellEmpty(well) {
@@ -1160,6 +1188,22 @@ function isWellEmpty(well) {
1160
1188
  if (raw.constructive || raw.geologic) return false;
1161
1189
  return well.lithology?.length === 0 && well.fractures?.length === 0 && well.caves?.length === 0 && well.bore_hole?.length === 0 && well.hole_fill?.length === 0 && well.well_case?.length === 0 && well.well_screen?.length === 0;
1162
1190
  }
1191
+ function isFilled(value) {
1192
+ if (value === void 0 || value === null) return false;
1193
+ if (typeof value === "string") return value.trim() !== "";
1194
+ if (typeof value === "number") return value !== 0;
1195
+ if (Array.isArray(value)) return value.length > 0;
1196
+ if (typeof value === "object") return Object.values(value).some(isFilled);
1197
+ return true;
1198
+ }
1199
+ var NON_DATA_ROOT_KEYS = /* @__PURE__ */ new Set(["version", "$schema", "@context"]);
1200
+ function hasWellData(well) {
1201
+ if (!well) return false;
1202
+ if (!isWellEmpty(well)) return true;
1203
+ return Object.entries(well).some(
1204
+ ([key, value]) => !NON_DATA_ROOT_KEYS.has(key) && isFilled(value)
1205
+ );
1206
+ }
1163
1207
  function redactWell(well, visibility) {
1164
1208
  const v = visibility;
1165
1209
  const redacted = { ...well };
@@ -1203,6 +1247,10 @@ function redactWell(well, visibility) {
1203
1247
  if (hidden("production")) delete redacted.production;
1204
1248
  if (hidden("permits")) delete redacted.permits;
1205
1249
  if (hidden("water_quality")) delete redacted.water_samples;
1250
+ if (Object.values(v).some((shown) => shown === false)) {
1251
+ for (const key of unknownRootKeys(redacted))
1252
+ delete redacted[key];
1253
+ }
1206
1254
  return redacted;
1207
1255
  }
1208
1256
  function deserializeWell(jsonString) {
@@ -3393,6 +3441,7 @@ function expandedToStandardUncertainty(expanded, coverageFactor = 2) {
3393
3441
  getParameterLabel,
3394
3442
  getVocabEntry,
3395
3443
  getVocabLabel,
3444
+ hasWellData,
3396
3445
  hoursToMinutes,
3397
3446
  hpToKilowatts,
3398
3447
  inchesToMm,