제3장은 운용 계층을 다루었다. 잠수정이 작업하는 동안 내보내는 메시지로, 하나하나가 Phase 1 봉투에 담기고 순번이 붙는다. 이 장은 WIA-ENE-040의 다른 한 계층인 과학 기록으로 넘어간다. 과학 기록은 잠수정이 갑판으로 돌아온 뒤에도 연구 프로그램이 오래 보관하는 문서다. 어느 항해가 어떤 허가를 받아 어디로 갔는지, 잠수마다 무엇을 했는지, 어떤 생물을 보았는지, 어떤 실물 시료를 올렸고 그 시료가 지금 어디 있는지, 어떤 열수 분출공을 찾았는지, 어느 해역을 어떤 해상도로 측심했는지가 모두 여기에 남는다.
이 기록들은 원천 자료에 두 번 정의되어 있다. 핵심 명세는 §12에 JSON 예시로 보여 준다(§12.1 탐사, §12.2 잠수 기록, §12.3 관찰, §12.4 시료, §12.5 열수 분출공). TypeScript SDK @wia/ene-040는 types.ts에 인터페이스로 정의한다. 둘은 전체 골격은 같지만 필드 이름, 열거값 표기, 완결성에서 차이가 난다. 더 완전하고 더 정확한 쪽은 SDK 인터페이스이므로, 이 장은 인터페이스를 기준으로 삼고 §12 예시가 그 기준에 비추어 검증에 실패하는 지점을 하나씩 짚는다. 마지막 절은 §14.1의 접근 등급을 다룬다. 누가 어떤 좌표를 볼 수 있는지 말하지 못하는 기록 모델은 심해 작업에서 완결된 모델이라 할 수 없기 때문이다.
예시에 나오는 인명, 선박, 허가, 위치는 모두 가상이다. 예시의 수치 역시 물리적으로 그럴듯하고 서로 모순되지 않도록 고른 예시값이며 실측값이 아니다.
모든 과학 기록은 탐사(expedition)에 매달린다. 탐사는 잠수를 포함하고, 잠수는 관찰과 시료를 만들어 낸다. 이 나무 구조 안이 아니라 옆에 놓이는 기록이 두 가지 있다. 멀티빔 측심 조사는 탐사에는 속하지만 잠수에는 속하지 않는다. 측심은 보통 모선에서, 혹은 잠수로 기록되지 않는 AUV 임무로 수행되기 때문이다. 열수 분출공은 어느 탐사에도 속하지 않는다. 분출공은 여러 해에 걸쳐 여러 항해가 찾아가는 장소다. v1.0의 열수 분출공 기록에는 expeditionId도 diveId도 없으므로, 나머지 기록과는 위치로만 연결할 수 있다. 아래 그림은 이 관계를 보여 주며, 이 책이 제안하는 연결 하나는 따로 표시했다.
┌──────────────────────┐
│ Expedition │ expeditionId EXP-2025-004
│ vessel, permits[], │
│ scientists[] │
└──────────┬───────────┘
│ 1
│ 1 ┌──────────────────────┐
├──────────────┤ MultibeamSurvey │ surveyId (expeditionId FK)
│ N │ area.extent (bbox) │
│ N └──────────┬───────────┘
┌──────────┴───────────┐ : 공간 결합
│ DiveLog │ : (범위 안의 점)
│ diveId │ :
│ timeline, track │ ┌──────────┴───────────┐
└──┬────────┬──────┬───┘ │ HydrothermalVent │ ventId
│ 1 │ 1 │ 1 │ location, ventField │ (v1.0에는 expeditionId·diveId
│ N │ N │ N └──────────┬───────────┘ 없음: 위치로 연결)
┌──┴─────┐ ┌┴──────────────┐ ┌───────┴──────┐
│ Organism│ │ Samples │ │ (proposed) │
│ Obser- │ │ Biological │ │ visits[]: │
│ vation │ │ Rock │ │ diveId list │
│ obs.Id │ │ SedimentCore* │ └──────────────┘
└─────────┘ │ eDNA │
│ └─ IsobaricSampler.sample.sampleId
└───────────────┘
* SedimentCore의 키는 coreId이며 diveId는 선택 항목이다.
Expedition 인터페이스는 필드를 블록으로 묶는다. vessel 블록에는 선명과 운항 기관, 미터 단위 선체 길이, 선택 항목인 IMO 번호, 그리고 research_vessel 또는 commercial 둘 중 하나만 허용하는 분류가 들어간다. location 블록은 해역, 구역 이름, 대표 좌표, 선택 항목인 배타적 경제수역(EEZ)을 담는다. duration 블록은 시작·종료 시각과 daysAtSea 일수를 갖는다. 과학적 목적은 objectives[]와 targetDepthRange(min, max, 미터)가 표현한다. submersibles[]와 sensors[] 배열에는 사양 전체가 아니라 식별자만 들어간다. 따라서 잠수정 사양은 잠수정 등록부에 한 번만 쓰고, 그 잠수정을 쓰는 모든 항해가 이를 참조한다. 그 밖에 permits[], scientists[], 그리고 createdAt, updatedAt, 생애주기 상태 status(planning, active, completed, cancelled)를 담은 metadata 블록이 있다.
세 가지 세부 사항에는 별도의 규칙이 필요하다.
daysAtSea는 startDate와 endDate로 계산된다. 편의상 저장하되 정의를 정하고(이 책은 UTC 기준으로 양 끝 날을 모두 포함하는 달력 일수를 쓴다) 검사해야 한다. 3월 1일 00:00부터 3월 30일 23:59:59까지를 daysAtSea: 30으로 적은 §12.1 예시는 이 정의를 만족한다."classification": "Research Vessel"이라고 쓰지만 인터페이스가 허용하는 값은 research_vessel뿐이다. 검증기는 이 예시를 조용히 정규화하지 말고 거부해야 한다. 조용한 정규화는 생산자가 스키마를 따르지 않는다는 사실을 가려 버린다.permits[].validUntil은 ISO 8601 시각으로 정의되어 있는데 예시는 날짜만 쓴다. 날짜만 있으면 시간대가 없다. 허가가 그날이 끝날 때 만료된다면 UTC 시각 전체(2025-12-31T23:59:59Z)를 쓰거나, 날짜 전용 타입을 쓰기로 합의하고 스키마에 명시해야 한다.IMO 번호는 선택 항목이며 선택 항목으로 남아야 한다. 소형 연안 조사선을 비롯해 IMO 번호가 없는 연구 플랫폼이 많다. 번호가 있다면 일곱 자리이고 마지막 자리가 검증 숫자이므로 검증기가 확인할 수 있다. 예시나 시험 데이터에 임의로 지어낸 일곱 자리 번호를 넣어서는 안 된다. 검증 숫자를 우연히 통과한 번호는 실제 선박의 번호일 수 있고, 실제로 §12.1의 자리표시 번호도 검증 숫자를 통과한다. 이 책의 예시는 이 필드를 아예 생략한다.
사람은 chiefScientist, scientists[], 잠수 승무원 목록, identifiedBy, curator에 등장한다. 이것은 개인정보다. 이름은 본인이 인용되기를 원하는 형태로 저장한다. Phase 1의 SAMPLE_METADATA 메시지가 이미 채집자의 ORCID iD를 담고 있다면 그 식별자를 과학 기록에도 옮겨 적어, 개명이나 음역 차이가 있어도 저작 표시가 유지되게 한다.
{
"expeditionId": "EXP-2025-004",
"name": "Example Ridge Segment Survey 2025",
"chiefScientist": "Dr. Mina Example",
"vessel": {
"name": "R/V Example Explorer",
"operator": "Example Ocean Institute",
"length": 90.0,
"classification": "research_vessel"
},
"location": {
"region": "Example Ocean Basin",
"area": "Example Ridge, Segment A",
"coordinates": { "latitude": -17.4200, "longitude": -113.2100 },
"eez": "none (Area beyond national jurisdiction)"
},
"duration": {
"startDate": "2025-06-01T00:00:00Z",
"endDate": "2025-06-24T23:59:59Z",
"daysAtSea": 24
},
"objectives": [
"High-resolution multibeam survey of Segment A",
"ROV imaging transects across two vent fields"
],
"targetDepthRange": { "min": 2400, "max": 3800 },
"submersibles": ["ROV-EXAMPLE-01", "AUV-EXAMPLE-02"],
"sensors": ["CTD-SN-0421", "MBES-SN-1180"],
"permits": [
{
"type": "Marine scientific research consent",
"issuedBy": "Example Authority",
"permitNumber": "EXAMPLE-MSR-0001",
"validUntil": "2025-12-31T23:59:59Z"
}
],
"scientists": [
{ "name": "Dr. Mina Example", "affiliation": "Example University", "role": "chief_scientist" },
{ "name": "Jun Sample", "affiliation": "Example Ocean Institute", "role": "data_manager" }
],
"metadata": {
"createdAt": "2025-02-10T09:00:00Z",
"updatedAt": "2025-06-25T08:00:00Z",
"status": "completed"
}
}
§12 예시는 접두어와 연도가 붙은 식별자를 쓴다. EXP-2025-001, DIVE-HOV-2025-045, OBS-2025-001234, SAMPLE-2025-MT-001, VENT-EPR-2025-003이 그것이다. 명세는 문법을 한 번도 명시하지 않으므로, 아래 패턴은 예시에서 추론한 이 책의 제안이다. 이런 제안이라면 최소한 §12의 모든 예시를 받아들여야 하며, 아래 패턴은 그 조건을 만족한다. 규칙이 전혀 없는 식별자도 두 개 있다. 퇴적물 코어는 sampleId가 아니라 coreId를 키로 쓰고, 측심 조사는 surveyId를 쓰는데, 둘 다 예시에 나오지 않는다.
// Identifier patterns inferred from the ENE-040 §12 examples.
// v1.0 shows examples only; these regular expressions are this book's proposal.
export const ID_PATTERNS: Record<string, RegExp> = {
expeditionId: /^EXP-\d{4}-\d{3,}$/, // EXP-2025-004
diveId: /^DIVE-(HOV|ROV|AUV|LANDER|CRAWLER)-\d{4}-\d{3,}$/, // DIVE-ROV-2025-017
observationId:/^OBS-\d{4}-\d{6,}$/, // OBS-2025-000318
sampleId: /^SAMPLE-\d{4}-[A-Z0-9]{2,8}-\d{3,}$/, // SAMPLE-2025-ERA-012
ventId: /^VENT-[A-Z0-9]{2,8}-\d{4}-\d{3,}$/, // VENT-ERA-2025-003
};
export function checkId(field: string, value: string): string | null {
const re = ID_PATTERNS[field];
if (!re) return null; // no convention for coreId, surveyId
return re.test(value) ? null : `${field} "${value}" does not match ${re}`;
}
주의할 점이 두 가지 있다. 첫째, 읽기 쉬운 ID가 곧 영구 식별자는 아니다. 연도와 구역 코드가 들어 있으면 스프레드시트에서 읽기는 편하지만, 구역 이름이 바뀔 때 기록 이름까지 바꾸고 싶은 유혹이 생긴다. 한 번 발급한 문자열은 의미 없는 값으로 취급해야 한다. 둘째, ID는 한 데이터 시스템 안에서만 유효하다. 시스템 밖에서 인용하려면 데이터셋에 DOI를 붙이고(제7장), 실물 시료라면 IGSN 같은 전 지구 등록형 시료 식별자를 검토한다.
DiveLog 인터페이스는 한 잠수정의 한 번의 투입을 기록한다. submersible 블록에는 vehicleId, SubmersibleType 열거형(hov, rov, auv, lander, crawler)의 type, 그리고 역할이 pilot, scientist, observer 중 하나인 선택적 승무원 목록이 들어간다. §12.2 예시는 세 군데서 어긋난다. 유형을 "HOV"로, 역할을 "Pilot", "Scientist"로 쓰고, vehicleId 대신 잠수정 이름과 최대 수심을 직접 넣었다. 인터페이스를 따라야 한다. 정격 수심은 잠수정의 속성이며, 이를 잠수 기록마다 복사해 두면 복사본끼리 어긋나기 마련이다.
timeline 블록에는 네 개의 시각과 하나의 지속 시간이 있다. launch, onBottom, offBottom, recovery, 그리고 초 단위 bottomTime이다. bottomTime은 파생값으로 offBottom − onBottom과 같다. 검색과 통계 질의가 이 값을 쓰므로 저장해 둘 가치는 있지만, 저장된 파생값에는 일관성 검사가 따라야 한다. 네 시각은 순서도 지켜야 한다. 해저에 닿기 전에 해저를 떠날 수는 없다.
§12.2 예시가 이 검사가 왜 필요한지 보여 준다. 예시의 offBottom은 "2025-03-15-15:30:00Z"로, T 구분자 자리에 하이픈이 들어가 있다. 이것은 유효한 ISO 8601 시각이 아니며, 관대한 파서는 이를 거부하거나 엉뚱하게 해석할 수 있다. 2025-03-15T15:30:00Z로 고치면 예시는 일관된다. 11:30부터 15:30까지는 14,400초로 bottomTime과 일치한다. 아래 검증기는 먼저 엄격하게 파싱한 뒤 순서와 지속 시간을 검사한다. 허용 오차 기본값은 Phase 1의 ±1초 시각 동기화 요구에 맞추어 1초로 두었다.
interface Timeline {
launch: string; onBottom: string; offBottom: string;
recovery: string; bottomTime: number;
}
const ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
export function checkDiveTimeline(t: Timeline, toleranceS = 1): string[] {
const errors: string[] = [];
const keys = ["launch", "onBottom", "offBottom", "recovery"] as const;
const ms: number[] = [];
for (const k of keys) {
const v = t[k];
if (!ISO_UTC.test(v) || Number.isNaN(Date.parse(v))) {
errors.push(`timeline.${k}: not an ISO 8601 UTC timestamp: "${v}"`);
}
ms.push(Date.parse(v));
}
if (errors.length) return errors; // ordering is meaningless now
for (let i = 1; i < ms.length; i++) {
if (ms[i] < ms[i - 1]) {
errors.push(`timeline.${keys[i]} is earlier than timeline.${keys[i - 1]}`);
}
}
const derived = (ms[2] - ms[1]) / 1000; // offBottom - onBottom, seconds
if (Math.abs(derived - t.bottomTime) > toleranceS) {
errors.push(`timeline.bottomTime ${t.bottomTime} s != offBottom - onBottom ${derived} s`);
}
return errors;
}
// The spec's own example (ENE-040 §12.2) fails the first rule:
// checkDiveTimeline({ ..., offBottom: "2025-03-15-15:30:00Z", ... })
// -> ['timeline.offBottom: not an ISO 8601 UTC timestamp: "2025-03-15-15:30:00Z"']
navigation 블록에는 launchPosition, bottomTrack, 미터 단위 maxDepth와 averageDepth, 제곱킬로미터 단위 areaExplored가 있다. bottomTrack은 GeoJSON LineString이거나 파일 경로 문자열이다. 두 형태 모두 정당하다. 1 Hz 항법으로 몇 시간 잠수하면 위치가 수만 개 쌓이는데, 목록 조회 때마다 불러오는 기록에 이를 모두 넣기에는 너무 많다. 표시용으로 솎아 낸 항적은 기록에 넣고, 전체 해상도 항적은 파일로 참조하는 것이 합리적이다.
GeoJSON에는 흔히 걸려 넘어지는 규약이 두 가지 있다. 위치는 경도, 위도 순서다(RFC 7946). ENE-040의 다른 모든 곳에서 쓰는 latitude/longitude 순서와 반대다. 선택적인 세 번째 요소는 위쪽이 양수인 고도이므로, 수면 아래 3,547 m 지점은 -3547로 쓴다. 명세는 항적의 수직축에 대해 아무 말도 하지 않고, 기록의 나머지 부분은 아래쪽이 양수인 수심을 쓴다. 어느 쪽을 택했는지 데이터셋 메타데이터에 밝히고 검사해야 한다. 검증기는 항적의 가장 깊은 점을 navigation.maxDepth와 비교해, 부호가 다르거나 항법 불확도보다 크게 차이 나면 실패로 처리할 수 있다.
같은 블록에 작은 불일치가 하나 더 있다. §12.2 예시는 launchPosition을 {"lat": …, "lon": …}로 쓰지만 Coordinates 타입은 latitude와 longitude를 쓴다. 타입을 따르는 소비자는 예시의 위치를 누락된 값으로 읽게 된다.
observations 블록은 계수기 모음이다. organisms, newSpecies, samples, rocks, videos, photos가 있다. 이 값들은 하위 기록의 요약이지 기록 그 자체가 아니다. 원칙은 하위 기록이 원본이고, 계수기는 하위 기록이 추가되거나 삭제될 때 다시 계산하는 캐시라는 것이다. newSpecies는 특히 조심해야 한다. 어떤 표본이 미기재종인지는 수년이 걸릴 수도 있는 분류학 작업으로 판가름 난다. 따라서 잠수 시점에 정직하게 적을 수 있는 값은 신종 후보 수이며, 기재 논문이 나올 때까지 0으로 두는 프로그램도 많다.
environmental 블록은 해저 부근 조건을 담는다. °C 단위 bottomTemperature, PSU 단위 bottomSalinity, mg/L 단위 bottomOxygen, 선택 항목 bottomPH가 있다. 이것은 잠수 단위 요약값이다. 원래의 시계열은 Phase 1 OCEANOGRAPHIC_DATA 메시지에 속하며, 그곳에서는 값마다 센서 ID와 교정일이 따라붙는다. 잠수 기록은 요약값을 어떻게 만들었는지(예를 들어 해저 체류 구간의 중앙값)를 notes나 데이터셋 문서에 밝혀야 한다. incidents[] 목록은 운용 중 사건을 severity(minor, moderate, major)와 resolved 플래그와 함께 기록한다. 안전 관리 체계는 아니지만, 데이터 사용자가 어떤 횡단 조사가 왜 일찍 끝났는지 알아낼 수 있는 곳이다.
아래의 완전한 잠수 기록은 인터페이스를 따르며 시간표 검사를 통과한다. 항적에는 점이 세 개뿐인데, 앞에서 말한 표시용으로 솎아 낸 항적에 해당한다.
{
"diveId": "DIVE-ROV-2025-017",
"expeditionId": "EXP-2025-004",
"submersible": {
"vehicleId": "ROV-EXAMPLE-01",
"type": "rov",
"crew": [
{ "name": "Pilot A (example)", "role": "pilot" },
{ "name": "Dr. Mina Example", "role": "scientist" }
]
},
"timeline": {
"launch": "2025-06-12T06:40:00Z",
"onBottom": "2025-06-12T08:35:00Z",
"offBottom": "2025-06-12T16:05:00Z",
"recovery": "2025-06-12T18:10:00Z",
"bottomTime": 27000
},
"navigation": {
"launchPosition": { "latitude": -17.4182, "longitude": -113.2071 },
"bottomTrack": {
"type": "LineString",
"coordinates": [
[-113.2069, -17.4185, -3531.0],
[-113.2061, -17.4190, -3547.2],
[-113.2050, -17.4197, -3539.8]
]
},
"maxDepth": 3547.2,
"averageDepth": 3538.5,
"areaExplored": 0.35
},
"observations": {
"organisms": 41, "newSpecies": 0, "samples": 6,
"rocks": 2, "videos": 9, "photos": 312
},
"environmental": {
"bottomTemperature": 2.1,
"bottomSalinity": 34.7,
"bottomOxygen": 5.9,
"bottomPH": 7.8
},
"notes": "Transect T2 aborted after 40 min: tether snag, cleared.",
"incidents": [
{ "type": "tether_snag", "severity": "minor",
"description": "Tether caught on talus block, freed by pilot.",
"resolved": true }
]
}
OrganismObservation은 잠수 중 어느 장소와 시각에 어떤 생물이 관찰되었다는 진술이다. 위도·경도·수심·depthZone·habitat을 담은 location, 동정을 위한 organism 블록, morphology 블록, behavior, abundance, associatedSpecies[], media[], 그리고 두 품질 필드인 quality(excellent부터 poor까지)와 verified로 이루어진다.
organism 블록에는 인터페이스가 WoRMS AphiaID 등으로 설명하는 선택 항목 taxonId, 선택 항목 scientificName, 문(phylum)부터 종(species)까지의 계급, 그리고 필수 필드 세 개가 있다. 필수 필드는 identificationLevel(phylum부터 subspecies까지), identifiedBy, identificationMethod(visual, genetic, morphological)다.
taxonId와 scientificName은 따로 관리해야 한다. 학명은 텍스트다. 철자가 틀릴 수 있고, 개정될 수 있으며, 서로 다른 계(界)에 같은 이름이 있을 수도 있다. 분류 등록부의 식별자는 안정적이고 조회할 수 있다. §12.3 예시는 분류군 ID로 "AMPHIPODA-SP001"을 쓴다. 아직 이름이 붙지 않은 동물에 쓰는 작업용 '형태종' 코드로서는 쓸모 있는 표지이지만, 등록부 식별자는 아니므로 등록부 식별자로 내보내서는 안 된다. 권장하는 방법은 이렇다. 등록부 식별자가 있으면 taxonId에 넣고(WoRMS의 LSID 형식은 urn:lsid:marinespecies.org:taxname: 뒤에 AphiaID를 붙인 것이다), scientificName에는 확신할 수 있는 가장 낮은 계급의 이름을 쓰며, 내부 형태종 코드는 별도 필드나 비고에 둔다.
심해 영상은 과나 목 수준까지만 동정되는 경우가 많다. 이것은 결함이 아니며, identificationLevel은 바로 그것을 기록하려고 있는 필드다. 속까지 동정된 관찰은 속명을 scientificName으로, genus를 계급으로 갖는다. "cf."나 "sp." 같은 한정어는 학명 문자열 안이 아니라 한정어 필드에 둔다. Darwin Core도 그렇게 다룬다. identificationMethod는 후속 사용자에게 중요하다. 영상을 보고 한 육안 동정과 표본으로 한 유전자 동정은 신뢰도가 크게 다르므로, 그 차이로 걸러 낼 수 있어야 한다.
인터페이스 주석에 따르면 형태 측정값의 단위는 밀리미터다(weight는 그램). 명세 예시는 "length": 34.0을 단위 없이 쓴다. 대형 단각류라면 밀리미터로 읽어 그럴듯하지만, 스키마가 단위를 고정하지 않으면 이 값은 쓸 수 없다. 영상에서 추정한 크기는 보통 레이저 스케일러, 즉 간격을 아는 평행 레이저 두 개 이상으로 얻는다. 측정 방법을 값과 함께 기록해야 한다. 레이저로 잰 크기와 캘리퍼스로 잰 표본 크기는 불확도가 전혀 다르다.
behavior는 열거형이다. feeding, resting, swimming, scavenging, burrowing, attached, unknown 중 하나다. §12.3 예시는 "Feeding"이라고 써서 역시 대소문자 구분 검사에 걸린다. abundance는 두 원천 모두에서 자유 텍스트다("15-20 individuals", "dense aggregation"). 자유 텍스트는 영상으로 개체 수를 어떻게 추정하는지를 솔직하게 드러내지만, 합산하거나 걸러 낼 수는 없다. 내보낼 때는 개수가 정말 정확할 때만 숫자 필드에 넣고, 범위나 정성적 추정은 비고로 옮긴다.
media[] 항목마다 type(video 또는 photo), file, 선택 항목 timecode와 resolution이 있다. 명세 예시는 벽시계 시각 14-35-22를 이름으로 쓴 파일에 "02:35:22"라는 타임코드를 붙였다. 읽는 사람은 이 타임코드가 파일 안에서의 경과 시간인지 하루 중 시각인지 알 수 없다. 관찰의 timestamp를 기준이 되는 UTC 시각으로 삼고, timecode는 참조한 파일 시작점으로부터의 경과 시간으로 정의해야 한다. 영상 시스템이 프레임까지 포함한 SMPTE 타임코드를 쓴다면 그렇다고 밝힌다. 몇 시간짜리 영상의 정확한 초에 생물을 연결해 주는 기록은, 파일까지만 연결해 주는 기록보다 훨씬 값지다.
ENE-040 §11.2.2는 생물 메타데이터에 Darwin Core, 지리공간 메타데이터에 ISO 19115를 지정하고 OBIS를 목표 데이터베이스로 든다. 표준 자체에는 필드 대응표가 없으므로 아래 표로 그 빈자리를 채운다. Darwin Core 용어는 이름공간 접두어 없이 적었다. OBIS에 관해서는 세 가지를 챙겨야 한다. 출현 기록에는 ENE-040에 없는 occurrenceStatus와 basisOfRecord가 있어야 한다. 수심은 최소·최대 두 용어로 나누어 넣는다(한 지점 관찰이면 두 값이 같다). ENE-040이 전반적으로 WGS 84를 전제하더라도 측지 기준계는 명시해야 한다.
| ENE-040 필드 | Darwin Core / OBIS 용어 | ISO 19115 요소(데이터셋 수준) | 비고 |
|---|---|---|---|
observationId | occurrenceID | — | 내보낸 뒤에는 전역 유일해야 하므로 데이터셋 이름공간을 접두어로 붙인다. |
diveId / expeditionId | eventID / parentEventID | 자원 식별자, 집합 정보 | 잠수가 채집 사건이고 탐사가 그 상위 사건이다. |
timestamp | eventDate | 시간 범위 | 양쪽 모두 UTC 기준 ISO 8601. |
location.latitude / longitude | decimalLatitude / decimalLongitude, geodeticDatum | 지리 경계 상자 | 기준계 WGS84를 명시한다. |
| USBL·항법 정확도 | coordinateUncertaintyInMeters | 위치 정확도(데이터 품질) | 관찰 기록의 ENE-040 필드가 아니므로 항법 해에서 끌어온다. |
location.depth | minimumDepthInMeters = maximumDepthInMeters | 수직 범위 | 아래쪽 양수, 미터. |
location.habitat | habitat | — | 열거값을 텍스트로 내보낸다. |
organism.scientificName | scientificName | — | 확신할 수 있는 가장 낮은 계급의 이름, 한정어 제외. |
organism.taxonId | scientificNameID | — | 등록부 식별자(예: WoRMS LSID)일 때만. |
organism.identificationLevel | taxonRank | — | 한정어는 identificationQualifier에. |
organism.identifiedBy / identificationMethod | identifiedBy / identificationRemarks | — | 방법은 통제된 문구로 비고에 적는다. |
behavior | behavior | — | 그대로 대응. |
abundance | individualCount 또는 organismQuantity + organismQuantityType; occurrenceRemarks | — | 정확한 개수만 individualCount에. |
associatedSpecies[] | associatedTaxa | — | Darwin Core 텍스트 파일에서는 파이프(|)로 구분. |
media[] | associatedMedia | 미리보기 그래픽 | 로컬 파일명이 아니라 조회 가능한 URL. |
| (ENE-040에 없음) | basisOfRecord, occurrenceStatus | — | 내보낼 때 설정한다. 예: HumanObservation 또는 MachineObservation, 그리고 present. |
탐사 name, objectives | (데이터셋 메타데이터) | 제목, 초록, 목적 | 탐사 기록에서 가져온다. |
탐사 scientists[] | recordedBy(기록별) | 연락처 / 책임 당사자 | 가능하면 ORCID iD를 쓴다. |
주석을 단 영상 관찰을 HumanObservation으로 볼지 MachineObservation으로 볼지는 프로젝트 정책의 문제이며 프로그램마다 판단이 다르다. 데이터셋 단위로 한 번 정하고 문서에 남긴다.
관찰은 세상에 대한 진술이다. 시료는 10년 뒤에도 누군가 찾아낼 수 있어야 하는 실물이다. 그래서 시료 기록에서 가장 중요한 부분은 과학적 서술이 아니라 세 질문에 대한 답이다. 그 물건이 지금 어디 있는가, 누가 책임지는가, 어떤 조건으로 쓸 수 있는가. 여기서 다루는 기록 관리의 관점은 식별, 위치, 관리 이력, 가용성, 법적 조건에 한정된다. 표본을 실험실에서 어떻게 보존·처리·분석하는지는 이 책과 데이터 모델의 범위 밖이며, 데이터 모델은 그 결과(보존 유형, 분석 목록)만 기록한다.
types.ts는 실물 시료를 위해 다섯 가지 기록 유형을 정의한다. 공통 골격(ID, 상위 연결, 시각, 위치, 보관 블록)은 같지만 키와 보관 블록이 표현할 수 있는 범위가 다르다.
| 기록 유형 | 키 | 상위 연결 | 유형 필드 | 보관 블록 | 가용성 |
|---|---|---|---|---|---|
BiologicalSample | sampleId | expeditionId, diveId | sampleType: whole_organism, tissue, genetic, environmental_dna | facility, repository, catalogNumber, freezer?, box?, position? | available / restricted / depleted, curator |
RockSample | sampleId | expeditionId, diveId | rockType: basalt … manganese_nodule, crust | facility, catalogNumber, location | 모델링되지 않음 |
SedimentCore | coreId | expeditionId, diveId? | coreType: push, gravity, piston, box | facility, repository, archiveHalf, workingHalf | 하위 시료별: available / analyzed / depleted |
EnvironmentalDNASample | sampleId | expeditionId, diveId? | 해수 시료 블록 | facility, freezer, box, position, remainingVolume(µL) | 잔량으로 간접 표현 |
IsobaricSampler | samplerId | sample.sampleId | 채수기 사양 | 없음(상태와 무결성) | sealed / opened / failed |
이 표를 보면 구현자가 해결해야 할 불일치 세 가지가 드러난다.
"sampleType": "sediment_core"라고 적었다. 이 값은 BiologicalSample 열거형에 없다. SDK에서 코어는 coreId를 키로 하는 별도 기록(SedimentCore)이며 /api/v1/geological/cores로 등록하고, 암석은 /api/v1/geological/rocks로 등록한다. SDK를 따라야 한다. 요청 절차처럼 하나의 시료 목록이 필요한 경우에는, 모든 기록에 공통 키와 유형 구분자를 갖춘 목록 항목을 하나씩 만든다."availability": "Available upon request"를 쓰고, 암석 시료에는 가용성 필드가 아예 없다. 세 값짜리 같은 필드를 모든 시료 유형에 추가하고, '요청 시 제공'을 available의 기본 의미로 삼는다.nagoyadCompliant로 적는다. 불리언으로는 시료가 어떻게 준수하는지 보여 줄 수 없다. SDK 호환을 위해 필드는 남기되, 준수를 입증하는 문서에 대한 참조를 추가한다(아래 참조).보관 블록은 위치 계층이다. 시설, 저장소 또는 소장품, 냉동고, 상자, 칸의 순서다. 이 계층으로 간단한 무결성 규칙을 세울 수 있다. 소진되지 않은 두 시료가 같은 시각에 같은 시설·냉동고·상자·칸을 차지해서는 안 된다. 정기 감사도 가능하다. 기록을 무작위로 골라 해당 칸에 가서 라벨을 확인하면 된다. 둘 다 비용이 적게 들고, 시료 소장품에서 가장 흔한 실패인 '있기는 한데 찾을 수 없는 물건'을 잡아낸다.
v1.0에 없는 것은 이력이다. 보관 블록은 시료가 지금 어디 있는지만 말하고 어떻게 거기까지 왔는지는 말하지 않는다. 허가를 받아 채집하고, 여러 실험실로 나누고, 국경을 넘어 운송한 시료라면 그 이력이 곧 관리 이력(chain of custody)이다. 이 책은 확장으로 추가만 가능한 custodyEvents[] 목록을 제안한다. 각 사건에는 UTC 시각, 행위(collected, received, preserved, subsampled, transferred, returned, depleted), 수행자, 해당하는 경우 출발지·도착지 쌍이 들어간다. 하위 시료는 자기 ID와 parentSampleId를 갖는다. SedimentCore의 subsamples[] 배열은 요약 보기로 남겨 두되 그 항목들은 별도 기록이 된다. 현재의 보관 블록은 최신 사건들을 투영한 결과가 된다. 이것은 v1.0이 아니라 확장이며, 예시에서는 기록 맨 끝에 두어 구분했다.
{
"sampleId": "SAMPLE-2025-ERA-015",
"expeditionId": "EXP-2025-004",
"diveId": "DIVE-ROV-2025-017",
"timestamp": "2025-06-12T11:02:40Z",
"location": { "latitude": -17.4191, "longitude": -113.2058,
"depth": 3544, "habitat": "hydrothermal_vent" },
"sampleType": "whole_organism",
"organism": { "scientificName": "Gastropoda", "lifestage": "adult" },
"method": "slurp_gun",
"preservation": { "type": "ethanol", "concentration": 95 },
"storage": {
"facility": "Example Ocean Institute Repository",
"repository": "Invertebrate collection",
"catalogNumber": "EOI-INV-2025-0412",
"box": "ERA-2025-B", "position": "C4"
},
"analyses": ["Morphology", "DNA barcoding"],
"curator": "Collections Manager (example)",
"availability": "available",
"nagoyadCompliant": true,
"materialTransferAgreement": "MTA-EXAMPLE-2025-07",
"custodyEvents": [
{ "at": "2025-06-12T11:02:40Z", "action": "collected", "by": "ROV-EXAMPLE-01", "location": "seafloor" },
{ "at": "2025-06-12T18:40:00Z", "action": "received", "by": "Jun Sample", "location": "ship wet lab" },
{ "at": "2025-06-12T19:15:00Z", "action": "preserved", "by": "Jun Sample", "location": "ship wet lab" },
{ "at": "2025-07-03T10:00:00Z", "action": "transferred", "by": "courier (example)",
"from": "R/V Example Explorer", "to": "Example Ocean Institute Repository" }
]
}
EnvironmentalDNASample은 기록 수준에서 보면 해수 시료다. 부피, 채수 방법(niskin_bottle, rov_sampler, ctd_rosette), 필터 크기, 그리고 remainingVolume이 붙은 보관 위치를 갖는다. 인터페이스에는 실험실 처리 과정을 요약하는 블록과, 선택 항목으로 염기서열 분석 결과를 요약하는 블록도 있다. 이 책은 그 과정을 어떻게 수행하는지는 다루지 않는다. 데이터 모델 관점에서는 eDNA 시료 기록이 파생 염기서열 데이터와 식별자로 연결되어야 하고, 내보낼 때는 새 용어를 만들지 말고 DNA 유래 출현 데이터에 관한 Darwin Core와 OBIS 지침을 따라야 한다는 것으로 충분하다. 기록 관리에서 쓸모 있는 규칙은 remainingVolume을 잔액처럼 다루는 것이다. 하위 시료 사건이 생길 때마다 줄고, 0이 되면 시료는 depleted가 된다.
IsobaricSampler 기록은 압력 유지형 채수기와 그 안에 든 시료 하나를 기술한다. 채취 수심, MPa 단위의 채취 압력과 유지 압력, 온도, status(sealed, opened, failed), integrity(intact, compromised)가 있다. 중요한 데이터는 유지 압력과 채취 압력의 비율이다. 이 비율이 현장 조건이 얼마나 잘 보존되었는지를 보여 준다. 인터페이스는 압력이 절대압인지 게이지압인지 밝히지 않는다. 3,547 m에서 그 차이는 약 35.8 MPa 가운데 0.1 MPa 정도에 불과하지만, 같은 것을 잰다고 주장하는 두 숫자 사이의 차이임은 변함없다. 절대압으로 선언한다. 또 maxPressure는 채수기의 속성이고 나머지 필드는 한 번의 투입에 속한다. 재사용하는 채수기라면 투입마다 기록을 하나씩 두거나 투입 하위 기록을 두어야 한다.
기록 API는 POST /api/v1/samples/{sampleId}/request로 시료 접근을 제공하며, SDK는 이를 requestSample(sampleId, {requester, institution, purpose})로 감싼다. getAvailableSamples()는 availability=available인 시료를 나열한다. 명세는 요청이 제출된 뒤의 상태를 정의하지 않는다. 실무에 쓸 만한 생애주기는 submitted → under_review(큐레이터) → approved 또는 declined → shipped → returned 또는 consumed이다. 발송은 매번 관리 이력 사건으로 기록하고, 소비될 때마다 잔량을 줄인다. 엔드포인트 자체는 제5장에서 다룬다.
import { DeepSeaExplorationSDK } from '@wia/ene-040';
const sdk = new DeepSeaExplorationSDK({
apiKey: process.env.WIA_API_KEY!,
endpoint: 'https://api.example.org',
});
const res = await sdk.requestSample('SAMPLE-2025-ERA-015', {
requester: 'Dr. Requester Example',
institution: 'Example University',
purpose: 'Morphological comparison with specimens from Segment B',
});
if (!res.success) {
// e.g. HTTP_404 when the ID is unknown, HTTP_403 when the tier forbids it
console.error(res.error?.code, res.error?.message);
}
법적 조건은 요청이 아니라 시료를 따라다닌다. ENE-040 §14.2는 나고야 의정서의 접근 및 이익 공유(ABS), 사전 통보 승인(PIC), 상호 합의 조건(MAT)을 든다. 데이터로는 이것들이 참조가 된다. 시료를 채취할 때 근거가 된 탐사 permits[]의 허가, PIC 문서와 MAT 문서, 발급된 경우 국제적으로 인정된 준수 인증서, 그리고 이전할 때마다의 materialTransferAgreement다. 이 조건을 벗어나 시료를 반출하게 되는 요청은 큐레이터의 기억에 맡길 것이 아니라 시스템이 막아야 한다.
HydrothermalVent 기록은 장소를 기술한다. ventId, ventField 이름, discoveryDate, 지구조 환경 tectonicSetting(mid_ocean_ridge, back_arc_basin, volcanic_arc, intraplate)이 붙은 위치, ventType, structure 블록(미터 단위 높이와 지름, 구성 물질, 굴뚝 상태 active·inactive·collapsed), fluidChemistry 블록, biology 블록, monitoring 블록(lastVisit, 그리고 status: active·waning·inactive)으로 이루어진다.
SDK의 ventType 열거형은 black_smoker, white_smoker, diffuse_flow, lost_city_type이다. 명세 본문 §8은 이 가운데 셋(흑색 분출공, 백색 분출공, 'Lost City 유형')을 설명하고 각각의 전형적인 범위를 제시한다. 이 범위는 서술일 뿐이므로 개별 분출공의 유형을 판정하는 검증 규칙으로 바꾸어서는 안 된다. 분류는 현장 전체를 보고 내리는 전문가 판단이며, 측정값 하나가 교과서 범위를 벗어났다면 그것은 오류가 아니라 발견이다. 물론 각 값에는 Phase 1의 일반 범위 검사(예: 수온 −2~400 °C)가 여전히 적용된다.
두 원천이 가장 크게 어긋나는 곳이 유체 화학 블록이다. 인터페이스는 temperature(°C), ph, h2s, ch4, 선택 항목 h2(mmol/kg)를 두고, metals 객체에서는 fe(µmol/kg)에만 단위 주석을 달았다. §12.5 예시는 pH 키를 ph와는 다른 키인 "pH"로 쓰고, 금속 값에는 단위를 전혀 붙이지 않았다. 분출 유체의 농도는 성분에 따라 몇 자릿수씩 차이 나는 일이 흔하므로, 단위 없는 숫자는 해석할 수 없다.
해법은 Phase 1이 해양 관측값에 이미 적용하는 것과 같다. 모든 값이 자기 단위를 갖는 것이다. 아래 수정 예시는 화학 값을 모두 {value, unit}으로 쓰고, CF-NetCDF 내보내기(제7장)가 기대하는 UDUNITS 방식 단위 문자열을 쓰며, pH의 기준 조건을 밝힌다. 회수한 유체 시료를 실험실 온도·압력에서 잰 pH는 현장 pH가 아니기 때문이다. v1.0에 없는 연결도 두 개 추가했다. 화학 분석에 쓴 실물 유체 시료를 가리키는 sampleRef, 그리고 분출공을 방문한 잠수 목록 visits[]다. 이 두 연결은 엔터티 그림에서 위치에만 기댄 결합을 명시적 참조로 바꾼다. 둘 다 제안이며, 알 수 없는 필드를 무시하는 v1.0 소비자도 이 기록을 그대로 읽을 수 있다.
{
"ventId": "VENT-ERA-2025-003",
"ventField": "Example Ridge, Segment A, field 2",
"discoveryDate": "2025-06-14T00:00:00Z",
"location": {
"latitude": -17.4193, "longitude": -113.2056, "depth": 3545,
"region": "Example Ocean Basin",
"tectonicSetting": "mid_ocean_ridge"
},
"ventType": "black_smoker",
"structure": {
"height": 12.0, "diameter": 1.8,
"material": "Sulfide (FeS, CuS, ZnS)", "chimneyType": "active"
},
"fluidChemistry": {
"temperature": { "value": 352, "unit": "degC" },
"ph": { "value": 3.6, "unit": "1", "reference": "measured at 25 degC, 1 atm" },
"h2s": { "value": 7.1, "unit": "mmol/kg" },
"ch4": { "value": 0.06, "unit": "mmol/kg" },
"metals": {
"fe": { "value": 1500, "unit": "umol/kg" },
"mn": { "value": 420, "unit": "umol/kg" }
},
"samplingMethod": "isobaric gas-tight sampler",
"sampleRef": "SAMPLE-2025-ERA-012"
},
"biology": {
"dominantSpecies": "Alvinocarididae (shrimp), not identified to species",
"abundance": "high",
"community": ["Gastropoda", "Polychaeta"]
},
"monitoring": {
"lastVisit": "2025-06-19T11:20:00Z",
"status": "active"
},
"visits": ["DIVE-ROV-2025-017", "DIVE-ROV-2025-021"]
}
손봐야 할 필드가 두 개 더 있다. discoveryDate는 시각 타입인데 명세 예시는 날짜만 쓴다. biology.abundance는 인터페이스에서는 열거형(low, medium, high)이지만 예시에서는 밀도를 적은 자유 텍스트다. 걸러 내기에는 열거형을 유지하고, 정량적 밀도는 그 값이 나온 관찰 기록에 방법과 날짜와 함께 둔다.
MultibeamSurvey는 한 번의 측심 조사와 그 산출물을 기술한다. surveyId, expeditionId, surveyDate, 이름·경계 상자(minLat, maxLat, minLon, maxLon)·km² 단위 totalArea를 담은 area, sonarSystem(제조사, 모델, kHz 단위 주파수, 빔 수, 미터 단위 관측 폭), processing 블록, deliverables 블록, 수심 statistics(최소, 최대, 평균, 표준편차)가 있다.
processing 블록은 처리 소프트웨어, 미터 단위 격자 해상도, 수직·수평 불확도, 그리고 음속 프로파일과 조석 보정을 적용했는지를 나타내는 불리언 두 개를 기록한다. 가장 약한 곳이 이 불리언이다. 재처리하는 사람은 어떤 음속 프로파일과 어떤 조석 모델을 썼는지 알아야 한다. Phase 1의 BATHYMETRIC_DATA 메시지에는 이미 SVP 참조를 담는 processingParameters 블록이 있으므로, 조사 기록도 예/아니요 대신 같은 참조를 담아야 한다.
deliverables 블록에는 bathymetryGrid 파일 또는 URL, 선택 항목 backscatterMosaic, format(geotiff, netcdf, xyz, bag; 마지막은 공개 형식인 Bathymetric Attributed Grid), crs가 들어간다. 여기서 두 가지를 살펴야 한다.
EPSG:4326으로, 격자 칸을 도(degree)로 재는 지리 좌표계다. 반면 gridResolution은 미터다. 50 m 격자는 해당 UTM 구역 같은 투영 좌표계에서는 명확하게 정의되지만, EPSG:4326에서는 위도에 따라 달라지는 근사일 뿐이다. 투영 좌표계로 납품하거나, 격자 자신의 단위로 칸 크기를 함께 기록해야 한다.statistics와 ENE-040의 나머지 부분은 아래쪽이 양수인 수심을 쓴다. minDepth 2,400과 격자 최솟값 −3,800이 같은 조사를 기술할 수도 있다. 산출물마다 어떤 규약을 쓰는지 적어야 한다.날짜 변경선(경도 180°)을 가로지르는 경계 상자는 minLon이 maxLon보다 크다. SDK의 getBathymetryGrid()는 경계 상자를 bbox 문자열로 그대로 넘긴다. 서버는 이 경우를 처리하거나 명시적으로 거부해야 한다. 지구의 나머지 350°를 돌려주어서는 안 된다.
측심 조사는 위치로 분출공·잠수와 연결된다. 분출공 위치가 조사 범위 안에 들면 그 분출공은 조사에 '포함'되고, 조사 격자는 잠수 항적을 그리는 바탕 지도가 된다. 조사의 수평 불확도와 잠수의 항법 불확도가 같은 단위, 같은 기준계를 써야 하는 이유가 여기에 있다.
ENE-040 §14.1은 데이터 접근 등급 세 가지를 정의한다. 아래 표는 이를 기록 관점에서 다시 정리한 것이다.
| 등급 | 명세가 이 등급에 넣은 것 | 기록 수준의 효과 |
|---|---|---|
| 공개(Public) | 저해상도 멀티빔(50 m 격자), 종 수준 관찰 목록, 탐사 메타데이터(개요, 위치, 날짜) | 탐사·잠수 요약, 분류군이나 현장의 민감도에 따라 일반화된 좌표 또는 정확한 좌표를 가진 관찰, 50 m 이상으로 거친 측심 격자 |
| 제한(Restricted) | 고해상도 멀티빔(10 m 미만), 2년 엠바고; 연구자 동의가 필요한 생물 시료 유전자 데이터; 광물 자원의 정밀 위치 | embargoUntil까지는 공개 격자보다 세밀한 측심 산출물 제한; eDNA·유전자 시료 기록; 역할에 따라 열리는 시료 요청 절차 |
| 비공개(Confidential) | 상업 탐사 데이터, 군사 관련 데이터, 민감 생태계의 정확한 좌표 | 표시된 분출공과 서식지의 정확한 위치는 시스템 밖으로 나가지 않으며, 공개 사본에는 일반화된 좌표만 남는다 |
명세는 등급을 서술하지만 이를 담을 필드는 두지 않았다. SDK 인터페이스에도 없다. 이 책은 모든 과학 기록에 작은 access 블록을 두자고 제안한다. tier, embargoUntil(UTC 시각), license, 자유 텍스트 reason이다. 엠바고 날짜는 명시한 사건(예: 탐사 종료일)을 기준으로 계산해 저장해야 하며, 읽을 때마다 다시 계산해서는 안 된다. 그렇게 하면 규칙이 바뀌는 순간 날짜가 소리 없이 바뀐다. 제7장에서 이 필드들을 Phase 4의 역할과 라이선스에 연결한다.
비공개 등급에서는 기록의 정확한 위치가 어떤 공개 출력에도 나타나서는 안 되지만, 기록 자체는 대개 검색될 수 있어야 한다. 표준적인 기법은 일반화다. 더 거친 위치를 공개하고 그 불확도를 밝힌다. Darwin Core는 무엇을 했는지 적는 dataGeneralizations와, 더 정밀한 데이터가 있음을 알리는 informationWithheld를 제공한다.
아래 함수는 한 점을 고정 격자 칸의 중심으로 옮기고 수심을 반올림한다. 옮기는 방식이 결정적이라는 점이 핵심 설계다. 요청마다 무작위 잡음을 더하는 쪽이 더 안전해 보이지만, 여러 번 요청할 수 있는 사용자는 평균을 내어 잡음을 지울 수 있다. 결정적 일반화는 언제나 같은 거친 점을 돌려준다. 칸 크기와 수심 단계는 명세의 값이 아니라 정책 값이다.
type Tier = 'public' | 'restricted' | 'confidential';
interface Point { latitude: number; longitude: number; depth: number; }
interface Generalized extends Point {
coordinateUncertaintyInMeters: number;
dataGeneralizations?: string;
}
const M_PER_DEG_LAT = 111_320; // approximate, WGS84 mean
// Snap to the centre of a fixed grid cell: deterministic, so repeated
// requests cannot be averaged back to the true position.
function snap(v: number, cellDeg: number): number {
return (Math.floor(v / cellDeg) + 0.5) * cellDeg;
}
export function generalizeForTier(p: Point, tier: Tier, usblSigmaM = 10): Generalized {
if (tier !== 'confidential') {
return { ...p, coordinateUncertaintyInMeters: usblSigmaM };
}
const cellDeg = 0.1; // policy value, not from the spec
const lat = snap(p.latitude, cellDeg);
const lon = snap(p.longitude, cellDeg);
const depth = Math.round(p.depth / 500) * 500; // depth also narrows the location
// upper bound on the half-diagonal: the latitude span is the longer side
const half = (cellDeg * M_PER_DEG_LAT) / 2;
return {
latitude: Number(lat.toFixed(4)),
longitude: Number(lon.toFixed(4)),
depth,
coordinateUncertaintyInMeters: Math.ceil(Math.SQRT2 * half + usblSigmaM),
dataGeneralizations: `snapped to ${cellDeg} deg grid cell centre; depth rounded to 500 m`,
};
}
§4.5의 분출공에 이 함수를 적용하면 위도 −17.45, 경도 −113.25, 수심 3,500 m, 불확도 약 7.9 km가 나온다. 해령의 이 부근에 분출공 지대가 있다는 것은 알 수 있지만, 배를 그곳으로 이끌기에는 부족한 정보다. 점 하나를 일반화했다고 일이 끝나지는 않는다. 비공개 위치는 다른 경로로도 새어 나가며, 경로마다 규칙이 따로 필요하다.
bottomTrack과 launchPosition, 같은 잠수의 관찰과 시료, 분출공의 visits[]가 모두 현장 위치를 알려 준다. 함께 일반화하거나 항적을 비공개로 돌린다.ventField나 위치를 이름으로 쓴 영상 파일은 격자를 무력화한다. 내보낼 때 자유 텍스트와 미디어 이름을 검사한다.types.ts의 SDK 인터페이스다. §12 예시는 여러 곳에서 이를 통과하지 못한다. 대소문자를 구분하는 열거값, lat/lon 키, 형식이 잘못된 offBottom, 존재하지 않는 sediment_core 시료 유형, 분출공 화학 값의 단위 누락이 그렇다.bottomTime, daysAtSea, 요약 계수, 잔량)은 편의를 위해 저장하되 원천과 대조해 검사해야 한다.basisOfRecord, occurrenceStatus를 명시적으로 처리하면 관찰 기록은 Darwin Core와 OBIS에 깔끔하게 대응된다.checkDiveTimeline을 통과하도록 고치고, DiveLog 인터페이스를 따르기 위해 필요한 다른 수정 두 가지를 들어라.scientificName, identificationLevel, identificationMethod와 관련 Darwin Core 용어를 채워라."sampleType": "sediment_core"로 등록되었다. 어떤 기록 유형과 엔드포인트를 써야 했는가? 그러면서도 하나의 목록에서 요청할 수 있게 하려면 어떻게 하는가?crs: "EPSG:4326"과 함께 gridResolution: 25를 보고했다. 무엇이 모호하며, 생산자에게 무엇을 바꾸라고 요청하겠는가?