[ відкрити тікет → ]
$ tail -n 20 ./logs/dev.log
// інженерні замітки та технічні рішення

Інженерний Журнал

Фільтр:
[ 2026-03-19 18:40 ] #VELDER-SOFT [ TYPE: BUG_FIXED ]

Усунення зміщення інспекційних міток при масштабуванні векторних PDF-креслень

Проблема: Під час розробки інструменту розмітки та інспекції для компанії Velder ми додали можливість встановлювати мітки на технічних схемах. Проте при масштабуванні (pinch zoom) PDF-креслення координати зміщувалися, через що мітки «з'їжджали» зі своїх первинних точок на полотні.

Першопричина: Початкова реалізація фіксувала точку масштабування статично (зліва вгорі екрану). При зміні масштабу координатна сітка збільшувалась, але скрол вікна не коригувався пропорційно до центру жесту користувача, через що фонове зображення зміщувалося.

Вирішення:

  1. Ми почали відстежувати координати двох пальців на початку жесту для розрахунку точного центру фокусування (pinch focal point) .
  2. Застосували динамічне зміщення transform-origin на основі пропорцій фокальної точки відносно поточної рамки полотна під час жесту.
  3. При відпусканні пальців ми фіксуємо фокальні коефіцієнти ( focalRatioX , focalRatioY ), запускаємо перерендер сторінки через PDF.js у новому масштабі, та миттєво компенсуємо зсув в'юпорту прокруткою за допомогою window.scrollBy .

Результат: Креслення та мітки залишаються намертво зафіксованими під пальцями під час будь-какого масштабування, гарантуючи 100% точність даних на планах лікарні.

// Фіксація фокальних коефіцієнтів перед скиданням тимчасового CSS-трансформу
var cRect = canvas.getBoundingClientRect();
var focalRatioX = cRect.width > 0 ? (pinchFocalClientX - cRect.left) / cRect.width : 0.5;
var focalRatioY = cRect.height > 0 ? (pinchFocalClientY - cRect.top) / cRect.height : 0.5;

renderPage().then(function() {
  wrap.style.transform = '';
  wrap.style.transformOrigin = 'top left';
  // Коригуємо скрол вікна так, щоб фокальна точка залишилася на тому самому місці екрану
  requestAnimationFrame(function() {
    var newRect = canvas.getBoundingClientRect();
    var newFocalX = newRect.left + focalRatioX * newRect.width;
    var newFocalY = newRect.top + focalRatioY * newRect.height;
    window.scrollBy(newFocalX - pinchFocalClientX, newFocalY - pinchFocalClientY);
  });
});
[ 2026-03-12 11:20 ] #VELDER-SOFT [ TYPE: FEATURE ]

Локальний NLP-парсер голосу для швидкого створення завдань (hands-free)

Проблема: Техніки компанії Velder виконують монтаж та обслуговування мереж медичного газу в лікарняних зонах (операційні, SOR, машинні відділення). Оскільки вони працюють у захисних масках та міцних робочих рукавичках, вводити звіти вручну на сенсорному екрані вкрай незручно. Крім того, залізобетонні стіни підвалів повністю блокують мобільний зв'язок, що робить ручне введення складним.

Обмеження: Підвали лікарень та бункери — це зони без зв'язку. Використання хмарних сервісів NLP (як-от Dialogflow, OpenAI чи Google Cloud NLP) було неможливим через регулярні тайм-аути інтернету.

Вирішення: Ми розробили легкий локальний парсер природної мови (NLP) спеціально для польської мови, що працює на пристрої. Після того як вбудований у систему Speech-to-Text переводить голос у сирий текст, наш парсер:

  • Визначає відносні дати (наприклад, "jutro" [завтра], "dzisiaj" [сьогодні], дні тижня, порядкові числівники на кшталт "pierwszego" [першого числа]).
  • Виокремлює час (наприклад, "rano" [вранці], "wieczorem" [ввечері], "o 14" [о 14:00]).
  • Очищає речення від польських сполучників та допоміжних слів, виділяючи чисту суть завдання та автоматично плануючи нагадування в локальній базі.

Результат: Робітникам достатньо сказати: "przypomnij mi jutro o 10 o teście szczelności" (нагадай мені завтра о 10 про тест герметичності) — і додаток автоматично створить завдання "teście szczelności" (тест герметичності) на завтра на 10:00, працюючи на 100% офлайн у підвалі.

// Спрощена логіка розбору дат з польського тексту на пристрої
export function parsePolishDate(input: string): Date {
  const now = new Date();
  let target = new Date(now);
  
  if (/jutro/i.test(input)) {
    target.setDate(now.getDate() + 1);
  }
  
  const timeMatch = input.match(/o\s+(\d{1,2})/i);
  if (timeMatch) {
    target.setHours(parseInt(timeMatch[1], 10), 0, 0, 0);
  }
  return target;
}
[ 2026-05-28 14:15 ] #TEPLO-HUB [ TYPE: FEATURE ]

Фонова синхронізація та черга транзакцій SQLite для офлайн-кошторисів

Проблема: Інженерні об'єкти часто розташовані в глибоких підвалах або віддалених заміських зонах із нульовим покриттям. Технікам потрібно створювати детальні кошториси з сотнями деталей. При втраті зв'язку додаток видавав помилки, що призводило до втрати даних.

Вирішення: Ми впровадили транзакційну чергу на базі Expo SQLite, що працює за принципом offline-first. Коли технік додає матеріали офлайн:

  1. Зміни записуються в локальну БД та серіалізуються в таблицю offline_mutations у вигляді дельта-мутацій.
  2. NetInfo у фоновому режимі стежить за мережею.
  3. При відновленні сигналу менеджер черги послідовно відправляє транзакції на Firebase Cloud Functions.

Результат: 100% збереження даних із автоматичним вирішенням конфліктів ("останній запис перемагає" для брудних полів).

// Послідовне відправлення накопичених офлайн-мутацій з SQLite
async function syncOfflineQueue() {
  const mutations = await db.getAllAsync('SELECT * FROM offline_mutations ORDER BY timestamp ASC');
  for (const mut of mutations) {
    try {
      await api.post('/sync-mutation', JSON.parse(mut.payload));
      await db.runAsync('DELETE FROM offline_mutations WHERE id = ?', mut.id);
    } catch (err) {
      if (err.status === 409) {
        await resolveConflict(mut);
      } else {
        break; // retry on next network change
      }
    }
  }
}
[ 2026-05-15 09:30 ] #TEPLO-HUB [ TYPE: PERFORMANCE ]

Генерація брендованих багатосторінкових PDF-кошторисів на пристрої менш ніж за 1.5 сек

Проблема: Генерація великих PDF-кошторисів безпосередньо на смартфонах викликала фризи інтерфейсу та вильоти через брак пам'яті на бюджетних Android-пристроях через рендеринг важких шрифтів та графіки.

Вирішення: Ми переписали процес компіляції PDF за допомогою react-native-html-to-pdf в ізольованому потоці. Перевели графічні логотипи в оптимізований формат WebP Base64, інтегрували легкі шрифти Google Fonts безпосередньо в CSS і побудували асинхронний посторінковий рендеринг.

Результат: Навантаження на оперативну пам'ять знизилось на 70%, а час генерації скоротився з 6 до 1.2 секунди без використання серверних ресурсів.

// Асинхронна збірка PDF у фоновому потоці
const htmlContent = `
  
    
    \${renderEstimateTable(estimateData)}
  
`;
const file = await RNHTMLtoPDF.convert({
  html: htmlContent,
  fileName: \`estimate_\${estimateId}\`,
  directory: 'Documents',
});