Jump to content

Kristopher Wiggins

Seeq Team
  • Posts

    52
  • Joined

  • Last visited

  • Days Won

    18

Posts posted by Kristopher Wiggins

  1. For others running into this, the export was being performed in Capsule Time view. As of R62, Capsule Time only display capsules that are fully enclosed in the display, and as a result the subsequent export also only showed these capsules. To see these capsules, the display range had to be increased. In the case of the current capsule, we had to expand the display range to be slightly in the future.

  2. Hey Michael,

    This may be easier to communicate over a meeting so feel free to sign up for an Office Hours session if you need additional help in implementing this. As for the steps to do this

    Step 0: Data Cleansing

    Your sensor does not look to be noisy here but often when you need to calculate rates of changes, sensors can be noisy causing your rate of change to also be noisy. We recommend using the Signal Smoothing tool to smooth your sensor to reduce those oscillations

    Step 1: Derivative

    This can be done in Formula using a syntax similar to $signal.derivative() where $signal is the tag you have displayed

    Step 2: Capture periods where weight is being added

    This can be done with the Value Search tool to capture periods where your derivative is increasing. You may not be able to use 0 exactly but a small number close to 0 should be fine

    Step 3: Capture times between your loading

    This can be done in Formula. There are multiple ways of doing this depending upon the result you'd want. I've added some examples below where $increases is the result of Step 2. You may want to use different parts depending on what you're looking for

    $increases.growEnd(1wk) - $increases // Extends increase capsule to next increase only if it is within 1 week of each other. For the most recent increase, it gets extended into the future until a week from when it started
    //$increases.inverse() and past() // Captures time between increases, only considers capsules during the past

     

  3. Feel free to use the ipywidgets code instead. The spy.widgets are built on top of ipywidgets to make it easier for people to use but if you're already using ipywidgets, go ahead. If you're looking for a new challenge, you can try using the ipyvuetify library, which has more modern widgets but can often be more difficult to learn and use than ipywidgets.

  4. Hi Nitish,

    I was able to run your code successfully after tchanging the property in your spy.search to be Name since your if statement only works if the property searched for include Name. Even when having the Data ID as the property, running the code didn't return an error, just no search results. Can you send your full code and what version of SPy you're using by running spy.__version__

  5. Hi David,

    In my testing on a subset of your tree, the insertion worked when removing the forward slashes from the roll_up_parameters. If this doesn't work for you it may be easier to work through it together over an Office Hours session.

    DCS_Cont_Tree.insert(name = 'Unit Total',
                     roll_up_statistic = 'Counts',
                     roll_up_parameters = '.*x.* >> Nonconforming',
                     parent='Unit_??')
  6. Hi tai,

    As noted in the error, the issue is due to you not having data during these error windows. By default, the .toSignal() function places samples every 1 day and since your error condition tends to last less than a minute, the .toSignal() may not have samples during this window. To fix this, you can supply a time parameter into .toSignal() to have a more frequent sampling. For example, 1.toSignal(10s) to have it samples every 10 seconds.

    Some other things you may want to consider are

    • For your error condition, rather than just making a value search on when the value is 0, also restricting it to past data. replaceNotValid will also place results in the future when there isn't data but appending .within(past()) to your "replace error power totalizer" should only show these 0 values for past data
    • validValues() is another useful function when you run into data gaps. It "connects the dots" by ignoring the gaps and connecting samples if they're within the maximum interpolation of the signal. You can change this interpolation using .setMaxInterpolation(time_input)
  7. Hi Taylor, below are some steps to achieve this. Feel free to sign up for Office Hours if you'd like help implementing this. 

    1. Use Value Search to determine when your Rate < Trigger
    2. Use Signal from Condition to totalize your Target during the output of Step 1. Repeat for Actual. Note your tags have unrecognized units of Metric Tons/hour. If you'd like to make these into units Seeq recognizes, you can include a Step 1b where you use Formula and $tag.setUnits('t/hr') to apply these units. The outputs of Step 2 would then have units of metric tons.
    3. Subtract outputs of Step 2 to find your loss and divide by the target to get weighted duration
  8. Hello,

    Can you confirm that you're on a version of Seeq with built-in notifications (i.e. R60+)? Also, are you clicking your profile name and not the three bar icon? Your user profile will be where you'll see Notifications Management. 

    image.png

  9. Hi Manoel,

    In the case of multiple images you'll need to get more complex with how you capture sections of the HTML. Below are two routes I thought of but I'm sure there are others that may be better suited based on your particular scenario. Feel free to file a ticket at support.seeq.com for help. The options mentioned are based on a scenario where we're looking to update 2 images.

    Option 1: Continue logic from previous comment and manually specify portions of HTML to capture before and after each image (good for a few number of replacements with standardized names)

    # Similar code to previous comment
    pulled_workbooks = spy.workbooks.pull(spy.workbooks.search({'Name':'Organizer Topic Name'}))
    org_topic = pulled_workbooks[0] # Note you may need to confirm that the first item in pulled_workbooks is the topic of interest
    ws_to_update = org_topic.worksheets[0] # Choose the index based on the worksheet intended to be updated
    replace_html_1 = ws_to_update.report.add_image(filename = "ImageName1.png")
    replace_html_2 = ws_to_update.report.add_image(filename = "ImageName2.png")
    
    import re
    image_name_1 = "ImageName1"
    image_name_2 = "ImageName2"
    before_html = re.findall(r"(.*)<img.*?src=.*{image_name_1}_v\d*.png.*?>".format(image_name_1 = image_name_1), ws_to_update.html)[0] # Capture everything before the 1st image
    middle_html = re.findall(r"<img.*?src=.*{image_name_1}_v\d*.png.*?>(.*)<img.*?src=.*?{image_name_2}_v\d*.png.*?>".format(image_name_1 = image_name_1, image_name_2 = image_name_2), ws_to_update.html)[0] # Captures between the 1st and 2nd image
    after_html = re.findall(r".*<img.*?src=.*?{image_name_2}_v\d*.png.*?>(.*)".format(image_name_2 = image_name_2), ws_to_update.html)[0] # Captures everything after the image
    full_html = before_html + replace_html_1 + middle_html + replace_html_2 + after_html # Combine the before and after with the html generated for the new picture
    
    ws_to_update.html = full_html # Reassign the html to the worksheet and push it back to Seeq
    spy.workbooks.push(pulled_workbooks)

    Option 2: Bulk find all images and capture the HTML for them. Insert new image HTML based on position/order (better for large number of replacements where image name can change)

    # Same code as previous comment
    pulled_workbooks = spy.workbooks.pull(spy.workbooks.search({'Name':'Organizer Topic Name'}))
    org_topic = pulled_workbooks[0] # Note you may need to confirm that the first item in pulled_workbooks is the topic of interest
    ws_to_update = org_topic.worksheets[0] # Choose the index based on the worksheet intended to be updated
    replace_html_1 = ws_to_update.report.add_image(filename = "ImageName1.png")
    replace_html_2 = ws_to_update.report.add_image(filename = "ImageName2.png")
      
    import re
    image_name_1 = "ImageName1"
    image_name_2 = "ImageName2"
    before_html_new = re.findall(r"(.*?)<img.*?src=.*?_v\d*.png.*?>", ws_to_update.html) # Capture everything before and in between each image
    after_html_new = re.findall(r".*<img.*?src=.*?_v\d*.png.*?>(.*)", ws_to_update.html)[0] # Captures everything after the image
    replace_html_list = [replace_html_1, replace_html_2]
    full_html_list = []
    for num, entry in enumerate(before_html_new): # Iterates through the non-image HTML and new image HTML, combining them based on their order in the list
        full_html_list.append(before_html_new[num])
        full_html_list.append(replace_html_list[num])
        if num == len(before_html_new)-1:
            full_html_list.append(after_html_new)
    full_html = "".join(full_html_list)
    ws_to_update.html = full_html # Reassign the html to the worksheet and push it back to Seeq
    spy.workbooks.push(pulled_workbooks)

     

  10. With spy.assets.Tree() there isn't a direct route to reference attributes belonging to child attributes in calculations. Instead you can create a rollup that will create an attribute based on the child attributes, and then use that roll-up attribute as an input into your calculations. For example in the code below, we need to create roll-up attributes that reference the child attributes. In this case I'm looking at a particular Area's but you can refine the roll_up_parameters to find the item you're looking for. Once the roll-up attribute is created you can then include it in other calculations at that level of the tree, as is done in the last tree insertion step.

    test.insert(name='Cooling Tower Temp',
                roll_up_statistic='Average',
                roll_up_parameters='Area B >> Temperature',
                parent='Cooling Tower 1')
    test.insert(name='Cooling Tower Temp',
                roll_up_statistic='Average',
                roll_up_parameters='Area D >> Temperature',
                parent='Cooling Tower 2')
    test.insert(name='Doing Something to Temperature',
                formula='$temp*100',
                formula_parameters={'$temp':'Cooling Tower Temp'},
                parent='Cooling Tower ?')

     

  11. Correct the statistic will mirror what's seen in the GUI. To find out which ones are available, I ran the code mentioned above with an incorrect statistic and SPy returned an error with the possible options. Note that just like in the GUI only certain stats can be used for signals and condition Measured Items. 

    image.png

  12. When generating a prediction in Seeq, some users often come across the error The number of target samples is not sufficient to conduct a regression. As the error suggests, there isn't enough data to perform the prediction but it can be difficult to determine the particular cause, especially when there are numerous inputs into the prediction. The steps below can be used to troubleshoot why a prediction is receiving this error and help suggest steps to resolve it.

    Step 1: Confirm data in your signals
    Seeq's prediction tool requires there to be interpolatable data at the timestamps of the target signal during the training window at least N+1 times, where N is the number of inputs into the model. These inputs aren't necessarily the number of signals, but the number of terms in the model (so for a polynomial scale, N = 2 * number of signals). In the case of a linear model that has 3 inputs, there needs to be at least 4 times when all of the inputs have data at the target's timestamps. If one of the signals never has data then the above requirement is not possible and that signal should be removed.

    If there is data in every signal, the next thing to look at if there is data within the training window. Seeq's Prediction tool by default accepts a time range as the training window but this can be further refined by limiting your training window to a condition. To check if the requirement is still met, the formula below can be used, where each $signal_i is a signal used in the model, $conditon is the training window condition and $target is the target of the model.

    ($signal_1 * $signal_2 ... * $signal_N).within($condition).resample($target).validValues()

    You can then match your display range with the time range chosen in the prediction and check the count of the resulting formula. This count either be determined using a Simple Scorecard Metric or in the Details Pane to check if it meets the requirement

    Step 2: Evaluate the Prediction

    If Step 1 suggests that all of your signals has data and should meet the necessary number of samples, then there could be an issue with the Prediction that prevents the requirement from being met. Some examples of this are

    1. Logarithmic Scale and Inputs with Zero or Negative Data: Its mathematically impossible to take a logarithm of zero or negative number so signals with data like this can't have these samples considered. To eliminate data like this from the inputs, formulas like $signal.within($signal > 0) can be used in conjunction with the previously mentioned formula to get an accurate count.
    2. Divide By Zero: There are cases when the prediction model can evaluate and fail to include samples due to divide by zero errors. To check if this is the problem, try using Principle Component Regression (PCR) instead of the standard Ordinary Least Squares (OLS) that's used by default in the Prediction tool.
  13. The Users.AuthenticationFailures.Meter only provides as system wide view of how many users login attempts fail. To determine causes of these failures, you'll need to go into the logs as you mentioned. The particular log to look at varies based on your authentication mechanism though. Feel free to send an email to support@seeq.com and we can dive into the details of how your Seeq server is configured to determine which log file you'll need to look at.

  14. Users of OSIsoft Asset Framework often want to filter elements and attributes based on the AF Templates they were built on. At this time though, the spy.search command in Seeq Data Lab only filters based on the properties Type, Name, Description, Path, Asset, Datasource Class, Datasource ID, Datasource Name, Data ID, Cache Enabled, and Scoped To. This post discusses a way in which we can still filter elements and/or attributes based on AF Template.

    Step 1: Retrieve all elements in the AF Database

    The code below will return all assets in an AF Database that are based on a AF Template whose name contains Location.  

    asset_search = spy.search({"Path":"Example-AF", "Type":"Asset"}, all_properties=True) #Make sure to include all properties since this will also return the AF Template
    asset_search.dropna(subset=['Template'], inplace=True) # Remove assets not based on a template since we can't filter with NaN values
    asset_search_location = asset_search[asset_search['Template'].str.contains('Location')] # Apply filter to only consider Location AF Template assets

    Step 2: Find all relevant attributes

    This code will retrieve the desired attributes. Note wildcards and regular expression can be used to find multiple attributes.

    signal_search = spy.search({"Path":"Example-AF", "Type":"Signal", "Name":"Compressor Power"}) #Find desired attributes

    Step 3: Filter attributes based on if they come from an element from the desired AF template

    Last step cross references the signals returned with the desired elements. This is done by looking at their paths.

    # Define a function to recreate paths, items directly beneath the database asset don't have a Path
    def path_merger(row):
        row = row.dropna()
        return ' >> '.join(row)
    
    asset_search_location['Full Path'] = asset_search_location[['Path', 'Asset', 'Name']].apply(lambda row: path_merger(row),axis=1) # Create path for the asset that includes its name
    signal_search['Parent Path'] = signal_search[['Path', 'Asset']].apply(lambda row: path_merger(row),axis=1) # Create path for the parents of the signals
    signal_search_location = signal_search[signal_search['Parent Path'].isin((asset_search_location['Full Path']))] # Cross reference parent path in signals with full paths in assets to see if these signals are children of the desired elements

     

    • Like 3
  15. Hi Stephen,

    At this time it is not possible in a supported way via SPy to manipulate these aspects of your display. The worksheet properties that can be modified are display_items, display_range, scatter_plot_series (items displayed in scatter plot), scorecard_date_display, scorecard_date_format (for pre-51 Seeq versions), table_date_display, table_date_format, table_mode (R52+ Seeq), time_zone, and the view (Table, Trend, Treemap, etc.). There are ways to manipulate other worksheet properties by editing the workstep data but those methods may no longer work in future Seeq versions if we change things on the backend as we introduce new features. If you would like assistance in seeing how these particular properties you mentioned can be changed, please send a request to support@seeq.com.

  16. I tried this on R54.1.4 and came across a similar error but fixed it by appending .toString() to $seq. Below is the updated formula code.

    //creates a condition for 1 minute of time encompassing 30 seconds on either side of a transition
    $Transition = $CompressorStage.toCondition().beforeStart(0.5min).afterStart(1min)
    
    //Assigns the mode on both sides of the step change to a concatenated string that is a property of the capsule. 
    $Transition  
      .transform( $cap -> $cap.setProperty('StartModeEndMode',
        $CompressorStage.toCondition()
             .toGroup($cap, CAPSULEBOUNDARY.INTERSECT)
             .reduce("", ($seq, $stepCap) -> $seq.toString() +
                   $stepCap.getProperty('Value')
                           //Changes the format of the stage names for more clear de-lineation as a property in the capsules pane. 
                           .replace('STAGE 1','-STAGE1-').replace('STAGE 2','-STAGE2-').replace('TRANSITION','-TRANSITION-').replace('OFF','-OFF-')
                           )))

     

    • Like 1
×
×
  • Create New...