Map (map.ts, styles.css, index.html):
- Cluster icons are now donut charts split green (free) / red (busy) by the
occupancy of the surfaces they contain, count in the centre. Custom
clusterIconLayout with a hit-area shape; hover/click preserved.
- gridSize 64 -> 112 so nearby clusters stop overlapping at the overview zoom.
- Live legend: "свободна: X · занята: Y · поверхностей: N", reacts to filters.
- "Без координат" is now a clickable badge opening a compact codes-only panel
(header "Без координат") so the missing-coordinate surfaces can be fixed.
- Search-to-locate: when a filter narrows to one surface the map flies in and
opens its balloon.
- Balloon restyled to match the timeline tooltip; muted map tiles (ground-pane
filter) so the markers stand out.
- Cluster hover tooltip shows the brand for occupied surfaces; legend plate
raised above the Yandex controls.
Header (index.html, styles.css, main.ts):
- Scale + appearance + checkboxes recomposed into one compact block; shorter
slider; the "Масштаб" label uses the shared field-label style again.
- "Оформление" is now a round "+" button pinned to the header's bottom-right
corner with a hover tooltip; opens the same appearance menu.
- Counters relabelled "Поверхности" / "Брони"; the counters plate and the
view-mode switch now share one borderless grey fill; mode buttons sit on a
more visible grey frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend (styles.css, index.html, main.ts):
- Design tokens: single accent/border/radius/shadow/spacing system; flat header
(dropped the boxed groups) with a subtle bottom divider.
- Unified controls: 36px height, consistent focus ring; search field gets a
magnifier icon; view-mode switch is now a compact vertical segmented control.
- Fixed the dropdown option font: `.field label` styles were leaking into the
option <label>s (they're inside .field), forcing uppercase + bold; options are
now normal weight / normal case.
- Header alignment: mode switch fills the row so its bottom lines up with the
date fields; the counters block is pinned to the same bottom line.
- Dropdown lists no longer show the column-resizer line bleeding through: the
header now establishes a stacking context (position:relative + z-index) so its
panels sit above the timeline.
- Dark theme is implemented (tokens + no-flash) but hidden for now (toggle
display:none, forced light) until it's polished — easy to re-enable.
Timeline tooltip (timeline.ts, types.ts):
- Replaced the plain multi-line text tooltip with a structured card: bold brand
title + task №, company subtitle, localized dates + duration, status as a
coloured dot, manager, a distinct collision section, and a "click to open in
Planfix" hint. Left accent stripe in the bar colour; matches the map tooltip.
- Values are escaped (escapeHtml) before going into innerHTML.
Backend (main.py):
- /api/bookings now returns `manager` (Array(String)), canonicalized to the
"Фамилия Имя" form (see canon_manager) so the tooltip can show it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
/api/meta built the city/brand/dimension/manager/status option lists with five
sequential `await ch_query(...)` calls, so its latency was the sum of five
round-trips to ClickHouse. The queries are independent, and the frontend hits
/api/meta on every filter change, so this was 5x latency for no reason.
Wrap them in asyncio.gather so they fire in parallel; total time is now ~the
slowest single query plus overhead. Output verified identical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both tables are ReplacingMergeTree; dedup happens only during background part
merges, not at read time. The ongoing etl_rw feed keeps inserting into pf_board,
so right after an insert that updates an existing row there is a short window
(before the forced merge, min_age_to_force_merge_seconds=15) where the same
task_id / board_key exists in two parts. A plain SELECT reads both parts and
returns the row twice.
The /api/boards query already collapses duplicates via argMax + GROUP BY, but
/api/bookings and the two /api/map queries read raw rows, so they could
intermittently double a booking (timeline/balloon) or a surface marker.
Add FINAL to those three reads (pf_board in bookings + map current, board_info
in map surfaces). At 463/515 rows the cost is negligible and it removes the
class of transient duplicates regardless of merge timing. Output verified
unchanged against the single-part baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Planfix has emitted manager names in two word orders over time ("Имя Фамилия"
from the 26 Jul 2026 n8n backfill, "Фамилия Имя" from the ongoing etl_rw feed),
so one person appeared as two distinct filter options (e.g. "Александра Столбова"
vs "Столбова Александра"), splitting their tasks across two facet values.
Fix without touching stored data:
- MANAGER_KEY_ARR: SQL expression building a word-order- and case-independent
key (lowercase, split, arraySort, rejoin) per manager array element.
- MANAGER_COND / MAP_MANAGER_COND now match on that key, so a selected manager
catches rows stored under either order.
- parse_filters normalizes incoming manager values with the same key (_mkey).
- /api/meta returns canonical "Фамилия Имя" names (CANONICAL_MANAGERS),
deduplicated; unknown names pass through unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>