Jump to content

John Brezovec

Seeq Team
  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by John Brezovec

  1. This page from our SPy documentation walks through a very similar example - again, the key is to specify the mustache ({{}}) notation on the image alt text, rather than just in the body of the document.
  2. Thanks for the additional context. I agree I'm not sold on the utility of stacking the capsules end-to-end, but I figured I'd give an example of how it could be done since it may help move you in the right direction. In much the same way we calculated when the capsule should end based on the start_value + reset_quantity, we can find when the capsule should start based on the reset quantities of all the capsules before it in the same year (since we reset the totalizer every year). In practice this adds a bit of complexity to the formula 😉 $grow_amount = 3wk $condition_with_reset_quantity .move(-$grow_amount, $grow_amount) // grow the condition since a transform can only output capsules if they are within the originals .transform($c -> { $actual_start_key = $c.startKey() + $grow_amount // un-grown capsule start $actual_end_key = $c.endKey() - $grow_amount // un-grown capsule end $actual_input_capsule = capsule($actual_start_key, $actual_end_key) $current_year_capsule = $years.toGroup($actual_input_capsule, CAPSULEBOUNDARY.overlap) .first() $current_year_start = $current_year_capsule.startKey() // find all the reset quantity capsules that overlap the year // then sum up all the 'Reset Quantities' for those capsules. $start_value = $condition_with_reset_quantity.toGroup(capsule($current_year_start, $actual_start_key+1ns), CAPSULEBOUNDARY.INTERSECT) .forEach($cap -> $cap.property('Reset Quantity')) .sum() - $c.property('Reset Quantity') // what value should we stop the capsule at? $end_value = $start_value + $c.property('Reset Quantity') // generate a condition internal to the transform that tells us when we reach this end value $cond_volume_reached = $yearly_integral == $end_value // turn this condition back into a capsule so we can grab its start key $cap_volume_reached = $cond_volume_reached.removeLongerThan(1s) .toGroup($c) .last() // do the same thing with the start value $start_volume_reached = $yearly_integral == $start_value $cap_start_volume_reached = $start_volume_reached.removelongerthan(1s) .toGroup($c) .last() // create the output condition. Manually reassign the Reset Quantity property capsule($cap_start_volume_reached.startkey(), $cap_volume_reached.startKey()) .setProperty('Reset Quantity', $c.property('Reset Quantity')) .setProperty('Start Quantity', $start_value) .setProperty('End Quantity', $end_value) }) Notice how the resultant purple capsules stack end-to-end. The first capsule property on the purple condition is the reset quantity for that capsule, the second is the volume we should start that capsule at, and the third is the volume we want to end the capsule at. As a noted before, notice how the capsules are starting to creep forward over time.
  3. If you do that, you're likely going to keep pushing your capsules further and further out into the future. Can you share more about the use case so I can make sure we're going down the right path?
  4. So is the desired behavior to start integrating at the start of each capsule, and then only close the capsule once the flow rate has been reached? By nature of that logic you'll have overlapping or non-continuous capsules. The issue with the formula you wrote is that $flow_integral_signal isn't going to reset at the start of each capsule, so floor won't actually find the point that it rolls past Rvol, but instead find when the yearly integral rolls past the a multiple of Rvol. We can take a slightly different approach and calculate what value the yearly integral needs to be before the capsule should end, find the timestamp of that event, and use that as the end key of your capsule: $condition_with_reset_quantity .move(0, 1wk) // grow the condition since a transform can only output capsules if they are within the originals .transform($c -> { // find the value of the yearly integral at the start of the capsule $start_value = $yearly_integral.valueAt($c.startKey()) // what value should we stop the capsule at? $end_value = $start_value + $c.property('Reset Quantity') // generate a condition internal to the transform that tells us when we reach this end value $cond_volume_reached = $yearly_integral == $end_value // turn this condition back into a capsule so we can grab its start key $cap_volume_reached = $cond_volume_reached.removeLongerThan(10d).toGroup($c).first() // create the output condition. Manually reassign the Reset Quantity property capsule($c.startKey(), $cap_volume_reached.startKey()) .setProperty('Reset Quantity', $c.property('Reset Quantity')) }) // just to prove our capsules generated correctly .setProperty('Delta', $yearly_integral, delta()) This results in a condition that looks like: Note how each blue capsule has the same start of a pink capsule, but only ends once the corresponding volume has been achieved.
  5. For that case, I'd suggest doing the running integral over each time period that has a different $reset_quantity. In your example, if you have a different reset quantity for each month, do the running integral over each month. This will keep the behavior across reset quantities consistent since the integral will reset: If you approach it this way, all you have to do is swap out the $reset_quantity for a signal: $months = months() // pull your reset quantity from a capsule property to a signal // in this case, I'm just creating a dummy signal $reset_quantity = $months.toSignal('Month') * 1000 $monthly_integral = $flow.integral($months) $fill_events = floor($monthly_integral/$reset_quantity).toCondition() $flow.integral($fill_events.removeLongerThan(10d))
  6. Modifying the formula of a calculated signal/condition in Data Lab is the same as going into workbench and editing the formula by hand. So just as in workbench, once the formula updates, anytime you query that item (whether that's from workbench or SPy), it will perform the calculations based on the active formula. Let me know if that clarifies things.
  7. You can use the spy.push function to push metadata changes to existing items (as well as create new ones). Included in this metadata is the item's formula. You can programmatically write a new formula in python that incorporates the new lookup table values, and then push this back into Seeq. Here's a basic example of this workflow: # search for item to update. The workbook ID is used to find an item scoped to a particular workbook item = spy.search({'Name': 'Formula to Update'}, workbook='0EED58CF-1C10-62C0-BD3D-50D15376DF5E', all_properties=True) # write the new formula programmatically (in your case a lookup table) new_formula = '50' # assign it back to the item that you pulled item['Formula'] = new_formula # push the change back into Seeq spy.push(metadata=item, worksheet=None)
  8. The URL Builder does not necessarily generate new workbooks. If you specify a workbook ID and worksheet ID that already exists that a user has access to (can be read-only), you can create a URL that will open a worksheet in read-only view with a specified display range. The two keys here are to specify viewMode=view and freshStart=false for it to work as expected: https://your-instance-url/workbook/builder?workbookName=0EED4BAB-44DF-EEC0-981A-AC7357B32994&worksheetName=0EED4BAB-48D9-FFD0-9DED-68487D309251&displayStartTime=*-14d&displayEndTime=*&viewMode=view&startFresh=false Since this is a fully documented and supported feature I'd suggest going this route. As for timezone, the URL Builder will take it from a user's profile (which is detected from the browser). Are you facing any particular issues with this option?
  9. To clarify, you're using the experimental_lookupTable function in formula, and want to update the values in the lookup table string programmatically with SPy?
  10. Since you're working locally, I'd make sure that both seeq and seeq-spy are loaded in the same virtual environment and with the same python version. Here's an example of generating a new virtual environment and installing the needed packages (for Seeq version 62.0): python -m venv . source bin/activate pip install -U seeq~=62.0 pip install -U seeq-spy
  11. For that I'd suggest using our Workbook URL Builder, which allows you to build new worksheets as well as modify existing worksheets using URLs with query parameters. I'd recommend reading through the linked KB article, but for example you could take an existing worksheet and load it with a new display range (last 2 weeks) using the following URL: https://your-instance-url/workbook/builder?workbookName=WORKBOOK_ID&worksheetName=WORKSHEET_ID&displayStartTime=*-14d&displayEndTime=*
  12. This video provides a brief overview on how to set up and manage user-configurable email Notifications in Workbench and Organizer using a simple point-and-click interface.
  13. To summarize the issue here for posterity: the Union/or operator does not preserve capsule properties, while combineWith() does. If you're just trying to combine multiple conditions together, most of the time it is best to use combineWith so you don't have to use something like mergeProperties to get them back.
  14. 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))
  15. 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
  16. This guide is a good starting point for understanding how to view and work with capsule properties in Seeq:
  17. As Chris replied in this other thread, this is a feature that has been added in an upcoming release:
  18. 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
  19. Depending on the file, Jupyter may have generated checkpoint files that can be recovered. What type of file got deleted in your project?
  20. 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
  21. 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:
  22. By default, functions like periods and shifts will use UTC time. There is an optional timeZone argument to these functions that will make them timezone/DST aware. An example from the documentation: periods(4year, 4year, '2021-01-20T12:00:00', 'America/New_York')
  23. 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.
  24. Since you already have identified: When the equipment is running (Purple Condition) Time periods that you want to aggregate within (Yellow Condition) We should be able to accomplish this with a single Signal from Condition, using the 'Equipment Running' condition as the selected condition, total duration as the statistic, and the 'Time Between Replacement' condition as your bounding condition. The setup should look something like this: If you prefer to do it in formula, the equivalent would be: $equipmentRunning.setMaximumDuration(500day).aggregate(totalDuration("h"), $timeBetweenReplacement.setMaximumDuration(500day), durationKey()) If you still get an error from that let me know what it is.
  25. Hey Matt, I believe users have had success with ipyleaflet in the past. Running the basic example from the documentation:
  • Create New...