Files
houseplan-card/docs/specs/484-pdf-exterior-dimension-chain.md
T
2026-09-07 20:37:04 +03:00

22 KiB
Raw Blame History

#484 — достаточная внешняя размерная цепь ступенчатого фасада в PDF

Issue: #484

1. Сценарий

Персона — Home admin из docs/SCOPE.md. В View администратор экспортирует уже поддерживаемое пространство с прямоугольным выступом фасада в одностраничный PDF и передаёт лист монтажнику, подрядчику или страховщику. По внешней размерной цепи получатель должен однозначно восстановить положение и глубину выступа без доступа к интерактивному плану.

2. Что человек увидит до и после

До исправления PDF показывает только высоту выступа; после исправления рядом со ступенчатым фасадом видны расстояния сверху и снизу, высота выступа и один размер его глубины, без диагоналей и линий сквозь посторонние стены.

3. Проблема и подтверждённая причина

После #482 кандидаты внешних размеров проходят две корректные стадии:

  1. dedupeOppositeDimensionEdges() оставляет один из двух одинаковых размеров глубины выступа и не объединяет разные вертикальные участки;
  2. groupCollinearDimensionEdges() раскладывает независимые линии фасада по собственным полосам.

Регрессия появляется на следующей стадии в src/pdf/pdf-scene.ts. externalPlacementTouchesArchitecture() проверяет размерную линию, обе выносные линии (extension lines) и shelf через общий pdfSegmentTouchesGeometry(). Параметр allowStartBoundary разрешает только касание в самой начальной точке. Любое коллинеарное наложение и любое следующее пересечение границы считаются коллизией.

На обычном выпуклом прямоугольном углу выносная линия сразу уходит наружу. На ступени перпендикуляр от конца измеряемого ребра может сначала совпасть с примыкающим участком фасада или пройти через тело соединённой в этом же узле стены. Это не столкновение с посторонней архитектурой, а неизбежный выход из собственного измеряемого угла. Текущая проверка отвергает его на каждой полосе 0…80 mm, и группа размеров молча не печатается.

4. Цели

  • восстановить минимально достаточную внешнюю размерную цепь ортогонального ступенчатого фасада;
  • сохранить точный запрет на повторный вход выносной линии в архитектуру;
  • сохранить локальную дедупликацию #482 и отсутствие диагональных размеров;
  • закрепить регрессию unit-, browser- и визуальным свидетельствами.

5. Не входит в задачу

  • изменение геометрии плана, модели стен, model_version или сохранённого конфига;
  • новые настройки PDF или изменение диалога экспорта;
  • общий CAD-размерный движок, цепи между произвольными опорными осями, автоматические этажные размерные сетки;
  • изменение внутренних размеров комнат, площадей, масштаба, компаса, штриховки, проёмов или других результатов #482;
  • разрешение размерным линиям проходить сквозь несвязанную архитектуру;
  • принятие golden-эталонов без полного Linux-артефакта и явного визуального ревью.

6. Контракт внешней цепи

6.1 Минимально достаточный набор для прямоугольного выступа

Для внешнего ring

(0,0) → (10,0) → (10,3) → (12,3) → (12,6) → (10,6) → (10,10) → (0,10)

PDF выводит четыре осевых размера, необходимые для положения выступа:

  1. вертикальное расстояние от верхней границы здания до начала выступа;
  2. вертикальную высоту выступа;
  3. вертикальное расстояние от конца выступа до нижней границы здания;
  4. одну горизонтальную глубину выступа.

Две горизонтальные ступени одинаковой глубины образуют локальную противоположную пару, поэтому печатается ровно один из этих двух размеров. Детерминированный placement score #482 выбирает сторону с безопасной полосой и свободным местом; при полной ничьей действует существующий stable source key.

Равные по числу, но топологически разные вертикальные участки не объединяются. Порядок обхода ring и его winding не меняют множество напечатанных размеров.

6.2 Разрешённый начальный выход из собственного узла

Послабление применяется только к двум внешним extension lines конкретного DimensionEdge. Полная печатная линия по-прежнему начинается в фактической точке sourceA/sourceB; визуальный разрыв или перенос начала за стену не допускается.

Для проверки коллизии extension line делится точными параметрами пересечений с архитектурными rings на последовательные интервалы. Разрешён только её непрерывный начальный префикс от source point до первого строго внешнего интервала. В этот префикс могут входить:

  • общая начальная точка измеряемой и примыкающей граней;
  • коллинеарное движение вдоль границы стены, инцидентной source point;
  • выход через тело физической стены, непрерывно примыкающее к source point.

