diff --git a/functions/dist/questions/assignDailyQuestion.js b/functions/dist/questions/assignDailyQuestion.js index 042511d3..f663c356 100644 --- a/functions/dist/questions/assignDailyQuestion.js +++ b/functions/dist/questions/assignDailyQuestion.js @@ -34,6 +34,8 @@ var __importStar = (this && this.__importStar) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.assignDailyQuestionCallable = exports.assignDailyQuestion = void 0; +exports.dayOfYearUtc = dayOfYearUtc; +exports.isWildcardDay = isWildcardDay; exports.formatCstDate = formatCstDate; exports.parseCstDate = parseCstDate; exports.timestampAt6PmCst = timestampAt6PmCst; @@ -202,12 +204,26 @@ function epochDayOf(dateStr) { const [y, m, d] = dateStr.split('-').map(Number); return Math.floor(Date.UTC(y, m - 1, d) / 86400000); } +// The client promotes ~10% of days to a "Wildcard" surprise theme instead of the weekday mode +// (DailyModeResolver.resolve(): dayOfYear % 10 === 3). FROZEN to mirror that tag + cadence. +const WILDCARD_MODE_TAG = 'mode_wildcard'; +/** 1-based day of the year for a YYYY-MM-DD date (UTC) — matches Java's Calendar.DAY_OF_YEAR, + * which the client's DailyModeResolver uses for its wildcard cadence. */ +function dayOfYearUtc(dateStr) { + const [y, m, d] = dateStr.split('-').map(Number); + return Math.floor((Date.UTC(y, m - 1, d) - Date.UTC(y, 0, 1)) / 86400000) + 1; +} +/** Mirrors the client's DailyModeResolver ~10% wildcard override (dayOfYear % 10 === 3). */ +function isWildcardDay(dateStr) { + return dayOfYearUtc(dateStr) % 10 === 3; +} /** * Deterministically pick the SAME daily question the free client would compute for [dateStr]: - * today's weekday mode, ordered by id, indexed by epochDay % poolSize. Making this SERVER-side and - * authoritative also fixes partners in different time zones getting different questions (the client - * falls back to device-local weekday only when there is no server assignment). Returns null if the - * questions pool is unseeded so the caller can no-op rather than write an unresolvable id. + * today's mode (weekday, or wildcard on ~10% of days), ordered by id, indexed by epochDay % + * poolSize. Making this SERVER-side and authoritative also fixes partners in different time zones + * getting different questions (the client falls back to device-local weekday only when there is no + * server assignment). Returns null if the questions pool is unseeded so the caller can no-op rather + * than write an unresolvable id. */ async function pickDailyQuestionId(dateStr) { const db = admin.firestore(); @@ -220,14 +236,16 @@ async function pickDailyQuestionId(dateStr) { .get(); if (snapshot.empty) return null; - const weekday = new Date(`${dateStr}T12:00:00Z`).getUTCDay(); - const modeTag = WEEKDAY_MODE_TAGS[weekday]; - const inMode = snapshot.docs - .filter((d) => d.get('modeTag') === modeTag) - .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); - const pool = inMode.length > 0 - ? inMode - : snapshot.docs.slice().sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)); // wildcard/empty-mode fallback + const byId = (a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0); + const inTag = (tag) => snapshot.docs.filter((d) => d.get('modeTag') === tag).sort(byId); + const weekdayTag = WEEKDAY_MODE_TAGS[new Date(`${dateStr}T12:00:00Z`).getUTCDay()]; + // On a wildcard day prefer the wildcard pool; if it isn't seeded yet, fall back to today's + // WEEKDAY mode (not the whole pool), so this stays a safe no-op until wildcard content lands. + let pool = isWildcardDay(dateStr) ? inTag(WILDCARD_MODE_TAG) : []; + if (pool.length === 0) + pool = inTag(weekdayTag); + if (pool.length === 0) + pool = snapshot.docs.slice().sort(byId); // last-resort: mode unseeded const offset = ((epochDayOf(dateStr) % pool.length) + pool.length) % pool.length; return pool[offset].id; } diff --git a/functions/dist/questions/assignDailyQuestion.js.map b/functions/dist/questions/assignDailyQuestion.js.map index 9682dcf5..37b5808b 100644 --- a/functions/dist/questions/assignDailyQuestion.js.map +++ b/functions/dist/questions/assignDailyQuestion.js.map @@ -1 +1 @@ -{"version":3,"file":"assignDailyQuestion.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"assignDailyQuestion.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/questions/assignDailyQuestion.test.js b/functions/dist/questions/assignDailyQuestion.test.js index b4a19863..dec4350d 100644 --- a/functions/dist/questions/assignDailyQuestion.test.js +++ b/functions/dist/questions/assignDailyQuestion.test.js @@ -35,4 +35,32 @@ describe('Chicago date helpers (DST-safe)', () => { } }); }); +// Wildcard cadence must mirror the client's DailyModeResolver (dayOfYear % 10 === 3), or the +// server would assign a weekday question on days the app themes "Wildcard" (and vice versa). +describe('wildcard-day cadence (mirrors client DailyModeResolver)', () => { + it('computes 1-based day-of-year like Calendar.DAY_OF_YEAR', () => { + expect((0, assignDailyQuestion_1.dayOfYearUtc)('2026-01-01')).toBe(1); + expect((0, assignDailyQuestion_1.dayOfYearUtc)('2026-01-03')).toBe(3); + expect((0, assignDailyQuestion_1.dayOfYearUtc)('2026-02-01')).toBe(32); + expect((0, assignDailyQuestion_1.dayOfYearUtc)('2026-12-31')).toBe(365); // 2026 is not a leap year + expect((0, assignDailyQuestion_1.dayOfYearUtc)('2028-12-31')).toBe(366); // leap year + }); + it('fires wildcard exactly when day-of-year mod 10 === 3', () => { + expect((0, assignDailyQuestion_1.isWildcardDay)('2026-01-03')).toBe(true); // doy 3 + expect((0, assignDailyQuestion_1.isWildcardDay)('2026-01-13')).toBe(true); // doy 13 + expect((0, assignDailyQuestion_1.isWildcardDay)('2026-02-01')).toBe(false); // doy 32 + expect((0, assignDailyQuestion_1.isWildcardDay)('2026-01-01')).toBe(false); // doy 1 + expect((0, assignDailyQuestion_1.isWildcardDay)('2026-01-04')).toBe(false); // doy 4 + }); + it('flags ~10% of the year as wildcard days', () => { + let wild = 0; + for (let doy = 1; doy <= 365; doy++) { + const d = new Date(Date.UTC(2026, 0, doy)); + const dateStr = d.toISOString().slice(0, 10); + if ((0, assignDailyQuestion_1.isWildcardDay)(dateStr)) + wild++; + } + expect(wild).toBe(36); // doy ∈ {3,13,…,363} → 36 days + }); +}); //# sourceMappingURL=assignDailyQuestion.test.js.map \ No newline at end of file diff --git a/functions/dist/questions/assignDailyQuestion.test.js.map b/functions/dist/questions/assignDailyQuestion.test.js.map index bc89f3cb..85d36bfb 100644 --- a/functions/dist/questions/assignDailyQuestion.test.js.map +++ b/functions/dist/questions/assignDailyQuestion.test.js.map @@ -1 +1 @@ -{"version":3,"file":"assignDailyQuestion.test.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.test.ts"],"names":[],"mappings":";;AAAA,+DAAsF;AAEtF,iFAAiF;AACjF,uEAAuE;AACvE,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,gFAAgF;QAChF,uEAAuE;QACvE,MAAM,CAAC,IAAA,mCAAa,EAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC1E,oFAAoF;QACpF,gFAAgF;QAChF,MAAM,CAAC,IAAA,mCAAa,EAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,4CAA4C;QAC5C,MAAM,CAAC,IAAA,mCAAa,EAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC1E,MAAM,CAAC,IAAA,mCAAa,EAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC5E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,iCAAiC;QACjC,MAAM,CAAC,IAAA,kCAAY,EAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QACjF,iCAAiC;QACjC,MAAM,CAAC,IAAA,kCAAY,EAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,8BAA8B;QAC9B,MAAM,CAAC,IAAA,uCAAiB,EAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;QAC/F,uCAAuC;QACvC,MAAM,CAAC,IAAA,uCAAiB,EAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAA;IACjG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,KAAK,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC,EAAE,CAAC;YACzF,MAAM,CAAC,IAAA,mCAAa,EAAC,IAAA,kCAAY,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACpD,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"assignDailyQuestion.test.js","sourceRoot":"","sources":["../../src/questions/assignDailyQuestion.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/src/questions/assignDailyQuestion.test.ts b/functions/src/questions/assignDailyQuestion.test.ts index 089bdfe3..11b8d203 100644 --- a/functions/src/questions/assignDailyQuestion.test.ts +++ b/functions/src/questions/assignDailyQuestion.test.ts @@ -1,4 +1,4 @@ -import { formatCstDate, parseCstDate, timestampAt6PmCst } from './assignDailyQuestion' +import { formatCstDate, parseCstDate, timestampAt6PmCst, dayOfYearUtc, isWildcardDay } from './assignDailyQuestion' // DST regression tests for the Chicago date helpers (the old hardcoded -6 offset // mislabeled dates and shifted reveal times during the ~8 CDT months). @@ -38,3 +38,33 @@ describe('Chicago date helpers (DST-safe)', () => { } }) }) + +// Wildcard cadence must mirror the client's DailyModeResolver (dayOfYear % 10 === 3), or the +// server would assign a weekday question on days the app themes "Wildcard" (and vice versa). +describe('wildcard-day cadence (mirrors client DailyModeResolver)', () => { + it('computes 1-based day-of-year like Calendar.DAY_OF_YEAR', () => { + expect(dayOfYearUtc('2026-01-01')).toBe(1) + expect(dayOfYearUtc('2026-01-03')).toBe(3) + expect(dayOfYearUtc('2026-02-01')).toBe(32) + expect(dayOfYearUtc('2026-12-31')).toBe(365) // 2026 is not a leap year + expect(dayOfYearUtc('2028-12-31')).toBe(366) // leap year + }) + + it('fires wildcard exactly when day-of-year mod 10 === 3', () => { + expect(isWildcardDay('2026-01-03')).toBe(true) // doy 3 + expect(isWildcardDay('2026-01-13')).toBe(true) // doy 13 + expect(isWildcardDay('2026-02-01')).toBe(false) // doy 32 + expect(isWildcardDay('2026-01-01')).toBe(false) // doy 1 + expect(isWildcardDay('2026-01-04')).toBe(false) // doy 4 + }) + + it('flags ~10% of the year as wildcard days', () => { + let wild = 0 + for (let doy = 1; doy <= 365; doy++) { + const d = new Date(Date.UTC(2026, 0, doy)) + const dateStr = d.toISOString().slice(0, 10) + if (isWildcardDay(dateStr)) wild++ + } + expect(wild).toBe(37) // doy ∈ {3,13,…,363} → 37 days (~10.1%) + }) +}) diff --git a/functions/src/questions/assignDailyQuestion.ts b/functions/src/questions/assignDailyQuestion.ts index 55504cb1..2673f84d 100644 --- a/functions/src/questions/assignDailyQuestion.ts +++ b/functions/src/questions/assignDailyQuestion.ts @@ -177,12 +177,29 @@ function epochDayOf(dateStr: string): number { return Math.floor(Date.UTC(y, m - 1, d) / 86_400_000) } +// The client promotes ~10% of days to a "Wildcard" surprise theme instead of the weekday mode +// (DailyModeResolver.resolve(): dayOfYear % 10 === 3). FROZEN to mirror that tag + cadence. +const WILDCARD_MODE_TAG = 'mode_wildcard' + +/** 1-based day of the year for a YYYY-MM-DD date (UTC) — matches Java's Calendar.DAY_OF_YEAR, + * which the client's DailyModeResolver uses for its wildcard cadence. */ +export function dayOfYearUtc(dateStr: string): number { + const [y, m, d] = dateStr.split('-').map(Number) + return Math.floor((Date.UTC(y, m - 1, d) - Date.UTC(y, 0, 1)) / 86_400_000) + 1 +} + +/** Mirrors the client's DailyModeResolver ~10% wildcard override (dayOfYear % 10 === 3). */ +export function isWildcardDay(dateStr: string): boolean { + return dayOfYearUtc(dateStr) % 10 === 3 +} + /** * Deterministically pick the SAME daily question the free client would compute for [dateStr]: - * today's weekday mode, ordered by id, indexed by epochDay % poolSize. Making this SERVER-side and - * authoritative also fixes partners in different time zones getting different questions (the client - * falls back to device-local weekday only when there is no server assignment). Returns null if the - * questions pool is unseeded so the caller can no-op rather than write an unresolvable id. + * today's mode (weekday, or wildcard on ~10% of days), ordered by id, indexed by epochDay % + * poolSize. Making this SERVER-side and authoritative also fixes partners in different time zones + * getting different questions (the client falls back to device-local weekday only when there is no + * server assignment). Returns null if the questions pool is unseeded so the caller can no-op rather + * than write an unresolvable id. */ async function pickDailyQuestionId(dateStr: string): Promise { const db = admin.firestore() @@ -195,14 +212,16 @@ async function pickDailyQuestionId(dateStr: string): Promise { .get() if (snapshot.empty) return null - const weekday = new Date(`${dateStr}T12:00:00Z`).getUTCDay() - const modeTag = WEEKDAY_MODE_TAGS[weekday] - const inMode = snapshot.docs - .filter((d) => d.get('modeTag') === modeTag) - .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) - const pool = inMode.length > 0 - ? inMode - : snapshot.docs.slice().sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)) // wildcard/empty-mode fallback + type Doc = FirebaseFirestore.QueryDocumentSnapshot + const byId = (a: Doc, b: Doc): number => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0) + const inTag = (tag: string): Doc[] => snapshot.docs.filter((d) => d.get('modeTag') === tag).sort(byId) + + const weekdayTag = WEEKDAY_MODE_TAGS[new Date(`${dateStr}T12:00:00Z`).getUTCDay()] + // On a wildcard day prefer the wildcard pool; if it isn't seeded yet, fall back to today's + // WEEKDAY mode (not the whole pool), so this stays a safe no-op until wildcard content lands. + let pool: Doc[] = isWildcardDay(dateStr) ? inTag(WILDCARD_MODE_TAG) : [] + if (pool.length === 0) pool = inTag(weekdayTag) + if (pool.length === 0) pool = snapshot.docs.slice().sort(byId) // last-resort: mode unseeded const offset = ((epochDayOf(dateStr) % pool.length) + pool.length) % pool.length return pool[offset].id } diff --git a/seed/questions/DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md b/seed/questions/DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md index 54dce04b..f8e12689 100644 --- a/seed/questions/DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md +++ b/seed/questions/DAILY_SINGLE_CHOICE_WEEKDAY_SYSTEM.md @@ -554,6 +554,107 @@ Required tag: daily_sunday_slow_burn ``` +## Wildcard Mode (Surprise Days) — CONTENT NEEDED + +Wildcard is **not a weekday**. It is a surprise override that can land on **any** day. + +The app already promotes roughly **10% of days** to a "Wildcard" theme (it fires when the +day-of-year mod 10 equals 3 — about 37 days a year). On those days the app shows the header +**"Wildcard — The app has opinions"** with the action line **"Wildcard mission: say yes to one +tiny weird idea."** Today there are **zero** wildcard questions, so those days silently show a +normal weekday question under the Wildcard banner. This section defines the missing content. + +### Wildcard voice + +Playful, surprising, a little weird, low pressure. The feeling is *the app just picked something +unexpected for the two of you.* Think tiny dares, silly "would you rather", spontaneous micro-plans, +harmless chaos, "say yes to the odd option." + +Because a wildcard day can land on **any** weekday, wildcard questions must be **day-agnostic** — +never reference Monday/Friday/the weekend or a specific weekday vibe. They must read well no matter +which day they appear on. + +Same gates as the weekday packs (see Daily Fun Gate, Option Quality Standard, Banned Tone Words): +quick, warm, PG-13 max on the free tier, 4 options preferred (4–6 allowed), single choice. + +### Wildcard counts + +* **12 free** `single_choice` wildcard questions. **Free only** — the daily picker never draws from + the premium pool, so premium wildcards would never be served. Do not author premium wildcards. +* This is **in addition** to the existing 500 (75 free + 425 premium) weekday questions. + +### Wildcard IDs + +Continue the existing sequence so ids stay sortable and unique — the picker sorts by id: + +```text +daily_single_choice_weekly_v1_501 … daily_single_choice_weekly_v1_512 +``` + +### Wildcard tags (EXACT — this is an app contract) + +Every wildcard question must carry these tags, and **must NOT** carry any `weekday_*` tag or any +weekday `mode_*` tag (a weekday mode tag would wrongly pull the wildcard into that weekday's pool): + +```json +"tags": [ + "daily", + "single_choice", + "best_fit", + "mode_wildcard", // REQUIRED — the app selects wildcard questions on this EXACT tag. + "daily_wildcard", // new-scheme weekday-style tag, kept for consistency + "quick_answer" +] +``` + +> **`mode_wildcard` is non-negotiable.** The client (`DailyModeResolver.MODES["wildcard"].modeTag`) +> and the server both filter on the literal string `mode_wildcard`. A wildcard question without this +> exact tag is invisible to the wildcard picker. + +### Wildcard question schema (copy this shape) + +```json +{ + "id": "daily_single_choice_weekly_v1_501", + "category_id": "daily_fun_mc", + "type": "single_choice", + "text": "The app dares you: which tiny weird idea do we say yes to tonight?", + "depth": 1, + "access": "free", + "sex": "neutral", + "tags": ["daily", "single_choice", "best_fit", "mode_wildcard", "daily_wildcard", "quick_answer"], + "options": [ + { "id": "swap_one_chore_for_a_dance", "text": "Swap one chore for a 2-song dance break" }, + { "id": "dessert_for_dinner_experiment", "text": "Dessert-for-dinner experiment" }, + { "id": "text_each_other_only_in_emoji", "text": "Only talk in emoji for an hour" }, + { "id": "rename_the_wifi_together", "text": "Rename the wifi together, right now" } + ], + "answer_config": { + "options": [ + { "id": "swap_one_chore_for_a_dance", "text": "Swap one chore for a 2-song dance break" }, + { "id": "dessert_for_dinner_experiment", "text": "Dessert-for-dinner experiment" }, + { "id": "text_each_other_only_in_emoji", "text": "Only talk in emoji for an hour" }, + { "id": "rename_the_wifi_together", "text": "Rename the wifi together, right now" } + ], + "selection_style": "single" + } +} +``` + +### After the JSON is authored — engineer rollout (NOT the content agent's job) + +Once the 12 wildcard questions are in `daily_fun_multiple_choice_v3.json`, an engineer lands them: + +1. **Asset DB:** insert the 12 rows into `app/src/main/assets/database/app.db` `question` table + **data-only** (`category_id='daily_fun_mc'`, `status='active'`, `is_premium=0`, `tags` containing + `mode_wildcard`). **Do NOT run `build_db.py`** — a full rebuild changes the Room identity hash and + breaks the shipped DB (see project notes). Data-only inserts are safe. +2. **Firestore pointer docs:** seed one `questions/{id}` doc per wildcard question: + `{ questionId, modeTag: "mode_wildcard", active: true, isPremium: false, type: "single_choice", + source: "asset_db_daily_fun_mc", seededAt }`. +3. **Deploy** the updated `assignDailyQuestion` function (the `mode_wildcard` picker branch is already + in code; until wildcard rows exist it safely falls back to the day's weekday question). + ## Required Tags Each question must include exactly one new weekday tag: @@ -565,6 +666,7 @@ Each question must include exactly one new weekday tag: * daily_friday_flirty * daily_saturday_side_quest * daily_sunday_slow_burn +* daily_wildcard (wildcard surprise days — see Wildcard Mode above; pairs with the REQUIRED `mode_wildcard` app tag) If the app code still uses older mode tags, include the compatibility tag too, but only one new weekday tag.