From afe261744f25523801182567b816ab65d92e2b5f Mon Sep 17 00:00:00 2001 From: Dilan Gilluly Date: Tue, 30 Jun 2026 17:42:30 -0400 Subject: [PATCH] (add): Counts backend logic, placeholders --- api/app/data/reports.py | 15 + api/app/db.py | 7 + client/drizzle/0000_init.sql | 40 ++ client/drizzle/meta/0000_snapshot.json | 387 ++++++++++++++++++ client/drizzle/meta/_journal.json | 13 + client/src/lib/server/db/schema.js | 11 + client/src/routes/+page.svelte | 13 +- .../src/routes/baskets/[prefix]/+page.svelte | 6 + .../src/routes/drawing/[prefix]/+page.svelte | 6 + .../src/routes/tickets/[prefix]/+page.svelte | 6 + 10 files changed, 502 insertions(+), 2 deletions(-) create mode 100644 client/drizzle/0000_init.sql create mode 100644 client/drizzle/meta/0000_snapshot.json create mode 100644 client/drizzle/meta/_journal.json diff --git a/api/app/data/reports.py b/api/app/data/reports.py index 8c95fea..f7da84d 100644 --- a/api/app/data/reports.py +++ b/api/app/data/reports.py @@ -27,6 +27,12 @@ class ReportByBasketLine: phone_number: str = "" pref: str = "" +@dataclass +class ReportCountLine: + prefix: str = "" + unique_buyers: int = 0 + total_buys: int = 0 + class ReportsRepo(RepoTemplate): def get_by_name_report(self, prefix: str): self.cur.execute("SELECT * FROM report_by_name WHERE prefix = ?", (prefix,)) @@ -36,6 +42,10 @@ class ReportsRepo(RepoTemplate): self.cur.execute("SELECT * FROM report_by_basket WHERE prefix = ?", (prefix,)) results = self.cur.fetchall() return [ReportByBasketLine(*r) for r in results] + def get_counts_report(self): + self.cur.execute("SELECT * FROM report_counts") + results = self.cur.fetchall() + return [ReportCountLine(*r) for r in results] reports_router = APIRouter(prefix="/api/reports") @@ -48,3 +58,8 @@ def get_report_by_name(prefix: str, tam_key: str = Header("")): def get_report_by_basket(prefix: str, tam_key: str = Header("")): AuthRepo().verify_key(tam_key) return ReportsRepo().get_by_basket_report(prefix) + +@reports_router.get("/counts") +def get_report_counts(tam_key: str = Header("")): + AuthRepo().verify_key(tam_key) + return ReportsRepo().get_counts_report() diff --git a/api/app/db.py b/api/app/db.py index 474bb7d..cdeb9eb 100644 --- a/api/app/db.py +++ b/api/app/db.py @@ -33,6 +33,13 @@ def init_db(): SELECT b.*, t.last_name, t.first_name, t.phone_number, t.pref FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id ORDER BY b.prefix, b.b_id""") + cur.execute("""CREATE VIEW IF NOT EXISTS report_counts AS + SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys + FROM tickets + GROUP BY prefix + UNION ALL + SELECT 'Totals', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*) + FROM tickets""") conn.commit() conn.close() diff --git a/client/drizzle/0000_init.sql b/client/drizzle/0000_init.sql new file mode 100644 index 0000000..f3fa0c3 --- /dev/null +++ b/client/drizzle/0000_init.sql @@ -0,0 +1,40 @@ +CREATE TABLE `baskets` ( + `prefix` text, + `b_id` integer, + `description` text, + `donors` text, + `winning_ticket` integer, + PRIMARY KEY(`prefix`, `b_id`) +); +--> statement-breakpoint +CREATE TABLE `prefixes` ( + `prefix` text PRIMARY KEY NOT NULL, + `color` text, + `weight` integer +); +--> statement-breakpoint +CREATE TABLE `tickets` ( + `prefix` text, + `t_id` integer, + `first_name` text, + `last_name` text, + `phone_number` text, + `pref` text, + PRIMARY KEY(`prefix`, `t_id`) +); +--> statement-breakpoint +CREATE VIEW `drawing` AS SELECT b.prefix, b.b_id, b.description, b.winning_ticket, t.last_name, t.first_name, t.phone_number + FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id + ORDER BY b.prefix, b.b_id;--> statement-breakpoint +CREATE VIEW `report_by_basket` AS SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref + FROM baskets b LEFT JOIN tickets t on b.prefix = t.prefix AND b.winning_ticket = t.t_id + ORDER BY b.prefix, b.b_id;--> statement-breakpoint +CREATE VIEW `report_by_name` AS SELECT t.last_name, t.first_name, t.phone_number, t.pref, b.prefix, b.b_id, b.description, b.donors, b.winning_ticket + FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id + ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id;--> statement-breakpoint +CREATE VIEW `report_counts` AS SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys + FROM tickets + GROUP BY prefix + UNION ALL + SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*) + FROM tickets; \ No newline at end of file diff --git a/client/drizzle/meta/0000_snapshot.json b/client/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..33f5578 --- /dev/null +++ b/client/drizzle/meta/0000_snapshot.json @@ -0,0 +1,387 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7fccde71-a8a3-4a93-b53b-9ad3bc7e2efc", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "baskets": { + "name": "baskets", + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "b_id": { + "name": "b_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "donors": { + "name": "donors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "winning_ticket": { + "name": "winning_ticket", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "baskets_prefix_b_id_pk": { + "columns": [ + "prefix", + "b_id" + ], + "name": "baskets_prefix_b_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "prefixes": { + "name": "prefixes", + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tickets": { + "name": "tickets", + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "t_id": { + "name": "t_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pref": { + "name": "pref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tickets_prefix_t_id_pk": { + "columns": [ + "prefix", + "t_id" + ], + "name": "tickets_prefix_t_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": { + "drawing": { + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "b_id": { + "name": "b_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "winning_ticket": { + "name": "winning_ticket", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "name": "drawing", + "isExisting": false, + "definition": "SELECT b.prefix, b.b_id, b.description, b.winning_ticket, t.last_name, t.first_name, t.phone_number\n FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id\n ORDER BY b.prefix, b.b_id" + }, + "report_by_basket": { + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "b_id": { + "name": "b_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "donors": { + "name": "donors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "winning_ticket": { + "name": "winning_ticket", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pref": { + "name": "pref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "name": "report_by_basket", + "isExisting": false, + "definition": "SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref\n FROM baskets b LEFT JOIN tickets t on b.prefix = t.prefix AND b.winning_ticket = t.t_id\n ORDER BY b.prefix, b.b_id" + }, + "report_by_name": { + "columns": { + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone_number": { + "name": "phone_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pref": { + "name": "pref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "b_id": { + "name": "b_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "donors": { + "name": "donors", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "winning_ticket": { + "name": "winning_ticket", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "name": "report_by_name", + "isExisting": false, + "definition": "SELECT t.last_name, t.first_name, t.phone_number, t.pref, b.prefix, b.b_id, b.description, b.donors, b.winning_ticket\n FROM baskets b LEFT JOIN tickets t ON b.prefix = t.prefix AND b.winning_ticket = t.t_id\n ORDER BY t.last_name, t.first_name, t.phone_number, b.prefix, b.b_id" + }, + "report_counts": { + "columns": { + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unique_buyers": { + "name": "unique_buyers", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_buys": { + "name": "total_buys", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "name": "report_counts", + "isExisting": false, + "definition": "SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys\n FROM tickets\n GROUP BY prefix\n UNION ALL\n SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*)\n FROM tickets" + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/client/drizzle/meta/_journal.json b/client/drizzle/meta/_journal.json new file mode 100644 index 0000000..35fbc84 --- /dev/null +++ b/client/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "sqlite", + "entries": [ + { + "idx": 0, + "version": "6", + "when": 1782829676932, + "tag": "0000_init", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/client/src/lib/server/db/schema.js b/client/src/lib/server/db/schema.js index 6a21462..88f762b 100644 --- a/client/src/lib/server/db/schema.js +++ b/client/src/lib/server/db/schema.js @@ -74,3 +74,14 @@ export const reportByBasket = sqliteView('report_by_basket', { .as(sql`SELECT b.prefix, b.b_id, b.description, b.donors, b.winning_ticket, t.last_name, t.first_name, t.phone_number, t.pref FROM baskets b LEFT JOIN tickets t on b.prefix = t.prefix AND b.winning_ticket = t.t_id ORDER BY b.prefix, b.b_id`); + +export const reportCounts = sqliteView('report_counts', { + prefix: text('prefix'), + unique_buyers: integer('unique_buyers'), + total_buys: integer('total_buys') +}).as(sql`SELECT prefix, COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))) AS unique_buyers, COUNT(*) AS total_buys + FROM tickets + GROUP BY prefix + UNION ALL + SELECT 'Total', COUNT(DISTINCT(CONCAT(first_name, last_name, phone_number))), COUNT(*) + FROM tickets`) diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte index 17a88b8..e635bf8 100644 --- a/client/src/routes/+page.svelte +++ b/client/src/routes/+page.svelte @@ -1,4 +1,5 @@