После того как линия впервые вышла в свободное пространство, любое более позднее касание, пересечение, коллинеарное наложение или вход в физическую архитектуру снова считается коллизией. Это включает другую стену, другой участок того же connected component, колонну и возвращение в исходную стену. Касания в одной и той же точке перехода «собственный префикс → свободное пространство» группируются с физическим epsilon и не считаются повторным входом.

Невалидные координаты, неоднозначная классификация интервала и линия, которая так и не получает свободного участка, обрабатываются fail closed: кандидат считается столкнувшимся.

6.3 Остальные коллизии

Без изменений остаются:

  • collision label box с архитектурой и ранее размещёнными подписями;
  • collision основной dimension line и shelf с архитектурой;
  • пересечения extension lines с чужими label boxes;
  • 1 mm clearance, общий lane для коллинеарной группы и поиск полос 0…80 mm;
  • безопасный отказ от конкретной группы, если после узкого исключения ни одна полоса не подходит.

Послабление не становится default общего pdfSegmentTouchesGeometry() и не используется внутренними размерами, декором либо другими PDF-примитивами.

7. Геометрические и визуальные инварианты

  • Размеры остаются только horizontal/vertical и near-axis в действующем допуске #482; диагональная хорда не создаётся и не проецируется.
  • Прямоугольный внешний ring по-прежнему получает одну горизонтальную и одну вертикальную величину после дедупликации противоположных сторон.
  • Внешние подписи центрированы на измеряемом участке; весь group lane сдвигается целиком, а не отдельными фрагментами.
  • Допустимый начальный префикс влияет только на collision decision. Цвет, толщина, начало и конец напечатанной extension line не меняются.
  • Если посторонняя архитектура перекрывает все полосы, небезопасный размер не печатается. Исправление не заменяет fail-closed поведение на overlap.

8. Модель, совместимость и миграция

config_version, model_version, space schema и PDF option storage не меняются. Экспорт остаётся read-only и работает поверх временной печатной сцены. Миграция и downgrade converter не нужны; старые и новые карточки читают один и тот же план.

9. UX, i18n и touch

Новых элементов управления и строк интерфейса нет; ключи i18n не добавляются. Исправляется только содержимое скачанного PDF при уже включённой опции размеров.

Экспорт вызывается из View и остаётся полностью поддерживаемым на touch. Диалог, tap/pointer-поведение и скачивание не меняются. Результат PDF одинаков для desktop, touch и HA Companion при одинаковом plan/config.

10. Производительность и безопасность

Проверка собственного начального выхода использует конечный набор сегментов уже подготовленных architectureRings; сетевой ввод, DOM и пользовательский HTML не появляются. Она выполняется только при явном PDF-экспорте с включёнными размерами.

Действующий budget #482 — построение large-house scene <200 ms на референсной Node-среде — сохраняется. Initial View graph и lazy boundary PDF не меняются. Детерминированность PDF bytes для одинаковых now, plan/config и options сохраняется.

11. План реализации

  1. Расширить точный segment/ring collision helper в src/pdf/pdf-collision.ts отдельным opt-in режимом начального выхода: собрать и отсортировать intersection parameters, классифицировать открытые интервалы и разрешить только непрерывный source-connected prefix.
  2. В src/pdf/pdf-scene.ts включить этот режим только для двух внешних extension lines. Dimension line, shelf, labels и внутренние размеры оставить на строгом пути.
  3. Добавить чистые unit-матрицы: обычный выход наружу; начальное коллинеарное наложение; начальный проход через инцидентное тело; касание в точке выхода; повторный вход; касание посторонней стены; invalid/fail-closed.
  4. Добавить scene fixture ступенчатого фасада в прямом и обратном winding и проверить полный набор четырёх внешних величин, единственность глубины, отсутствие диагонали и неизменный rectangle contract.
  5. Добавить focused PDF browser/golden fixture либо расширить pdf-export-polish-light так, чтобы выступ и все четыре размера были видимы и проверялись semantic guard, а не только пиксельным снимком.
  6. Обновить канонические документы, changelog и screenshot fingerprints по правилам процесса.

12. Acceptance criteria и доказательства

