pretix-event-analytics 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. pretix_event_analytics-2.2.1/CHANGELOG.md +392 -0
  2. pretix_event_analytics-2.2.1/LICENSE +191 -0
  3. pretix_event_analytics-2.2.1/MANIFEST.in +8 -0
  4. pretix_event_analytics-2.2.1/PKG-INFO +354 -0
  5. pretix_event_analytics-2.2.1/README.md +326 -0
  6. pretix_event_analytics-2.2.1/pretix_event_analytics/__init__.py +24 -0
  7. pretix_event_analytics-2.2.1/pretix_event_analytics/_compat.py +32 -0
  8. pretix_event_analytics-2.2.1/pretix_event_analytics/_safety.py +53 -0
  9. pretix_event_analytics-2.2.1/pretix_event_analytics/admin.py +77 -0
  10. pretix_event_analytics-2.2.1/pretix_event_analytics/apps.py +17 -0
  11. pretix_event_analytics-2.2.1/pretix_event_analytics/exporters.py +353 -0
  12. pretix_event_analytics-2.2.1/pretix_event_analytics/forms.py +381 -0
  13. pretix_event_analytics-2.2.1/pretix_event_analytics/locale/README.md +19 -0
  14. pretix_event_analytics-2.2.1/pretix_event_analytics/management/__init__.py +0 -0
  15. pretix_event_analytics-2.2.1/pretix_event_analytics/management/commands/__init__.py +0 -0
  16. pretix_event_analytics-2.2.1/pretix_event_analytics/management/commands/analytics_resync.py +176 -0
  17. pretix_event_analytics-2.2.1/pretix_event_analytics/management/commands/generate_test_data.py +518 -0
  18. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0001_initial.py +238 -0
  19. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0002_analyticsidentity_and_more.py +135 -0
  20. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0003_audit_fixes.py +60 -0
  21. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0004_performance_and_integrity.py +42 -0
  22. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0005_suite_v2.py +233 -0
  23. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0006_people_resolved_at.py +18 -0
  24. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0007_sales_annotations.py +30 -0
  25. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0008_pace_alerts.py +28 -0
  26. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0009_accuracy_2_1.py +93 -0
  27. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/0010_nationality_bank_country.py +38 -0
  28. pretix_event_analytics-2.2.1/pretix_event_analytics/migrations/__init__.py +0 -0
  29. pretix_event_analytics-2.2.1/pretix_event_analytics/models.py +520 -0
  30. pretix_event_analytics-2.2.1/pretix_event_analytics/services/__init__.py +0 -0
  31. pretix_event_analytics-2.2.1/pretix_event_analytics/services/age_bucketer.py +140 -0
  32. pretix_event_analytics-2.2.1/pretix_event_analytics/services/alerts.py +114 -0
  33. pretix_event_analytics-2.2.1/pretix_event_analytics/services/attendance.py +118 -0
  34. pretix_event_analytics-2.2.1/pretix_event_analytics/services/cohort_service.py +61 -0
  35. pretix_event_analytics-2.2.1/pretix_event_analytics/services/country_resolver.py +261 -0
  36. pretix_event_analytics-2.2.1/pretix_event_analytics/services/hash_service.py +136 -0
  37. pretix_event_analytics-2.2.1/pretix_event_analytics/services/id_country.py +180 -0
  38. pretix_event_analytics-2.2.1/pretix_event_analytics/services/identity_keys.py +72 -0
  39. pretix_event_analytics-2.2.1/pretix_event_analytics/services/ingest.py +172 -0
  40. pretix_event_analytics-2.2.1/pretix_event_analytics/services/legacy.py +138 -0
  41. pretix_event_analytics-2.2.1/pretix_event_analytics/services/normalizer.py +425 -0
  42. pretix_event_analytics-2.2.1/pretix_event_analytics/services/payment_info.py +122 -0
  43. pretix_event_analytics-2.2.1/pretix_event_analytics/services/people.py +722 -0
  44. pretix_event_analytics-2.2.1/pretix_event_analytics/services/predictor.py +84 -0
  45. pretix_event_analytics-2.2.1/pretix_event_analytics/services/repeat_detector.py +121 -0
  46. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/__init__.py +0 -0
  47. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/audience.py +253 -0
  48. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/charts.py +70 -0
  49. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/loyalty.py +323 -0
  50. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/market.py +53 -0
  51. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/operations.py +170 -0
  52. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/overview.py +112 -0
  53. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/sales.py +491 -0
  54. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/scope.py +232 -0
  55. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/series.py +62 -0
  56. pretix_event_analytics-2.2.1/pretix_event_analytics/services/reports/tickets.py +143 -0
  57. pretix_event_analytics-2.2.1/pretix_event_analytics/services/resale.py +295 -0
  58. pretix_event_analytics-2.2.1/pretix_event_analytics/services/resync_service.py +173 -0
  59. pretix_event_analytics-2.2.1/pretix_event_analytics/services/van_length_bucketer.py +120 -0
  60. pretix_event_analytics-2.2.1/pretix_event_analytics/services/versioning.py +66 -0
  61. pretix_event_analytics-2.2.1/pretix_event_analytics/shredder.py +79 -0
  62. pretix_event_analytics-2.2.1/pretix_event_analytics/signals.py +277 -0
  63. pretix_event_analytics-2.2.1/pretix_event_analytics/static/pretix_event_analytics/chart.umd.min.js +19 -0
  64. pretix_event_analytics-2.2.1/pretix_event_analytics/static/pretix_event_analytics/dashboard.css +273 -0
  65. pretix_event_analytics-2.2.1/pretix_event_analytics/static/pretix_event_analytics/dashboard.js +368 -0
  66. pretix_event_analytics-2.2.1/pretix_event_analytics/tasks.py +225 -0
  67. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/_chart.html +27 -0
  68. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/base.html +132 -0
  69. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/config.html +184 -0
  70. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/email/pace_alert.txt +7 -0
  71. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/audience.html +119 -0
  72. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/loyalty.html +225 -0
  73. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/operations.html +89 -0
  74. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/overview.html +93 -0
  75. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/resale.html +75 -0
  76. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/sales.html +203 -0
  77. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pages/tickets.html +110 -0
  78. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/pdf_report.html +132 -0
  79. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/series_confirm_delete.html +38 -0
  80. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/series_detail.html +116 -0
  81. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/series_form.html +65 -0
  82. pretix_event_analytics-2.2.1/pretix_event_analytics/templates/pretix_event_analytics/series_list.html +84 -0
  83. pretix_event_analytics-2.2.1/pretix_event_analytics/templatetags/__init__.py +0 -0
  84. pretix_event_analytics-2.2.1/pretix_event_analytics/templatetags/analytics_tags.py +39 -0
  85. pretix_event_analytics-2.2.1/pretix_event_analytics/urls.py +38 -0
  86. pretix_event_analytics-2.2.1/pretix_event_analytics/views.py +600 -0
  87. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/PKG-INFO +354 -0
  88. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/SOURCES.txt +108 -0
  89. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/dependency_links.txt +1 -0
  90. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/entry_points.txt +2 -0
  91. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/not-zip-safe +1 -0
  92. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/requires.txt +2 -0
  93. pretix_event_analytics-2.2.1/pretix_event_analytics.egg-info/top_level.txt +1 -0
  94. pretix_event_analytics-2.2.1/setup.cfg +55 -0
  95. pretix_event_analytics-2.2.1/setup.py +2 -0
  96. pretix_event_analytics-2.2.1/tests/test_alerts.py +78 -0
  97. pretix_event_analytics-2.2.1/tests/test_correctness.py +140 -0
  98. pretix_event_analytics-2.2.1/tests/test_country.py +295 -0
  99. pretix_event_analytics-2.2.1/tests/test_csp.py +52 -0
  100. pretix_event_analytics-2.2.1/tests/test_features.py +156 -0
  101. pretix_event_analytics-2.2.1/tests/test_matching.py +153 -0
  102. pretix_event_analytics-2.2.1/tests/test_migrations.py +34 -0
  103. pretix_event_analytics-2.2.1/tests/test_packaging.py +50 -0
  104. pretix_event_analytics-2.2.1/tests/test_payment_info.py +51 -0
  105. pretix_event_analytics-2.2.1/tests/test_people.py +159 -0
  106. pretix_event_analytics-2.2.1/tests/test_reports.py +104 -0
  107. pretix_event_analytics-2.2.1/tests/test_resale.py +142 -0
  108. pretix_event_analytics-2.2.1/tests/test_smoke.py +57 -0
  109. pretix_event_analytics-2.2.1/tests/test_views.py +272 -0
