add mapper. add v2 handler. add tag description short and full versions. delete topic[1-5]. #5

Merged
APervov merged 5 commits from edge5modbus into main 2026-06-19 06:15:29 +00:00
17 changed files with 1057 additions and 110 deletions

View File

@@ -7,6 +7,7 @@ RUN npm ci
COPY tsconfig.json ./
COPY public ./public
# Runtime mappers read JSON files from src/mappings, so they must be copied into the image.
COPY src ./src
CMD ["npm", "run", "dev"]

View File

@@ -3,10 +3,9 @@
"private": true,
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"start": "tsx src/index.ts",
"dev": "tsx watch --env-file=.env src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit"
"start": "node --env-file=.env dist/index.js"
},
"dependencies": {
"@types/node": "^25.9.1",

15
app/src/cloud-ingest.ts Normal file
View File

@@ -0,0 +1,15 @@
const DEFAULT_CLOUD_INGEST_URL = 'http://localhost:3101/ingest'
Review

Значения по умолчанию для переменных окружения должны быть вынесены в отдельный модуль валидации значений переменных окружения.

Значение переменной CLOUD_INGEST_URL является критическим для такого сервиса как mqtt-ingest, при не указанном значении CLOUD_INGEST_URL сервис не сможет отправлять данные, а значит - выполнять свою функцию, а значит в случае отсутствия значения CLOUD_INGEST_URL - приложение при старте должно ругнуться и не запуститься вовсе, чтобы при деплое на продакшн, это сразу было заметно.

Значения по умолчанию для переменных окружения должны быть вынесены в отдельный модуль валидации значений переменных окружения. Значение переменной CLOUD_INGEST_URL является критическим для такого сервиса как mqtt-ingest, при не указанном значении CLOUD_INGEST_URL сервис не сможет отправлять данные, а значит - выполнять свою функцию, а значит в случае отсутствия значения CLOUD_INGEST_URL - приложение при старте должно ругнуться и не запуститься вовсе, чтобы при деплое на продакшн, это сразу было заметно.
export const cloudIngestUrl =
process.env.CLOUD_INGEST_URL ?? DEFAULT_CLOUD_INGEST_URL
export function postCloudIngest(body: unknown): Promise<Response> {
NBizyaev marked this conversation as resolved
Review

function getHeaders(): Record<string, string> {
return {
'content-type': 'application/json',
'x-api-key': process.env.CLOUD_INGEST_API_KEY
}
}

function getHeaders(): Record<string, string> { return { 'content-type': 'application/json', 'x-api-key': process.env.CLOUD_INGEST_API_KEY } }
return fetch(cloudIngestUrl, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-api-key': process.env.CLOUD_INGEST_API_KEY as string,
},
body: JSON.stringify(body),
})
}

View File

@@ -0,0 +1,47 @@
import { postCloudIngest } from '../cloud-ingest.js'
import { parseJson } from '../helpers/parse.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'data/demo/plc/v1'
const EDGE = 'demo'
type DemoPlcPayload = {
tag: string
value: number
}
type DemoPlcMetric = {
edge: string
timestamp: number
NBizyaev marked this conversation as resolved
Review

Избыточная валидация

Избыточная валидация
tag: string
value: number
}
function toMetric(payload: DemoPlcPayload, timestamp: number): DemoPlcMetric {
NBizyaev marked this conversation as resolved
Review

Избыточная валидация

Избыточная валидация
return {
edge: EDGE,
timestamp,
tag: payload.tag,
value: payload.value,
}
}
NBizyaev marked this conversation as resolved
Review

Избыточная валидация

Избыточная валидация
async function postMetric(metric: DemoPlcMetric): Promise<void> {
const response = await postCloudIngest([metric])
if (!response.ok) {
const text = await response.text()
throw new Error(text)
}
console.log('demo.plc.posted', { tag: metric.tag })
}
export const handleDemoPlc: TopicHandler = async (topic, payload) => {
console.log('demo.plc.received', { topic, bytes: payload.length })
const value = parseJson<DemoPlcPayload>(payload)
const metric = toMetric(value, Date.now())
await postMetric(metric)
}

View File

