PanOpticon — Scope
Users, navigation, scheduling direction, and multi-user data sharing Version 0.3 · August 2026 · against PanOpticon v2.16.0
Supersedes docs/multi_user_scope.md (v0.1). Section 7.2 corrects a factual error in that document. Section 3 is revised from v0.2 following confirmation of who actually uses this.
1. Purpose of this document
Three questions, addressed together because they interact:
- Can PanOpticon be made easier to use without removing what daily users rely on?
- Can it become a scheduling and planning aid, not only a tracking one?
- Can several people contribute to one job's data without the constant file-shuffling that manual tracking already inflicts?
They interact because sharing and scheduling both add concepts — devices, sync, target dates, sequence — to a program that is already dense. Either built carelessly makes the usability problem worse, not better.
Findings below come from parsing the v2.16.0 source and from running the concurrency behaviour directly, not from reading the code and estimating.
2. What PanOpticon is today
A single-user Windows desktop application. Scope is imported from CAD pre-fab exports; field progress is ticked off against it; budget hours come from the accounting variance report; the gap between them is surfaced rather than smoothed over.
The design commitment throughout is that disagreements are shown, never silently resolved. That principle should survive everything proposed here.
2.1 Measured size
| Module | Lines | Role |
|---|---|---|
gui.py | 4,802 | All six screens and their dialogs |
db.py | 1,058 | Schema, 13 tables, all persistence |
importer.py | 523 | CAD folder discovery, Column ID → phase mapping |
hours.py | 491 | Budget allocation, connection weighting, projections |
metrics.py | 481 | Progress, daily series, burn-up |
excel_out.py | 490 | Workbook export and write-back |
checklist_pdf.py | 462 | Field checklist round-trip |
cli.py | 457 | Headless operations |
gaps.py | 443 | Reconcile checks |
fittings.py | 405 | FITTINGS / BOM PDF parsing |
variance.py | 349 | Variance report parsing |
| Others | 875 | parser, writer, scanner, picker |
| Total | 10,836 |
gui.py is 44% of the codebase. That is the first structural observation worth acting on, and it shows up in the usability findings below.
3. Who uses this
Confirmed, and it changes the design brief materially:
| Role | Uses PanOpticon for |
|---|---|
| Superintendent | Look-ahead, crew allocation, budget position |
| Field Manager / Crew Leader II | Recording installed work, tracking their own crews |
| Project Engineer (occasional) | Scope import, variance reconciliation |
Not field labourers. Everyone entering data is a competent professional who uses the tool regularly and knows the trade.
That invalidates part of v0.2. The goal is not to make PanOpticon novice-proof — nobody here is a novice, and hand-holding would slow down people who use this every day. The real problems are different:
- Density in the wrong places. Not "too much information" — too much of it repeated, or too far from the decision it informs.
- Undiscoverable features. A superintendent will not find right-click bulk crew assignment by accident, and will not read a manual either.
- Navigation that fragments one workflow across several tabs.
Simplification here means fewer places to look, not fewer capabilities.
4. Navigation
4.1 What changed (implemented)
Six top-level tabs became four:
| Before | After |
|---|---|
| Scope · Dashboard · Checklist · Productivity Log · Takeoff · Reconcile | Plan · Production · Takeoff · Data |
Plan = Progress · Daily · Hours Production = Log Work · History · Calendar Data = Scope · Reconcile
Rationale:
- Scope and Dashboard answered halves of the same question. "What is Level 4" was one tab; "how much of Level 4 is left" was another. The Dashboard's Systems sub-tab duplicated six of the Scope tree's eight columns outright, and the per-system breakdown added a third view of the same fact. The Systems sub-tab is now removed and its one unique column, Remaining, folded into the Scope tree.
- The Productivity Log is a consequence of the Checklist. Log entries are created by ticking pieces off; they are not typed independently. Presenting them as separate top-level features made one workflow look like two.
- Scope belonged with Reconcile, not with the analysis. It is an administrative screen — touched when scope arrives, not daily — and the corrections Reconcile asks for are made in Scope, so every fix was a round trip between two top-level tabs. They now sit side by side under Data. The counter-argument, that a new user's first action is a scope import which is now one tab further away, is answered by an empty-state button on Progress that jumps straight there and opens the import dialog.
Sub-tabs are flat — no third level of tabs. The level selector and stat strip appear only above the level-scoped views (not Hours, which is scoped by phase code and needs the height for its table), and the log filters only above History and Calendar.
Regression coverage: simtest2.py now asserts the tab structure, the absence of a third tab level, every jump target, header visibility per sub-tab, and that the Scope tree retains every column the removed Systems tab carried.
5. GUI analysis
5.1 Measured complexity
Measured before the §4 merge — six top-level tabs, nine sub-tabs. Now four top-level tabs and seven sub-tabs, with the same controls:
| Screen | Controls | Notes |
|---|---|---|
| Checklist | 19 | The daily driver — and the densest screen |
| History (was Productivity Log) | 11 | 3 combo filters, 2 date pickers |
| Dashboard | 11 | Plus 4 sub-tabs |
| Scope | 7 | |
| Reconcile | 4 | |
| Takeoff | 3 |
Fourteen tables/trees, the widest carrying nine columns. Seventy QMessageBox call sites. Eighteen tooltips across the entire application.
Those last two numbers are the finding. PanOpticon explains itself almost entirely through modal dialogs that appear after you act, and almost not at all before you act. A new user learns the program by being interrupted.
5.2 The core problem: everything has equal weight
Not that there is too much on screen — a superintendent can handle a dense screen — but that nothing on it is ranked.
The daily task, recording installed pieces, sits on a screen offering twelve other buttons: export for iPad, import checklist, open spool sheet, open spool map, select-all, clear, expand, collapse, date stepper, crew size, hours per man. "Log installed" is styled identically to "Export for iPad…", which is used perhaps monthly.
The information is correct and wanted. It is the ranking that is absent: the hourly action looks the same as the monthly one.
5.3 Specific findings
F1 — The daily task is not the visually dominant one. "Log installed" is one button among thirteen on the Checklist screen, with no visual weight distinguishing it from "Export for iPad…".
F2 — Empty states teach nothing. Only four empty-state strings exist in 4,802 lines, and they state a fact ("No systems on this level yet") without offering the next action. A first launch shows four tabs of empty tables and no indication that everything begins with Import Pre-Fab Spool Folders.
F3 — Discoverability depends on tooltips that mostly do not exist. Eighteen tooltips for roughly 55 interactive controls. Features added recently — crew assignment by double-click, right-click column management, right-click bulk assign — are discoverable only by accident or by reading the basis text beneath the Hours table.
F4 — Terminology assumes the domain and the tool. "Uncommitted Remaining", "PAP Changes", "Proj vs Est Hours", "Crew days", "Over / Under". A pipefitter knows the trade but not necessarily this report's vocabulary. The Hours tab's basis paragraph does explain these, but it sits below the table it explains.
F5 — Destructive and routine actions look identical. "Clear assignments" and "Reset to defaults" sit in the same button row, in the same style, as "Add crew…". Confirmation dialogs catch this, which is correct — but the visual language does not.
F6 — Nine-column tables with no column control except on Hours. The Hours tab now supports hide/reorder/reset. Scope (9 columns), Systems (9) and History (8) do not. The capability exists and is proven; it is simply not applied consistently. (The Dashboard's Systems table, also 9 columns, has since been removed as a duplicate — see §4.1.)
F7 — No undo outside the log. Reversal exists for install entries, correctly. But crew assignment, column layout, config changes, and scope deletion have no undo — only confirmation prompts.
5.4 What is already good, and should not be disturbed
Stated so that a redesign does not discard it:
- The Reconcile tab's whole premise. Surfacing disagreement instead of auto-resolving is the product's differentiator.
- Append-only-by-intent logging with reversal rather than editing.
- Progressive disclosure in trees. Group rows carry their own rollups, so a level total reads without expanding — this is the pattern the rest of the app should copy.
- The basis text under Hours. Genuinely explains the arithmetic. The problem is placement and inconsistency, not the writing.
- Colour discipline. Red means over budget. It is not spent elsewhere.
5.5 Proposal
Revised for the confirmed audience: these are daily professional users, so every item below removes friction rather than adding guidance.
P1 — Primary-action styling. One visually dominant button per screen — the thing that screen is for. "Log installed" on Log Work; "Import Pre-Fab Spool Folders" on Scope. Secondary actions recede. Styling, not structure.
P2 — Empty states that start the workflow. Every empty table gets a sentence and a button. Not hand-holding — a first launch currently shows empty tables with no indication that everything begins with a scope import, and that costs a superintendent ten minutes once.
P3 — Column control everywhere. Apply the Hours tab's existing hide/reorder/reset to the Scope tree, the History table, and Takeoff. Each person tunes their own density; nobody decides centrally what to remove. Directly addresses "dense in the wrong places" without losing anything.
P4 — Surface the hidden interactions. Double-click to assign a crew, right-click for bulk assign, right-click headers for columns. All undiscoverable today. A short affordance — a hint row, a context-menu cue — costs little and unlocks features already built.
P5 — Destructive actions styled as destructive. "Clear assignments" should not look identical to "Add crew".
P6 — A glossary, reachable and inline. "What the numbers mean", plus ? on dense headers. Content largely exists in the Hours basis paragraphs already; it is placement that is wrong.
P7 — Split gui.py. 4,802 lines in one file makes every change riskier than it should be. One module per tab, mechanical and behaviour-preserving.
Ordered by value per unit of effort: P2, P1, P3, P4, P5, P6, P7.
(The "Today screen" proposed in v0.2 is withdrawn. With Plan and Production merged, a fifth summary screen would be a fourth view of data already visible — the mistake the merge just corrected.)
6. Direction: scheduling and planning
The stated goal is for PanOpticon to become a scheduling and planning aid, not only a tracking one. This is a genuine change in kind, so it is worth being precise about the gap.
6.1 What PanOpticon already has
More than half the foundation, which is why the goal is realistic:
- Scope — every spool that exists, with LF, joints and connection type
- Demonstrated productivity — measured LF per man-hour from real logs
- Budget — man-hours per phase from the variance report
- Crews — named, sized, assigned to specific spools
- Duration arithmetic — hours ÷ (crew × hrs/day), with finish dates
- Working-week model — 5-day, 4×10, 6-day
PanOpticon can already answer "how long will the remaining work take this crew".
6.2 What is missing for scheduling
| Gap | Why it matters |
|---|---|
| No target dates | Every projection counts forward from today. There is no "this must be done by the 14th", so there is no such thing as behind — only how long left |
| No sequence or dependencies | Underground before slab, risers before branches. PanOpticon treats remaining spools as an unordered pool |
| No look-ahead window | Three-week look-ahead is the standard planning artefact in this trade. PanOpticon has no concept of a window |
| No crew calendar | Crews have a size but no availability — no holidays, no "Ruiz is on another job Thursday" |
| No plan-vs-actual | Without a recorded plan, there is nothing to compare actuals against |
6.3 The one that unlocks the rest
Target dates on scope. A due date per system (or per spool) converts every existing projection from "11 days of work" into "11 days of work, due in 8 — three days short."
That single addition turns the Hours tab from a calculator into a schedule, and it requires no new arithmetic: the durations, crew sizes, working-week model and finish dates already exist and are tested. It is a field, a way to set it, and a comparison.
6.4 A staged path
S1 — Target dates. A due date per system, entered manually or imported. Hours tab gains Due and Slack columns; a crew running past its date goes red. Small, and immediately useful.
S2 — Look-ahead view. A three-week window listing what each crew should be on, derived from assignments, durations and dates. Printable — the artefact superintendents already produce by hand.
S3 — Sequence constraints. "This system cannot start until that one finishes." Enables realistic dates instead of everything starting today.
S4 — Crew availability. Per-crew calendars, so durations account for a crew being elsewhere.
S5 — Plan-vs-actual. Snapshot the plan; compare against what happened. The scheduling equivalent of what Reconcile does for scope.
S1 and S2 deliver most of the practical value. S3–S5 are real projects.
6.5 Honest caveat
PanOpticon will not become Primavera or MS Project, and should not try. Its advantage is that its durations come from measured production on this job rather than a planner's estimate. The right ambition is a look-ahead tool that is right about durations — not a general-purpose CPM scheduler.
7. Multi-user data sharing
7.1 The requirement
Several people contributing to one job's data, "without complications like constantly updating" — meaning no manual export/import ritual, no merge prompts, no wondering whose copy is current.
7.2 Correction to the v0.1 scope document
The previous document's central premise was:
"PanOpticon's log is already append-only… Nothing is ever edited in place or deleted."
This is not accurate as of v2.16.0. db.delete_log_entry() hard-deletes rows from the log table, and cascades to delete related reversal rows. Reversal is append-only; deletion is not.
This matters because event-journal merging depends on it. Deletion does not make the approach impossible, but it means deletion must itself become an event ("event X retracted") rather than a row disappearing. A deleted row cannot propagate — the other machine simply never learns the row is gone, and re-adds it on the next sync.
7.3 A second obstacle the v0.1 document did not identify
Events reference pieces by machine-local integer:
piece_id INTEGER PRIMARY KEY AUTOINCREMENT -- assigned per database
log.piece_ids TEXT -- JSON array of those integers
Piece 4,182 on one laptop is a different physical piece from 4,182 on another. Unioning event journals without translation would attach installs to the wrong pipe — silently, and in a way that still balances arithmetically. That is the worst possible failure for this product.
It is solvable, because a natural key already exists:
UNIQUE(system_id, spool_no, item_no, csv_row)
Every event must therefore be translated to and from that natural key on the way in and out of a journal. This is real work — it is not a schema annotation — and it needs to happen before history accumulates.
7.4 Measured concurrency behaviour
Run against v2.16.0 rather than assumed:
| Setting | Value | Consequence |
|---|---|---|
journal_mode | delete | Default rollback journal |
busy_timeout | 5,000 ms | Already set — waits rather than failing instantly |
synchronous | 2 (FULL) | Durable |
Direct test of two writers:
second writer: BLOCKED -> database is locked
Readers were not blocked during a write in either delete or wal mode at this database size, so WAL is not the unlock some guidance suggests. The write lock is the constraint, and it is a correct constraint — it is what prevents corruption.
7.5 Options assessed
Option A — Shared database file on a network drive
Point everyone at Z:\…\panopticon.db.
Not recommended. SQLite's own documentation states that file locking is unreliable over SMB/NFS. The failure mode is not an error message; it is a damaged file, discovered days later. For a product whose name means "the truth", this fails in exactly the wrong direction.
Option B — Database in OneDrive/Dropbox/SharePoint
Strongly not recommended. Sync clients replicate whole files, not transactions. Concurrent edits produce a "conflicted copy" and one person's work is discarded with no indication which copy was right.
Option C — Per-device event journals in a shared folder
Each device writes only its own journal file; nobody writes a file another person is writing, which removes the locking and conflict failures above. On launch and on demand, PanOpticon reads the others and applies unseen events.
Requires the two fixes in §7.2 and §7.3 first.
- Works on a company
Z:drive — no data leaves the premises, no IT provisioning. - Works offline; sync resumes when the folder is reachable.
- Journals are plain JSON — auditable, backupable.
- Not real-time. Minutes, not seconds.
- No record locking: two people logging the same spool is detected afterwards, on Reconcile, not prevented.
Option D — Hosted database server (PostgreSQL)
True concurrent multi-user, real-time, proper accounts and permissions. Requires hosting, IT involvement, ongoing cost, and network dependence — PanOpticon stops working in a basement with no signal, which today it does not.
Option E — One writer, many readers
One person owns entry; everyone else gets a scheduled Excel export to a shared folder. The Excel write-back already exists, so this is close to free.
Not sharing in the strict sense, but if the real need is visibility rather than contribution, it delivers most of the value this week rather than this quarter.
7.6 Comparison
| A: Net share | B: OneDrive | C: Journals | D: Server | E: Export | |
|---|---|---|---|---|---|
| Concurrent writers | Corrupts | Loses data | Yes (eventual) | Yes (real-time) | No |
| Offline | Yes | Partly | Yes | No | Yes |
| Data leaves premises | No | Yes | No (or optional) | Depends | No |
| Infrastructure | None | None | Shared folder | Hosting + IT | None |
| Effort | — | — | Substantial | Large | Very small |
| Risk of silent wrong data | High | High | Low | Low | Low |
| Recommended | No | No | Yes, staged | Later | Yes, now |
7.7 Recommendation
Immediately — Option E, if the need is largely visibility. Scheduled export to a shared workbook, using machinery that already exists.
Foundation work now, regardless of what follows:
- Add UUID
event_idanddevice_idto every log row, with UTC timestamps. - Convert
delete_log_entryto a retraction event rather than a row deletion (§7.2). - Store piece references in events by natural key, not local integer (§7.3).
These cannot be retrofitted cleanly onto history that accumulates without them, and each is independently defensible as a correctness improvement even if sharing is never built.
Then Option C, in phases, once the number of people actually entering data is known.
Option D only if the organisation outgrows C — and the event model migrates to it rather than being thrown away.
7.8 On "without constantly updating"
This deserves a direct answer rather than a reassuring one.
Option C can sync automatically on launch, on a timer, and on demand, so nobody presses a Sync button. What it cannot do is make two people's screens agree within seconds. If someone logs Level 4 while you are looking at Level 4, you will not see it until the next sync cycle.
Only Option D gives second-by-second agreement. If the expectation is "we both see it instantly", C will disappoint, and it is better to know that before it is built than after.
8. Open decisions
- How many people enter data, versus only view it? This single answer decides between Option E and Option C, and it is the cheapest question to answer.
- Is the priority contribution or visibility?
- Is device-name attribution sufficient, or are named users required?
- Shared folder location —
Z:project folder, or elsewhere? - One journal set per job, or across all jobs? Per job is cleaner to archive at closeout.
- For scheduling: do target dates already exist somewhere importable (a P6 schedule export, a spreadsheet), or would they be entered by hand? This decides whether S1 is an import or a data-entry feature.
- Is the three-week look-ahead (S2) the artefact that would actually get used, or is something else the working document?
9. Recommended sequence
Now, low risk, high return P3 empty states · P2 primary actions · P6 destructive styling · Option E export
Next P4 column control everywhere · P5 glossary · Sharing foundation work (§4.7 items 1–3)
Then, given answers to §8 S2 look-ahead view · P7 split gui.py · Option C phased sharing
Later, only if outgrown Option D server
10. Risks
| Risk | Mitigation |
|---|---|
| Simplification removes something relied on | Nothing is removed; ordering and defaults change. Column control is per-user |
| Sharing built before piece-identity fix | Installs attach to wrong pipe, silently. §7.3 is a hard prerequisite |
| Deletion not converted to a retraction event | Deleted entries resurrect on next sync. §7.2 is a hard prerequisite |
| Expectation of real-time sync from Option C | Stated plainly in §7.8 before building |
| Shared folder unreachable in the field | Work continues locally; sync resumes |
| Clock skew between machines | UTC timestamps; ordering by event ID, not wall clock |
gui.py growth makes changes risky | P7, mechanical and behaviour-preserving |
| Scheduling scope-creeps toward a CPM tool | §6.5 — the advantage is measured durations, not planning breadth |
| Target dates entered once and never maintained | S1 is only useful if dates are kept current; confirm ownership before building |
| Regression during refactor | simtest.py (31 checks) and simtest2.py (81 checks) run against any change |