@@ -0,0 +1,392 @@
1
+ # Changelog
2
+
3
+ All user-visible changes to this project are documented here. Dates are in
4
+ ISO 8601. The project follows [Semantic Versioning](https://semver.org/).
5
+
6
+ ## [2.2.1] — 2026-10-02
7
+
8
+ No migration, no resync needed.
9
+
10
+ ### Changed
11
+ - The sales-pace chart (Overview, Sales) marks only this edition's and the previous edition's moments;
12
+ with every edition's it became a fence of lines. Each edition's own moments stay on its Sales page.
13
+ - Examples and placeholders are generic ("My Festival") instead of one organiser's event.
14
+ - Requires `pretix>=2026.2` in the package metadata (it already refused older versions at runtime).
15
+ - README: screenshots, a *Theming* section documenting the `--pa-*` CSS variables as public API,
16
+ current badges and import description.
17
+ - Deployment notes for a specific installation moved out of this repository.
18
+
19
+ ## [2.2.0] — 2026-09-26
20
+
21
+ **Run a resync of every edition after upgrading** (data version 4). One database
22
+ migration (0010, fields added only, reversible).
23
+
24
+ ### Added
25
+ - **Nationality, kept apart from residence.** Per ticket holder, from a
26
+ nationality question (country names *and* demonyms such as "Portuguese") or
27
+ from an ID document that proves citizenship. New "Nationality" panel on the
28
+ Audience page (replaces "ID-document country"), and `nationality` columns in
29
+ the order and ticket CSV exports.
30
+ - ID-document rules now declare what they prove (`id_country.PROVES`): citizens'
31
+ IDs (Spanish DNI, Portuguese Cartão de Cidadão / civil number, Italian codice
32
+ fiscale of someone born in Italy) prove **nationality**; the Spanish NIE (issued
33
+ to foreigners), UK driving licence and Belgian national number / eID prove
34
+ **residence**.
35
+
36
+ ### Changed
37
+ - **Exact residence = what the buyer stated** (residence question, invoice,
38
+ PayPal, card billing address) or a residence document. The paying bank's
39
+ country (card issuer, IBAN) and the buyer's nationality are now **probable**
40
+ residence only. Measured on SUTI 2026, the card issuer agreed with the ID
41
+ document 79% and the invoice address 86% of the time, and foreign-issued
42
+ cards (Israel, UK, …) mostly belonged to people with Portuguese documents.
43
+ - Derived-country order: same customer's other orders (inferred) → buyer's
44
+ nationality → card issuer → IBAN → e-mail domain (all probable).
45
+ - "Same customer's other orders" only uses exact countries as evidence, so a
46
+ bank's country never spreads to other orders.
47
+ - "Local buyer" now needs an exact residence country.
48
+
49
+ ### Fixed
50
+ - Placeholder ID numbers made of one repeated digit ("00000000") no longer
51
+ resolve to a country.
52
+
53
+ ## [2.1.1] — 2026-09-25
54
+
55
+ ### Fixed
56
+ - **Resale showed 0 TicketSwap resales** with TicketSwap 1.x, which keeps
57
+ swaps in its own table (`TicketSwapSwap`, successful rows) instead of on
58
+ the ticket. Both record shapes are now read (1.x table, 2.x ticket
59
+ counters), read-only — only positions, dates and the success flag, never
60
+ the stored names or e-mails.
61
+ - TicketSwap swap dates (1.x) now appear on the monthly chart, stacked with
62
+ manual name changes.
63
+ - Resale counts **admission tickets only** in both channels and in the
64
+ total, so add-ons and stand-alone extras (parking, camper passes) no longer
65
+ skew the rate.
66
+
67
+ No migration. No resync needed (Resale reads Pretix data live).
68
+
69
+ ## [2.1.0] — 2026-09-25
70
+
71
+ **Run a resync of every edition after upgrading** — returning-people figures
72
+ change meaning (see below). One database migration (fields added, one
73
+ constraint widened).
74
+
75
+ ### Changed
76
+ - **Returning people are matched on name + birth date only.** A person is a
77
+ ticket holder. Sharing a card, a PayPal account or an e-mail no longer makes
78
+ two ticket holders the same person (people buy for friends). Names are
79
+ compared ignoring accents, case, word order, middle names and added second
80
+ surnames; different birth dates never match.
81
+ - **When in doubt, unknown.** Tickets without a usable name + birth date,
82
+ ambiguous names and placeholder data (the same identity on 3+ tickets of one
83
+ edition) are left out of returning-people figures instead of being guessed.
84
+ A conservative *probable* tier (no birth date, one matching person, shared
85
+ e-mail or card) is shown only as "including probable matches".
86
+ - **Customers** (returning buyers) are matched on the order e-mail only;
87
+ payment fingerprints are no longer used to link buyers.
88
+ - **Country of residence** comes from the first available source, stored with
89
+ it: residence question → invoice address → PayPal address/account → card
90
+ billing address → ID-document country (opt-in) → IBAN → card's issuing
91
+ bank. Invoice addresses now beat card countries. Exact country names only
92
+ (no fuzzy matching).
93
+ - **Resale** shows one "changed hands" total: TicketSwap resales (read-only,
94
+ from the TicketSwap plugin's ticket data) plus manual name changes, counted
95
+ once per ticket, with a channel breakdown, by product, current holders and a
96
+ CSV of order codes. A manual name change counts only with evidence (birth
97
+ date or attendee e-mail changed too, or a clearly different second rename);
98
+ other single edits are reported as "unclear".
99
+
100
+ ### Added
101
+ - **Derived countries, kept apart from exact ones.** For orders with no exact
102
+ source: *inferred* — the same customer's country on their other orders
103
+ (same order e-mail, or the buyer holding a ticket as the same person),
104
+ only when all of them agree; *probable* — the e-mail's country domain
105
+ (.pt, .es, .uk …; generic and vanity domains ignored). Stored in separate
106
+ fields, never in the country used by filters and other pages. Audience has
107
+ an **Exact / + inferred / + probable** switch; orders CSV has the columns.
108
+ - PayPal v2 order data: country from `payer.address` and
109
+ `payment_source.paypal.address` (pretix 2025+ stores v2).
110
+ - "Travelling from" question support and a breakdown on the Audience page,
111
+ plus "How the country is known" coverage by source.
112
+ - Opt-in ID-document country (settings → "Where people come from"): issuing
113
+ country from national ID formats with a valid check digit; the number is
114
+ never stored.
115
+ - Past-edition imports accept a CSV with name + birth-date columns.
116
+ - Tickets CSV: match tier, returning incl. probable, document country.
117
+
118
+ ### Fixed
119
+ - Dashboard widgets showed raw HTML on Pretix 2026.7 (content is now marked safe).
120
+ - Two tickets of one order carrying the same signal (e.g. the same attendee
121
+ e-mail) kept it only on the first ticket.
122
+ - Win-back list and loyalty CSV mixed customer and person keys.
123
+
124
+ ## [2.0.3] — 2026-09-25
125
+
126
+ ### Fixed
127
+ - Stripe card details were read from the wrong place. Pretix stores Stripe
128
+ payments as PaymentIntents (card under `charges.data[].payment_method_details`),
129
+ so card country and card fingerprint were never found: buyers without an
130
+ invoice address showed as "Unknown" country, and returning buyers who
131
+ changed e-mail but paid with the same card were not recognised. Now reads
132
+ PaymentIntent, `latest_charge`, Charge and legacy card-source shapes; also
133
+ accepts the `stripe_cc` / `paypal2` provider identifiers.
134
+ **Run a resync after upgrading** so existing orders pick this up.
135
+
136
+ ## [2.0.2] — 2026-09-25
137
+
138
+ ### Changed
139
+ - New `--pa-accent` variable for UI states (active tab, selected segment,
140
+ quick-range pill, callout border, toggles, attendance dots). It defaults to
141
+ the first chart colour, so nothing changes visually, but a theme can now
142
+ give the UI its brand accent without recolouring the chart series.
143
+
144
+ ## [2.0.1] — 2026-09-25
145
+
146
+ ### Fixed
147
+ - Themed (e.g. dark) control panels: every colour is now a CSS variable and
148
+ every surface sets its own text colour, while text placed directly on the
149
+ page follows the host theme. Previously the settings page used fixed white
150
+ cards, so headings and labels became invisible under a dark theme with
151
+ light text, and some dashboard text was drawn dark on a dark page.
152
+ - The settings and series pages share the dashboard's variable scope, so a
153
+ theme that remaps `--pa-*` variables styles them too.
154
+ - Chart tooltips take their colours from variables.
155
+
156
+ ### Changed
157
+ - Plugin author shown in Pretix is now "Hexadexa".
158
+
159
+ ## [2.0.0] — 2026-09-25
160
+
161
+ A rebuild into a full analytics suite. **Upgrade steps:** update the
162
+ package, run `python -m pretix migrate`, `python -m pretix rebuild`, set the
163
+ salt in `pretix.cfg` if you used a Django setting that Pretix never
164
+ loaded, then run `python -m pretix analytics_resync --all`. Until the
165
+ resync runs, the dashboard shows a notice for rows produced by 1.x.
166
+
167
+ ### New — dashboard suite
168
+ - Seven pages sharing one filter bar: Overview, Sales, Audience, Loyalty,
169
+ Tickets, Operations, Resale; sidebar sub-navigation.
170
+ - **Sales:** tickets and revenue over time as aligned charts (day / week /
171
+ month, cumulative toggle), split by product, category, new vs returning
172
+ or country, by order or payment date; price-tier markers; editions
173
+ compared on a days-before-event axis; same-point comparison table;
174
+ forecast with range; ticket and revenue targets; weekday × hour heatmap;
175
+ purchase-timing buckets.
176
+ - **Loyalty:** first-timer share, times attended before, came back after a
177
+ gap, lost since the last edition, at every edition; choose any editions
178
+ to compare — attended once/twice/…, overlap matrix, attendance patterns,
179
+ edition flow, first-timer cohorts; people or buyers-only; first-timer
180
+ segments by product, country, age and purchase timing.
181
+ - **Audience, Tickets, Operations:** product performance, categories,
182
+ variations, price tiers, add-ons, vouchers, check-in and arrivals,
183
+ no-shows, cancellations over time, opt-in question breakdowns.
184
+ - **Series overview** page and **past attendee-list import** (hashed).
185
+ - **Like-for-like KPIs** against the previous edition at the same point
186
+ in its sales cycle.
187
+ - **Exports:** tickets CSV, "returning buyers by order code" CSV, full PDF
188
+ report, and exporters in Pretix's own Export menu (CSV/Excel).
189
+
190
+ ### Fixed — correctness
191
+ - Orders with several tickets of the same product lost tickets during live
192
+ ingestion and were skipped entirely by resync.
193
+ - Returning status depended on the order in which editions were synced;
194
+ it is now resolved series-wide after every change.
195
+ - Attendee e-mails were hashed but never used; a friend's ticket in one
196
+ year and an own purchase later now match. Group orders no longer merge
197
+ their attendees into one person.
198
+ - The cohort matrix used a different definition of "returning" than the
199
+ KPIs; both now use the same resolved people.
200
+ - A check-in removed the early-purchase points from the return score.
201
+ - With several events selected, ticket, add-on and refund figures still
202
+ showed only the current event.
203
+ - Resync emptied the table first (blank dashboard during a resync, partial
204
+ data after a failure) and dropped canceled/refunded orders, so the
205
+ refund rate read 0 after any resync.
206
+ - Daily/hourly charts used UTC instead of the event's timezone; the hourly
207
+ chart also shifted by the viewer's browser timezone.
208
+ - Ages were computed on the day of the sync instead of the event date.
209
+ - Persona "add-on rate" actually showed the caravan rate.
210
+ - Net revenue and tax ignored fees.
211
+ - Series pages crashed on Pretix 2026 (non-existent base template).
212
+ - Cohort tooltips showed "0 of buyers".
213
+ - The salt could only be set as a Django setting, which Pretix installs do
214
+ not load; `pretix.cfg` and environment variables now work.
215
+
216
+ ### New — also in 2.0.0
217
+ - **Win-back lists:** people from the previous edition who have not
218
+ bought yet, and people who came earlier but skipped the last edition —
219
+ exported as the order code of their most recent visit.
220
+ - **Moments:** dated notes ("line-up announced") drawn on the sales charts
221
+ and on the edition comparison, next to automatic price-tier markers.
222
+ - **Capacity** per quota (paid, pending, available, waiting list), live
223
+ from Pretix.
224
+ - **Payment completion** by method: paid vs pending vs expired unpaid.
225
+ - **Top cities** in the Audience page.
226
+ - **Quick date ranges** (7 / 30 / 90 days, all time) in the filter bar.
227
+ - **Pretix dashboard widgets:** returning buyers and first-time attendees
228
+ on the event's main dashboard.
229
+ - **Pace alerts:** e-mail chosen addresses when ticket sales trail the
230
+ previous edition (same number of days before the event) by a chosen
231
+ percentage; at most once a week while behind. Runs from Pretix's cron.
232
+ - **Calmer settings and filters:** the settings page is grouped into
233
+ *This edition* (required) and collapsible *Goals & alerts*, *Question
234
+ insights* and *Advanced* sections, each with a one-line summary; the
235
+ dashboard filter bar keeps the common filters visible and folds the rest
236
+ under *More filters* (opens automatically when one is in use); long pages
237
+ get an "On this page" jump bar.
238
+
239
+ ### Security
240
+ - The "merge editions" filter and all series-wide figures (Loyalty,
241
+ first-timer headline, edition comparisons, resale by edition, series
242
+ overview) respect per-event team permissions: a member limited to one
243
+ event can no longer read other editions' data.
244
+ - CSV exports neutralise spreadsheet formulas in buyer-supplied fields.
245
+
246
+ ### Compatibility
247
+ - Tested on Pretix 2026.2.0 and 2026.3.1. Uses the new permission names
248
+ (`event.orders:read`, …) on 2026.3+ and the legacy ones before.
249
+ - Declared as an event + organizer plugin: **enable it in the organizer's
250
+ plugin settings as well as per event.**
251
+ - All markup is compatible with Pretix's Content-Security-Policy
252
+ (`style-src 'self'`): no inline style attributes; 1.x's inline styles
253
+ were silently ignored by browsers.
254
+
255
+ ### Changed
256
+ - Resync upserts instead of delete-and-rebuild, always includes check-ins,
257
+ keeps its lock alive per chunk, and resolves returning people once per
258
+ series. Live orders wait for a running resync instead of being dropped.
259
+ - Returning-people resolution runs as one debounced background task per
260
+ series (with a Celery worker) or from Pretix's periodic cron task
261
+ (without one) — never inside a buyer's payment request. Admin actions
262
+ queue it instead of blocking the page.
263
+ - Payment fingerprints shared by more than four orders in one edition
264
+ (agency, company account, box office) no longer link people together.
265
+ - Order changes, attendee edits, reactivations and splits are re-ingested.
266
+ - Report results are cached per organizer data version and plugin version.
267
+ - Charts: validated colour-blind-safe palette, no dual axes, table view.
268
+
269
+ ### Development
270
+ - pytest suite (~80 tests) against real Pretix objects.
271
+ - `scripts/dev_seed_orders.py` seeds real orders into a local dev DB.
272
+
273
+ ## [1.2.0] — 2026-04-24
274
+
275
+ A correctness, safety, and polish release. No breaking schema changes;
276
+ existing installations upgrade by updating the package and running
277
+ `python -m pretix migrate` (no-op if migrations are already applied).
278
+
279
+ ### Safety — read-only against the Pretix ticketing database
280
+
281
+ - Added `scripts/check_isolation.py`, a standalone static check that
282
+ greps the plugin source for any call that would write, update, or
283
+ delete a Pretix core table (Order, OrderPosition, OrderPayment,
284
+ LogEntry, Event, Item, Question, Checkin, …). Runs with no database,
285
+ suitable for CI.
286
+ - Added `pretix_event_analytics/_safety.py` as the single source of
287
+ truth for the read-only contract.
288
+ - Removed the synthetic `LogEntry` rows that `generate_test_data`
289
+ previously inserted into the Pretix audit log for secondary-market
290
+ testing. The plugin now writes to its own tables only.
291
+ - `generate_test_data` now refuses to run with `DEBUG=False` unless
292
+ `--i-understand-this-is-fake-data` is passed. `--clear` prompts for
293
+ explicit confirmation when real analytics rows would be deleted.
294
+
295
+ ### Correctness
296
+
297
+ - `PRETIX_ANALYTICS_SECRET_SALT` is now required in production and
298
+ validated to be at least 16 characters. Missing / too-short salt
299
+ raises `ImproperlyConfigured` on first use. In `DEBUG=True`
300
+ environments, a `SECRET_KEY`-derived fallback is used with a clear
301
+ one-time log warning.
302
+ - `generate_repeat_hash("")` now returns an empty string. Previously,
303
+ every order without an email collided on the HMAC of the empty
304
+ string and was wrongly flagged as a repeat buyer of every other
305
+ email-less order.
306
+ - Resync and live ingestion no longer race. A shared cache lock is
307
+ published while a resync is rebuilding facts; `order_paid`,
308
+ `order_canceled`, and `checkin_created` Celery tasks detect the
309
+ lock and requeue themselves until the rebuild finishes.
310
+ - Per-order repeat detection is now wrapped in `transaction.atomic()`
311
+ inside the resync path, matching the live-ingestion path.
312
+ - `process_order_paid` distinguishes expected `ValueError` (bad data,
313
+ skip) from unexpected exceptions (bug, retry with backoff) so that
314
+ a single upstream schema surprise no longer silently drops every
315
+ paid order.
316
+ - `process_checkin_created` now requeues with backoff when the
317
+ corresponding `AnalyticsOrderFact` is not yet created, instead of
318
+ silently dropping the check-in.
319
+ - Unicode normalization (NFKC) applied before hashing so that
320
+ visually-identical emails with different Unicode representations
321
+ hash identically.
322
+ - Van-length buckets (`<6m`, `6–8m`, `>8m`) have documented inclusive
323
+ / exclusive boundaries and reject negative input lengths.
324
+ - Normalizer rejects orders with zero positions (previously stored a
325
+ degenerate fact row with `ticket_count=0`).
326
+
327
+ ### Security
328
+
329
+ - Export filenames are now strictly `[A-Za-z0-9_-]+`, truncated to 50
330
+ chars; null bytes, path separators, and traversal sequences are
331
+ dropped.
332
+ - `TriggerResyncView` adds a per-user 60-second throttle in addition
333
+ to the existing per-event in-progress lock, so a refresh-loop or
334
+ scripted client can no longer flood the Celery queue.
335
+ - `DashboardFilterForm.editions` is now scoped to the current event's
336
+ organizer on both `choices` population and `clean()`, so posting
337
+ event ids from another organizer has no effect.
338
+
339
+ ### Performance
340
+
341
+ - Dashboard refund stats folded into a single aggregate query (was
342
+ two `.count()` calls against the same unfiltered queryset).
343
+ - Add-on attach-rate segmentation rewritten: previously one subquery
344
+ per age bucket (N+1 against `AnalyticsTicketFact`); now a single
345
+ `values("age_range").annotate(...)` with pre-computed add-on set
346
+ membership. Payload reduces from 1 + 2 + 6 queries down to 3.
347
+
348
+ ### UI / UX
349
+
350
+ - Cohort retention heatmap has a visible colour legend and
351
+ distinguishes "no data" cells from "source year is later" cells.
352
+ - Dashboard shows active-filter chips below the filter bar so the
353
+ currently applied filters are always visible.
354
+ - Date range filter (`From` / `To`) now validates that the start is
355
+ on or before the end.
356
+ - PDF export is fully `{% trans %}`-wrapped and now includes the
357
+ event date in the header, a cohort colour legend, and pluralised
358
+ buyer counts.
359
+ - `pretix_event_analytics/locale/` skeleton directory added so
360
+ translators can be on-boarded via `makemessages` / Transifex /
361
+ Crowdin.
362
+
363
+ ### Internal
364
+
365
+ - Age-confirmation keyword list consolidated in `age_bucketer.py`
366
+ (was duplicated between `age_bucketer.py` and `normalizer.py`).
367
+ - Module docstrings in `tasks.py` and `resync_service.py` now state
368
+ the read-only invariant explicitly.
369
+
370
+ ## [1.1.1] — 2026-04-08
371
+
372
+ - Consolidated dashboard count queries into a single aggregate
373
+ (6 queries → 1).
374
+ - Added `acks_late` to all Celery tasks to prevent message loss if a
375
+ worker crashes mid-task.
376
+
377
+ ## [1.1.0] — 2026-04-08
378
+
379
+ Initial public release.
380
+
381
+ - HMAC-SHA256 identity resolution across email, Stripe card
382
+ fingerprints, PayPal payer IDs, bank IBANs, and name + DOB
383
+ composites.
384
+ - Forward-looking cohort retention heatmap.
385
+ - Deterministic repeat-probability scoring (0–100), refined
386
+ post-event via check-in data.
387
+ - Revenue KPIs, geographic and demographic breakdowns, sales pacing
388
+ chart, buyer-persona segmentation, add-on attach rates, caravan
389
+ analysis, and secondary-market (resale) detection.
390
+ - Multi-edition filtering, CSV and PDF export.
391
+ - GDPR data shredder integrated with Pretix's deletion workflow.
392
+ - Asynchronous Celery ingestion with retry + exponential backoff.
@@ -0,0 +1,191 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to the Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by the Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding any notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ Copyright 2024-2026 Andre Vidal
180
+
181
+ Licensed under the Apache License, Version 2.0 (the "License");
182
+ you may not use this file except in compliance with the License.
183
+ You may obtain a copy of the License at
184
+
185
+ http://www.apache.org/licenses/LICENSE-2.0
186
+
187
+ Unless required by applicable law or agreed to in writing, software
188
+ distributed under the License is distributed on an "AS IS" BASIS,
189
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
+ See the License for the specific language governing permissions and
191
+ limitations under the License.
@@ -0,0 +1,8 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include pretix_event_analytics/templates *
4
+ recursive-include pretix_event_analytics/static *
5
+ recursive-include pretix_event_analytics/locale *
6
+ include pretix_event_analytics/migrations/*.py
7
+ include CHANGELOG.md
8
+ global-exclude *.map *.pyc .DS_Store