refactor: finalize the event data model#126
Merged
Merged
Conversation
Closes the last internal `geolocation` names the merge + entity rename
left behind. Behaviour-preserving pure rename (37 insertions, 37 deletions):
- ORM relationship attributes become event(s): User.events, Media.event,
Tag.events, EventClaim.event, and the matching Event back_populates.
Internal Python only, no query traverses them by the old name.
- The max_files_per_geolocation setting becomes max_files_per_event.
- The last deferred column proof_images.geolocation_id becomes event_id
(migration g9i1k3m5o7q9: column + FK constraint + index; the partial
orphan index predicate follows the rename automatically).
The deliberately-kept external surface is untouched: the /bounties view,
the status values, the "geolocation_id" delete-receipt key, and the
/{geolocation_id} path-param name. data-model.md already documented
event_id, so this only lands the code side.
ruff / ruff format / mypy (82) / pytest (667) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: vidit-admin <admin@vidit.app>
- design.md: rewrite around the selectable accent (orange = default, not THE colour); drop the duplicate colour lookup and the two ~500-word Forms / Field-help walls (-43% words); fix stale component refs (GeolocationCard/DetailBody -> EntityCard/EventDetailBody, TweetImportBanner path). - engineering.md: fix stale frontend refs (GeolocationStatus -> EventStatus, components/geolocation/ -> event/ + landing/, events/ sub-router list); trim the CI / Dependabot / DCO justification prose. - api.md: trim CHANGELOG-style narration on points, possible-duplicates, tweet-extract, media origin, and archive import (contract preserved). - data-model.md: drop a v0.3.1 version milestone (AGENTS.md rule 2) plus assorted redundant prose. - next.md: fold the landed Architecture / Sequencing essays into CHANGELOG pointers; fix stale `submitted` -> `geolocated` status literals and em dashes. - roadmap.md: same `submitted` -> `geolocated` status fix. No behaviour change; documentation only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: vidit-admin <admin@vidit.app>
Give the backend a dead-code gate analogous to the frontend's knip. vulture finds unused functions, classes, methods, and fields that ruff's F401 (unused imports only) misses. Config under [tool.vulture] in backend/pyproject.toml scans app + scripts, excludes alembic/versions, and runs at min_confidence 60 so a genuinely orphaned module-level function (which scores 60) trips the gate; the default 80 would only ever catch unused locals. The framework noise a 60 floor surfaces is cleared two ways: ignore_decorators (route and middleware handlers, Pydantic validators) plus ignore_names (model_config, the validator cls) for the blanket classes, and backend/vulture_whitelist.py for the residual attribute-access false positives (SQLAlchemy Mapped columns, Pydantic response-model fields, dataclass fields, one test-only helper). The gate runs in the backend-lint CI job after mypy app, and locally via make hygiene (or make vulture). vulture_whitelist.py is excluded from ruff because its bare-name entries are vulture's own format, not runnable Python. Baseline triage found no genuine dead code: 108 findings at confidence 60, all framework false positives. One column, users.claimed_at, has no explicit Python producer or consumer yet (its server_default stamps it and the assembled-profile claim flow that would write it is a deferred v0.4 item); it stays as a live, documented column the ORM hydrates on load. ruff / mypy (82) / vulture / pytest (667) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: vidit-admin <admin@vidit.app>
The geolocation_id -> event_id rename in b886021 shortened two list-comprehension lines below ruff's wrap threshold, so ruff format collapses each onto one line. The reformat was not re-run after the mypy-driven fix to those two files, so it landed as drift on the branch. No behaviour change; ruff / mypy / pytest stay green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: vidit-admin <admin@vidit.app>
Complete the model the earlier bounty + geolocation merge started, so
bounties, geolocations, and detections are one events lifecycle end to end.
Backend:
* split author_id into owner_id (edit rights, moves to the fulfiller at
geolocate) plus a durable event_geolocators credit table
* rename event_claims to event_investigators; the claim action becomes
POST / DELETE /events/{id}/investigate
* rename location to event_coords, add an optional capture_source_coords
camera point, per-state stamps (requested_at, detected_at, geolocated_at),
close_reason, and before_closed_status (the requested vs detected
discriminator that keeps close unified without losing the distinction)
* fold proof_images into media(role = source | proof); proof images upload
at publish via multipart proof_files[] with placeholder:// rewrite, so no
unattached S3 object and no orphan reaper
* drop stored IP and user agent from media and auth_events (privacy)
* four events CHECK constraints: status domain, coords when geolocated, the
two terminal stamps, and before_closed_status as a full iff
* remove the /bounties router; bounty, geolocation, and detection are now
filtered views over /events
Frontend: regenerate api-types, repoint the data layer and routes, and add
the rich flows (proof-at-publish editor, close dialog with reason, investigate
toggle, capture_source_coords inputs, single-source enforcement).
Migration h0j2l4n6p8r0 is a clean redefinition plus reseed (the dev DB is
wipeable), reversible, with backfills that satisfy the new CHECKs.
Fixes surfaced by an adversarial review and closed here: the geolocate row
lock reused a stale identity-mapped row (populate_existing); the
before_closed_status CHECK let a NULL slip through on a closed row (explicit
IS NOT NULL); the create and request forms posted the source under the wrong
multipart key; the possible-duplicates probe leaked requested and closed rows
once the coords CHECK relaxed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: vidit-admin <admin@vidit.app>
A three-reviewer pass over the branch surfaced a race, a perf regression, and several convention gaps. Fixes: * close() now takes the same row lock as geolocate (populate_existing plus with_for_update), so a concurrent fulfil of a requested event can't be silently overwritten by an owner-only close reading a stale status * the card and list media eager-loaders scope to role='source', so read paths stop fetching proof rows only to discard them (delete and admin still lazy-load every role) * guard the detection re-import against a coord-less detected row, since to_shape(None) would otherwise abort the whole import * centralize owner-plus-geolocator credit in one _credit_geolocator helper so the owner-among-geolocators invariant can't be half-applied * move the investigate and uninvestigate business logic out of the router into the service, matching their sibling verbs * restore the shared ACCEPTED_IMAGE_MIME constant in the proof editor, factor createEventRequest onto the shared form assembler, and reject a partially-numeric capture coordinate * drop dash-as-punctuation from added comments and docstrings, fix the stale extract_client_ip and geolocations references, regenerate api-types Backend 688 tests, frontend 155, ruff / mypy / tsc / lint / build clean, make hygiene clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: vidit-admin <admin@vidit.app>
Move the event-model refactor entry to the top of Unreleased Changed (newest first) and remove the two intermediate merge/rename entries it supersedes, so the bounty-to-events story reads as one. Drop the removed /bounties endpoint from the v0.5 vetted_only filter row in next.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: vidit-admin <admin@vidit.app>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finalizes the event data model that the earlier bounty + geolocation merge started, so bounties, geolocations, and detections are one
eventslifecycle end to end (requested → detected → geolocated → closed, one verb per transition).Model
author_idsplits intoowner_id(edit rights, moves to the fulfiller at geolocate) plus a durableevent_geolocatorscredit table.event_claimsbecomesevent_investigators; the claim action is nowPOST/DELETE /events/{id}/investigate.locationbecomesevent_coords, plus a new optionalcapture_source_coordscamera point, per-state stamps (requested_at,detected_at,geolocated_at),close_reason, andbefore_closed_status(therequestedvsdetecteddiscriminator that keepscloseunified without losing the distinction).proof_imagesfolds intomedia(role = source | proof). Proof images upload at publish via multipartproof_files[]with aplaceholder://rewrite, so nothing sits unattached on S3 and there is no orphan reaper.mediaandauth_events.eventsCHECK constraints: the status domain, coordinates whengeolocated, the two terminal stamps, andbefore_closed_statusas a full iff./bountiesrouter is removed; bounty, geolocation, and detection are filtered views over/events.Frontend
Regenerated
api-types, repointed the data layer and routes, and added the rich flows: the proof-at-publish editor, a close dialog with a reason, the investigate toggle,capture_source_coordsinputs, and single-source enforcement. Every element composes from existing primitives (no new one-off).Migration
h0j2l4n6p8r0is a clean redefinition plus reseed (the dev DB is wipeable), reversible, with backfills that run before the new CHECKs so an existing dev DB upgrades cleanly.Fixes surfaced by an adversarial review
A fresh-context review pass caught four defects that the test suites and a manual smoke test all missed, now closed here:
filesvs the backend'sfile), so both flows 422'd from the UI. No test crossed the real HTTP boundary.with_for_update()(fixed withpopulate_existing()), so a concurrent geolocate could double-fulfil.before_closed_statusCHECK let a NULL slip through on aclosedrow (NULL IN (...)is unknown, not false), fixed with an explicitIS NOT NULL.requestedguesses andclosedrows once the coords CHECK relaxed, fixed with a status predicate.Verification
Backend 688 tests, frontend 154 tests,
ruff/mypy/tsc/lint/buildclean,make hygieneclean (jscpd, vulture, knip, palette). The dev DB was upgraded and reseeded, and the read paths plus the submit form were verified live.🤖 Generated with Claude Code