fix: complete caller analytics reliability remediation

This commit is contained in:
hectorzhao
2026-09-01 10:03:29 +08:00
parent a116926867
commit 7f8825cf3a
25 changed files with 850 additions and 202 deletions
+13 -4
View File
@@ -6,7 +6,7 @@ function run(events: AnalyticsEvent[]) { return projectAnalytics(events.reduce<A
describe('caller analytics business state', () => {
it.each([180,183])('counts %i as connected even if final call fails', code => {
const [r] = run([event('ATTEMPT'), event('PROGRESS', { code }), event('END', { code: 487, source: 'verified-dialog-final' })]);
expect(analyticsRates(r)).toMatchObject({ totalCalls: 1, connectedCalls: 1, answeredCalls: 0, failedCalls: 0, notConnectedCalls: 0, connectionRate: 100, overallAnswerRate: 0, connectedAnswerRate: 0 });
expect(analyticsRates(r)).toMatchObject({ totalCalls: 1, connectedCalls: 1, answeredCalls: 0, failedCalls: 0, notConnectedCalls: 0, connectionRate: 100, overallAnswerRate: 0, connectedAnswerRate: 0, durationUnknownCalls:0 });
});
it.each([100,181,182])('does not treat %i as connected', code => {
const [r] = run([event('ATTEMPT'), event('PROGRESS', { code }), event('END', { code: 486, source: 'verified-dialog-final' })]);
@@ -15,8 +15,8 @@ describe('caller analytics business state', () => {
it('counts positive sub-second duration, not just 200', () => {
const [zero] = run([event('ATTEMPT'), event('ACCEPTED', { code: 200 })]);
expect(zero.answeredCalls).toBe(0);
const [positive] = run([event('ATTEMPT'), event('ACCEPTED', { code: 200 }), event('END', { at: start + 1500, source: 'verified-dialog-final' })]);
expect(positive).toMatchObject({ connectedCalls: 1, answeredCalls: 1, talkMs: 500 });
const [positive] = run([event('ATTEMPT'), event('ACCEPTED', { code: 200 }), event('TALK_START', { at:start+1100, source:'dialog-acked' }), event('END', { at: start + 1500, source: 'verified-dialog-final' })]);
expect(positive).toMatchObject({ connectedCalls: 1, answeredCalls: 1, talkMs: 400 });
});
it('preserves zero actual duration', () => {
const [r] = run([event('ATTEMPT'), event('ACCEPTED', { code: 200 }), event('END', { talkMs: 0, source: 'verified-dialog-final' })]);
@@ -44,7 +44,7 @@ describe('caller analytics business state', () => {
expect(() => run([event('ATTEMPT'), event('PROGRESS', { customerId: 'other', code: 180 })])).toThrow('Conflicting');
});
it('calculates the three different business rates', () => {
expect(analyticsRates({ totalCalls:100, connectedCalls:60, answeredCalls:30, failedCalls:25, pendingCalls:15, unknownCalls:0, activeCalls:15, talkMs:0 })).toMatchObject({ notConnectedCalls:40, connectionRate:60, overallAnswerRate:30, connectedAnswerRate:50 });
expect(analyticsRates({ totalCalls:100, connectedCalls:60, answeredCalls:30, failedCalls:25, pendingCalls:15, unknownCalls:0, activeCalls:15, talkMs:0, durationUnknownCalls:0, provisionalAnsweredCalls:0 })).toMatchObject({ notConnectedCalls:40, connectionRate:60, overallAnswerRate:30, connectedAnswerRate:50 });
});
it('does not erase missing progress evidence with an end event', () => {
expect(run([event('END',{code:487,source:'verified-dialog-final'})])[0]).toMatchObject({unknownCalls:1,failedCalls:0});
@@ -59,4 +59,13 @@ describe('caller analytics business state', () => {
expect(run([event('ATTEMPT'),event('ACCEPTED',{code:200}),...sequence,event('END',{at:start+5000,source:'verified-dialog-final'})])[0]).toMatchObject({connectedCalls:1,answeredCalls:0,talkMs:0});
}
});
it('does not infer answer duration from 2xx without ACK evidence',()=>{
const[r]=run([event('ATTEMPT'),event('ACCEPTED',{code:200}),event('END',{at:start+5000,source:'verified-dialog-final'})]);
expect(analyticsRates(r)).toMatchObject({connectedCalls:1,answeredCalls:0,talkMs:0,durationUnknownCalls:1,overallAnswerRate:null,connectedAnswerRate:null});
});
it('keeps a version 2 original call pending until explicit close',()=>{
const base={schemaVersion:2};
expect(run([event('START',base),event('ATTEMPT',base),event('END',{...base,code:486,source:'verified-dialog-final'})])[0]).toMatchObject({activeCalls:1,pendingCalls:1,failedCalls:0});
expect(run([event('START',base),event('ATTEMPT',base),event('END',{...base,code:486,source:'verified-dialog-final'}),event('CLOSE',{...base,source:'attempts-exhausted'})])[0]).toMatchObject({activeCalls:0,pendingCalls:0,failedCalls:1});
});
});