mobile-read-performance-baseline.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. // Baseline captured before bounded concurrent mobile reads, 2026-11-10.
  2. async function appCompanionOverview(input,current) {
  3. const uid=await authorizeRequestedUser(current,input.uid),coach=number(current.get('legacyGroupId'))===3,history=input.history===true,p=mobilePage(input);
  4. assertLessonTenant(current);
  5. if(!coach&&input.source!=='database')return appStudentCompanionOverview(input,current,uid,p);
  6. const args=[DEFAULT_COMPANY_ID,String(uid)],clauses=['a."company"=$1','a."'+(coach?'pl':'szyh')+'"=$2'];
  7. const date="to_char(xs_appointment_day(a.\"bxrq\",a.\"yysj\"),'YYYY-MM-DD')";
  8. const add=(sql,value)=>{args.push(value);clauses.push(sql.replace('?', '$'+args.length));};
  9. if(history)clauses.push('(a."dszt"::numeric>=30 OR c."status"=-2)');else clauses.push('COALESCE(c."status",99)<>-2');
  10. if(coach&&number(input.studentId))add('a."szyh"=?',String(input.studentId));
  11. if(input.search)add('c."title" ILIKE ?','%'+String(input.search).trim().slice(0,100)+'%');
  12. if(input.month&&/^\d{4}-\d{2}$/.test(input.month))add('LEFT('+date+',7)=?',input.month);
  13. const join=' FROM "CourseAppointment" a LEFT JOIN "CommonModel" c ON c."company"=a."company" AND c."modelId"=54 AND c."itemId"=a."id"';
  14. const summaryWhere=clauses.join(' AND '),summaryArgs=[...args];
  15. const dates=await Psql.query('SELECT '+date+' AS date,COUNT(*) FILTER(WHERE a."dszt"::numeric>=30 AND COALESCE(c."status",99)<>-2)::int finished,COUNT(*) FILTER(WHERE a."dszt"::numeric<30 AND COALESCE(c."status",99)<>-2)::int planned'+join+' WHERE '+summaryWhere+' GROUP BY '+date,summaryArgs);
  16. const studentOptions=coach?await Psql.query('SELECT a."szyh" AS id,MAX('+companionStudentNameSql()+') AS name,COUNT(*)::int count'+join+' LEFT JOIN "_User" u ON u."company"=a."company" AND u."legacyUserId"::text=a."szyh" WHERE a."company"=$1 AND a."pl"=$2 AND COALESCE(c."status",99)<>-2 GROUP BY a."szyh" ORDER BY name',[DEFAULT_COMPANY_ID,String(uid)]):[];
  17. if(input.dateStart)add(date+'>=?',String(input.dateStart));
  18. if(input.dateEnd)add(date+'<=?',String(input.dateEnd));
  19. if(input.filter==='finished')clauses.push('a."dszt"::numeric>=30 AND COALESCE(c."status",99)<>-2');
  20. if(input.filter==='pending'){clauses.push('a."dszt"::numeric<30 AND COALESCE(c."status",99)<>-2');add(date+'<?',appOverviewToday());}
  21. const where=clauses.join(' AND '),count=await Psql.one('SELECT COUNT(*)::int total'+join+' WHERE '+where,args);
  22. const rows=await Psql.query('SELECT c."generalId",c."title",c."createTime",c."status" AS "commonStatus",a.*,n."nodeName" AS "courseTitle"'+join+' LEFT JOIN "Node" n ON n."company"=a."company" AND n."nodeId"::text=a."kcid" WHERE '+where+' ORDER BY a."yysj" '+(history?'DESC':'ASC')+',a."id" DESC LIMIT $'+(args.length+1)+' OFFSET $'+(args.length+2),[...args,p.size,(p.index-1)*p.size]);
  23. const courses=coach?[]:await Psql.query('SELECT c."generalId",c."title",b.*,b."objectId" AS "bindingObjectId",n."nodeName" AS "courseTitle" FROM "CourseBinding" b JOIN "CommonModel" c ON c."company"=b."company" AND c."modelId"=58 AND c."itemId"=b."id" LEFT JOIN "Node" n ON n."company"=b."company" AND n."nodeId"::text=b."kcid" WHERE b."company"=$1 AND b."yhid"=$2 AND b."enrollmentStatus"=\'active\' AND c."status"=99 AND COALESCE(n."zstatus",99)=99 ORDER BY b."id" DESC',[DEFAULT_COMPANY_ID,String(uid)]);
  24. let earningsSummary, scheduleRows=rows;
  25. if(coach&&history){
  26. const allRows=await Psql.query('SELECT c."generalId",c."status" AS "commonStatus",a.dszt'+join+' WHERE '+where,args);
  27. const paidRows=await teacherSchedulePay(allRows,uid);
  28. earningsSummary={amount:paidRows.reduce((sum,row)=>sum+(Number(row.commonStatus)!==-2&&row.payStatus==='earned'?Math.round(Number(row.teacherPay)*100):0),0)/100,pendingCount:paidRows.filter(row=>Number(row.commonStatus)!==-2&&row.payStatus!=='earned').length};
  29. const payById=new Map(paidRows.map(row=>[String(row.generalId),row]));
  30. scheduleRows=rows.map(row=>{
  31. const pay=payById.get(String(row.generalId));
  32. if(!pay)return row;
  33. return {...row,teacherPay:pay.teacherPay,payStatus:pay.payStatus,payReason:pay.payReason,feedbackStatus:pay.feedbackStatus};
  34. });
  35. }
  36. const live=input.source==='database'?await databaseAppointmentVersions({studentId:coach?input.studentId:undefined},current):await appLiveVersionSnapshot(uid,['courses','appointments'],current);return {earningsSummary,schedules:scheduleRows.map(appOverviewScheduleRow),courses:courses.map(appOverviewCourseRow),page:mobilePageMeta(count.total,p),dates,studentOptions,...mobileSource(),...live};
  37. }
  38. async function appAccountOverview(input,current) {
  39. const uid=await authorizeRequestedUser(current,input.uid),groupId=number(current.get('legacyGroupId')||legacyData(current).GroupID),profile={...legacyUser(current),...legacyUserAddon(current)},today=appOverviewToday(),todayCompact=today.replace(/-/g,'');
  40. if(groupId===3){const [lessons,schedules,reviews]=await Promise.all([
  41. Psql.one('SELECT COUNT(*)::int AS total,COUNT(*) FILTER (WHERE "kclx"=\'1\')::int AS t30,COUNT(*) FILTER (WHERE "kclx"=\'2\')::int AS t60,COUNT(*) FILTER (WHERE "kclx"=\'3\')::int AS trial FROM "LessonRecord" WHERE "company"=$1 AND "jsmz"=$2',[DEFAULT_COMPANY_ID,String(uid)]),
  42. Psql.one('SELECT COUNT(*) FILTER (WHERE REPLACE(LEFT(COALESCE(NULLIF("bxrq",\'\'),"yysj"),10),\'-\',\'\')=$3)::int AS today,COUNT(*) FILTER (WHERE "dszt"~\'^\\d+(?:\\.0+)?$\' AND "dszt"::numeric>=30)::int AS completed FROM "CourseAppointment" WHERE NOT EXISTS (SELECT 1 FROM "CommonModel" cancelled WHERE cancelled."company"="CourseAppointment"."company" AND cancelled."modelId"=54 AND cancelled."itemId"="CourseAppointment"."id" AND cancelled."status"=-2) AND "company"=$1 AND "pl"=$2',[DEFAULT_COMPANY_ID,String(uid),todayCompact]),
  43. Psql.one('SELECT COUNT(*)::int AS total,COUNT(*) FILTER (WHERE "fxzt"=\'1\')::int AS completed FROM "MemoryPracticeRecord" WHERE "company"=$1 AND "plid"=$2',[DEFAULT_COMPANY_ID,uid])
  44. ]);const t30=number(lessons.t30),t60=number(lessons.t60),trial=number(lessons.trial),live=await appLiveVersionSnapshot(uid,['appointments','review','account'],current);return{profile,coachPanel:{todaySchedules:number(schedules.today),completedLessons:number(schedules.completed),...await teacherIncomeSummary(DEFAULT_COMPANY_ID,uid),reviewRecords:number(reviews.total),totalLessons:number(lessons.total),totalHours:t30*.5+t60+trial,course30:t30,course60:t60,trialLessons:trial},...live};}
  45. const [legacyReviewSync,legacyAppointmentSync]=await Promise.all([syncLegacyReviews(uid,current,input.refresh===true),input.source==='database'?Promise.resolve({state:'database',items:[]}):syncLegacyAppointments('szyh',uid,input.refresh===true)]),[companion,memory]=await Promise.all([
  46. Psql.one('SELECT COUNT(*) FILTER (WHERE "dszt"~\'^\\d+(?:\\.0+)?$\' AND "dszt"::numeric>=30)::int AS completed,COALESCE(SUM(CASE WHEN "dszt"~\'^\\d+(?:\\.0+)?$\' AND "dszt"::numeric>=30 THEN CASE WHEN "dslx"=\'1\' THEN 30 WHEN "dslx" IN (\'2\',\'3\') THEN 60 ELSE 0 END ELSE 0 END),0)::int AS minutes FROM "CourseAppointment" WHERE NOT EXISTS (SELECT 1 FROM "CommonModel" cancelled WHERE cancelled."company"="CourseAppointment"."company" AND cancelled."modelId"=54 AND cancelled."itemId"="CourseAppointment"."id" AND cancelled."status"=-2) AND "company"=$1 AND "szyh"=$2',[DEFAULT_COMPANY_ID,String(uid)]),
  47. Psql.one('SELECT COUNT(*)::int AS total,COUNT(*) FILTER (WHERE "fxzt"=\'1\')::int AS completed,COUNT(*) FILTER (WHERE LEFT(COALESCE(NULLIF("kywrq",\'\'),"kywsj"),10)=$3)::int AS today_total,COUNT(*) FILTER (WHERE LEFT(COALESCE(NULLIF("kywrq",\'\'),"kywsj"),10)=$3 AND "fxzt"=\'1\')::int AS today_completed,COUNT(*) FILTER (WHERE COALESCE("fxzt",\'0\')<>\'1\' AND LEFT(COALESCE(NULLIF("kywrq",\'\'),"kywsj"),10)<=$3)::int AS due_pending,COUNT(*) FILTER (WHERE COALESCE("fxzt",\'0\')<>\'1\' AND LEFT(COALESCE(NULLIF("kywrq",\'\'),"kywsj"),10)>$3)::int AS scheduled,COUNT(DISTINCT LEFT(COALESCE(NULLIF("kywrq",\'\'),"kywsj"),10))::int AS scheduled_days FROM "MemoryPracticeRecord" WHERE "company"=$1 AND "yhid"=$2',[DEFAULT_COMPANY_ID,uid,today])
  48. ]);const total=number(memory.total),completed=number(memory.completed),legacyCompleted=legacyAppointmentSync.state==='current'?legacyAppointmentSync.items.filter((row)=>row.cancelled!==true&&number(row.dszt)>=30):null,completedSessions=legacyCompleted?legacyCompleted.length:number(companion.completed),completedMinutes=legacyCompleted?legacyCompleted.reduce((sum,row)=>sum+(number(row.dslx)===1?30:[2,3].includes(number(row.dslx))?60:0),0):number(companion.minutes),scopes=['appointments','review','account'],live=withLegacyAppointmentVersion(await appLiveVersionSnapshot(uid,scopes,current),scopes,legacyAppointmentSync);return{profile,companionSummary:{completedSessions,completedMinutes},antiForgetSummary:{total,completed,progress:total?Math.round(completed/total*100):0,todayTotal:number(memory.today_total),todayCompleted:number(memory.today_completed),duePending:number(memory.due_pending),scheduled:number(memory.scheduled),scheduledDays:number(memory.scheduled_days)},legacyReviewSync,legacyAppointmentSync,...live};
  49. }
  50. function learnedDate(row) { const raw = String(row.dqrq || '').trim(); return /^\d{8}$/.test(raw) ? raw.slice(0,4) + '-' + raw.slice(4,6) + '-' + raw.slice(6,8) : String(row.kywsj || '').slice(0,10); }
  51. function learnedAt(row) { for (const value of [row.studyCommonCreateTime,row.studyCommonCreatedAt,row.studyRecordCreatedAt,row.studyRecordUpdatedAt]) { if (!value) continue; const date = value instanceof Date ? value : new Date(value); if (!Number.isNaN(date.getTime()) && date.getUTCFullYear() > 1970) return date.toISOString(); } return learnedDate(row); }