Plain language
How we protect your anonymity
CivicMeter is a community ledger of JEA bills for Duval County. The public should be able to see what households pay. They should not be able to see who paid it.
The short version
- Your email lives in a login table. A bill row does not contain your email, name, account number, or street address.
- The account number and service address are normalized and hashed with a secret key. We store the hash. We do not store the original.
- Averages are hidden unless at least 5 households are in the group. Extra groups are hidden when showing them would give a small group away.
- A bill photo or PDF is read in memory, compared to what you typed, and wiped. It is not saved.
- There are no analytics or advertising trackers.
- An optional home survey is stored with the household hash, not with your email. You can skip every question and still file a bill.
What you type
To sign in, an email address. To claim a household, a JEA account number, the service street address, ZIP, square footage, and optionally bedrooms and home type. Each month, the billing month, kWh, an optional water amount, the total due, and optional line items.
There is no notes field. Free text is where names leak in.
What we keep
The login table stores the email, whether you want a reminder, and the last month we reminded you. A separate household row stores the two hashes, ZIP, square footage, optional bedrooms and home type, and a size bucket. Each bill stores the month, kWh, water gallons if you entered them, dollar amounts in cents, whether a bill file matched, and a review status.
The bill points at the household hash, not at your user id. That is so a public query has nothing to join to an email. The household row does point at the login, because you need to edit your own bills and delete them. The application never joins those tables for a public response.
What we throw away
The account number, the street address, and the uploaded file. A JEA bill also shows the customer name, phone, meter numbers, and a QR code. Those are identifying and are never stored or written to a log. Rate-limit records store a hash of the network address, not the address itself. Sign-in links are stored as a hash and expire in 20 minutes.
What the public sees
Counts and averages for a ZIP, a home-size bucket, and a month, and only when the group is large enough. Verified counts are hidden when either the matched or unmatched side is smaller than 5, because a percentage plus a total would reveal that small side. Water averages are hidden when fewer than 5 bills in the group included water.
What a determined person could still do
- Invent an account number that looks real. We cannot check it against JEA. A matching bill file is the optional check, and the reader for photos is not built yet.
- Type a neighbor’s account number if they have the bill. The hash will match that household, and the first person to claim it blocks the other.
- File many households. Rate limits and the review queue slow that down. They do not make it impossible.
- Someone who operates the database and holds the hash secret can join the login table to the household table. Anonymity against the public is enforced in the product. Anonymity against the operator is a policy, not a mathematical guarantee, until those databases are split.
- ZIP plus home size plus month is still a quasi-identifier. Five is a floor, not a promise that nobody with outside knowledge can guess.
Cookies and reminders
One cookie, civicmeter_session, httpOnly, keeps you signed in for 30 days. That is the only cookie. Reminders are optional and contain no usage, ZIP, or address.
Deleting your data
From My bills, delete one month or delete the whole account. Deleting the account removes the email, the household link, every bill under that hash, and any survey answers stored with it. Aggregates are computed from what remains, so your rows leave the averages.
The optional home survey
After you can add a bill, we may ask a few multiple-choice questions about the home: how many people, cooling and heating, and similar ranges. Every question includes “Prefer not to say,” and none of them are required to file. The first time is the full set. Later months only ask whether anything changed, and earlier answers carry forward. We do not ask you to type a sentence.
Those answers are stored under the same household hash as the bills, not under the email. A public chart of them uses the same rule as the bill averages: a range is shown only when at least 5 households match, and “Prefer not to say” is not charted. Duval County and JEA are the current territory. A different county would use its own question list, still in ranges.
Bot check
If Turnstile keys are configured, a Cloudflare widget runs on sign-in, household setup, and a new bill. It is there to slow scripts. It is not an analytics tag. When the keys are absent, local development skips the check and says so on the form.