-
Notifications
You must be signed in to change notification settings - Fork 0
Fix the ₹0-collected class of read (D1 100-parameter cap) and feed the empty team screens #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
cf4d00f
3a9b2d9
a0d2801
d3c96c4
83d939c
5c91b77
4863327
06095c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * D1 caps a single query at 100 bound parameters. A `WHERE id IN (?,?,...)` built from a result set | ||||||||||||||||||||||
| * therefore works in every test fixture and every young environment, then starts failing the moment | ||||||||||||||||||||||
| * real volume arrives - and because these reads are wrapped in swallow-and-continue helpers, the | ||||||||||||||||||||||
| * failure surfaces as a confident zero rather than an error. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Staging showed exactly that: /team/analytics reported 331 bookings and GMV of Rs 3,24,472 next to | ||||||||||||||||||||||
| * "Collected Rs 0" on every single service line, because the payments read for those 331 bookings | ||||||||||||||||||||||
| * asked D1 for 331 bound parameters and was discarded. | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Every IN-list read over an unbounded set goes through here: the ids are split into chunks that fit | ||||||||||||||||||||||
| * inside the cap and the chunk results are concatenated, so the answer is identical to the one the | ||||||||||||||||||||||
| * single query was meant to give. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export const D1_IN_CHUNK = 80; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function idChunks<T>(ids: readonly T[], size = D1_IN_CHUNK): T[][] { | ||||||||||||||||||||||
| if (size < 1) throw new Error("Chunk size must be at least 1"); | ||||||||||||||||||||||
| const chunks: T[][] = []; | ||||||||||||||||||||||
| for (let index = 0; index < ids.length; index += size) chunks.push(ids.slice(index, index + size)); | ||||||||||||||||||||||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Validate the chunk size as a positive integer. Line 18 accepts Proposed fix- if (size < 1) throw new Error("Chunk size must be at least 1");
+ if (!Number.isSafeInteger(size) || size < 1) {
+ throw new Error("Chunk size must be a positive integer");
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| return chunks; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Runs `read` once per chunk and concatenates the rows. `read` receives the chunk plus the matching | ||||||||||||||||||||||
| * `?,?,...` placeholder string, so call sites keep their own SQL, bindings and error handling. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export async function chunkedIn<T, R>( | ||||||||||||||||||||||
| ids: readonly T[], | ||||||||||||||||||||||
| read: (chunk: T[], placeholders: string) => Promise<R[]>, | ||||||||||||||||||||||
| size = D1_IN_CHUNK, | ||||||||||||||||||||||
| ): Promise<R[]> { | ||||||||||||||||||||||
| if (!ids.length) return []; | ||||||||||||||||||||||
| const results = await Promise.all(idChunks(ids, size).map((chunk) => read(chunk, chunk.map(() => "?").join(",")))); | ||||||||||||||||||||||
| return results.flat(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { currentGroomerBracket, computeGroomerMonthlyIncentive } from "./groomin | |
| import { computeTrainerMonthlyIncentive } from "./trainer-incentive-engine"; | ||
| import { dailyClosureReadiness } from "./rep-daily-closure-governance"; | ||
| import { dailyTalkTimeSummary } from "./talk-time-governance"; | ||
| import{chunkedIn}from"./d1-chunked-in"; | ||
|
|
||
| type Db=D1Database; | ||
| type Row=Record<string,unknown>; | ||
|
|
@@ -34,9 +35,8 @@ async function employeesInScope(db:Db,scope:Scope){ | |
| return rows.results; | ||
| } | ||
| if(!scope.employeeEmails.length)return[]; | ||
| const placeholders=scope.employeeEmails.map(()=>"?").join(","); | ||
| const rows=await db.prepare(`SELECT e.id,e.work_email,e.user_email,e.display_name,v.title,v.team_code FROM employees e JOIN employee_employment_versions v ON v.employee_id=e.id AND v.effective_until IS NULL WHERE lower(COALESCE(e.user_email,e.work_email)) IN (${placeholders}) ORDER BY e.display_name`).bind(...scope.employeeEmails).all<Row>(); | ||
| return rows.results; | ||
| return chunkedIn(scope.employeeEmails,async(chunk,placeholders)=>(await db.prepare(`SELECT e.id,e.work_email,e.user_email,e.display_name,v.title,v.team_code FROM employees e JOIN employee_employment_versions v ON v.employee_id=e.id AND v.effective_until IS NULL WHERE lower(COALESCE(e.user_email,e.work_email)) IN (${placeholders}) ORDER BY e.display_name`).bind(...chunk).all<Row>()).results); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Restore global employee ordering after chunking. Each SQL query orders only its own chunk. Sort the combined rows by 🤖 Prompt for AI Agents |
||
|
|
||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Chunk the remaining per-vertical booking reads.
The boarding, sitting, and training payout queries still build one placeholder per booking. A service with more than 100 bookings will still fail D1 binding limits.
safeAllthen converts the failure to an empty result, which reports missing cost data instead of the real cost.Use
chunkedInfor all three queries.🤖 Prompt for AI Agents