fix: make rectification follow-up actionable
This commit is contained in:
@@ -458,6 +458,21 @@ function boundaryDistance(range: { readonly startTime: string; readonly endTime:
|
||||
return Math.max(0, Math.min(value - start, end - value));
|
||||
}
|
||||
|
||||
async function rectificationPacketStage<Value>(
|
||||
stage: "score_events" | "scan" | "merge_scans" | "candidate_differences" | "time_links" | "technical_packet",
|
||||
operation: () => Value | Promise<Value>,
|
||||
): Promise<Value> {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
const errorKind = error instanceof Error ? error.name : typeof error;
|
||||
const status = error !== null && typeof error === "object" && "status" in error
|
||||
&& typeof error.status === "number" ? error.status : null;
|
||||
console.error(`[birth-time-conversation-packet] stage=${stage} error=${errorKind}${status === null ? "" : ` status=${status}`}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function buildProductionConversationalRectificationPacket(
|
||||
engine: BirthTimeJourneyEngine,
|
||||
input: ConversationalRectificationPacketBuildInput,
|
||||
@@ -466,21 +481,23 @@ export async function buildProductionConversationalRectificationPacket(
|
||||
if (place.latitude === undefined || place.longitude === undefined) {
|
||||
throw new ConversationalRectificationError("profile_incomplete");
|
||||
}
|
||||
const latitude = place.latitude;
|
||||
const longitude = place.longitude;
|
||||
const baseRange = currentRange(input);
|
||||
const events = scoreableLifeEvents(
|
||||
input.evidence as readonly LifeEventEvidence[],
|
||||
input.declaredBirthInput.birthDate,
|
||||
);
|
||||
const eventScore: CandidateResult | null = events.length >= 3
|
||||
? await engine.scoreEvents({
|
||||
? await rectificationPacketStage("score_events", () => engine.scoreEvents({
|
||||
birthDate: input.declaredBirthInput.birthDate,
|
||||
startTime: baseRange.startTime,
|
||||
endTime: baseRange.endTime,
|
||||
lat: place.latitude,
|
||||
lon: place.longitude,
|
||||
lat: latitude,
|
||||
lon: longitude,
|
||||
tz: place.timezoneOffset,
|
||||
events,
|
||||
})
|
||||
}))
|
||||
: null;
|
||||
const selectedRange = !input.preserveCandidateRange && eventScore?.winningSegment
|
||||
? { startTime: eventScore.winningSegment.startTime, endTime: eventScore.winningSegment.endTime }
|
||||
@@ -488,25 +505,28 @@ export async function buildProductionConversationalRectificationPacket(
|
||||
const questionnaires: RectificationQuestionnaire[] = [];
|
||||
for (const scanRange of boundedScanRanges(selectedRange)) {
|
||||
const scanPoint = scanCoordinates(scanRange);
|
||||
const { questionnaire } = await engine.scan({
|
||||
const { questionnaire } = await rectificationPacketStage("scan", () => engine.scan({
|
||||
birthTime: `${input.declaredBirthInput.birthDate} ${scanPoint.centerTime}`,
|
||||
uncertaintyMinutes: scanPoint.uncertaintyMinutes,
|
||||
lat: place.latitude,
|
||||
lon: place.longitude,
|
||||
lat: latitude,
|
||||
lon: longitude,
|
||||
tz: place.timezoneOffset,
|
||||
ayanamsa: "lahiri",
|
||||
});
|
||||
}));
|
||||
questionnaires.push(questionnaire);
|
||||
}
|
||||
const questionnaire = mergeQuestionnaireScans(questionnaires, selectedRange);
|
||||
const candidateDifferences = await engine.buildDifferencePacket({
|
||||
const questionnaire = await rectificationPacketStage(
|
||||
"merge_scans",
|
||||
() => mergeQuestionnaireScans(questionnaires, selectedRange),
|
||||
);
|
||||
const candidateDifferences = await rectificationPacketStage("candidate_differences", () => engine.buildDifferencePacket({
|
||||
caseId: input.caseId,
|
||||
asOfDate: input.asOfDate,
|
||||
birthDate: input.declaredBirthInput.birthDate,
|
||||
startTime: selectedRange.startTime,
|
||||
endTime: selectedRange.endTime,
|
||||
lat: place.latitude,
|
||||
lon: place.longitude,
|
||||
lat: latitude,
|
||||
lon: longitude,
|
||||
tz: place.timezoneOffset,
|
||||
evidence: [],
|
||||
events,
|
||||
@@ -515,18 +535,22 @@ export async function buildProductionConversationalRectificationPacket(
|
||||
partitionFingerprints: [],
|
||||
recentRanges: [],
|
||||
candidateModel: null,
|
||||
});
|
||||
}));
|
||||
const calculationVersion = eventScore
|
||||
? `${candidateDifferences.packet.scoringVersion}+${eventScore.algorithmVersion}`
|
||||
: candidateDifferences.packet.scoringVersion;
|
||||
const metadata = layerMetadata(questionnaire, calculationVersion);
|
||||
const timeLinkedScanSamples = await rectificationPacketStage(
|
||||
"time_links",
|
||||
() => sampleTimes(questionnaire),
|
||||
);
|
||||
const representative = eventScore?.winningSegment?.representativeTime
|
||||
?? scanCoordinates(selectedRange).centerTime;
|
||||
const { buildRectificationTechnicalPacket } = await import(
|
||||
"../../../lib/conversational-rectification/technical-packet.ts"
|
||||
);
|
||||
return {
|
||||
packet: buildRectificationTechnicalPacket({
|
||||
packet: await rectificationPacketStage("technical_packet", () => buildRectificationTechnicalPacket({
|
||||
scan: questionnaire,
|
||||
candidateDifferences,
|
||||
eventScore: input.preserveCandidateRange && eventScore
|
||||
@@ -537,11 +561,11 @@ export async function buildProductionConversationalRectificationPacket(
|
||||
calculationVersion,
|
||||
availableLayers: metadata.availableLayers,
|
||||
layerReferences: metadata.layerReferences,
|
||||
timeLinkedScanSamples: sampleTimes(questionnaire),
|
||||
timeLinkedScanSamples,
|
||||
boundaryDistanceMinutes: boundaryDistance(selectedRange, representative),
|
||||
futureWindows: [],
|
||||
},
|
||||
}),
|
||||
})),
|
||||
resultId: eventScore?.resultId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -459,8 +459,17 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.conversational-domain-picker { display: grid; gap: var(--space-3); margin: 0; padding: 0; border: 0; }
|
||||
.conversational-domain-picker .conversational-domain-question { margin-bottom: var(--space-2); color: var(--color-ink); font-size: var(--type-body-md); font-weight: 600; line-height: 1.5; }
|
||||
.conversational-domain-picker > div { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-2); }
|
||||
.conversational-domain-picker button { padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas); color: var(--color-ink); cursor: pointer; text-align: left; }
|
||||
.conversational-domain-picker button { display: grid; gap: var(--space-1); padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas); color: var(--color-ink); cursor: pointer; text-align: left; }
|
||||
.conversational-domain-picker button > span { font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-domain-picker button > small { color: var(--color-ink-secondary); font-size: var(--type-overline); font-weight: 400; }
|
||||
.conversational-domain-picker button[aria-pressed="true"] { border-color: var(--color-action); background: var(--color-action-soft); color: var(--color-action); }
|
||||
.conversational-domain-picker button[aria-label$="下一步建议"] { border-color: color-mix(in srgb, var(--color-action) 52%, var(--color-border)); background: var(--color-action-soft); }
|
||||
.conversational-answer-pending { min-width: 0; display: grid; grid-template-columns: 40px minmax(0, 1fr); align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); border: 1px solid color-mix(in srgb, var(--color-action) 24%, var(--color-border)); border-radius: var(--radius-lg); background: var(--color-action-soft); }
|
||||
.conversational-answer-pending .app-loading-symbol { width: 40px; height: 40px; }
|
||||
.conversational-answer-pending .app-loading-mark { width: 22px; height: 22px; }
|
||||
.conversational-answer-pending > div:last-child { min-width: 0; display: grid; gap: var(--space-1); }
|
||||
.conversational-answer-pending strong { color: var(--color-ink); font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-answer-pending span { color: var(--color-ink-secondary); font-size: var(--type-caption); line-height: 1.5; }
|
||||
.conversational-composer { display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-lg); background: var(--color-canvas); }
|
||||
.conversational-composer label { display: grid; gap: var(--space-1); font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-composer label span { color: var(--color-ink-secondary); font-size: var(--type-caption); font-weight: 400; line-height: 1.5; }
|
||||
@@ -486,6 +495,9 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.conversational-candidate dt { color: var(--color-ink-tertiary); font-size: var(--type-overline); }
|
||||
.conversational-candidate dd { min-width: 0; margin: 0; color: var(--color-ink); font-size: var(--type-body-sm); overflow-wrap: anywhere; }
|
||||
.conversational-candidate time { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
||||
.conversational-candidate-progress { min-width: 0; display: grid; gap: var(--space-1); padding-top: var(--space-3); border-top: 1px solid var(--color-border); }
|
||||
.conversational-candidate-progress strong { color: var(--color-ink); font-size: var(--type-body-sm); font-weight: 600; }
|
||||
.conversational-candidate-progress span { color: var(--color-ink-secondary); font-size: var(--type-caption); line-height: 1.55; text-wrap: pretty; }
|
||||
.conversational-confirmation { border-color: color-mix(in srgb, var(--color-action) 44%, var(--color-border)); background: var(--color-action-soft); }
|
||||
.conversational-status { min-width: 0; min-height: 1px; }
|
||||
.conversational-original-question { background: var(--color-success-muted); }
|
||||
|
||||
@@ -1953,12 +1953,16 @@ export default function Home() {
|
||||
&& session.rectificationCaseId === null)
|
||||
?? null
|
||||
: null;
|
||||
const rectificationSession = sourceSession.sessionType === "birth_time_rectification"
|
||||
const canReuseSourceRectificationSession = action === "resume"
|
||||
&& sourceSession.sessionType === "birth_time_rectification"
|
||||
&& account.rectificationCase !== null
|
||||
&& (sourceSession.rectificationCaseId === account.rectificationCase.caseId
|
||||
|| sourceSession.rectificationCaseId === null);
|
||||
const rectificationSession = canReuseSourceRectificationSession
|
||||
? sourceSession
|
||||
: resumableSession ?? createSession(modelCatalog.defaultModelId, "birth_time_rectification");
|
||||
const reusingRectificationSession = rectificationSession !== sourceSession
|
||||
? resumableSession !== null
|
||||
: sourceSession.sessionType === "birth_time_rectification";
|
||||
const reusingRectificationSession = canReuseSourceRectificationSession
|
||||
|| resumableSession !== null;
|
||||
const requestedQuestion = pendingConsultationQuestion
|
||||
?? (reusingRectificationSession
|
||||
? null
|
||||
|
||||
Reference in New Issue
Block a user