@@ -0,0 +1,45 @@
import { postCloudIngest } from '../cloud-ingest.js'
import { createMetricMapper, type MappedMetric } from '../mapping.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'data/edge5/modbus/v2'
const mapper = createMetricMapper({
filePath:
process.env.EDGE5_MODBUS_V2_MAPPING_FILE ??
new URL('../mappings/edge5-modbus.json', import.meta.url),
})
const REGISTER_COUNT = mapper.tagCount
function parseValues(payload: Buffer): number[] {
const value = JSON.parse(payload.toString('utf8')) as unknown
if (
Array.isArray(value) &&
NBizyaev marked this conversation as resolved
Review

избыточная валидация
JSON.parse при некорректной JSON‑строке сам выкинет исключение SyntaxError

избыточная валидация JSON.parse при некорректной JSON‑строке сам выкинет исключение SyntaxError
value.length === REGISTER_COUNT &&
value.every((item) => typeof item === 'number' && Number.isFinite(item))
) {
return value
}
throw new Error(`Expected JSON number[${REGISTER_COUNT}]`)
}
async function postMetrics(metrics: MappedMetric[]): Promise<void> {
const response = await postCloudIngest(metrics)
if (!response.ok) {
const text = await response.text()
throw new Error(text)
}
console.log('edge5.modbus.v2.posted', { count: metrics.length })
}
export const handleEdge5ModbusV2: TopicHandler = async (topic, payload) => {
console.log('edge5.modbus.v2.received', { topic, bytes: payload.length })
const values = parseValues(payload)
const metrics = mapper.mapValues(values, Date.now())
await postMetrics(metrics)
}
NBizyaev marked this conversation as resolved
Review

Просто бросить исключение new Error(text)

Просто бросить исключение new Error(text)

View File

