Jump to content

John Brezovec

Seeq Team
  • Posts

    70
  • Joined

  • Last visited

  • Days Won

    11

Community Answers

  1. John Brezovec's post in Import multiple signals to multiple parents in an asset tree was marked as the answer   
    Check out inserting with references, which will do what you're looking for:
    search_results = spy.search({'Name': '/Area [D,E,F]_Compressor Power/', 'Datasource Name': 'Example Data'}, order_by='Name') tree = spy.assets.Tree('My Tree') tree.insert(children=['Area D', 'Area E', 'Area F']) tree.insert( children=search_results, friendly_name='{{Name}}', parent='{{Name}(Area ?)_*}' ) Results in a tree that looks like:
    My Tree |-- Area D | |-- Area D_Compressor Power |-- Area E | |-- Area E_Compressor Power |-- Area F |-- Area F_Compressor Power In most cases though you're going to want the leaf node to have a 'generic' name (i.e. Compressor Power), and use the context of the tree to tell you what area it belongs to. You can also accomplish this using references:
    search_results = spy.search({'Name': '/Area [D,E,F]_Compressor Power/', 'Datasource Name': 'Example Data'}, order_by='Name') tree = spy.assets.Tree('My Tree') tree.insert(children=['Area D', 'Area E', 'Area F']) tree.insert( children=search_results, friendly_name='{{Name}Area ?_(*)}', # note the new inclusion of the capture group parent='{{Name}(Area ?)_*}' )
  2. John Brezovec's post in Pulling Categorical Data with wrong UOM was marked as the answer   
    You're correct that it looks like this signal has been assigned the improper unit. These errors get raised outside of SPy, so there isn't a way to ignore them in Python. If you try trending this same signal in workbench you should get the same error. This is either a datasource configuration issue or a historian misconfiguration -- I'd suggest raising a support ticket so we can help you track down the source of the issue: https://support.seeq.com/kb/latest/cloud/
  3. John Brezovec's post in Trying to only display MAX Value within a condition was marked as the answer   
    Your last formula is pretty close! You also need to include where you want to place the result of the aggregation - do you want it to display for the duration of the capsule, or maybe put it at the start or the end?
    $signal.aggregate(maxValue(), $condition, startKey()) // could replace startKey() with endKey(), middleKey(), durationKey(), maxKey() or minKey() This aggregation could also be performed using the Signal from Condition tool, which will let you pick your inputs in a point-and-click UI
  4. John Brezovec's post in Volume-Calculation count up flow signal until a max Volume is reached was marked as the answer   
    If I understand correctly, you have a Flow Signal (L/hr), and want to create a capsule every time that flow sensor sees a certain amount of material. It sounds like you want to do some math with multiple flow sensors, but I think that'll be very similar to how we can treat just one.
    The problem requires us to choose an inception time -- that is, when do we first start counting? Let's assume we want to start counting every year. We can represent that by creating a periodic condition with a capsule every year. In the periodic condition tool, select a Yearly duration:

    We can then do a running integral over each year using the integral function:
    $flow.integral($years) This will end up looking like:

    Then, we can generate a capsule every time a certain quantity passes through this meter (say 1000L) using some additional formula, where $integral is the running integral we just created.
    $reset_quantity = 1000L floor($integral/$reset_quantity).toCondition() They key to this formula is the floor function, which will round down to the nearest integer, allowing us to 'chop up' the integral into 1000L chunks. This will generate the blue condition below, where we have a new capsule every 1000L:

    If desired, you could then do another integral on top of the blue condition to give you a running integral that resets every 1000L:

    This result could also be expressed as a single formula:
    $years = years() $reset_quantity = 1000 L $yearly_integral = $flow.integral($years) $fill_events = floor($yearly_integral/$reset_quantity).toCondition() $flow.integral($fill_events.removeLongerThan(10d))
  5. John Brezovec's post in Create condition for past N batches was marked as the answer   
    Here's an alternative method to getting the last X batches in the last 30 days:
    // return X most recent batches in the past Y amount of time $numBatches = 20 // X $lookback = 1mo // Y // create a rolling condition with capsules that contain X adjacent capsules $rollingBatches = $batchCondition.removeLongerThan($lookback) .toCapsulesByCount($numBatches, $lookback) // find the last capsule in the rolling condition that's within the lookback period $currentLookback = capsule(now()-$lookback, now()) $batchWindow = condition( $lookback, $rollingBatches.toGroup($currentLookback, CAPSULEBOUNDARY.ENDSIN).last() ) // find all the batches within the capsule identified // ensure all the batches are within the lookback period $batchCondition.inside($batchWindow) .touches(condition($lookback, $currentLookback)) This is similar to yours in that it uses toGroup, but the key is in the use of toCapsulesByCount as a way to get a grouping of X capsules in a condition.
    You can see an example output below. All capsules will show up as hollow because by the nature of the rolling 'Last X days' the result will always be uncertain.

  6. John Brezovec's post in Join Capsules by Matching Capsule Properties was marked as the answer   
    Solution 1 - Simple Join
    If the batches are orderly and the end of one batch does not overlap with the start of the next batch, then using the Join logic in the Composite Condition tool is the easiest option.

    However, if the end of one batch overlaps with the start of another, then some batches may not be joined. In the screenshot below the third batch on June 6 is not joined.

     
    Solution 2 - Use Formula to join based on matching the batch ID capsule property
    By using join in Formula all the batches in this scenario can be joined. In addition the batch ID property is retained in the new capsules.

    // 40h is the maximum capsule duration // true tells Seeq to try to make the biggest capsules if there are multiple start capsules per end capsule // BatchID is the capsule that is common between the start and end $startCondition.join($endCondition, 40h, true, 'BatchID')  
    Content Verified DEC2023
  7. John Brezovec's post in Adding and Using Capsule Properties was marked as the answer   
    This guide is a good starting point for understanding how to view and work with capsule properties in Seeq: 
     
  8. John Brezovec's post in Using Scorecard Metrics in Subsequent Signal/Condition Calculations was marked as the answer   
    The Scorecard Metric tool does not actually create a signal that can be used in further calculations, despite being able to be viewed in trend view. In many cases the Signal from Condition tool can be used to accomplish the same calculation as Scorecard Metric and produces a signal that can be built on top of. The most common exception to this are calculations based on your current display range. These calculations can currently only be done using a Scorecard Metric or a simple table.
    If there is a desire to use the calculated parameter (via either scorecard metric or signal from condition) in further calculations AND have the ability to add colored thresholds, we recommend first using signal from condition to do the calculation of the parameter, then referencing your signal from condition in the scorecard metric tool (without actually calculating the metric).
    Content Verified DEC2023
  9. John Brezovec's post in Invert signal? was marked as the answer   
    If you set your axis limits manually you can specify an inverted axis, however it looks like there's a bug where that results in the tick marks disappearing from the axis.
    As an alternative, your idea of making the signal negative seems like a clever workaround. Taking it a step further, you could modify the number format of the negative signal in order to not show the (-) sign. For example, #,##0.00;#,##0.00

  10. John Brezovec's post in Shift Pattern not changing with Day Light saving was marked as the answer   
    Looking at this again, since your schedule is a little simpler than EOWeO, I'd create this condition a little differently.
    I'd represent each shift as a union (AND) of several smaller periodic conditions. For example, the day shifts for Shift A in your company could be represented as times when all the following conditions are present:
    Day Shifts (08:00-20:00 every day) 4 Days On (A periodic condition 4 days long that occurs every 8 days) First 2 Days of the 4 Days On (Periodic condition 2 days long that occurs every 4 days) Visually Shift A would look like:
     the AND of all the conditions marked with 1 the AND of all the conditions marked with 2
    Then you can shift that condition by increments of 2 days to get the other three of your shifts. We can put this all into one formula that looks like:
    $shift_identifier = 'A' // TODO: change this identifier $offset = 0d // TODO: Change offset for each shift $tz = 'Europe/London' $days = shifts(8, 12, $tz) $nights = shifts(20, 12, $tz) $on4 = periods(4d, 8d, toTime('2000-01-01T08:00:00Z') + $offset, $tz) $first2 = periods(2d, 4d, toTime('2000-01-01T08:00:00Z') + $offset, $tz) $second2 = periods(2d, 4d, toTime('2000-01-01T08:00:00Z') + 2d + $offset, $tz) combineWith( $days and $on4 and $first2, $nights and $on4 and $second2 ).setProperty('Shift', $shift_identifier) You would copy this four times, one for each shift, adjusting the shift identifier as well as the offset. I'm adjusting the offset directly within the periods() function in order to keep everything DST aware rather than using the move() function, which can mess up when DST takes effect.
    The final result of this looks like this:

  11. John Brezovec's post in Create a Scalar on an Asset Tree Upload was marked as the answer   
    Doing individual insert calls can get pretty slow when working with larger trees. In these cases I'd suggest doing a single insert of all of your limits with a DataFrame.
    For this we'll have to reshape the DataFrame Kin How suggested into something that looks like:

    I did that with a little pandas manipulation:
    csv["Path"] = csv[["Level 1", "Level 2", "Level 3"]].apply( lambda x: ">>".join(x), axis=1 ) df = pd.concat( [ csv[["Path", "Limits 1", "Limits 1 Name"]].rename( columns={"Limits 1": "Formula", "Limits 1 Name": "Name"} ), csv[["Path", "Limits 2", "Limits 2 Name"]].rename( columns={"Limits 2": "Formula", "Limits 2 Name": "Name"} ), ] ) df["Formula"] = df["Formula"].astype(str) Note that the Formula column has to be a string so I called astype(str) on it in the last line.
     
    At this point I can now do a single call to insert to add all my limits at once:
    tree.insert(children=df) This should give the same results as Kin How's method, but should be more performant when working with larger trees.
×
×
  • Create New...