Jump to content

John Brezovec

Seeq Team
  • Posts

    70
  • Joined

  • Last visited

  • Days Won

    11

John Brezovec last won the day on December 4 2023

John Brezovec had the most liked content!

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

John Brezovec's Achievements

  1. 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/
  2. The max duration always has to be specified as a scalar (fixed number) -- you should try to pick the shortest max duration you're comfortable with for performance reasons, but if your use case requires a super long max duration then that's what your use case requires!
  3. 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. 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 ?)_*}' )
  5. 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.
  6. 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.
  7. 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?
  8. 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.
  9. 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))
  10. 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.
  11. 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)
  12. 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?
  13. 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?
  14. 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
  15. 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=*
×
×
  • Create New...