@@ -1,11 +1,10 @@
import { postCloudIngest } from '../cloud-ingest.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'data/edge5/modbus/v1'
const EDGE = 'edge5'
const REGISTER_COUNT = 125
const postUrl =
process.env.CLOUD_INGEST_URL ?? 'https://demo.backend.drill.greact.ru/api/ingest'
type Edge5ModbusMetric = {
edge: string
@@ -44,14 +43,7 @@ function toMetrics(values: number[], timestamp: number): Edge5ModbusMetric[] {
}
async function postMetrics(metrics: Edge5ModbusMetric[]): Promise<void> {
const response = await fetch(postUrl, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(metrics),
})
const response = await postCloudIngest(metrics)
if (!response.ok) {
const text = await response.text()

View File

@@ -1,16 +1,13 @@
import { register } from '../registry.js'
import { handleTopic1, TOPIC as topic1 } from './topic1.js'
import { handleTopic2, TOPIC as topic2 } from './topic2.js'
import { handleTopic3, TOPIC as topic3 } from './topic3.js'
import { handleTopic4, TOPIC as topic4 } from './topic4.js'
import { handleTopic5, TOPIC as topic5 } from './topic5.js'
import { handleDemoPlc, TOPIC as demoPlc } from './demo-plc.js'
import { handleEdge5Modbus, TOPIC as edge5Modbus } from './edge5-modbus.js'
import {
handleEdge5ModbusV2,
TOPIC as edge5ModbusV2,
} from './edge5-modbus-v2.js'
import { handleEdge5Video, TOPIC as edge5Video } from './edge5-video.js'
register(topic1, handleTopic1)
register(topic2, handleTopic2)
register(topic3, handleTopic3)
register(topic4, handleTopic4)
register(topic5, handleTopic5)
register(demoPlc, handleDemoPlc)
register(edge5Modbus, handleEdge5Modbus)
register(edge5ModbusV2, handleEdge5ModbusV2)
register(edge5Video, handleEdge5Video)

View File

@@ -1,16 +0,0 @@
import { parseJson } from '../helpers/parse.js'
import { isArrayOfChunks125 } from '../helpers/validate.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'poc/topic1'
export const handleTopic1: TopicHandler = async (topic, payload) => {
const raw = parseJson(payload)
if (!isArrayOfChunks125(raw)) {
console.warn('topic1.invalid_payload', { topic })
return
}
console.log('topic1.received', { topic, chunks: raw.length })
}

View File

@@ -1,16 +0,0 @@
import { parseJson } from '../helpers/parse.js'
import { isArrayOfLength125 } from '../helpers/validate.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'poc/topic2'
export const handleTopic2: TopicHandler = async (topic, payload) => {
const raw = parseJson(payload)
if (!isArrayOfLength125(raw)) {
console.warn('topic2.invalid_payload', { topic })
return
}
console.log('topic2.received', { topic, length: raw.length })
}

View File

@@ -1,16 +0,0 @@
import { parseUtf8 } from '../helpers/parse.js'
import { isNonEmptyString } from '../helpers/validate.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'poc/topic3'
export const handleTopic3: TopicHandler = async (topic, payload) => {
const text = parseUtf8(payload)
if (!isNonEmptyString(text)) {
console.warn('topic3.invalid_payload', { topic })
return
}
console.log('topic3.received', { topic, text })
}

View File

@@ -1,29 +0,0 @@
import { parseJson } from '../helpers/parse.js'
import { isFiniteNumber } from '../helpers/validate.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'poc/topic4'
type Topic4Payload = {
value: number
}
function isTopic4Payload(value: unknown): value is Topic4Payload {
return (
typeof value === 'object' &&
value !== null &&
'value' in value &&
isFiniteNumber((value as Topic4Payload).value)
)
}
export const handleTopic4: TopicHandler = async (topic, payload) => {
const raw = parseJson(payload)
if (!isTopic4Payload(raw)) {
console.warn('topic4.invalid_payload', { topic })
return
}
console.log('topic4.received', { topic, value: raw.value })
}

View File

@@ -1,10 +0,0 @@
import { parseJsonArray } from '../helpers/parse.js'
import type { TopicHandler } from '../types.js'
export const TOPIC = 'poc/topic5'
export const handleTopic5: TopicHandler = async (topic, payload) => {
const items = parseJsonArray(payload)
console.log('topic5.received', { topic, count: items.length })
}

46
app/src/mapping.ts Normal file
View File

@@ -0,0 +1,46 @@
import { readFileSync } from 'node:fs'
export type MappedMetric = {
edge: string
timestamp: number
tag: string
value: number
}
type MappingConfig = {
edge: string
tags: string[]
}
export type MetricMapper = {
edge: string
tagCount: number
mapValues: (values: number[], timestamp: number) => MappedMetric[]
}
type MetricMapperOptions = {
// filePath contract:
// - built-in mappings are passed as URL via new URL('../mappings/*.json', import.meta.url);
// - env overrides are expected as absolute paths inside the runtime container.
filePath: string | URL
}
function readMappingConfig(filePath: string | URL): MappingConfig {
return JSON.parse(readFileSync(filePath, 'utf8')) as MappingConfig
}
export function createMetricMapper(options: MetricMapperOptions): MetricMapper {
const config = readMappingConfig(options.filePath)
return {
edge: config.edge,
tagCount: config.tags.length,
mapValues: (values, timestamp) =>
values.map((value, index) => ({
edge: config.edge,
timestamp,
NBizyaev marked this conversation as resolved
Review

Обработка ошибок хандлера должна быть не в самом хандлере, а выше - на уровне роутинга входящих пакетов из топиков на хандлеры (app\src\router.ts), а все проблемы/ошибки/несоответствия ожиданиям/ваыпадения из позитивного сценария -- должны бросать исключение
в том числе некорректные данные,
в том числе отсутствие каких-либо файлов,
в том числе вообще все ошибки, которые не будут покрыты отдельным специальным if.
В этом смысле чем меньше отдельных специальных if - тем лучше, потому что проблемы всё равно обработаются и выловятся уровнем выше, но кода при этом будет меньше.

Обработка ошибок хандлера должна быть не в самом хандлере, а выше - на уровне роутинга входящих пакетов из топиков на хандлеры (app\src\router.ts), а все проблемы/ошибки/несоответствия ожиданиям/ваыпадения из позитивного сценария -- должны бросать исключение в том числе некорректные данные, в том числе отсутствие каких-либо файлов, в том числе вообще все ошибки, которые не будут покрыты отдельным специальным if. В этом смысле чем меньше отдельных специальных if - тем лучше, потому что проблемы всё равно обработаются и выловятся уровнем выше, но кода при этом будет меньше.
tag: config.tags[index],
value,
})),
}
}

View File

@@ -0,0 +1,130 @@
{
"edge": "edge5",
"tags": [
"hook_weight_1",
"hookblock_position",
"manifold_pressure_1",
"hydraulic_tong_torque",
"hook_weight_2",
"outlet_flow",
"gas_1_lfl",
"gas_1_h2s",
"gas_2_h2s",
"gas_3_h2s",
"outlet_temperature",
"inlet_flow_1",
"akb_tong_torque",
"rotary_torque",
"rotary_rpm",
"manifold_pressure_2",
"machine_tong_torque",
"inlet_temperature",
"gas_4_h2s",
"density_1",
"density_2",
"density_3",
"density_4",
"density_5",
"density_6",
"level_1",
"level_2",
"level_3",
"level_4",
"level_5",
"level_6",
"volume_1",
"volume_2",
"volume_3",
"volume_4",
"volume_5",
"volume_6",
"total_volume",
"weight_on_bit",
"penetration_rate",
"tripping_speed",
"tool_depth",
"bottom_depth",
"outlet_flow_change",
"stand_feed",
"lowered_stands_count",
"inlet_flow_2",
"slips_position",
"gas_2_lfl",
"level_7",
"volume_7",
"top_drive_rpm",
"top_drive_torque",
"level_8",
"volume_8",
"pump_1_strokes",
"pump_2_strokes",
"pump_strokes_total",
"gas_3_lfl",
"pump_3_strokes",
"level_9",
"volume_9",
"level_10",
"volume_10",
"level_11",
"volume_11",
"level_12",
"volume_12",
"level_13",
"volume_13",
"level_14",
"volume_14",
"total_volume_change",
"casing_tong_1_torque",
"casing_tong_2_torque",
"drill_pipe_tong_hydraulic_pressure",
"drill_pipe_tong_high_low_speed",
"hydraulic_distributor_section_1",
"hydraulic_distributor_section_2",
"tong_type",
"gas_4_lfl",
"gas_5_lfl",
"gas_6_lfl",
"gas_7_lfl",
"gas_8_lfl",
"guyline_tension_1",
"guyline_tension_2",
"guyline_tension_3",
"guyline_tension_4",
"gas_9_lfl_ch4",
"gas_10_lfl_ch4",
"gas_1_lfl_c3h8",
"gas_2_lfl_c3h8",
"gas_3_lfl_c3h8",
"gas_4_lfl_c3h8",
"gas_5_lfl_c3h8",
"gas_6_lfl_c3h8",
"gas_7_lfl_c3h8",
"gas_8_lfl_c3h8",
"gas_9_lfl_c3h8",
"gas_10_lfl_c3h8",
"rotary_torque_2",
"density_7",
"density_8",
"density_9",
"density_10",
"bit_position_above_bottom",
"disperser_pump_pressure",
"disperser_pump_strokes",
"disperser_pump_outlet_flow",
"pump_1_outlet_flow",
"pump_2_outlet_flow",
"drilling_line_runtime",
"tripping_fill_volume_total",
"pump_1_outlet_pressure",
"pump_2_outlet_pressure",
"drilling_time_per_meter",
"gas_11_lfl_ch4",
"gas_12_lfl_ch4",
"gas_13_lfl_ch4",
"pump_3_outlet_flow",
"pump_3_outlet_pressure",
"gas_5_h2s",
"gas_6_h2s",
"gas_7_h2s"
]
}

View File

@@ -0,0 +1,130 @@
{
"edge": "edge5",
"tags": [
"Вес на крюке 1",
"Положение крюкоблока",
"Давление в манифольде 1",
"Момент на гидроключе",
"Вес на крюке 2",
"Расход на выходе",
"Загазованность 1 (НКПР)",
"Загазованность 1 (H2S)",
"Загазованность 2 (H2S)",
"Загазованность 3 (H2S)",
"Температура на выходе",
"Расход на входе 1",
"Момент на ключе АКБ",
"Момент на роторе",
"Обороты ротора",
"Давление в манифольде 2",
"Момент на машинном ключе",
"Температура на входе",
"Загазованность 4 (H2S)",
"Плотность 1",
"Плотность 2",
"Плотность 3",
"Плотность 4",
"Плотность 5",
"Плотность 6",
"Уровень 1",
"Уровень 2",
"Уровень 3",
"Уровень 4",
"Уровень 5",
"Уровень 6",
"Объем 1",
"Объем 2",
"Объем 3",
"Объем 4",
"Объем 5",
"Объем 6",
"Объем суммарный",
"Нагрузка на долото",
"Скорость проходки",
"Скорость СПО",
"Глубина инструмента",
"Глубина забоя",
"Изм. расхода на выходе",
"Подача свечи",
"Количество опущенных свеч",
"Расход на входе 2",
"Положение клиньев",
"Загазованность 2 (НКПР)",
"Уровень 7",
"Объем 7",
"Обороты СВП",
"Крутящий момент СВП",
"Уровень 8",
"Объем 8",
"Ходы насоса 1",
"Ходы насоса 2",
"Сумма ходов насосов",
"Загазованность 3 (НКПР)",
"Ходы насоса 3",
"Уровень 9",
"Объем 9",
"Уровень 10",
"Объем 10",
"Уровень 11",
"Объем 11",
"Уровень 12",
"Объем 12",
"Уровень 13",
"Объем 13",
"Уровень 14",
"Объем 14",
"Изменение суммарного объема (±)",
"Момент на ключе 1 обсадных труб",
"Момент на ключе 2 обсадных труб",
"Давление в гидросистеме ключа для бурильных труб",
"Высокая/низкая скорость ключа бурильных труб",
"Секция 1 гидрораспределителя",
"Секция 2 гидрораспределителя",
"Тип ключа",
"Загазованность 4 (НКПР)",
"Загазованность 5 (НКПР)",
"Загазованность 6 (НКПР)",
"Загазованность 7 (НКПР)",
"Загазованность 8 (НКПР)",
"Усилие в растяжке 1",
"Усилие в растяжке 2",
"Усилие в растяжке 3",
"Усилие в растяжке 4",
"Загазованность 9 (НКПР) CH4",
"Загазованность 10 (НКПР) CH4",
"Загазованность 1 (НКПР) C3H8",
"Загазованность 2 (НКПР) C3H8",
"Загазованность 3 (НКПР) C3H8",
"Загазованность 4 (НКПР) C3H8",
"Загазованность 5 (НКПР) C3H8",
"Загазованность 6 (НКПР) C3H8",
"Загазованность 7 (НКПР) C3H8",
"Загазованность 8 (НКПР) C3H8",
"Загазованность 9 (НКПР) C3H8",
"Загазованность 10 (НКПР) C3H8",
"Момент на роторе 2",
"Плотность 7",
"Плотность 8",
"Плотность 9",
"Плотность 10",
"Положение долота над забоем",
"Давление насоса диспергации",
"Число ходов насоса диспергации",
"Расход на выходе насоса диспергации",
"Расход на выходе насоса 1",
"Расход на выходе насоса 2",
"Наработка талевого каната (в реальном времени)",
"Суммарный объем долива скважины при СПО",
"Давление на выходе насоса 1",
"Давление на выходе насоса 2",
"Время бурения 1м проходки",
"Загазованность 11 (НКПР) CH4",
"Загазованность 12 (НКПР) CH4",
"Загазованность 13 (НКПР) CH4",
"Расход на выходе насоса 3",
"Давление на выходе насоса 3",
"Загазованность 5 (H2S)",
"Загазованность 6 (H2S)",
"Загазованность 7 (H2S)"
]
}

View File

@@ -0,0 +1,630 @@
{
"edge": "edge5",
"tags": [
{
"key": "hook_weight_1",
"name": "Вес на крюке 1",
"unit": "тс"
},
{
"key": "hookblock_position",
"name": "Положение крюкоблока",
"unit": "м"
},
{
"key": "manifold_pressure_1",
"name": "Давление в манифольде 1",
"unit": "МПа"
},
{
"key": "hydraulic_tong_torque",
"name": "Момент на гидроключе",
"unit": "кг*м"
},
{
"key": "hook_weight_2",
"name": "Вес на крюке 2",
"unit": "тс"
},
{
"key": "outlet_flow",
"name": "Расход на выходе",
"unit": "%"
},
{
"key": "gas_1_lfl",
"name": "Загазованность 1 (НКПР)",
"unit": "%"
},
{
"key": "gas_1_h2s",
"name": "Загазованность 1 (H2S)",
"unit": "мг/м3"
},
{
"key": "gas_2_h2s",
"name": "Загазованность 2 (H2S)",
"unit": "мг/м3"
},
{
"key": "gas_3_h2s",
"name": "Загазованность 3 (H2S)",
"unit": "мг/м3"
},
{
"key": "outlet_temperature",
"name": "Температура на выходе",
"unit": "гр.C"
},
{
"key": "inlet_flow_1",
"name": "Расход на входе 1",
"unit": "л/сек"
},
{
"key": "akb_tong_torque",
"name": "Момент на ключе АКБ",
"unit": "кН*м"
},
{
"key": "rotary_torque",
"name": "Момент на роторе",
"unit": "кН*м"
},
{
"key": "rotary_rpm",
"name": "Обороты ротора",
"unit": "об/мин"
},
{
"key": "manifold_pressure_2",
"name": "Давление в манифольде 2",
"unit": "МПа"
},
{
"key": "machine_tong_torque",
"name": "Момент на машинном ключе",
"unit": "кН*м"
},
{
"key": "inlet_temperature",
"name": "Температура на входе",
"unit": "гр.C"
},
{
"key": "gas_4_h2s",
"name": "Загазованность 4 (H2S)",
"unit": "мг/м3"
},
{
"key": "density_1",
"name": "Плотность 1",
"unit": "г/см3"
},
{
"key": "density_2",
"name": "Плотность 2",
"unit": "г/см3"
},
{
"key": "density_3",
"name": "Плотность 3",
"unit": "г/см3"
},
{
"key": "density_4",
"name": "Плотность 4",
"unit": "г/см3"
},
{
"key": "density_5",
"name": "Плотность 5",
"unit": "г/см3"
},
{
"key": "density_6",
"name": "Плотность 6",
"unit": "г/см3"
},
{
"key": "level_1",
"name": "Уровень 1",
"unit": "см"
},
{
"key": "level_2",
"name": "Уровень 2",
"unit": "см"
},
{
"key": "level_3",
"name": "Уровень 3",
"unit": "см"
},
{
"key": "level_4",
"name": "Уровень 4",
"unit": "см"
},
{
"key": "level_5",
"name": "Уровень 5",
"unit": "см"
},
{
"key": "level_6",
"name": "Уровень 6",
"unit": "см"
},
{
"key": "volume_1",
"name": "Объем 1",
"unit": "м3"
},
{
"key": "volume_2",
"name": "Объем 2",
"unit": "м3"
},
{
"key": "volume_3",
"name": "Объем 3",
"unit": "м3"
},
{
"key": "volume_4",
"name": "Объем 4",
"unit": "м3"
},
{
"key": "volume_5",
"name": "Объем 5",
"unit": "м3"
},
{
"key": "volume_6",
"name": "Объем 6",
"unit": "м3"
},
{
"key": "total_volume",
"name": "Объем суммарный",
"unit": "м3"
},
{
"key": "weight_on_bit",
"name": "Нагрузка на долото",
"unit": "тс"
},
{
"key": "penetration_rate",
"name": "Скорость проходки",
"unit": "м/ч"
},
{
"key": "tripping_speed",
"name": "Скорость СПО",
"unit": "м/с"
},
{
"key": "tool_depth",
"name": "Глубина инструмента",
"unit": "м"
},
{
"key": "bottom_depth",
"name": "Глубина забоя",
"unit": "м"
},
{
"key": "outlet_flow_change",
"name": "Изм. расхода на выходе",
"unit": "%"
},
{
"key": "stand_feed",
"name": "Подача свечи",
"unit": "м"
},
{
"key": "lowered_stands_count",
"name": "Количество опущенных свеч",
"unit": ""
},
{
"key": "inlet_flow_2",
"name": "Расход на входе 2",
"unit": "л/сек"
},
{
"key": "slips_position",
"name": "Положение клиньев",
"unit": ""
},
{
"key": "gas_2_lfl",
"name": "Загазованность 2 (НКПР)",
"unit": "%"
},
{
"key": "level_7",
"name": "Уровень 7",
"unit": "см"
},
{
"key": "volume_7",
"name": "Объем 7",
"unit": "м3"
},
{
"key": "top_drive_rpm",
"name": "Обороты СВП",
"unit": "об/мин"
},
{
"key": "top_drive_torque",
"name": "Крутящий момент СВП",
"unit": "кНм"
},
{
"key": "level_8",
"name": "Уровень 8",
"unit": "см"
},
{
"key": "volume_8",
"name": "Объем 8",
"unit": "м3"
},
{
"key": "pump_1_strokes",
"name": "Ходы насоса 1",
"unit": "ход/мин"
},
{
"key": "pump_2_strokes",
"name": "Ходы насоса 2",
"unit": "ход/мин"
},
{
"key": "pump_strokes_total",
"name": "Сумма ходов насосов",
"unit": "ход/мин"
},
{
"key": "gas_3_lfl",
"name": "Загазованность 3 (НКПР)",
"unit": "%"
},
{
"key": "pump_3_strokes",
"name": "Ходы насоса 3",
"unit": "ход/мин"
},
{
"key": "level_9",
"name": "Уровень 9",
"unit": "см"
},
{
"key": "volume_9",
"name": "Объем 9",
"unit": "м3"
},
{
"key": "level_10",
"name": "Уровень 10",
"unit": "см"
},
{
"key": "volume_10",
"name": "Объем 10",
"unit": "м3"
},
{
"key": "level_11",
"name": "Уровень 11",
"unit": "см"
},
{
"key": "volume_11",
"name": "Объем 11",
"unit": "м3"
},
{
"key": "level_12",
"name": "Уровень 12",
"unit": "см"
},
{
"key": "volume_12",
"name": "Объем 12",
"unit": "м3"
},
{
"key": "level_13",
"name": "Уровень 13",
"unit": "см"
},
{
"key": "volume_13",
"name": "Объем 13",
"unit": "м3"
},
{
"key": "level_14",
"name": "Уровень 14",
"unit": "см"
},
{
"key": "volume_14",
"name": "Объем 14",
"unit": "м3"
},
{
"key": "total_volume_change",
"name": "Изменение суммарного объема (±)",
"unit": "м3"
},
{
"key": "casing_tong_1_torque",
"name": "Момент на ключе 1 обсадных труб",
"unit": "кгм"
},
{
"key": "casing_tong_2_torque",
"name": "Момент на ключе 2 обсадных труб",
"unit": "кгм"
},
{
"key": "drill_pipe_tong_hydraulic_pressure",
"name": "Давление в гидросистеме ключа для бурильных труб",
"unit": "МПа"
},
{
"key": "drill_pipe_tong_high_low_speed",
"name": "Высокая/низкая скорость ключа бурильных труб",
"unit": ""
},
{
"key": "hydraulic_distributor_section_1",
"name": "Секция 1 гидрораспределителя",
"unit": ""
},
{
"key": "hydraulic_distributor_section_2",
"name": "Секция 2 гидрораспределителя",
"unit": ""
},
{
"key": "tong_type",
"name": "Тип ключа",
"unit": ""
},
{
"key": "gas_4_lfl",
"name": "Загазованность 4 (НКПР)",
"unit": "%"
},
{
"key": "gas_5_lfl",
"name": "Загазованность 5 (НКПР)",
"unit": "%"
},
{
"key": "gas_6_lfl",
"name": "Загазованность 6 (НКПР)",
"unit": "%"
},
{
"key": "gas_7_lfl",
"name": "Загазованность 7 (НКПР)",
"unit": "%"
},
{
"key": "gas_8_lfl",
"name": "Загазованность 8 (НКПР)",
"unit": "%"
},
{
"key": "guyline_tension_1",
"name": "Усилие в растяжке 1",
"unit": "кг"
},
{
"key": "guyline_tension_2",
"name": "Усилие в растяжке 2",
"unit": "кг"
},
{
"key": "guyline_tension_3",
"name": "Усилие в растяжке 3",
"unit": "кг"
},
{
"key": "guyline_tension_4",
"name": "Усилие в растяжке 4",
"unit": "кг"
},
{
"key": "gas_9_lfl_ch4",
"name": "Загазованность 9 (НКПР) CH4",
"unit": "%"
},
{
"key": "gas_10_lfl_ch4",
"name": "Загазованность 10 (НКПР) CH4",
"unit": "%"
},
{
"key": "gas_1_lfl_c3h8",
"name": "Загазованность 1 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_2_lfl_c3h8",
"name": "Загазованность 2 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_3_lfl_c3h8",
"name": "Загазованность 3 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_4_lfl_c3h8",
"name": "Загазованность 4 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_5_lfl_c3h8",
"name": "Загазованность 5 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_6_lfl_c3h8",
"name": "Загазованность 6 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_7_lfl_c3h8",
"name": "Загазованность 7 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_8_lfl_c3h8",
"name": "Загазованность 8 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_9_lfl_c3h8",
"name": "Загазованность 9 (НКПР) C3H8",
"unit": "%"
},
{
"key": "gas_10_lfl_c3h8",
"name": "Загазованность 10 (НКПР) C3H8",
"unit": "%"
},
{
"key": "rotary_torque_2",
"name": "Момент на роторе 2",
"unit": "кН*м"
},
{
"key": "density_7",
"name": "Плотность 7",
"unit": "г/см3"
},
{
"key": "density_8",
"name": "Плотность 8",
"unit": "г/см3"
},
{
"key": "density_9",
"name": "Плотность 9",
"unit": "г/см3"
},
{
"key": "density_10",
"name": "Плотность 10",
"unit": "г/см3"
},
{
"key": "bit_position_above_bottom",
"name": "Положение долота над забоем",
"unit": "м"
},
{
"key": "disperser_pump_pressure",
"name": "Давление насоса диспергации",
"unit": "МПа"
},
{
"key": "disperser_pump_strokes",
"name": "Число ходов насоса диспергации",
"unit": "ход/мин"
},
{
"key": "disperser_pump_outlet_flow",
"name": "Расход на выходе насоса диспергации",
"unit": "л/с"
},
{
"key": "pump_1_outlet_flow",
"name": "Расход на выходе насоса 1",
"unit": "л/с"
},
{
"key": "pump_2_outlet_flow",
"name": "Расход на выходе насоса 2",
"unit": "л/с"
},
{
"key": "drilling_line_runtime",
"name": "Наработка талевого каната (в реальном времени)",
"unit": "час."
},
{
"key": "tripping_fill_volume_total",
"name": "Суммарный объем долива скважины при СПО",
"unit": "м3"
},
{
"key": "pump_1_outlet_pressure",
"name": "Давление на выходе насоса 1",
"unit": "МПа"
},
{
"key": "pump_2_outlet_pressure",
"name": "Давление на выходе насоса 2",
"unit": "МПа"
},
{
"key": "drilling_time_per_meter",
"name": "Время бурения 1м проходки",
"unit": "час."
},
{
"key": "gas_11_lfl_ch4",
"name": "Загазованность 11 (НКПР) CH4",
"unit": "%"
},
{
"key": "gas_12_lfl_ch4",
"name": "Загазованность 12 (НКПР) CH4",
"unit": "%"
},
{
"key": "gas_13_lfl_ch4",
"name": "Загазованность 13 (НКПР) CH4",
"unit": "%"
},
{
"key": "pump_3_outlet_flow",
"name": "Расход на выходе насоса 3",
"unit": "л/с"
},
{
"key": "pump_3_outlet_pressure",
"name": "Давление на выходе насоса 3",
"unit": "МПа"
},
{
"key": "gas_5_h2s",
"name": "Загазованность 5 (H2S)",
"unit": "мг/м3"
},
{
"key": "gas_6_h2s",
"name": "Загазованность 6 (H2S)",
"unit": "мг/м3"
},
{
"key": "gas_7_h2s",
"name": "Загазованность 7 (H2S)",
"unit": "мг/м3"
}
]
}

View File

@@ -20,6 +20,8 @@ services:
HTTP_PORT: "80"
WS_PORT: "9090"
CLOUD_INGEST_URL: ${CLOUD_INGEST_URL:-https://demo.backend.drill.greact.ru/ingest}
CLOUD_INGEST_API_KEY: ${CLOUD_INGEST_API_KEY:-}
EDGE5_MODBUS_V2_MAPPING_FILE: ${EDGE5_MODBUS_V2_MAPPING_FILE:-/app/src/mappings/edge5-modbus.json}
volumes:
mosquitto_data: