@@ -189,7 +189,7 @@ function collectBoundaries(datedRows: IWorkRow[]): Date[] {
189189
190190 if ( row . dateEnd ) {
191191 const afterEnd = startOfDay ( row . dateEnd )
192- afterEnd . setDate ( afterEnd . getDate ( ) + 1 )
192+ afterEnd . setUTCDate ( afterEnd . getUTCDate ( ) + 1 )
193193 if ( afterEnd <= today ) ms . add ( afterEnd . getTime ( ) )
194194 }
195195 }
@@ -199,9 +199,11 @@ function collectBoundaries(datedRows: IWorkRow[]): Date[] {
199199 . map ( ( t ) => new Date ( t ) )
200200}
201201
202- function orgsActiveAt ( datedRows : IWorkRow [ ] , boundaryDate : Date ) : IWorkRow [ ] {
203- return datedRows . filter ( ( role ) => {
204- const roleStart = startOfDay ( role . dateStart ?? '' )
202+ function orgsActiveAt ( rows : IWorkRow [ ] , boundaryDate : Date ) : IWorkRow [ ] {
203+ return rows . filter ( ( role ) => {
204+ if ( ! role . dateStart ) return true // undated: active at every boundary
205+
206+ const roleStart = startOfDay ( role . dateStart )
205207 const roleEnd = role . dateEnd ? startOfDay ( role . dateEnd ) : null
206208
207209 // org is active if the boundary date falls within its employment period
@@ -211,13 +213,13 @@ function orgsActiveAt(datedRows: IWorkRow[], boundaryDate: Date): IWorkRow[] {
211213
212214function startOfDay ( date : Date | string ) : Date {
213215 const d = new Date ( date )
214- d . setHours ( 0 , 0 , 0 , 0 )
216+ d . setUTCHours ( 0 , 0 , 0 , 0 )
215217 return d
216218}
217219
218220function dayBefore ( date : Date ) : Date {
219221 const d = new Date ( date )
220- d . setDate ( d . getDate ( ) - 1 )
222+ d . setUTCDate ( d . getUTCDate ( ) - 1 )
221223 return d
222224}
223225
@@ -247,7 +249,7 @@ function closeAffiliationWindow(
247249/** Iterates boundary intervals and builds non-overlapping affiliation windows. */
248250function buildTimeline (
249251 memberId : string ,
250- datedRows : IWorkRow [ ] ,
252+ allRows : IWorkRow [ ] ,
251253 fallbackOrg : IWorkRow | null ,
252254 boundaries : Date [ ] ,
253255) : IAffiliationPeriod [ ] {
@@ -256,9 +258,9 @@ function buildTimeline(
256258 let currentWindowStart : Date = null
257259 let uncoveredPeriodStart : Date = null
258260
259- for ( let i = 0 ; i < boundaries . length - 1 ; i ++ ) {
261+ for ( let i = 0 ; i < boundaries . length ; i ++ ) {
260262 const boundaryDate = boundaries [ i ]
261- const activeOrgsAtBoundary = orgsActiveAt ( datedRows , boundaryDate )
263+ const activeOrgsAtBoundary = orgsActiveAt ( allRows , boundaryDate )
262264
263265 log . info (
264266 {
@@ -420,10 +422,11 @@ function resolveAffiliationsForMember(memberId: string, rows: IWorkRow[]): IAffi
420422 'resolving affiliations' ,
421423 )
422424
423- // If one undated org is marked primary, drop all other undated orgs to avoid infinite conflicts
425+ // If one undated work-experience org is marked primary, drop other undated work-experience orgs
426+ // to avoid infinite conflicts. Manual affiliations (segmentId !== null) are never dropped.
424427 const primaryUndated = rows . find ( ( r ) => r . isPrimaryWorkExperience && ! r . dateStart && ! r . dateEnd )
425428 const cleaned = primaryUndated
426- ? rows . filter ( ( r ) => r . dateStart || r . id === primaryUndated . id )
429+ ? rows . filter ( ( r ) => r . segmentId !== null || r . dateStart || r . id === primaryUndated . id )
427430 : rows
428431
429432 if ( cleaned . length < rows . length ) {
@@ -456,7 +459,14 @@ function resolveAffiliationsForMember(memberId: string, rows: IWorkRow[]): IAffi
456459 )
457460
458461 if ( datedRows . length === 0 ) {
459- log . debug ( { memberId } , 'no dated rows — returning empty affiliations' )
462+ if ( fallbackOrg ) {
463+ log . debug (
464+ { memberId, fallbackOrg : fallbackOrg . organizationName } ,
465+ 'no dated rows — returning fallback as undated affiliation' ,
466+ )
467+ return [ { organization : fallbackOrg . organizationName , startDate : null , endDate : null } ]
468+ }
469+ log . debug ( { memberId } , 'no dated rows and no fallback — returning empty affiliations' )
460470 return [ ]
461471 }
462472
@@ -470,7 +480,8 @@ function resolveAffiliationsForMember(memberId: string, rows: IWorkRow[]): IAffi
470480 'collected boundaries' ,
471481 )
472482
473- const timeline = buildTimeline ( memberId , datedRows , fallbackOrg , boundaries )
483+ // Pass all cleaned rows (not just dated) so undated orgs compete at every boundary (bug 2 fix)
484+ const timeline = buildTimeline ( memberId , cleaned , fallbackOrg , boundaries )
474485
475486 log . debug (
476487 {
0 commit comments