fix(web): offer representative time after occupation notes cover
Dateless occupation_note stayed draft, so classic coverage never finished and offer-candidates stayed blocked. Confirm those notes, stop crowding dasha probes with encoded exam quality, and adopt once blocking methods are covered. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -80,6 +80,7 @@ test("local PostgreSQL applies the reviewed business schema and serves authentic
|
||||
assert.match(migration.stdout, /applied 20260818010000_admin_product_catalog_mutations\.sql/);
|
||||
assert.match(migration.stdout, /applied 20260819010000_rectification_ingest_precision_plateau\.sql/);
|
||||
assert.match(migration.stdout, /applied 20260822010000_declared_birth_window_clocks\.sql/);
|
||||
assert.match(migration.stdout, /applied 20260823010000_rectification_occupation_dateless\.sql/);
|
||||
|
||||
assert.equal(
|
||||
fixture.psql(`
|
||||
|
||||
@@ -372,3 +372,86 @@ test("distinguish follow-up copy forbids competing-chart ranking", () => {
|
||||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /自己写题干/);
|
||||
assert.match(plan.next_followup?.user_prompt_hint ?? "", /不得发明年份/);
|
||||
});
|
||||
|
||||
test("GET A/B card stays hidden once representative time can be offered", () => {
|
||||
const card = projectRectificationChoiceCard({
|
||||
evidence: [{
|
||||
status: "confirmed",
|
||||
domain: "education",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2016-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "relationship",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2018-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "career",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2019-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "confirmed",
|
||||
domain: "family",
|
||||
datePrecision: "year",
|
||||
occurredFrom: "2020-01-01",
|
||||
occurredTo: null,
|
||||
}, {
|
||||
status: "draft",
|
||||
domain: "occupation",
|
||||
datePrecision: "unknown",
|
||||
occurredFrom: null,
|
||||
occurredTo: null,
|
||||
eventKind: "occupation_note",
|
||||
}],
|
||||
eventProbes: [MOVE_PROBE],
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
activeFocus: {
|
||||
intent: "distinguish_candidates",
|
||||
targetDomain: "relocation",
|
||||
targetKind: "home_change",
|
||||
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
||||
},
|
||||
});
|
||||
assert.equal(card, null);
|
||||
});
|
||||
|
||||
test("GET reverse-verify card still appears after a time is accepted", () => {
|
||||
const card = projectRectificationChoiceCard({
|
||||
evidence: [{
|
||||
status: "confirmed",
|
||||
domain: "career",
|
||||
datePrecision: "day",
|
||||
occurredFrom: "2024-04-07",
|
||||
occurredTo: null,
|
||||
}],
|
||||
accepted: true,
|
||||
sessionOutcome: "adopt_representative",
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
eventProbes: [{
|
||||
year: 2018,
|
||||
year_label: "2018 年前后",
|
||||
domain: "career",
|
||||
event_family: "入职、升职或职责明显加重",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: true,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "年份锁定 2018 年前后。请写成一句自然语言,问是否入职或职责加重。",
|
||||
role: "reverse_verify",
|
||||
}],
|
||||
activeFocus: {
|
||||
intent: "reverse_verify",
|
||||
targetDomain: "career",
|
||||
targetKind: "career_change",
|
||||
expectedAnswerSchema: { choice: SAMPLE_COPY },
|
||||
},
|
||||
});
|
||||
assert.equal(card?.prompt, SAMPLE_COPY.prompt);
|
||||
assert.ok(parseRectificationChoiceCard(card));
|
||||
});
|
||||
|
||||
@@ -291,6 +291,20 @@ test("adopt_representative defers method follow-up instead of asking this turn",
|
||||
assert.equal(plan.session_outcome, "adopt_representative");
|
||||
});
|
||||
|
||||
test("adopt_representative ignores leftover distinguish focus", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: CLASSIC_COVERAGE.filter((item) => item.domain !== "horary"),
|
||||
sessionOutcome: "adopt_representative",
|
||||
activeFocus: {
|
||||
intent: "distinguish_candidates",
|
||||
targetDomain: "relocation",
|
||||
targetKind: "home_change",
|
||||
},
|
||||
});
|
||||
assert.equal(plan.next_followup, null);
|
||||
assert.equal(plan.session_outcome, "adopt_representative");
|
||||
});
|
||||
|
||||
test("declined relationship skips to career and leaves horary uncovered", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [{
|
||||
@@ -993,6 +1007,123 @@ test("career evidence does not cover occupation; occupation still blocks until a
|
||||
}), "collect_evidence");
|
||||
});
|
||||
|
||||
test("draft occupation_note without a date covers occupation and unblocks offering", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-05-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "career", datePrecision: "day", occurredFrom: "2019-09-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
||||
{
|
||||
status: "draft",
|
||||
domain: "occupation",
|
||||
datePrecision: "unknown",
|
||||
occurredFrom: null,
|
||||
occurredTo: null,
|
||||
eventKind: "occupation_note",
|
||||
summary: "职业类型轨迹为技术开发",
|
||||
},
|
||||
],
|
||||
});
|
||||
assert.equal(plan.methods.find((item) => item.method_id === "occupation")?.status, "covered");
|
||||
assert.notEqual(plan.next_followup?.method_id, "occupation");
|
||||
assert.equal(conversationalSessionOutcome({
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
confirmationAllowed: false,
|
||||
nextFollowup: plan.next_followup,
|
||||
methods: plan.methods,
|
||||
}), "adopt_representative");
|
||||
});
|
||||
|
||||
test("stale occupation collect focus does not keep interviewing after occupation is covered", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
{ status: "confirmed", domain: "education", datePrecision: "year", occurredFrom: "2016-01-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "relationship", datePrecision: "day", occurredFrom: "2024-05-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "career", datePrecision: "day", occurredFrom: "2019-09-01", occurredTo: null },
|
||||
{ status: "confirmed", domain: "family", datePrecision: "year", occurredFrom: "2020-01-01", occurredTo: null },
|
||||
{
|
||||
status: "draft",
|
||||
domain: "occupation",
|
||||
datePrecision: "unknown",
|
||||
occurredFrom: null,
|
||||
occurredTo: null,
|
||||
eventKind: "occupation_note",
|
||||
summary: "前端工程师",
|
||||
},
|
||||
],
|
||||
activeFocus: {
|
||||
intent: "collect_method_evidence",
|
||||
targetDomain: "occupation",
|
||||
targetKind: "occupation_note",
|
||||
},
|
||||
});
|
||||
assert.notEqual(plan.next_followup?.method_id, "active_focus");
|
||||
assert.notEqual(plan.next_followup?.method_id, "occupation");
|
||||
assert.equal(conversationalSessionOutcome({
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
confirmationAllowed: false,
|
||||
nextFollowup: plan.next_followup,
|
||||
methods: plan.methods,
|
||||
}), "adopt_representative");
|
||||
});
|
||||
|
||||
test("event_probe does not block offering once blocking methods are covered", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: CLASSIC_COVERAGE.filter((item) => item.domain !== "horary"),
|
||||
eventProbes: [{
|
||||
year: 2018,
|
||||
year_label: "2018 年前后",
|
||||
domain: "relocation",
|
||||
event_family: "搬家、离乡或长期异地",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: true,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "年份锁定 2018 年前后。请写成一句自然语言,问是否搬家。",
|
||||
role: "reverse_verify",
|
||||
}],
|
||||
});
|
||||
assert.equal(isOfferBlockingFollowup(plan.next_followup, plan.methods), false);
|
||||
assert.equal(conversationalSessionOutcome({
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
confirmationAllowed: false,
|
||||
nextFollowup: plan.next_followup,
|
||||
methods: plan.methods,
|
||||
}), "adopt_representative");
|
||||
});
|
||||
|
||||
test("accepted time reverse-verifies an uncovered year in a covered domain", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [{
|
||||
status: "confirmed",
|
||||
domain: "career",
|
||||
datePrecision: "day",
|
||||
occurredFrom: "2024-04-07",
|
||||
occurredTo: null,
|
||||
}],
|
||||
accepted: true,
|
||||
eventProbes: [{
|
||||
year: 2018,
|
||||
year_label: "2018 年前后",
|
||||
domain: "career",
|
||||
event_family: "入职、升职或职责明显加重",
|
||||
source: "dasha_activation",
|
||||
tracks: ["vimshottari", "narayana"],
|
||||
tracks_agree: true,
|
||||
unique_minute_claim: false,
|
||||
user_meaning: "年份锁定 2018 年前后。请写成一句自然语言,问是否入职或职责加重。",
|
||||
role: "reverse_verify",
|
||||
}],
|
||||
});
|
||||
assert.equal(plan.next_followup?.source, "reverse_verify");
|
||||
assert.equal(plan.next_followup?.domain, "career");
|
||||
assert.equal(plan.next_followup?.choice_frame?.period, "2018 年前后");
|
||||
});
|
||||
|
||||
test("declining occupation covers the method; declining horary is skipped_by_policy", () => {
|
||||
const plan = buildMethodFollowupPlan({
|
||||
evidence: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
@@ -228,3 +228,34 @@ test("receipt allowlist includes D5, D7 and D3 public methods", () => {
|
||||
);
|
||||
assert.match(d3Migration, /'d3-drekkana'/);
|
||||
});
|
||||
|
||||
test("dateless occupation_note confirms on write and backfills existing drafts", () => {
|
||||
const migration = readFileSync(
|
||||
new URL("../supabase/migrations/20260823010000_rectification_occupation_dateless.sql", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
assert.match(migration, /^begin;[\s\S]*^commit;$/m);
|
||||
assert.match(
|
||||
migration,
|
||||
/create or replace function public\.agentic_rectification_allows_dateless_confirm\(p_kind text\)/,
|
||||
);
|
||||
assert.match(migration, /p_kind in \('occupation_note', 'appearance_note', 'birthmark_or_scar'\)/);
|
||||
assert.match(
|
||||
migration,
|
||||
/if \(v_precision = 'unknown' or v_from is null\)\s+and not public\.agentic_rectification_allows_dateless_confirm\(v_kind\) then/,
|
||||
);
|
||||
assert.match(
|
||||
migration,
|
||||
/elsif public\.agentic_rectification_allows_dateless_confirm\(v_kind\)\s+and \(v_precision = 'unknown' or v_from is null\) then/,
|
||||
);
|
||||
assert.match(
|
||||
migration,
|
||||
/where event_kind in \('occupation_note', 'appearance_note', 'birthmark_or_scar'\)[\s\S]*status in \('draft', 'pending_confirmation'\)/,
|
||||
);
|
||||
assert.match(migration, /grant execute on function public\.record_agentic_rectification_evidence_batch/);
|
||||
assert.equal(
|
||||
existsSync(new URL("../db/migrations/20260823010000_rectification_occupation_dateless.sql", import.meta.url)),
|
||||
false,
|
||||
"business migration must not be copied into frontend/db/migrations (BUG-127/BUG-144)",
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user