AC Критерий Обязательное доказательство
AC1 Детерминированный stepped-ring fixture печатает верхний вертикальный участок, высоту выступа, нижний вертикальный участок и одну глубину test/pdf-scene.test.mjs: точные значения и позиции внешних text/line commands для обоих winding
AC2 Из двух равных глубин остаётся ровно одна; rectangle сохраняет одну H и одну V сторону; диагональных размеров нет unit matrix pdf-dimensions + scene assertions на axis/количество
AC3 Extension line может выйти из собственного узла через начальное коллинеарное/solid-примыкание, печатается полностью от source point и не получает визуального разрыва test/pdf-collision.test.mjs + scene command endpoints
AC4 После первого выхода наружу любое касание/пересечение/overlap архитектуры остаётся запрещённым; неразмещаемая группа fail closed отрицательная unit/scene matrix + mutation witness, глобально разрешающий post-exit collision
AC5 Label, dimension line, shelf, внутренние размеры, 1 mm clearance и lane/group contract #482 не меняются существующие pdf-collision/pdf-scene tests + код-ревью по call sites
AC6 Результат не зависит от winding/порядка ring, PDF остаётся deterministic и large-house scene укладывается в <200 ms> unit equality + существующий deterministic writer/scene perf test
AC7 В rasterized focused PDF визуально присутствуют все четыре элемента цепи, подписи не лежат в кладке и никакая extension line не пересекает постороннюю архитектуру browser PDF smoke + Linux golden/semantic probe с явной приёмкой полного артефакта
AC8 Канонические и пользовательские документы описывают достаточную цепь выступа; оба changelog ссылаются на #484; docs/golden fingerprints актуальны node scripts/check-docs.mjs, changelog/provenance gates, reviewed Linux docs/golden artifacts

12.1 Обязательные отрицательные свидетели защитных AC

Защитный AC Чем доказывается Чем обязан краснеть
AC3 stepped source-exit unit/scene fixture мутант pdf-own-exit-prefix-disabled возвращает строгий allowStartBoundary и теряет минимум один обязательный размер
AC4 post-exit re-entry и foreign-wall fixtures мутант pdf-post-exit-collision-ignored разрешает всю extension line и печатает заведомо небезопасный размер

Мутанты добавляются в scripts/mutation-gate.mjs, потому что защитные AC живут в продуктовом коде, а визуальный/browser witness дорог для повторного ручного снятия на код-ревью.

13. Release artifacts

  • docs/CHANGELOG.md и docs/CHANGELOG.ru.md: короткий bugfix со ссылкой на #484;
  • docs/PDF-EXPORT.md: правило достаточной внешней цепи прямоугольного выступа и узкий source-exit contract;
  • docs/USER-GUIDE.md и docs/USER-GUIDE.ru.md: пользовательское обещание размеров ступенчатого фасада;
  • docs/ARCHITECTURE.md: ownership source-aware collision режима внутри lazy PDF runtime;
  • docs/STATUS.md: текущая beta-line после фактического попадания изменения;
  • focused Linux golden и semantic contract; принимается только npm run golden:accept -- --reviewed --from=<полный Linux artifact>;
  • поскольку меняется src/**, полный Linux docs screenshot artifact обязателен для обновления fingerprint. Пиксельные изменения десяти кадров не ожидаются; любое изменение кадра разбирается отдельно, а не принимается автоматически.

Backend, HA API и security model не меняются; backend pytest не является целевым доказательством.

14. Риски и снижение

Риск Снижение
Узкое исключение превратится в общее разрешение линиям проходить через стены state machine «initial prefix → outside → any later hit rejects» и отрицательный мутант AC4
Касание нескольких граней в одной вершине ошибочно станет re-entry группировка intersection parameters с единым physical epsilon и corner fixtures
isSolid по-разному классифицирует точку на границе интервалы проверяются по внутренним midpoint, коллинеарные интервалы учитываются отдельно
Дедупликация снова удалит непарные равные участки существующая topology matrix #482 плюс exact stepped counts AC1/AC2
Исправление будет видно только в unit, но не в настоящем PDF browser export + rasterized focused golden + semantic probe AC7
Перебор пересечений замедлит крупный экспорт используется уже подготовленная архитектура, без повторного model build; сохраняется <200 ms budget AC6

15. Откат

Откат — возврат lazy PDF chunk к версии до #484. Данные и настройки не мигрируют, поэтому отдельный rollback данных не нужен. При откате возвращается только известная потеря размеров ступенчатого фасада; обычный экспорт остаётся совместимым.

16. Принято предположительно, можно менять на ревью

Следующие решения технические и не требуют продуктового ответа владельца:

  • точный helper может расширить pdfSegmentTouchesGeometry() opt-in опцией или быть отдельной функцией, если default строгого helper остаётся неизменным;
  • intersection sweep может хранить пары tStart/tEnd либо нормализованные breakpoints; внешний контракт задаётся §6.2, а не формой API;
  • focused visual witness может быть новым golden scenario либо расширением pdf-export-polish-light, если semantic guard однозначно отличает наличие каждого размера;
  • название и число внутренних тестовых helpers свободны при полном покрытии AC и двух обязательных мутантов.