mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
@@ -0,0 +1,264 @@
|
||||
# #484 — достаточная внешняя размерная цепь ступенчатого фасада в PDF
|
||||
|
||||
Issue: [#484](https://github.com/Matysh/houseplan-card/issues/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 и двух обязательных мутантов.
|
||||
|
||||
@@ -28,6 +28,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#484](https://github.com/Matysh/houseplan-card/issues/484) Внешняя размерная цепь ступенчатого фасада в PDF | [484-pdf-exterior-dimension-chain.md](484-pdf-exterior-dimension-chain.md) |
|
||||
| [#482](https://github.com/Matysh/houseplan-card/issues/482) Доводка экспорта пространства в PDF | [482-pdf-export-polish.md](482-pdf-export-polish.md) |
|
||||
| [#471](https://github.com/Matysh/houseplan-card/issues/471) Убрать белые raised plates вокруг маркеров и названий комнат | [471-isometric-overlay-white-plates.md](471-isometric-overlay-white-plates.md) |
|
||||
| [#6](https://github.com/Matysh/houseplan-card/issues/6) Vacuum XCME path segments | [006-vacuum-xcme-path.md](006-vacuum-xcme-path.md) |
|
||||
|
||||
Reference in New Issue
Block a user