{ "cells": [ { "cell_type": "markdown", "id": "c42d183b", "metadata": {}, "source": [ "# Perform a material compliance query" ] }, { "cell_type": "markdown", "id": "ca607a91", "metadata": {}, "source": [ "A material compliance query determines whether one or more materials are compliant with the specified indicators. This\n", "is done by first determining compliance for the substances associated with the material and then rolling up the\n", "results to the material." ] }, { "cell_type": "markdown", "id": "7e827de2", "metadata": {}, "source": [ "## Connect to Granta MI" ] }, { "cell_type": "markdown", "id": "81102c1b", "metadata": {}, "source": [ "Import the ``Connection`` class and create the connection. For more information, see the\n", "[Getting Started](../0_Getting_started.ipynb) example." ] }, { "cell_type": "code", "execution_count": null, "id": "dd8e3501", "metadata": { "tags": [] }, "outputs": [], "source": [ "from ansys.grantami.bomanalytics import Connection\n", "\n", "server_url = \"http://my_grantami_server/mi_servicelayer\"\n", "cxn = Connection(server_url).with_credentials(\"user_name\", \"password\").connect()" ] }, { "cell_type": "markdown", "id": "234e8945", "metadata": {}, "source": [ "## Define an indicator" ] }, { "cell_type": "markdown", "id": "77a631b3", "metadata": {}, "source": [ "A Compliance query determines compliance against indicators, as opposed to an Impacted Substances query which\n", "determines compliance directly against legislations.\n", "\n", "There are two types of indicator objects (``WatchListIndicator`` and ``RohsIndicator``), and the following syntax\n", "applies to both object types. The differences in the internal implementation of the two objects are described\n", "in the API documentation.\n", "\n", "Generally speaking, if a substance is impacted by a legislation associated with an indicator and in a quantity\n", "above a specified threshold, the substance is non-compliant with that indicator. This non-compliance applies to\n", "any other items in the BoM hierarchy that directly or indirectly include that substance." ] }, { "cell_type": "markdown", "id": "52e715ce", "metadata": {}, "source": [ "First, create two ``WatchListIndicator`` objects." ] }, { "cell_type": "code", "execution_count": null, "id": "59d1599a", "metadata": { "tags": [] }, "outputs": [], "source": [ "from ansys.grantami.bomanalytics import indicators\n", "\n", "svhc = indicators.WatchListIndicator(\n", " name=\"SVHC\",\n", " legislation_ids=[\"Candidate_AnnexXV\"],\n", " default_threshold_percentage=0.1,\n", ")\n", "sin = indicators.WatchListIndicator(\n", " name=\"SIN\",\n", " legislation_ids=[\"SINList\"]\n", ")" ] }, { "cell_type": "markdown", "id": "24fcb27c", "metadata": { "tags": [] }, "source": [ "## Build and run the query" ] }, { "cell_type": "markdown", "id": "a102e3e5", "metadata": {}, "source": [ "Next define the query itself. Materials can be referenced by Granta MI record reference or Material ID.\n", "The table containing the Material records is not required because this is enforced by the Restricted Substances\n", "database schema." ] }, { "cell_type": "code", "execution_count": null, "id": "43180a64", "metadata": { "tags": [] }, "outputs": [], "source": [ "from ansys.grantami.bomanalytics import queries\n", "\n", "mat_query = queries.MaterialComplianceQuery().with_indicators([svhc, sin])\n", "mat_query = mat_query.with_material_ids([\"plastic-pa66-60glassfiber\",\n", " \"zinc-pb-cdlow-alloy-z21220-rolled\",\n", " \"stainless-316h\"])" ] }, { "cell_type": "markdown", "id": "7cdfb429", "metadata": {}, "source": [ "Finally, run the query. Passing a ``MaterialComplianceQuery`` object to the ``Connection.run()`` method returns a\n", "``MaterialComplianceQueryResult`` object." ] }, { "cell_type": "code", "execution_count": null, "id": "02611209", "metadata": { "tags": [] }, "outputs": [], "source": [ "mat_result = cxn.run(mat_query)\n", "mat_result" ] }, { "cell_type": "markdown", "id": "0d464428", "metadata": { "tags": [] }, "source": [ "The result object contains two properties: ``compliance_by_material_and_indicator`` and ``compliance_by_indicator``." ] }, { "cell_type": "markdown", "id": "64ca12b7", "metadata": {}, "source": [ "## Group results by material" ] }, { "cell_type": "markdown", "id": "84e68d14", "metadata": { "tags": [] }, "source": [ "The ``compliance_by_material_and_indicator`` property contains a list of ``MaterialWithComplianceResult`` objects with\n", "the reference to the material record and the compliance status for each indicator. The\n", "``SubstanceWithComplianceResult`` objects are also included because compliance was determined based on the substances\n", "associated with the material object. These are also accompanied by their compliance status for each indicator." ] }, { "cell_type": "markdown", "id": "ccb128b9", "metadata": {}, "source": [ "Initially, you can print only the results for the reinforced PA66 record." ] }, { "cell_type": "code", "execution_count": null, "id": "ba6988cd", "metadata": { "lines_to_end_of_cell_marker": 0, "lines_to_next_cell": 1, "tags": [] }, "outputs": [], "source": [ "pa_66 = mat_result.compliance_by_material_and_indicator[0]\n", "print(f\"PA66 (60% glass fiber): {pa_66.indicators['SVHC'].flag.name}\")" ] }, { "cell_type": "markdown", "id": "ed73986b", "metadata": {}, "source": [ "The reinforced PA66 record has a status of ``WatchListHasSubstanceAboveThreshold``, which tells\n", "you that the material is not compliant with the indicator and therefore contains SVHCs above the\n", "0.1% threshold." ] }, { "cell_type": "markdown", "id": "b25a2874", "metadata": {}, "source": [ "To understand which substances have caused this status, you can print the substances that are not compliant with the\n", "legislation and the percentage amount of that substance in the parent material. The possible states of the indicator\n", "are available on the ``Indicator.available_flags`` attribute and can be compared using standard Python operators.\n", "\n", "For substances, the critical threshold is the ``WatchListAboveThreshold`` state." ] }, { "cell_type": "code", "execution_count": null, "id": "a7b6afa6", "metadata": { "tags": [] }, "outputs": [], "source": [ "def convert_substance_to_string(substance):\n", " result = f\"Substance record history identity: {substance.record_history_identity}\"\n", " if substance.percentage_amount is None:\n", " return f\"{result}, Unknown concentration\"\n", " else:\n", " return f\"{result}, {substance.percentage_amount}%\"\n", "\n", "\n", "above_threshold_flag = svhc.available_flags.WatchListAboveThreshold\n", "pa_66_svhcs = [sub for sub in pa_66.substances\n", " if sub.indicators[\"SVHC\"] >= above_threshold_flag\n", " ]\n", "print(f\"{len(pa_66_svhcs)} SVHCs\")\n", "for substance in pa_66_svhcs:\n", " print(convert_substance_to_string(substance))" ] }, { "cell_type": "markdown", "id": "e20be5da", "metadata": {}, "source": [ "Substances with an unknown amount are always treated as if they have a 100% concentration. Note that children of items\n", "passed into the compliance query are returned with record references based on record history identities only. The\n", "Granta MI Scripting Toolkit for Python can be used to translate record history identities into CAS numbers if\n", "required." ] }, { "cell_type": "markdown", "id": "cffd3c30", "metadata": {}, "source": [ "Next, look at the state of the zinc alloy record." ] }, { "cell_type": "code", "execution_count": null, "id": "3a803d17", "metadata": { "tags": [] }, "outputs": [], "source": [ "zn_pb_cd = mat_result.compliance_by_material_and_indicator[1]\n", "print(f\"Zn-Pb-Cd low alloy: {zn_pb_cd.indicators['SVHC'].flag.name}\")" ] }, { "cell_type": "markdown", "id": "b33fd6eb", "metadata": {}, "source": [ "The zinc alloy record has the status ``WatchListAllSubstancesBelowThreshold``, which means that there are\n", "substances present that are impacted by the legislation but are below the 0.1% threshold." ] }, { "cell_type": "markdown", "id": "6c6d5975", "metadata": {}, "source": [ "You can print these substances using the ``WatchListBelowThreshold`` flag as the threshold." ] }, { "cell_type": "code", "execution_count": null, "id": "1305eeef", "metadata": { "tags": [] }, "outputs": [], "source": [ "below_threshold_flag = svhc.available_flags.WatchListBelowThreshold\n", "zn_svhcs_below_threshold = [sub for sub in zn_pb_cd.substances\n", " if sub.indicators[\"SVHC\"].flag == below_threshold_flag]\n", "print(f\"{len(zn_svhcs_below_threshold)} SVHCs below threshold\")\n", "for substance in zn_svhcs_below_threshold:\n", " print(convert_substance_to_string(substance))" ] }, { "cell_type": "markdown", "id": "32a9dd64", "metadata": {}, "source": [ "Finally, look at the stainless steel record." ] }, { "cell_type": "code", "execution_count": null, "id": "d8884211", "metadata": { "tags": [] }, "outputs": [], "source": [ "ss_316h = mat_result.compliance_by_material_and_indicator[2]\n", "print(f\"316H stainless steel: {ss_316h.indicators['SVHC'].flag.name}\")" ] }, { "cell_type": "markdown", "id": "59adbf22", "metadata": {}, "source": [ "The stainless steel record has the status ``WatchListCompliant``, which means there are no impacted substances in the\n", "material." ] }, { "cell_type": "markdown", "id": "ee336616", "metadata": {}, "source": [ "You can print these substances using the ``WatchListNotImpacted`` flag as the threshold." ] }, { "cell_type": "code", "execution_count": null, "id": "22918c6b", "metadata": { "tags": [] }, "outputs": [], "source": [ "not_impacted_flag = svhc.available_flags.WatchListNotImpacted\n", "ss_not_impacted = [\n", " sub\n", " for sub in ss_316h.substances\n", " if sub.indicators[\"SVHC\"].flag == not_impacted_flag\n", "]\n", "print(f\"{len(ss_not_impacted)} non-SVHC substances\")\n", "for substance in ss_not_impacted:\n", " print(convert_substance_to_string(substance))" ] }, { "cell_type": "markdown", "id": "1bcc346d", "metadata": {}, "source": [ "## Group results by indicator" ] }, { "cell_type": "markdown", "id": "9e879f1f", "metadata": {}, "source": [ "Alternatively, using the ``compliance_by_indicator`` property provides a single indicator result that summarizes the\n", "results across all materials in the query. This would be useful in a situation where you have a *concept* assembly\n", "stored outside of Granta MI and want to determine its compliance. You know it contains the materials specified in\n", "the preceding query, and so using the ``compliance_by_indicator`` property tells you if that concept assembly is\n", "compliant based on the worst result from individual materials." ] }, { "cell_type": "code", "execution_count": null, "id": "8fb22ff6", "metadata": { "tags": [] }, "outputs": [], "source": [ "if mat_result.compliance_by_indicator[\"SVHC\"] >= above_threshold_flag:\n", " print(\"One or more materials contains an SVHC with a quantity greater than 0.1%.\")\n", "else:\n", " print(\"No SVHCs are present, or no SVHCs have a quantity less than 0.1%.\")" ] }, { "cell_type": "markdown", "id": "17cfbdc8", "metadata": {}, "source": [ "Note that this cannot tell you which material is responsible for the non-compliance. This would require either\n", "performing a more granular analysis as shown earlier or importing the assembly into Granta MI and running the\n", "compliance on that part record." ] } ], "metadata": { "jupytext": { "formats": "ipynb,py:light" }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }