99using AgGateway . ADAPT . ApplicationDataModel . Common ;
1010using AgGateway . ADAPT . ApplicationDataModel . Equipment ;
1111using AgGateway . ADAPT . ApplicationDataModel . LoggedData ;
12+ using AgGateway . ADAPT . ApplicationDataModel . Products ;
1213using AgGateway . ADAPT . ApplicationDataModel . Shapes ;
1314using AgGateway . ADAPT . ISOv4Plugin . ExtensionMethods ;
1415using AgGateway . ADAPT . ISOv4Plugin . ISOEnumerations ;
@@ -355,34 +356,44 @@ protected IEnumerable<OperationData> ImportTimeLog(ISOTask loggedTask, ISOTimeLo
355356 List < OperationData > operationDatas = new List < OperationData > ( ) ;
356357 foreach ( ISODevice dvc in loggedDeviceElementsByDevice . Keys )
357358 {
358- OperationData operationData = new OperationData ( ) ;
359-
360359 //Determine products
361- Dictionary < string , List < ISOProductAllocation > > productAllocations = GetProductAllocationsByDeviceElement ( loggedTask , dvc ) ;
362- List < int > productIDs = GetDistinctProductIDs ( TaskDataMapper , productAllocations ) ;
363-
364- //This line will necessarily invoke a spatial read in order to find
365- //1)The correct number of CondensedWorkState working datas to create
366- //2)Any Widths and Offsets stored in the spatial data
367- IEnumerable < DeviceElementUse > sections = sectionMapper . Map ( time ,
368- isoRecords ,
369- operationData . Id . ReferenceId ,
370- loggedDeviceElementsByDevice [ dvc ] ,
371- productAllocations ) ;
372-
373- var workingDatas = sections != null ? sections . SelectMany ( x => x . GetWorkingDatas ( ) ) . ToList ( ) : new List < WorkingData > ( ) ;
374-
375- operationData . GetSpatialRecords = ( ) => spatialMapper . Map ( isoRecords , workingDatas , productAllocations ) ;
376- operationData . MaxDepth = sections . Count ( ) > 0 ? sections . Select ( s => s . Depth ) . Max ( ) : 0 ;
377- operationData . GetDeviceElementUses = x => sectionMapper . ConvertToBaseTypes ( sections . Where ( s => s . Depth == x ) . ToList ( ) ) ;
378- operationData . PrescriptionId = prescriptionID ;
379- operationData . OperationType = GetOperationTypeFromLoggingDevices ( time ) ;
380- operationData . ProductIds = productIDs ;
381- if ( ! useDeferredExecution )
360+ Dictionary < string , List < ISOProductAllocation > > deviceProductAllocations = GetProductAllocationsByDeviceElement ( loggedTask , dvc ) ;
361+
362+ //Create a separate operation for each product form (liquid, granular or solid).
363+ List < List < string > > deviceElementGroups = SplitElementsByProductForm ( deviceProductAllocations , loggedDeviceElementsByDevice [ dvc ] , dvc ) ;
364+
365+ foreach ( var deviceElementGroup in deviceElementGroups )
382366 {
383- operationData . SpatialRecordCount = isoRecords . Count ( ) ; //We will leave this at 0 unless a consumer has overridden deferred execution of spatial data iteration
367+ OperationData operationData = new OperationData ( ) ;
368+
369+ Dictionary < string , List < ISOProductAllocation > > productAllocations = deviceProductAllocations
370+ . Where ( x => deviceElementGroup . Contains ( x . Key ) )
371+ . ToDictionary ( x => x . Key , x => x . Value ) ;
372+ List < int > productIDs = GetDistinctProductIDs ( TaskDataMapper , productAllocations ) ;
373+
374+ //This line will necessarily invoke a spatial read in order to find
375+ //1)The correct number of CondensedWorkState working datas to create
376+ //2)Any Widths and Offsets stored in the spatial data
377+ IEnumerable < DeviceElementUse > sections = sectionMapper . Map ( time ,
378+ isoRecords ,
379+ operationData . Id . ReferenceId ,
380+ deviceElementGroup ,
381+ productAllocations ) ;
382+
383+ var workingDatas = sections != null ? sections . SelectMany ( x => x . GetWorkingDatas ( ) ) . ToList ( ) : new List < WorkingData > ( ) ;
384+
385+ operationData . GetSpatialRecords = ( ) => spatialMapper . Map ( isoRecords , workingDatas , productAllocations ) ;
386+ operationData . MaxDepth = sections . Count ( ) > 0 ? sections . Select ( s => s . Depth ) . Max ( ) : 0 ;
387+ operationData . GetDeviceElementUses = x => sectionMapper . ConvertToBaseTypes ( sections . Where ( s => s . Depth == x ) . ToList ( ) ) ;
388+ operationData . PrescriptionId = prescriptionID ;
389+ operationData . OperationType = GetOperationTypeFromProductCategory ( productIDs ) ?? GetOperationTypeFromLoggingDevices ( time ) ;
390+ operationData . ProductIds = productIDs ;
391+ if ( ! useDeferredExecution )
392+ {
393+ operationData . SpatialRecordCount = isoRecords . Count ( ) ; //We will leave this at 0 unless a consumer has overridden deferred execution of spatial data iteration
394+ }
395+ operationDatas . Add ( operationData ) ;
384396 }
385- operationDatas . Add ( operationData ) ;
386397 }
387398
388399 //Set the CoincidentOperationDataIds property identifying Operation Datas from the same TimeLog.
@@ -393,6 +404,81 @@ protected IEnumerable<OperationData> ImportTimeLog(ISOTask loggedTask, ISOTimeLo
393404 return null ;
394405 }
395406
407+ private List < List < string > > SplitElementsByProductForm ( Dictionary < string , List < ISOProductAllocation > > productAllocations , HashSet < string > loggedDeviceElementIds , ISODevice dvc )
408+ {
409+ //This function splits device elements logged by single TimeLog into groups based
410+ //on product form referenced by these elements. This is done using following logic:
411+ // - determine used products forms and list of device element ids for each form
412+ // - for each product form determine device elements from all other forms
413+ // - remove these device elements and their children from a copy of device hierarchy elements
414+ // - this gives a list of device elements to keep for a product form
415+ var deviceElementIdsByProductForm = productAllocations
416+ . SelectMany ( x => x . Value . Select ( y => new { Form = GetProductFormByProductAllocation ( y ) , Id = x . Key } ) )
417+ . Where ( x => x . Form . HasValue )
418+ . GroupBy ( x => x . Form , x => x . Id )
419+ . Select ( x => x . Distinct ( ) . ToList ( ) )
420+ . ToList ( ) ;
421+
422+ List < List < string > > deviceElementGroups = new List < List < string > > ( ) ;
423+ if ( deviceElementIdsByProductForm . Count > 1 )
424+ {
425+ var deviceHierarchyElement = TaskDataMapper . DeviceElementHierarchies . Items [ dvc . DeviceId ] ;
426+
427+ var idsWithProduct = deviceElementIdsByProductForm . SelectMany ( x => x ) . ToList ( ) ;
428+ foreach ( var deviceElementIds in deviceElementIdsByProductForm )
429+ {
430+ var idsToRemove = idsWithProduct . Except ( deviceElementIds ) . ToList ( ) ;
431+ var idsToKeep = FilterDeviceElementIds ( deviceHierarchyElement , idsToRemove ) ;
432+
433+ deviceElementGroups . Add ( loggedDeviceElementIds . Intersect ( idsToKeep ) . ToList ( ) ) ;
434+ }
435+ }
436+ else
437+ {
438+ deviceElementGroups . Add ( loggedDeviceElementIds . ToList ( ) ) ;
439+ }
440+
441+ return deviceElementGroups ;
442+ }
443+
444+ private ProductFormEnum ? GetProductFormByProductAllocation ( ISOProductAllocation pan )
445+ {
446+ var adaptProductId = TaskDataMapper . InstanceIDMap . GetADAPTID ( pan . ProductIdRef ) ;
447+ var adaptProduct = TaskDataMapper . AdaptDataModel . Catalog . Products . FirstOrDefault ( x => x . Id . ReferenceId == adaptProductId ) ;
448+
449+ // Add an error if ProductAllocation is referencing non-existent product
450+ if ( adaptProduct == null )
451+ {
452+ TaskDataMapper . AddError ( $ "ProductAllocation referencing Product={ pan . ProductIdRef } skipped since no matching product found") ;
453+ }
454+ return adaptProduct ? . Form ;
455+ }
456+
457+ private List < string > FilterDeviceElementIds ( DeviceHierarchyElement deviceHierarchyElement , List < string > idsToRemove )
458+ {
459+ var elementIdsToKeep = new List < string > ( ) ;
460+ if ( ! idsToRemove . Contains ( deviceHierarchyElement . DeviceElement . DeviceElementId ) )
461+ {
462+ //By default we need to keep this element - covers scenario of no children elements
463+ bool addThisElement = true ;
464+ if ( deviceHierarchyElement . Children != null && deviceHierarchyElement . Children . Count > 0 )
465+ {
466+ foreach ( var c in deviceHierarchyElement . Children )
467+ {
468+ elementIdsToKeep . AddRange ( FilterDeviceElementIds ( c , idsToRemove ) ) ;
469+ }
470+ //Keep this element if at least one child element is kept
471+ addThisElement = elementIdsToKeep . Count > 0 ;
472+ }
473+
474+ if ( addThisElement )
475+ {
476+ elementIdsToKeep . Add ( deviceHierarchyElement . DeviceElement . DeviceElementId ) ;
477+ }
478+ }
479+ return elementIdsToKeep ;
480+ }
481+
396482 protected virtual ISOTime GetTimeElementFromTimeLog ( ISOTimeLog isoTimeLog )
397483 {
398484 return isoTimeLog . GetTimeElement ( this . TaskDataPath ) ;
@@ -500,6 +586,35 @@ private void AddProductAllocationsForDeviceElement(Dictionary<string, Dictionary
500586 }
501587 }
502588
589+ private OperationTypeEnum ? GetOperationTypeFromProductCategory ( List < int > productIds )
590+ {
591+ var productCategories = productIds
592+ . Select ( x => TaskDataMapper . AdaptDataModel . Catalog . Products . FirstOrDefault ( y => y . Id . ReferenceId == x ) )
593+ . Where ( x => x != null && x . Category != CategoryEnum . Unknown )
594+ . Select ( x => x . Category )
595+ . ToList ( ) ;
596+
597+ switch ( productCategories . FirstOrDefault ( ) )
598+ {
599+ case CategoryEnum . Variety :
600+ return OperationTypeEnum . SowingAndPlanting ;
601+
602+ case CategoryEnum . Fertilizer :
603+ case CategoryEnum . NitrogenStabilizer :
604+ case CategoryEnum . Manure :
605+ return OperationTypeEnum . Fertilizing ;
606+
607+ case CategoryEnum . Fungicide :
608+ case CategoryEnum . Herbicide :
609+ case CategoryEnum . Insecticide :
610+ case CategoryEnum . Pesticide :
611+ return OperationTypeEnum . CropProtection ;
612+
613+ default :
614+ return null ;
615+ }
616+ }
617+
503618 private OperationTypeEnum GetOperationTypeFromLoggingDevices ( ISOTime time )
504619 {
505620 HashSet < DeviceOperationType > representedTypes = new HashSet < DeviceOperationType > ( ) ;
0 commit comments