<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: InterSystems</title>
    <description>The latest articles on DEV Community by InterSystems (intersystems).</description>
    <link>https://dev.to/intersystems</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2450%2F5c611adb-602d-4948-b84b-5fe47046fd5c.png</url>
      <title>DEV Community: InterSystems</title>
      <link>https://dev.to/intersystems</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/intersystems"/>
    <language>en</language>
    <item>
      <title>Working with Dynamic Objects in IRIS: doubts that only come up when you need to deploy by tomorrow morning and it's 11 P.M.</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Sun, 30 Aug 2026 07:37:14 +0000</pubDate>
      <link>https://dev.to/intersystems/working-with-dynamic-objects-in-iris-doubts-that-only-come-up-when-you-need-to-deploy-by-tomorrow-4an0</link>
      <guid>https://dev.to/intersystems/working-with-dynamic-objects-in-iris-doubts-that-only-come-up-when-you-need-to-deploy-by-tomorrow-4an0</guid>
      <description>&lt;p&gt;If you have spent any time on the Developer Community, you have seen the many questions return in different costumes: &lt;em&gt;How do I turn a persistent object into JSON? How do I loop over a JSON payload whose shape I don't know? Why does &lt;/em&gt;&lt;code&gt;&lt;em&gt;Required&lt;/em&gt;&lt;/code&gt;&lt;em&gt; do nothing on my &lt;/em&gt;&lt;code&gt;&lt;em&gt;%DynamicArray&lt;/em&gt;&lt;/code&gt;&lt;em&gt; property? Why is my date coming back as &lt;/em&gt;&lt;code&gt;&lt;em&gt;31390&lt;/em&gt;&lt;/code&gt;&lt;em&gt;?&lt;/em&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Noticing that, we decided to write an article summarizing all the questions that come with something that the documentation alone couldn't provide: practice.&lt;/p&gt;
&lt;h2&gt;Let's start with the foundation&lt;/h2&gt;
&lt;p&gt;InterSystems IRIS gives you two classes for schema-less data: &lt;code&gt;%DynamicObject&lt;/code&gt; and &lt;code&gt;%DynamicArray&lt;/code&gt;. Both inherit from &lt;code&gt;%DynamicAbstractObject&lt;/code&gt;, and instances of either are called &lt;em&gt;dynamic entities&lt;/em&gt;. They map cleanly onto JSON: an object is a set of key/value pairs, an array is an ordered list. Unlike a persistent or registered class, a dynamic object keeps no predefined list of valid property names — every string key is legal, and you add or remove members at runtime.&lt;/p&gt;
&lt;p&gt;The most pleasant part is the literal syntax, which will look familiar to anyone coming from JavaScript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set person = {"name":"Ada","active":true,"roles":["admin","dev"]}
Set scores = [90, 85, 77]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also build them field by field. &lt;code&gt;%Set()&lt;/code&gt; returns the entity it modified, so calls chain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set obj = {}.%Set("a",1).%Set("b",2) // now obj is {"a":1,"b":2}

Do scores.%Push(100)                 // now scores is [90, 85, 77, 100]

Set last = scores.%Pop()             // now scores is [90, 85, 77] and last is 100&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;%Push()&lt;/code&gt; and &lt;code&gt;%Pop()&lt;/code&gt; exist only on arrays, but everything else works on both. To turn text into an entity, use &lt;code&gt;%FromJSON()&lt;/code&gt; and to serialize back, use &lt;code&gt;%ToJSON()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set jsonString = "{""field"": ""value""}"
Set obj = {}.%FromJSON(jsonString) // now obj is {"field": "value"}

Write obj.%ToJSON()                // this outputs "{""field"": ""value""}"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;%FromJSON()&lt;/code&gt; also accepts a stream, and &lt;code&gt;%FromJSONFile()&lt;/code&gt; reads straight from a filename (note: a filename string, rather than a &lt;code&gt;%File&lt;/code&gt; object — a common trip-up).&lt;/p&gt;
&lt;h2&gt;Iterating over structure you don't control&lt;/h2&gt;
&lt;p&gt;When a payload arrives from another system, you often don't know how many elements it has or what they're called. Don't reach for a &lt;code&gt;for&lt;/code&gt; loop by index. Dynamic arrays can be sparse, which means that an element can exist positionally without ever having been assigned, and a &lt;code&gt;for&lt;/code&gt; loop will happily hand you those empty slots. The correct tool is &lt;code&gt;%GetIterator()&lt;/code&gt;, which returns a &lt;code&gt;%Iterator.Object&lt;/code&gt; or &lt;code&gt;%Iterator.Array&lt;/code&gt;, driven by &lt;code&gt;%GetNext()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set iter = obj.%GetIterator()

While iter.%GetNext(.key, .value, .type) 
{
    Write !, key, " = ", value, " (", type, ")"
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;%GetNext()&lt;/code&gt; skips unassigned elements automatically, which is exactly why it is preferred. For an object, &lt;code&gt;key&lt;/code&gt; is the property name. For an array, &lt;code&gt;key&lt;/code&gt; is the index. To walk a nested structure, recurse whenever &lt;code&gt;$IsObject(value)&lt;/code&gt; is true, as that will return true for both sub-objects and sub-arrays.&lt;/p&gt;
&lt;p&gt;That third argument, &lt;code&gt;.type&lt;/code&gt;, is easy to ignore, but worth understanding. When present it does two useful things. First, it returns the element's original JSON datatype as a string. Second — and this is the practical part — it changes the conversion rules so you avoid &lt;code&gt;&amp;lt;MAXSTRING&amp;gt;&lt;/code&gt; errors: a very long JSON string is handed back as a read-only stream object instead of being force-fit into an ObjectScript string, and JSON numbers are returned in their original textual form rather than being coerced. If you're processing arbitrary external JSON, passing &lt;code&gt;.type&lt;/code&gt; is cheap insurance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiyzrzoat3xl0ld0dcdfn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiyzrzoat3xl0ld0dcdfn.png" alt=" " width="799" height="135"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;Type discovery inside a dynamic entity&lt;/h2&gt;
&lt;p&gt;Because dynamic entities are untyped containers, IRIS gives you tools to interrogate them. &lt;code&gt;%GetTypeOf(key)&lt;/code&gt; reports what a value actually is: &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;boolean&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;, &lt;code&gt;array&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;oref&lt;/code&gt;, or &lt;code&gt;unassigned&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set a = [1, "test", true, {"v":1}, [1,2,3]]&lt;br&gt;
// the indexes are:&lt;br&gt;
//       0,      1,    2,       3,       4

&lt;/code&gt;&lt;p&gt;&lt;code&gt;Write a.%GetTypeOf(2)   // true: boolean&lt;br&gt;&lt;br&gt;
Write a.%GetTypeOf(3)   // {"v":1}: object&lt;br&gt;&lt;br&gt;
Write a.%GetTypeOf(9)   // unassigned - it finishes at index 4&lt;br&gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;This matters because ObjectScript flattens JSON's richer type system on the way in. JSON &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt;, and &lt;code&gt;null&lt;/code&gt; all become ObjectScript-friendly values &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, and &lt;code&gt;""&lt;/code&gt; when you read them with dot syntax or &lt;code&gt;%Get()&lt;/code&gt;. If you need to tell a genuine &lt;code&gt;null&lt;/code&gt; apart from an empty string apart from a key that was never set, &lt;code&gt;%GetTypeOf()&lt;/code&gt; is the reliable discriminator. There is also &lt;code&gt;%IsDefined()&lt;/code&gt;, but it returns false for unassigned members and true for both &lt;code&gt;""&lt;/code&gt; and &amp;nbsp;&lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9nxsmdy6ndpph29triz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft9nxsmdy6ndpph29triz.png" alt=" " width="463" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4jhf0ls9hk7gn9uwchx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp4jhf0ls9hk7gn9uwchx.png" alt=" " width="387" height="167"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvqmadnf8atqn423j41k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyvqmadnf8atqn423j41k.png" alt=" " width="500" height="377"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;h3&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3&gt;The date gotcha&lt;/h3&gt;
&lt;p&gt;Type flattening is behind one of the community's recurring puzzles. Export a persistent object whose &lt;code&gt;DOB&lt;/code&gt; is &lt;code&gt;1926-12-11&lt;/code&gt; and you may see &lt;code&gt;"DOB":31390&lt;/code&gt; in the result — that &lt;code&gt;31390&lt;/code&gt; is the internal &lt;code&gt;$HOROLOG&lt;/code&gt; day count, not a corrupted value. The same logic bites the other direction in dynamic SQL: if you pass a query a literal like &lt;code&gt;'1926-12-11'&lt;/code&gt; and get zero rows, it's usually because the column expects &lt;code&gt;$HOROLOG&lt;/code&gt; internal format. The fix is to convert on the way in with &lt;code&gt;$ZDATEH("1926-12-11", 3)&lt;/code&gt;. Whenever a date crosses the boundary between JSON, SQL, and stored objects, ask which representation each side expects.&lt;/p&gt;
&lt;h2&gt;Two things both called "array"&lt;/h2&gt;
&lt;p&gt;Here is a distinction that quietly causes bugs. &lt;code&gt;%DynamicArray&lt;/code&gt; is a &lt;em&gt;positional&lt;/em&gt; list, indexed from 0. But ObjectScript also has typed &lt;strong&gt;collection&lt;/strong&gt; properties, and the &lt;code&gt;array of&lt;/code&gt; collection is not a positional array at all — it's a &lt;strong&gt;dictionary&lt;/strong&gt; (a keyed map). Compare:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Property Tags As array of %String;   // a dictionary: key -&amp;gt; value&lt;br&gt;
Property Notes As list of %String;   // an ordered, positional list&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You access an &lt;code&gt;array of&lt;/code&gt; collection by key, not by position:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Do obj.Tags.SetAt("high", "priority")

&lt;p&gt;Write obj.Tags.GetAt("priority")     // high&lt;/p&gt;

&lt;/code&gt;&lt;p&gt;&lt;code&gt;Set key = ""&lt;br&gt;&lt;br&gt;
For &lt;br&gt;&lt;br&gt;
{ &lt;br&gt;&lt;br&gt;
    Set value = obj.Tags.GetNext(.key)  Quit:key=""&lt;br&gt;&lt;br&gt;
    Write !, key, ": ", value &lt;br&gt;&lt;br&gt;
}&lt;br&gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;When a class using &lt;code&gt;%JSON.Adapter&lt;/code&gt; serializes an &lt;code&gt;array of&lt;/code&gt; property, it comes out as a &lt;strong&gt;JSON object&lt;/strong&gt;&lt;code&gt;{"priority":"high"}&lt;/code&gt;, whereas a &lt;code&gt;list of&lt;/code&gt; comes out as a JSON array &lt;code&gt;["high"]&lt;/code&gt;. So "typed array" can mean two very different shapes on the wire depending on which collection you chose. If you want positional JSON, use &lt;code&gt;list of&lt;/code&gt; (or a &lt;code&gt;%DynamicArray&lt;/code&gt;); if you genuinely want a keyed lookup, &lt;code&gt;array of&lt;/code&gt; is your dictionary.&lt;/p&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;Bridging persistent objects and dynamic objects&lt;/h2&gt;
&lt;p&gt;A frequent need is converting a stored object into a free-form dynamic one — for instance, to trim fields before returning them from a REST method. If your class extends &lt;code&gt;%JSON.Adapter&lt;/code&gt;, the clean, non-deprecated path is a two-step that fits on one line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set sc = person.%JSONExportToString(.json)&lt;br&gt;&lt;br&gt;
Set dynObj = {}.%FromJSON(json)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(For large objects, swap in &lt;code&gt;%JSONExportToStream()&lt;/code&gt; so you never hit the string length limit.) Two alternatives are worth knowing. Embedded SQL's &lt;code&gt;JSON_OBJECT()&lt;/code&gt; lets you cherry-pick and rename columns when you only want a subset:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;amp;sql(SELECT JSON_OBJECT('name':Name,'dob':DOB) INTO :json WHERE ID = 1)&lt;br&gt;&lt;br&gt;
Set dynObj = {}.%FromJSON(json)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And going the other way, &lt;code&gt;%JSONImport()&lt;/code&gt; populates a persistent object &lt;em&gt;from&lt;/em&gt; a dynamic one.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;Where dynamic freedom ends: validation&lt;/h2&gt;
&lt;p&gt;Finally, the caveat that surprises people building JSON request validators. The &lt;code&gt;Required&lt;/code&gt; property keyword works for literals, collections, streams, and object-valued properties — but it is silently ignored for &lt;code&gt;%DynamicArray&lt;/code&gt; and &lt;code&gt;%DynamicObject&lt;/code&gt; properties. The reason is mechanical: the generated getter defaults these to &lt;code&gt;[]&lt;/code&gt; and &lt;code&gt;{}&lt;/code&gt;, so even assigning &lt;code&gt;""&lt;/code&gt; gets overwritten with a non-empty default, and &lt;code&gt;%ValidateObject()&lt;/code&gt; never sees a missing value. If you need to enforce presence or shape on dynamic properties, don't rely on &lt;code&gt;Required&lt;/code&gt; — implement a &lt;code&gt;%OnValidateObject()&lt;/code&gt; callback and check them yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Method %OnValidateObject() As %Status&lt;br&gt;&lt;br&gt;
{&lt;br&gt;&lt;br&gt;
    If ..fieldOptions.%Size() = 0 &lt;br&gt;&lt;br&gt;
    {&lt;br&gt;&lt;br&gt;
        Return $$ERROR($$GeneralError, "fieldOptions is required")&lt;br&gt;&lt;br&gt;
    }

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return $$OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/code&gt;&lt;p&gt;&lt;code&gt;}&lt;br&gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;h2&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2&gt;Choosing well&lt;/h2&gt;
&lt;p&gt;Dynamic entities are the right tool when structure is unknown, external, or genuinely fluid — parsing payloads, assembling responses, staging data. Typed persistent classes remain the right tool when you want the database, indexes, and validation to enforce a contract. Most real systems use both, meeting at the &lt;code&gt;%JSON.Adapter&lt;/code&gt; boundary. Keep three habits and you'll avoid the classic pitfalls: iterate with &lt;code&gt;%GetNext()&lt;/code&gt; rather than by index, reach for &lt;code&gt;%GetTypeOf()&lt;/code&gt; whenever a value's type actually matters, and remember that &lt;code&gt;array of&lt;/code&gt; is a dictionary, not a list.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;em&gt;Disclaimer: this article has been written by human hands, but reviewed with Claude AI for (1) enhancing my English (not my first language) and (2) reminding me of topics I hadn't covered in the initial versions. The search and studies for sources and practice were all made by a human (me :D), and the agent used to review is trained only on my own articles, so that it copies my tone without plagiarism, with respect to my fellow community members. The final revision was done completely without AI.&amp;nbsp;&lt;/em&gt;&lt;/span&gt;&lt;/p&gt;


</description>
      <category>sql</category>
      <category>tooling</category>
      <category>json</category>
      <category>javascript</category>
    </item>
    <item>
      <title>InterSystems for dummies – IRIS Vector Search (Part II)</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Sun, 30 Aug 2026 07:30:53 +0000</pubDate>
      <link>https://dev.to/intersystems/intersystems-for-dummies-iris-vector-search-part-ii-3l0o</link>
      <guid>https://dev.to/intersystems/intersystems-for-dummies-iris-vector-search-part-ii-3l0o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fimukpqyxi2f37vaw87wj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fimukpqyxi2f37vaw87wj.png" alt=" " width="500" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After an extensive math lesson, we are going to put our new knowledge into practice and create one of the best context-related databases.&lt;/p&gt;

&lt;p&gt;Get your magnifying glasses and hats ready, because Vector Holmes is back.&lt;/p&gt;

&lt;h1&gt;
  
  
  Vector Calculation
&lt;/h1&gt;

&lt;p&gt;To calculate the vector associated with a text, image, or sound, we will use a Python library called sentence-transformers, which allows us to transform content into a vector.&lt;br&gt;
To implement this, we should create the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ClassMethod Embedding(Text) [ Language = python ]
{
    from sentence_transformers import SentenceTransformer

    model_name = 'sentence-transformers/all-MiniLM-L6-v2'

    # Cache in global variables of the embedded Python module (persistent per process).
    global _cached_embedding_model
    if '_cached_embedding_model' not in globals():
        _cached_embedding_model = SentenceTransformer(model_name)

    vector = _cached_embedding_model.encode(
        [Text],
        normalize_embeddings=True,
        convert_to_numpy=True,
        show_progress_bar=False
    )

    return str(vector[0].tolist())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method "vectorizes" the content of our text using a pre-trained library called "all-MiniLM-L6-v2". However, if you wish, you can modify it and utilize your own trained library.&lt;/p&gt;

&lt;h1&gt;
  
  
  First Steps
&lt;/h1&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To access the terminal of our Docker instance, use the following command:&lt;br&gt;
&lt;code&gt;Docker-compose exec iris iris session iris&lt;/code&gt;&lt;/p&gt;



&lt;p&gt;Let's use an example of a vectorized search. For this, we will utilize a table called &lt;em&gt;St.vectorsearch.Feeling&lt;/em&gt; that has the following fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Text&lt;/td&gt;
&lt;td&gt;%String&lt;/td&gt;
&lt;td&gt;Text about how I feel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;%Integer&lt;/td&gt;
&lt;td&gt;Identifier of my feeling (See attached table)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector&lt;/td&gt;
&lt;td&gt;%Vector&lt;/td&gt;
&lt;td&gt;Text vector value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can create the data by running the following command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Do ##class(St.vectorsearch.Data).Init()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, we are going to load the sentiment data from the &lt;em&gt;/opt/irisbuild/data/training.csv&lt;/em&gt; directory, using the Populate command from the &lt;em&gt;St.vectorsearch.Data&lt;/em&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER&amp;gt;do ##class(St.vectorsearch.Data).Populate()
Truncating table St_vectorsearch.Feeling
Preparing to load data from file training.csv
Loading data from file training.csv
Total records loaded: 2000
Calculating vectors for records...
Processing 496 of 2000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be patient, since it will create a vector for each record, and it might take quite a while.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Populate will initialize a data model of 2000 records. There is another file with 5410 records if you want more information in the data model. If you wish to work with that one, use the PopulateFull() command instead.&lt;/p&gt;




&lt;p&gt;The values of feelings are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feeling&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sadness&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;joy&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;love&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anger&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fear&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“I left with my bouquet of red and yellow tulips under my arm, feeling slightly more optimistic than when I arrived.”&lt;/em&gt; It has a value of 1 (Joy).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“I can’t walk into a shop anywhere where I do not feel comfortable.”&lt;/em&gt; It has a value of 4 (Fear).&lt;/p&gt;

&lt;p&gt;If we use this function to create vectors, the first text we pass will return the next vector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.034666668623685836791, .012147962115705013276, .020678628236055374146, 
.043785430490970611572, .030321707949042320251, -.0081106657162308692932, 
-.028869708999991416931, -.059950094670057296752, .067945346236228942871, 
-.070874534547328948974, -.060159780085086822509, .016239311546087265014, 
-.034665744751691818237, -.011642633005976676941, .080176420509815216064, 
………
.0099751437082886695861, .0098187308758497238159, .00016082286310847848653, .013545278459787368774, -.0049553057178854942321, .054155148565769195556, .025806473568081855773, -.038503900170326232911, .039657127112150192261, 
-.073851920664310455322, -.070615962147712707519, .066068030893802642822, 
-.082378372550010681152, -.043505564332008361816, -.0054294602014124393463
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The vector value has been reduced to keep the text from being too long; the vector actually has 384 values.&lt;/p&gt;




&lt;p&gt;If we look for another cheerful text when creating the vector, it will give us the following value.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Today, I’m very happy.”&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 0.08268272876739502, 0.004975424613803625, -0.062008269131183624, 
……
0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This data does not make sense right now because we are not going to make a value-by-value comparison. That means we are not going to look for a record with exactly the same value as the vector because each vector is different. For that, we will use the formulas explained in the first part.&lt;/p&gt;

&lt;p&gt;We are going to use the following SQL commands to see which vectors are the closest to the vector we have calculated.&lt;/p&gt;

&lt;h1&gt;
  
  
  &amp;nbsp;
&lt;/h1&gt;

&lt;h1&gt;
  
  
  Dot Product Search
&lt;/h1&gt;

&lt;p&gt;As we indicated in the first part, the dot product search tells us how aligned two different vectors are. That means the closer it is to 1, the closer, or rather more similar, two vectors are.&lt;/p&gt;

&lt;p&gt;To perform our search, we will use the condition &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_vectordotproduct" rel="noopener noreferrer"&gt;“VECTOR_DOT_PRODUCT”&lt;/a&gt; that compares two vectors to determine their alignment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nb"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;
 &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;St_vectorsearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Feeling&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;VECTOR_DOT_PRODUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This search gives us the following results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pb2n3m4axskljucjq55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0pb2n3m4axskljucjq55.png" alt=" " width="776" height="170"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So… &lt;em&gt;“Today, I’m very happy”&lt;/em&gt; returns most results with a value of 1, joy.&lt;/p&gt;

&lt;p&gt;But… what is the percentage of proximity our text has regarding the rest of the retrieved values?&lt;/p&gt;

&lt;p&gt;We can find that out by comparing it with the current vector and displaying the value in a percentage format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nb"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VECTOR_DOT_PRODUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
 -0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'990.99%'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;Percentage&lt;/span&gt;
 &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;St_vectorsearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Feeling&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;VECTOR_DOT_PRODUCT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This search gives us the following results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9qatwpd2253e3n0sahj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff9qatwpd2253e3n0sahj.png" alt=" " width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The percentage of similarity to the most matching text is 65%. Our mind knows that, among all the texts, the meaning of &lt;em&gt;“Today, I’m very happy”&lt;/em&gt; is a feeling of joy, meaning it should be closer to 100% comparing to the text &lt;em&gt;“I am feeling so happy”&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Cosine Similarity Search
&lt;/h1&gt;

&lt;p&gt;The cosine similarity search is performed using the cosine of the angle between the vectors being compared.&lt;/p&gt;

&lt;p&gt;If we want to perform vector search with cosine similarity, we would have to utilize the condition &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_vectorcosine" rel="noopener noreferrer"&gt;“VECTOR_COSINE”&lt;/a&gt;, just as we have previously done it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nb"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VECTOR_COSINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
 -0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'990.99%'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;Percentage&lt;/span&gt;
 &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;St_vectorsearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Feeling&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;VECTOR_COSINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh… surprise!! It gave us back the same results:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdutn4cpehtdroscqj7wk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdutn4cpehtdroscqj7wk.png" alt=" " width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As mentioned in the first part, this type of search is more advisable for comparing more than one parameter, such as movie genre, lead actor, etc. In other words, this is how the movie recommendation system of Netflix or HBO works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Does it Support Multiple Languages?
&lt;/h1&gt;

&lt;p&gt;What will happen if, instead of using the text &lt;em&gt;“Today, I’m very happy,”&lt;/em&gt; we do it in Spanish?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Hoy, estoy muy feliz.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The created vector (reducing the text for obvious reasons) will resemble the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[-0.020053215324878693, 0.08766797184944153, 0.04118209332227707, 0.027171766385436058, -0.026769554242491722, -0.045022152364254,
…..
 0.038541924208402634, 0.018977241590619087, 0.03448185324668884, 0.10012426972389221, 0.08426760882139206, -0.09713190793991089]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if we use it to perform the search as we did previously, we will get the result below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yv2fdkgysnbb8bm2vv8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8yv2fdkgysnbb8bm2vv8.png" alt=" " width="552" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We got a combination of love (2), fear (4), and joy (1), when the meaning is identical to the English text.&lt;/p&gt;

&lt;p&gt;This rather diverse result appeared because the word &lt;em&gt;"feliz"&lt;/em&gt; is closer to &lt;em&gt;"feel"&lt;/em&gt; than &lt;em&gt;"happy"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As you have noticed, the creation of vectors is closer to the phonetics of the texts than to their meanings.&lt;/p&gt;

&lt;p&gt;I recommend testing the same text in different languages.&lt;/p&gt;

&lt;p&gt;Here you have the result of searching for it in French &lt;em&gt;“Aujourd'hui, je suis très heureux”&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz0dvqoph06lum7munjn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz0dvqoph06lum7munjn6.png" alt=" " width="799" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, if we want to use this system to search for text in a document that we have fully "tokenized", it can be solved in a very simple way.&lt;/p&gt;

&lt;p&gt;In a nutshell, we are going to change the model we used to create the token to the model &lt;em&gt;paraphrase-multilingual-MiniLM-L12-v2&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Therefore, if we modify this line in the Embedding method, we can use multilingual support in data retrieval.&lt;/p&gt;

&lt;p&gt;This is the result of using this model with the search in Spanish:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F766qb9c8imei7uizsjbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F766qb9c8imei7uizsjbn.png" alt=" " width="375" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if you create a language-based token, why does it internally "translate" "&lt;em&gt;Feliz&lt;/em&gt;" to "&lt;em&gt;Happy&lt;/em&gt;"? Yes, I have used double quotation marks around "translate" because it is not a context-based translation like in any other online dictionary. It interprets the word by creating a token, which can cause some confusion when similar words are used.&lt;/p&gt;

&lt;p&gt;For example, in Spanish, the word "&lt;em&gt;tiempo&lt;/em&gt;" is the same word for the meaning of the word "&lt;em&gt;time&lt;/em&gt;" or "&lt;em&gt;weather&lt;/em&gt;", so the token for this word could be confusing. Fortunately, the token is not based on a single word, but on a set of words that make up the phrase. It means that the rest of the phrase's context helps us create a token much closer to the final one. Therefore, our vector seems to have translated the text we want to search for.&lt;/p&gt;

&lt;h1&gt;
  
  
  Case Study
&lt;/h1&gt;




&lt;p&gt;&lt;strong&gt;Very important note:&lt;/strong&gt; I encountered some difficulties developing these practical examples. When trying to create a vector from an IRIS production environment, the process would completely freeze.&lt;/p&gt;

&lt;p&gt;The solution to using the &lt;em&gt;St.Vectorsearch.Vector.Embedding&lt;/em&gt; method without any issues was to download the model locally instead of querying the online model for each request. When using the model locally, invoking this class from production did not require an Internet connection to download the model, making the vector creation process much faster.&lt;/p&gt;




&lt;p&gt;I have created a small demo to show how a text, entered from a form, is "tokenized" and searched in the database to find the five closest results.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:52773/csp/user/feeling.html" rel="noopener noreferrer"&gt;http://localhost:52773/csp/user/feeling.html&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Make sure production is running. To do this, log in to the Management Portal and start production if it was stopped.&lt;/p&gt;




&lt;p&gt;The first time you ask about a feeling, it may take a little while, because it caches the model for future queries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wxh2wb1e2vq683071j5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3wxh2wb1e2vq683071j5.png" alt=" " width="800" height="789"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This website invokes an API running on IRIS that converts the text into a vector. Then it invokes the SQL search as I mentioned previously.&lt;/p&gt;

&lt;p&gt;It performs a search for the 5 closest phrases to the calculated vector. Then the website displays those results and indicates which sentiment is repeated more often, showing the arithmetic mean of the most repeated results.&lt;/p&gt;

&lt;h1&gt;
  
  
  Can We Have More Than One Element to Create a Vector?
&lt;/h1&gt;

&lt;p&gt;In the sentiment example, the phrase included information we used to create a search vector. However, what do we need if we have more than one column to perform the search?&lt;/p&gt;

&lt;p&gt;If we want someone to recommend a similar movie to the one we have already seen, the result will depend on many factors. It could be related to the movie release year (because you like older films) or the genre (comedy, drama, science fiction), etc.&lt;/p&gt;

&lt;p&gt;In this case, we will create a vector based on the text that includes the fields we want to index. This way, we will have all the fields we wish to use for our query:&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The order of the data is important because if we are interested in films from the same year and genre, those columns will have the most weight in the index.&lt;/p&gt;




&lt;p&gt;If we are looking for recommendations based on the plot, genre, director, and actors, that would be the order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;“Overview: [Overview]. Genre: [Genre]. Directed by [Director] and starring by [Star1], [Star2], [Star3] and [Star4]. Movie year [Year]. Ranking: [Rating]."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We created the St.Vectorsearch.Movie table with the following fields:&lt;br&gt;
|Field|Type|Description|&lt;br&gt;
|-|-|&lt;br&gt;
|Link|%String|Link to the movie poster|&lt;br&gt;
|Title|%String|Movie title|&lt;br&gt;
|Year|%Integer|Year of the film release|&lt;br&gt;
|Certificate|%String|Age classification (see attached table)|&lt;br&gt;
|Runtime|%Integer|Film duration in minutes|&lt;br&gt;
|Genre|%String|Film genre|&lt;br&gt;
|Rating|%Decimal(3,1)|IMDb rating|&lt;br&gt;
|Overview|%String|Movie description|&lt;br&gt;
|Director|%String|Name of director|&lt;br&gt;
|Star1|%String|Names of actors/actresses|&lt;br&gt;
|Star2|%String|Names of actors/actresses|&lt;br&gt;
|Star3|%String|Names of actors/actresses|&lt;br&gt;
|Star4|%String|Names of actors/actresses|&lt;br&gt;
|Vector|%Vector|Vector value of the film card|&lt;/p&gt;

&lt;p&gt;To understand the age classifications better, I will show you the meaning of those values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Certificate&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;A is Adults (equivalent to the USA R).&lt;/td&gt;
&lt;td&gt;Rating in the UK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UA&lt;/td&gt;
&lt;td&gt;UA is for ages 12 and up with parental supervision.&lt;/td&gt;
&lt;td&gt;Rating in the UK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U&lt;/td&gt;
&lt;td&gt;U is Universal (for everyone).&lt;/td&gt;
&lt;td&gt;Rating in the UK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PG-13&lt;/td&gt;
&lt;td&gt;PG-13 (Parents Strongly Cautioned): Strong warning for parents. Some materials may be inappropriate for children under the age of 13.&lt;/td&gt;
&lt;td&gt;Current (MPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;R (Restricted): Those under 17 years of age must be accompanied by a parent or adult guardian because it contains adult material.&lt;/td&gt;
&lt;td&gt;Current (MPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PG&lt;/td&gt;
&lt;td&gt;PG (Parental Guidance Suggested): Parental guidance is suggested. Some content may not be suitable for young children.&lt;/td&gt;
&lt;td&gt;Current (MPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;G (General Audiences): For all audiences.&lt;/td&gt;
&lt;td&gt;Current (MPA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PASSED&lt;/td&gt;
&lt;td&gt;The film complies with the strict moral standards of the time to be shown in cinemas.&lt;/td&gt;
&lt;td&gt;They were used between the 1930s and 1960s under the famous Hays Code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TV-14&lt;/td&gt;
&lt;td&gt;TV-14: Parents strongly advised. Contains material that many parents would consider inappropriate for children under the age of 14.&lt;/td&gt;
&lt;td&gt;TV Parental Guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Not recommended for children under 16 years old.&lt;/td&gt;
&lt;td&gt;Typical numerical age classification of European or Latin American systems.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TV-MA&lt;/td&gt;
&lt;td&gt;It may be inappropriate for viewers under the age of 17&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;due to graphic violence, explicit sexual activity, or crude language.&lt;/td&gt;
&lt;td&gt;TV Parental Guidelines&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UNRATED&lt;/td&gt;
&lt;td&gt;Not classified.&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GP&lt;/td&gt;
&lt;td&gt;GP: It was a temporary code used in the early 1970s. It was equivalent to what we know today as PG (Parental Guidance Suggestion).&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APPROVED&lt;/td&gt;
&lt;td&gt;The film complies with the strict moral standards of the time in order to be shown in cinemas.&lt;/td&gt;
&lt;td&gt;They were used between the 1930s and 1960s under the famous Hays Code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TV-PG&lt;/td&gt;
&lt;td&gt;Recommended parental guidance.&lt;/td&gt;
&lt;td&gt;TV Parental Guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;U/A&lt;/td&gt;
&lt;td&gt;U is Universal (for everyone).&lt;/td&gt;
&lt;td&gt;Rating in the UK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the same way that we initialized the table and loaded the data from the Feeling table, we employ the following commands:&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; To access the terminal of our Docker instance, use the following command:&lt;br&gt;
&lt;code&gt;Docker-compose exec iris iris session iris&lt;/code&gt;&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do ##class(St.vectorsearch.Data).InitMovie()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Next, we will load the movie data from the &lt;em&gt;/opt/irisbuild/data/imdb_top_1000.csv&lt;/em&gt; directory, using the PopulateMovie command from the &lt;em&gt;St.vectorsearch.Data&lt;/em&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USER&amp;gt;do ##class(St.Vectorsearch.Data).PopulateMovie()
Truncating table St_Vectorsearch.Movie
Preparing to load data from file imdb_top_1000.csv
Loading data from file imdb_top_1000.csv
Total records loaded: 999
Calculating vectors for records...
Processing 116 of 999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; During the demo initialization process in Docker, the test data loading operations are already performed.&lt;/p&gt;




&lt;p&gt;In each movie, we have initialized the vector value using the phrase we have created with the different fields. I encourage everyone to make appropriate changes to the St.Vectorsearch.Data.PopulateMovie class to align with your priorities better.&lt;/p&gt;

&lt;p&gt;As I mentioned in the first part, there is another vector approximation method (by cosine similarity), which allows us to search vectors that form the closest angle (those that are closest in the same direction).&lt;/p&gt;

&lt;p&gt;That is why it is the best system to use if you wish to find data recommendations.&lt;/p&gt;

&lt;p&gt;To use this, we will need the condition &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_vectorcosine" rel="noopener noreferrer"&gt;“VECTOR_COSINE”&lt;/a&gt;, which can tell us the closest angles to the indicated vector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Year&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;St_Vectorsearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Movie&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;VECTOR_COSINE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TO_VECTOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, we are going to look for movies that approximate the angle of the vector associated with the film we have, without, of course, having it recommend itself.&lt;/p&gt;

&lt;p&gt;You can access the demo via the link below:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://localhost:52773/csp/user/movies.html" rel="noopener noreferrer"&gt;http://localhost:52773/csp/user/movies.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1o5nj7xujqfigz3hdkql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1o5nj7xujqfigz3hdkql.png" alt=" " width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In this case, the recommended films are similar to 1988's "Akira," although I would personally put "Kôkaku Kidôtai," also known as "Ghost in the Shell," first. They are similar because they are both animated films. Mental note: I still need to watch Papurika...&lt;/p&gt;

&lt;p&gt;So, that is all for now. You have everything you need to work with vector indices, including practical examples and an application you can use for your experiments.&lt;/p&gt;

&lt;p&gt;Please leave any suggestions or ideas you have in the comments, including practical ways to implement this knowledge, etc...&lt;/p&gt;

&lt;p&gt;See you at the next “InterSystems for Dummies”!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>python</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>InterSystems for dummies – IRIS Vector Search (Part I)</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:35:13 +0000</pubDate>
      <link>https://dev.to/intersystems/intersystems-for-dummies-iris-vector-search-part-i-2995</link>
      <guid>https://dev.to/intersystems/intersystems-for-dummies-iris-vector-search-part-i-2995</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwkbmacfzn2gnwsau9wj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnwkbmacfzn2gnwsau9wj.png" alt=" " width="500" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;You may have heard the term "Vector Search" before. Do not worry, though; it is not "Vector Holmes" investigating a crime from 221B Baker Street.&lt;/p&gt;

&lt;p&gt;So, let’s explain, step by step, how to tackle vector data searches.&lt;/p&gt;

&lt;p&gt;:::pagebreak:::&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Vectors?
&lt;/h2&gt;

&lt;p&gt;In mathematics, a vector is an arrow that indicates a quantity with three elements: its value (magnitude), its direction, and its course. It is used to represent things that cannot be described with a single number, such as velocity, force, or displacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Magnitude&lt;/strong&gt;: It is the length of the arrow, which indicates the numerical value of the magnitude.&lt;br&gt;
&lt;strong&gt;Direction:&lt;/strong&gt; It is the straight line on which the vector acts (it can be horizontal, vertical, or diagonal).&lt;br&gt;
&lt;strong&gt;Course:&lt;/strong&gt; Indicates where the arrow points (it is the endpoint of the direction).&lt;br&gt;
&lt;strong&gt;Point of application:&lt;/strong&gt; It is the starting point of the vector.&lt;/p&gt;

&lt;p&gt;Two vectors are equal when they have the same magnitude (modulus), direction, and course. It means that regardless of their position in space, if their components are identical, they are the same vector.&lt;/p&gt;

&lt;p&gt;Two vectors are opposite when they have the same magnitude and the same direction, but opposite course, meaning they are facing away from each other.&lt;/p&gt;

&lt;p&gt;In a two-dimensional system, a vector is represented by its start (origin) and end (destination) coordinates on the X and Y axes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjg8da6ev4yzwjagumul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjg8da6ev4yzwjagumul.png" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;br&gt;
These two vectors are represented as follows:&lt;/p&gt;

&lt;p&gt;{(1,1),(5,3)}&lt;/p&gt;

&lt;p&gt;{(2,4),(7,5)}&lt;/p&gt;

&lt;p&gt;In a three-dimensional space, coordinates include the X, Y, and Z axes.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Do Mathematical Vectors Have to Do with Vector Search?
&lt;/h2&gt;

&lt;p&gt;Vector search is a method of searching known in numerical representations as vectors. It is similar to the vectors we have seen previously, but with the origin point being point 0 in Euclidean space.&lt;/p&gt;

&lt;p&gt;Instead of looking for precise keyword matches, this technique examines similarities between vectors, as previously explained, enabling more accurate, semantically and contextually meaningful results, even if the words in the query are not exactly the same.&lt;/p&gt;

&lt;p&gt;For example, "Iron Maiden," "Van Halen," and "Scorpions" are all the names of heavy metal bands. If we convert these names into tokens to create vectors, their coordinates will be similar but not identical. There will be slight differences in magnitude and direction. Therefore, they will be “orbiting” in the same “band” zone.&lt;/p&gt;

&lt;p&gt;Conversely, "Bob Dylan" or "Genesis" will point in the opposite direction. They are still musical groups, but their styles do not have "the same direction".[&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayam4fl9u3w9otlvb5x7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fayam4fl9u3w9otlvb5x7.png" alt=" " width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we perform a traditional search, we must use the exact same word, which is"Music group". It will encompass all groups. However, if we want to look for a specific genre, for instance, "thrash rock", we will not know how to differentiate Metallica from Dire Straits.&lt;/p&gt;

&lt;p&gt;In contrast, if we perform a vector search, it would locate these groups based on their proximity in both genre and musical style, showing that Metallica and Slayer are similar.&lt;/p&gt;
&lt;h2&gt;
  
  
  Are Vectors Really “Arrows”?
&lt;/h2&gt;

&lt;p&gt;In computer science, a vector is a data structure that includes an array of numbers. In our case, these vectors store a digital summary of the dataset to which they have been applied. Consider it a summary or a digital fingerprint.&lt;/p&gt;

&lt;p&gt;Images can also be analyzed for similarities. If you had to develop an application to compare two images, how would you do it? If you simply compared every pixel of one image with every pixel of the other, you would only find images that are identical in resolution, color, encoding, and other aspects.&lt;br&gt;
However, if you could examine the images and generate vector embeddings of the content, you would be able to compare them and identify similarities. In the case of images, a vector embedding explains the content of each image and then allows for comparison.&lt;br&gt;
This constitutes a much more robust method for discovering similarities between images.&lt;/p&gt;

&lt;p&gt;A vector, speaking in computer science terms, would have a representation similar to the one below&lt;/p&gt;

&lt;p&gt;{10,-4,6,34,0,-35,67,203,466,4,356,3,-53,-3,0}&lt;/p&gt;

&lt;p&gt;It will be like indicating the vector's final position only, since the vector's origin is point 0.&lt;/p&gt;
&lt;h2&gt;
  
  
  Vector Search Algorithms
&lt;/h2&gt;

&lt;p&gt;The data, which includes images, texts, audio, etc., is encoded in numerical vectors called "Embeddings". These values have varying dimensions depending on how they are encoded. The more dimensions our vector has, the more specific its definition will be, and the better it can be grouped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector Similarity Searches:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can use different types of metrics in vector similarity searches, each with its own advantages and disadvantages. The appropriate metric will depend on the data type and the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Euclidean Distance:&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;To determine if two vectors are close, we should measure the “Euclidean distance”, which is the straight-line length between two points in Euclidean space. It equals the square root of the sum of the squared differences between the point coordinates:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabq94si34v4ezx8k8mwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fabq94si34v4ezx8k8mwy.png" alt=" " width="392" height="69"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In an n-dimensional space, it is measured as the difference between each of the dimensions squared.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw18j3ykkpekhj3im7xxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw18j3ykkpekhj3im7xxe.png" alt=" " width="718" height="63"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the value is smaller, we can deduce that the final vector points are closer to each other than the initial ones.&lt;/p&gt;

&lt;p&gt;If we compare it with other vectors in the same space, those with the smallest values will be approximate vectors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ybe3grtc5kntyap56xa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6ybe3grtc5kntyap56xa.png" alt=" " width="800" height="580"&gt;&lt;/a&gt;&lt;br&gt;
The distance between v1 and v3 is less than the distance between v1 and v2.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8z7amyyhjnmkyfaiohbd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8z7amyyhjnmkyfaiohbd.png" alt=" " width="800" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to know how to calculate the distance between two points because it will be used later for measuring the magnitude of a vector.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Magnitude of a Vector&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the distance from the vector base to its endpoint. In our case, all vectors originate at point 0, so the magnitude of a vector would be calculated using the following formula:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4yxwqr2jnlntwp38ccfy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4yxwqr2jnlntwp38ccfy.png" alt=" " width="195" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data above gives us the following magnitudes of the vectors:&lt;/p&gt;

&lt;p&gt;V1 = 5,83095189&lt;/p&gt;

&lt;p&gt;V2 = 10&lt;/p&gt;

&lt;p&gt;V3 = 5,6568542&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dot Product&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dot product (also called the scalar product) is an operation between two vectors that results in a real number (a scalar). It is one of the most important tools in mathematics and physics because it tells us how "aligned" two vectors are.&lt;/p&gt;

&lt;p&gt;It is calculated by multiplying the sum of the coordinates in each dimension of the vectors to be compared.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c6yjsf6fnmql20tx769.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5c6yjsf6fnmql20tx769.png" alt=" " width="306" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The information from our example gives us the dot product values as follows:&lt;/p&gt;

&lt;p&gt;Dot &amp;nbsp;product v1v2 = (3 * 6) + (5 * 8) = 58&lt;/p&gt;

&lt;p&gt;Dot product v2v3 = (6 * 4) + (8 * 4) = 56&lt;/p&gt;

&lt;p&gt;Dot product v1v3 = (3 * 4) + (5 * 4) = 32&lt;/p&gt;

&lt;p&gt;The larger the dot product, the greater the similarity between these two vectors.&lt;/p&gt;

&lt;p&gt;We can say that v1 and v2 are very similar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cosine Similarity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This search method allows us to look for vectors not by the distance between their points, but by the angle they form.&lt;/p&gt;

&lt;p&gt;The cosine similarity measures the orientation between two vectors in a multidimensional space, ignoring their magnitudes. It is calculated by dividing the dot product of two vectors (A) and (B) by the product of their magnitudes, resulting in a value between -1 and 1.&lt;/p&gt;

&lt;p&gt;It is calculated using the following formula:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2twlaw6fsv117o4yliaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2twlaw6fsv117o4yliaa.png" alt=" " width="348" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following the previous example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Similarity of the cosine of v1 and v2:
Point product v1 y v2 = (3 * 6) + (5 *8) = 18 + 40 = 58
Magnitude v1 = 5,83095189
Magnitude v2 = 10
Cosine(v1, v2) = 58 / 58,309 = 0,9947

Similarity of the cosine of v2 and v3
Point product v2 y v3 = (6 * 4) + (8 * 4) = 56
Magnitude v2 = 10
Magnitude v3 = 5,6568542
Cosine(v2, v3) = 58 / 58,309 = 0,9899

Similarity of the cosine of v1 y v3
Point product v1 y v3 = (3 * 4) + (5 * 4) = 32
Magnitude v1 = 5,83095189
Magnitude v3 = 5,6568542
Cosine(v1, v3) = 32 / 32,984 = 0,9701
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can confirm that v1 and v2 are more similar than v2 and v3, and v1 and v3.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In the case of multidimensional vectors, we should perform the sum of each of the dimensions.&lt;/p&gt;




&lt;p&gt;This approach returns vectors with similar directions, so it can help us find resemblances to make recommendations (this is how Netflix or HBO works).&lt;/p&gt;

&lt;p&gt;In the example of music groups, we can start by searching for "thrash rock" bands. Then we can suggest other groups not categorized as thrash rock but within the heavy metal genre. That is, we start by listening to Metallica and, through suggestions, move on to Van Halen. Now you understand why Netflix might suggest watching "The Nightmare Before Christmas" after seeing "Hotel Transylvania”.&lt;/p&gt;

&lt;p&gt;As Foghorn Leghorn once said, “It’s math, son!! The numbers don’t lie.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35vpwodjh27yhfx6m9g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F35vpwodjh27yhfx6m9g6.png" alt=" " width="662" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do We Use This Mathematical Knowledge In Vector Search?
&lt;/h2&gt;

&lt;p&gt;First, we need to convert our data (text, images, etc.) into a vector, as we have indicated before.&lt;/p&gt;

&lt;p&gt;However, vectors do not create themselves. That means it is not enough just to give them a random value defined by us.&lt;/p&gt;

&lt;p&gt;LLM models allow us to create the dot product and use cosine similarity to perform searches within &amp;nbsp;InterSystems IRIS. Euclidean search is not implemented, but could be applied with Python libraries.&lt;/p&gt;

&lt;p&gt;Some people have already thought about this because it will allow us to associate correct vectors based on the text context… Yet, I will explain this in the next article.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I am sorry for the math lesson. Yet, I had to explain how it works first to help you better understand the "search magic".&lt;/p&gt;




</description>
      <category>beginners</category>
      <category>python</category>
      <category>tooling</category>
      <category>vectordatabase</category>
    </item>
    <item>
      <title>The Gaia Planetarium: A Full-Stack Embedded Python Project</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Sun, 23 Aug 2026 15:26:23 +0000</pubDate>
      <link>https://dev.to/intersystems/the-gaia-planetarium-a-full-stack-embedded-python-project-5708</link>
      <guid>https://dev.to/intersystems/the-gaia-planetarium-a-full-stack-embedded-python-project-5708</guid>
      <description>&lt;p&gt;If you are a regular on the developer community, you may have seen some recent posts about the InterSystems First Employee Programming competition. The challenge is simple — transform some Gaia Epoch Photometry data, calculate the percentage change of flux (between the maximum and minimum values), and output a list of the stars with a percentage change of greater than 100.&lt;/p&gt;
&lt;p&gt;Now while this was a fun challenge, but I felt there was something missing: &lt;em&gt;a lack of scope for creativity&lt;/em&gt;. After all, we are looking at stars! How can something as magical as the night sky be distilled down to a CSV files of IDs and flux values?&lt;/p&gt;
&lt;p&gt;So I decided to do something different with my main entry, and visualise stars the way they should be viewed — in the night sky above us. Let's take a look at the Gaia planetarium:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8ROMg2bN3Vo"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
&lt;/p&gt;
&lt;h1&gt;Project Design&lt;/h1&gt;
&lt;p&gt;The first question was what information is needed to plot the stars in the nights sky? Now its easy to plot a point on earth - you just need a longitude and latitude. For stars, you also need two locating values, Right Ascension (RA) and Declination (Dec). Of course, it gets more complex if you want to know where they are in location to the earth, but that was solved later.&lt;/p&gt;
&lt;p&gt;These values aren't in the Photometry datasets, instead being found in the Gaia source table, which can helpfully be queried using a Python library called &lt;code&gt;astroquery&lt;/code&gt;. This made the decision to use Python a no-brainer.&lt;/p&gt;
&lt;p&gt;So I decided upon the following stack:&lt;/p&gt;
&lt;p&gt;&lt;br&gt;- &lt;strong&gt;PyProd production&lt;/strong&gt;&lt;br&gt;- Ingests Photometry data files&lt;br&gt;- Calculates output (and adds to csv file)&lt;br&gt;- Queries the Gaia Source table to get the location for each source ID.&lt;/p&gt;
&lt;p&gt;&lt;br&gt;- &lt;strong&gt;Flask Web Application&lt;/strong&gt; (hosted using IRIS WSGI hosting):&lt;br&gt;- Query data from IRIS tables using Embedded Python&lt;br&gt;- Create REST Service to send data to the front-end&lt;/p&gt;
&lt;p&gt;&lt;br&gt;- &lt;strong&gt;Front-end UI with HTML/CSS/JS&lt;/strong&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;Now, before going through the implementation in a bit more detail, I will detail one issue I found. The Gaia photometry data is &lt;em&gt;heavy&lt;/em&gt;. Each zipped file is around 15.5MB, which doesn't sound like that much, until you realise there is &amp;gt;3000 of them. What's more, each of these files maps a small portion of the sky in immense detail, whereas I am much more interested in covering the sky with stars that could be visible to the naked-eye.&lt;/p&gt;
&lt;p&gt;I therefore decided to add separate production components which can just query the Gaia source data, ordered by brightness to add the most visible stars to my planetarium. I've still included the photometry dataset and, because it is a production, its easy to throw more files into the watched directory to add them into map. The challenge results are available as an overlay, and may not be visible depending on whether the small portion of the sky that the photometry data maps is overhead.&lt;/p&gt;
&lt;p&gt;I also added star data from a different dataset — Hipparcos, because many of the most visible stars overload the Gaia sensor, so are not available in the Gaia dataset. Including Hipparcos stars was important for viewing constellations to my planetarium. I also added constellation data from Stellarium to visualise the constellations in the sky.&lt;/p&gt;
&lt;h1&gt;IRIS Implementation&lt;/h1&gt;
&lt;h2&gt;PyProd&lt;/h2&gt;
&lt;p&gt;I've &lt;a href="https://community.intersystems.com/post/csvgen-pyprod" rel="noopener nofollow noreferrer"&gt;recently written&lt;/a&gt; about PyProd for another example project, so I am going to skip the technical details in this case. One comment I will make though — it was really nice to be able to build a production in Python because I required a few steps which are super easy to do in Python, and not so easy in ObjectScript. The key example of this was querying the Gaia dataset directly with the &lt;code&gt;astroquery.gaia&lt;/code&gt; library.&lt;/p&gt;
&lt;p&gt;I also used this project as a moment to road test a new PyProd agent skill. This way, as soon as I start using PyProd, my agent can read the skill and see exact patterns of how it should be working with PyProd. A version of this skill is now available in the &lt;a href="https://github.com/intersystems-community/iris-agentic-dev/blob/master/skills/pyprod/SKILL.md" rel="noopener nofollow noreferrer"&gt;iris-agentic-dev skill library&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The Web App: Flask&lt;/h2&gt;
&lt;p&gt;Its not super well known that IRIS can host WSGI applications, lets face it, I didn't even know what a WSGI application was when I first heard this! Web-Server Gateway Interface is a Python standard for running web applications. Some of the most popular Python web application frameworks, including Flask and Django, run on WSGI.&lt;/p&gt;
&lt;p&gt;Flask is a lightweight framework for developing REST APIs. It has pretty simple syntax, where you define a function with a decorator to make it an REST endpoint. Combining this with Embedded Python makes it easy to get data from IRIS. For example, a simplified version of the endpoint which collects stars from the IRIS table is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from flask import Flask, request&lt;br&gt;
import iris # Embedded python IRIS import 

&lt;p&gt;app = Flask(&lt;strong&gt;name&lt;/strong&gt;, static_folder="/home/irisowner/dev/src/skymap/static)&lt;/p&gt;

&lt;h1&gt;
  
  
  Endpoint to access stars from the Database
&lt;/h1&gt;

&lt;p&gt;@app.route("/api/stars", methods=["GET"])&lt;br&gt;
def get_stars() &lt;br&gt;
    # SQL Query &lt;br&gt;
    query = """&lt;br&gt;
        SELECT TOP 1000 SourceId, Ra, DecDeg, PhotGMeanMag &lt;br&gt;
        FROM "Gaia.SourceLocation"&lt;br&gt;
        """&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Execute query 
rows = iris.sql.exec(query)

# Collect results
output = [] 
for row in rows: 
    output.append({
      "source_id": row[0],
        "ra"     : row[1],
        "dec"    : row[2],
        "mag"    : row[3]
    })

# Return results
return output
&lt;/code&gt;&lt;/pre&gt;

&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;p&gt;And we can also use this to activate a PyProd adapterless Business Service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from intersystems_pyprod import director 

&lt;h1&gt;
  
  
  POST endpoint
&lt;/h1&gt;

&lt;p&gt;@app.route("/api/more-stars", method=["POST"])&lt;br&gt;
def add_stars(): &lt;br&gt;
    # Get the info from the post request&lt;br&gt;
    n_stars = int(request.args.get("n_stars"))&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create Business Service
status, service = director.create_business_service("Gaia.StarCatalogService")

# Activate Business Service
service.process_input(n_stars)
&lt;/code&gt;&lt;/pre&gt;

&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;h3&gt;Flask hosting&lt;/h3&gt;
&lt;p&gt;WSGI applications can be hosted through the Management Portal at System -&amp;gt; Security -&amp;gt; Applications:&lt;/p&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhuf1td6l5m2gwxsnmpmm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhuf1td6l5m2gwxsnmpmm.png" alt=" " width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I wanted to do this programatically through Embedded Python. Using the Security.Applications.Create() function required me passing an IRIS array by reference into the function, which can be achieved using an &lt;code&gt;iris.arrayref(&amp;lt;python dict&amp;gt;)&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import iris
# Define settings 
props = iris.arrayref({
            'Type': 2,
            'NameSpace': 'USER',
            'WSGIAppLocation': '/home/irisowner/dev/src', # Path to flask project
            'WSGIAppName': 'skymap.server', # FolderName.FileName (without .py)
            'WSGICallable': 'app',
            'WSGIDebug': 0,
            'WSGIType': 1,
            'AutheEnabled': 64,
            'Enabled': 1,
            'Description': 'Gaia sky map Flask/WSGI application',
            "DispatchClass":"%SYS.Python.WSGI", # Needed for WSGI hosting
            "Path":"/home/irisowner/dev/src", # Path to flask project
            "WSGIDebug": 1, # Refresh when the code changes
            "MatchRoles": ":%All" # Security roles
        })

# Create Web App
sc = iris.Security.Applications.Create('/skymap', props)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An easy point to miss here if you normally create WSGI applications through the management portal: you need to set the DispatchClass to &lt;code&gt;"%SYS.Python.WSGI"&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Front-end&lt;/h2&gt;
&lt;p&gt;Unlike a pure Python framework like Streamlit, Flask include a Python front-end framework, instead relying on building a front-end with HTML/CSS/JavaScript (or some front-end framework).&lt;/p&gt;
&lt;p&gt;I'm going to skip most of the details on this because it was largely a question of me describing clearly what I wanted, and getting some AI generated code as a result.&lt;/p&gt;
&lt;p&gt;It is pure HTML/JS/CSS, which I personally am a big fan of. I know it has limits, but sometimes I think starting with a front-end framework overcomplicates things.&lt;/p&gt;
&lt;p&gt;The Front end is hosted in the Flask app through a static route:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from flask import Flask, jsonify, request, send_from_directory

STATIC_DIR = os.path.join(os.path.dirname(__file__), "static")
app = Flask(__name__, static_folder=STATIC_DIR)

@app.get("/")
def index():
        return send_from_directory(STATIC_DIR, "index.html")

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only other thing to mention is that Cross Origin Resource Sharing is also handled from Flask&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.after_request
def add_cors(response):
        response.headers["Access-Control-Allow-Origin"] = "*"
        return response
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;As I mentioned at the start of this article, this project started out as a competition entry for the Employee Programming Competition. However, it quickly spiralled into something else, once I realised the limitations of only using the Gaia Photometry Data. It has become a Python Full Stack Application, which integrates multiple data sources (Gaia, Hipparcus and files of constellations and star names).&lt;/p&gt;
&lt;p&gt;To be clear though, &lt;strong&gt;it still does what was asked by the competition&lt;/strong&gt;, and it even plots it as a bright yellow/orange/red patch (coloured by magnitude of flux change) of sky which is covered by the Photometry data. So if you've made it this far, please consider voting for my planetarium in the community vote!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu3b49tct6b6mt8hp63jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu3b49tct6b6mt8hp63jj.png" alt=" " width="793" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you've enjoyed reading about this Embedded Python project as much as I've enjoyed making it!&lt;/p&gt;

</description>
      <category>python</category>
      <category>resources</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>iris-agentic-dev -- Give Your AI a Live Connection to IRIS, Part 1: The Problem, the Tool, and Getting Started</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Thu, 13 Aug 2026 09:35:07 +0000</pubDate>
      <link>https://dev.to/intersystems/iris-agentic-dev-give-your-ai-a-live-connection-to-iris-part-1-the-problem-the-tool-and-1kc0</link>
      <guid>https://dev.to/intersystems/iris-agentic-dev-give-your-ai-a-live-connection-to-iris-part-1-the-problem-the-tool-and-1kc0</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1 of a series. Part 2 covers the full tool catalog. Part 3 covers ObjectScript skills. Part 4 covers benchmarking and measuring what actually improves.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Hiding in the Comments
&lt;/h2&gt;

&lt;p&gt;Thomas Mazur's post &lt;a href="https://community.intersystems.com/post/frogs-chickens-ai-and-vs-code" rel="noopener noreferrer"&gt;&lt;em&gt;"Frogs, Chickens, AI, and VS Code" &lt;/em&gt;&lt;/a&gt;on VS Code productivity — Peacock, scoped workspace files, Copilot Agent mode — drew a sharper problem in the comments. Pietro Di Leo and Mike.W pointed out that when you work server-side in VS Code, the &lt;code&gt;isfs://&lt;/code&gt; workspace most production IRIS shops use, Copilot can only see the files &lt;strong&gt;open in your editor&lt;/strong&gt;. It cannot index the virtual filesystem. On a mature IRIS application with thousands of classes, the AI works through a keyhole.&lt;/p&gt;

&lt;p&gt;John Murray pointed people at a project I've been building — &lt;a href="https://github.com/intersystems-community/iris-agentic-dev" rel="noopener noreferrer"&gt;iris-agentic-dev&lt;/a&gt; — and noted no Developer Community article existed for it yet. So here it is: why the problem exists, how the tool addresses it, and how to get it running in about five minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the AI Can't See Your Namespace
&lt;/h2&gt;

&lt;p&gt;When you open an &lt;code&gt;isfs://&lt;/code&gt; workspace, your IRIS classes live on the server, not on disk. The VS Code ObjectScript extension streams them to you on demand via the Atelier API — open a class, it fetches it; save it, it writes back. This works beautifully for editing.&lt;/p&gt;

&lt;p&gt;AI assistants such as Copilot work differently. They need a picture of the code around the file you're editing. Who calls this method? What inherits from this class? What other code touches this global? On a local project, the assistant can scan the files to answer those questions. An &lt;code&gt;isfs://&lt;/code&gt; workspace materializes files only when you open them, so there is nothing complete to scan.&lt;/p&gt;

&lt;p&gt;For a new project with a handful of classes, that may be tolerable. For a production IRIS system — ten thousand classes, Ensemble productions, custom &lt;code&gt;%Library&lt;/code&gt; subclasses, business logic accumulated across years of development — the AI becomes nearly useless for the hard questions. It can help you write a new method if you paste in the surrounding context yourself. It cannot help you understand the system.&lt;/p&gt;

&lt;p&gt;Give the AI a different kind of connection, one that can ask IRIS directly instead of crawling the disk.&lt;/p&gt;




&lt;h2&gt;
  
  
  What iris-agentic-dev Is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;iris-agentic-dev&lt;/code&gt; is an &lt;strong&gt;MCP server&lt;/strong&gt; — a background process that gives AI assistants a set of tools they can call to interact with a live IRIS instance. It works with GitHub Copilot (via the VS Code extension), Claude Code, Cursor, and OpenCode. The IRIS instance can run natively on Windows or Linux, or in Docker.&lt;/p&gt;

&lt;p&gt;Once configured, the MCP server's tools are available directly from chat. VS Code 1.99 and later support MCP for Copilot Agent mode; Claude Code and OpenCode have supported it since launch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;iris-agentic-dev&lt;/code&gt; connects to IRIS through the same Atelier REST API used by the ObjectScript extension. The assistant can then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search the entire namespace&lt;/strong&gt; — full-text, regex, by category, without opening anything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile classes&lt;/strong&gt; and get errors back with line numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run ObjectScript&lt;/strong&gt; and see the output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute SQL queries&lt;/strong&gt; against any namespace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introspect class definitions&lt;/strong&gt; — properties, methods, parameters, inheritance chains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect Ensemble productions&lt;/strong&gt; — which items are running, what's wired to what, message bodies, business rule logic, and drift between the running config and source control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run unit tests&lt;/strong&gt; and report results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug&lt;/strong&gt; — map INT line numbers back to original source lines, pull error logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 2 covers the complete tool catalog. Instead of guessing from a few open tabs, the assistant can ask IRIS about the namespace itself.&lt;/p&gt;

&lt;p&gt;&amp;nbsp;---&lt;/p&gt;

&lt;h2&gt;
  
  
  Built With the Community
&lt;/h2&gt;

&lt;p&gt;I started the project after running into this limitation repeatedly in my own IRIS work. Community contributions have shaped it since — often from the same people who show up more than once. John Murray, who pointed people at this project in the Frogs and Chickens thread, also built the Server Manager authentication integration you'll use in Step 2 below: instead of typing credentials into a config file, the MCP server reads them straight from the OS keychain through the same &lt;code&gt;AuthenticationProvider&lt;/code&gt; the Server Manager extension itself uses. Dorian TETU has contributed fixes across search accuracy, source control elicitation, and surgical-edit diffs.&lt;/p&gt;

&lt;p&gt;The project is open source under the &lt;code&gt;intersystems-community&lt;/code&gt; GitHub organization. Contributions and bug reports are welcome, including "It doesn't work on my setup."&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started: VS Code + GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;If you already use VS Code with the InterSystems ObjectScript extension, this is the fastest path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: VS Code, GitHub Copilot subscription, and the &lt;a href="https://marketplace.visualstudio.com/items?itemName=intersystems-community.vscode-objectscript" rel="noopener noreferrer"&gt;InterSystems ObjectScript extension&lt;/a&gt; (which you almost certainly already have).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Install the VS Code extension
&lt;/h3&gt;

&lt;p&gt;Search for &lt;strong&gt;iris-agentic-dev&lt;/strong&gt; in the VS Code Marketplace and install it. On first activation, the extension locates or downloads the MCP server binary: if you already have it on PATH (e.g. via &lt;code&gt;brew install iris-agentic-dev&lt;/code&gt;), it uses that; otherwise it downloads the right binary for your platform automatically. Either way, it registers itself with Copilot — no manual wiring required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfgb6u37vxwh8jln5j61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgfgb6u37vxwh8jln5j61.png" alt="The iris-agentic-dev tools available in GitHub Copilot Agent mode" width="800" height="528"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The iris-agentic-dev tool set appears in Copilot's Agent mode after installation.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Verify the connection
&lt;/h3&gt;

&lt;p&gt;Open Copilot Chat and switch to &lt;strong&gt;Agent mode&lt;/strong&gt;. Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Call check_config and show me the result."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see your IRIS connection details — host, port, namespace, Atelier API version. If the &lt;a href="https://marketplace.visualstudio.com/items?itemName=intersystems-community.servermanager" rel="noopener noreferrer"&gt;InterSystems Server Manager&lt;/a&gt; extension is installed, &lt;code&gt;iris-agentic-dev&lt;/code&gt; finds your server configuration and retrieves credentials from the OS keychain automatically. The VS Code extension follows the active &lt;code&gt;objectscript.conn&lt;/code&gt;, so developers with several Server Manager entries keep using the connection selected for that workspace. When running the MCP server outside the VS Code extension, set &lt;code&gt;IRIS_SERVER_NAME&lt;/code&gt; to the corresponding key from &lt;code&gt;intersystems.servers&lt;/code&gt; if more than one server is configured. The &lt;code&gt;check_config&lt;/code&gt; result shows which connection is active and which other servers were detected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7k1t7tguy0e9a0cyca9o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7k1t7tguy0e9a0cyca9o.png" alt="Copilot displaying the result of the iris-agentic-dev check\_config tool" width="693" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;check_config&lt;/code&gt;&lt;em&gt; confirms the IRIS host, port, namespace, and connection source Copilot is using.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Ask something that requires the whole namespace
&lt;/h3&gt;

&lt;p&gt;Now try a question that would be difficult to answer from open tabs alone:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Search for all classes in this namespace that extend &lt;/em&gt;&lt;code&gt;%Persistent&lt;/code&gt;&lt;em&gt;. How many are there?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What are the properties and methods on &lt;/em&gt;&lt;code&gt;MyApp.SomeClass&lt;/code&gt;&lt;em&gt;?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Compile &lt;/em&gt;&lt;code&gt;MyApp.*.cls&lt;/code&gt;&lt;em&gt; and show me any errors."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;None of these requires you to open the relevant files first. The assistant gets the answers from IRIS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started: Claude Code
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Install the binary&lt;/strong&gt; (Mac):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap intersystems-community/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;iris-agentic-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or download directly from the &lt;a href="https://github.com/intersystems-community/iris-agentic-dev/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt; for Mac Intel, Linux, or Windows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the connection.&lt;/strong&gt; Create &lt;code&gt;~/.iris-agentic-dev.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host = "localhost"
web_port = 52773
username = "_SYSTEM"
password = "SYS"
namespace = "USER"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Register with Claude Code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--scope&lt;/span&gt; user iris-agentic-dev &lt;span class="nt"&gt;--&lt;/span&gt; iris-agentic-dev mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Call check_config and show me the result.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example: How &lt;em&gt;iris-agentic-dev&lt;/em&gt; tools support analyzing an IRIS Interoperability application
&lt;/h2&gt;

&lt;p&gt;Here is a real exchange against the &lt;a href="https://github.com/intersystems/irisdemo-demo-readmission" rel="noopener noreferrer"&gt;irisdemo-demo-readmission&lt;/a&gt; production — a health interop demo that processes hospital discharge events and scores patients for readmission risk.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"How does an ADT A03 discharge message flow through this production?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 1: find what's compiled.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iris_symbols("IRISDemo.*")
→ 31 classes: BO.*, BP.*, BS.*, DTL.*, Util.*, and more
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key classes: &lt;code&gt;IRISDemo.BP.ReadmissionRisk.Process&lt;/code&gt;, &lt;code&gt;IRISDemo.DTL.HL7Discharge&lt;/code&gt;, &lt;code&gt;IRISDemo.DTL.HL7Update&lt;/code&gt;, &lt;code&gt;IRISDemo.HISHL7v2FileFeedRoutingRule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: find the router's rule.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extract_message_map_routing("IRISDemo.HISHL7v2FileFeedRoutingRule")
→ NOT_FOUND — Ens.Rule.Definition, not a routing table class
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Ens.Rule.Definition&lt;/code&gt; classes hold routing logic in XData. The tool can't map that structure, so read the class source directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iris_doc("IRISDemo.HISHL7v2FileFeedRoutingRule.cls") → XData rules:
  Rule 1: docName=ADT_A01 or ADT_A08  → transform DTL.HL7Update, target Readmission Risk Process
  Rule 2: docName=ADT_A03             → transform DTL.HL7Update, target Readmission Risk Process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A03 discharges go through &lt;code&gt;IRISDemo.DTL.HL7Update&lt;/code&gt;, which stamps &lt;code&gt;UpdateMessageType="A03"&lt;/code&gt; on the request — that field is what lets the business process branch differently for discharges vs. admits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: map the business process.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;extract_message_map_routing("IRISDemo.BP.ReadmissionRisk.Process")
→ kind: bpl, 4 outbound calls:
    Update Encounter          → LACE SOAP Operation
    Calculate Risk with LACE  → LACE SOAP Operation
    Calculate Risk with ML    → Readmission ML Model Consumer
    EMR Readmission Update    → HisDB Encounter Update Operation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: get the full step tree.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docs_introspect("IRISDemo.BP.ReadmissionRisk.Process") → xdata_flow:
  Call:  Update Encounter          → LACE SOAP Operation
  Call:  Calculate Risk with LACE  → LACE SOAP Operation
  Call:  Calculate Risk with ML    → Readmission ML Model Consumer
  Call:  EMR Readmission Update    → HisDB Encounter Update Operation  [async]
  If:    Discharge OK?
           (request.UpdateMessageType = "A03") &amp;amp;&amp;amp; (context.UpdateEncounterResult = 1)
    If:  Risk Alert?
           (context.RiskScore &amp;gt; 11) || (context.MLReadmissionRisk &amp;gt; 0.15)
      assign: Compose Alert Message
      Call:  Add Patient to Risk Program  → Care Team  [async]
      Call:  Alert Care Team              → Risk Alert Email Operation
      sync:  Follow up SLA 2 days
      If:    No follow up?  (synctimedout)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The session also flagged that &lt;code&gt;IRISDemo.DTL.HL7Discharge&lt;/code&gt; exists and maps 9 HL7 fields to a &lt;code&gt;DischargeRequest&lt;/code&gt; — but the routing rule never sends A03 through it. Dead code, spotted without opening a file.&lt;/p&gt;

&lt;p&gt;The full exchange — every tool call, response, and reasoning step — is in this &lt;a href="https://gist.github.com/isc-tdyar/58a19b90f604fa786eab555815283dde" rel="noopener noreferrer"&gt;GitHub Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In four steps the assistant answered the question: A03 discharges hit the router, get transformed into an &lt;code&gt;UpdateEncounterRequest&lt;/code&gt; with the trigger event stamped as the branch signal, and the business process runs LACE and ML risk scoring in sequence — alerting the care team and starting a 2-day follow-up if either score exceeds the threshold. No files were open. Everything came from IRIS.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's in the Next Parts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Part 2 — The Tools&lt;/strong&gt;: A practical walkthrough of the tool catalog: what each tool does, when to use it, and which IRIS-specific problems it solves. The search, introspection, and Ensemble tools are especially useful for questions that open editor buffers cannot answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3 — Skills&lt;/strong&gt;: A live connection does not fix an AI model's weak grasp of ObjectScript: subtle syntax differences, &lt;code&gt;%Status&lt;/code&gt; propagation, &lt;code&gt;$$$&lt;/code&gt; macros, and COS-specific idioms that are scarce in general training data. Skills are short instruction files that target these weaknesses. On my 22-task ObjectScript repair suite, a 205-word checklist called &lt;code&gt;objectscript-review&lt;/code&gt; took the pass rate from 73% to 100% against Claude Sonnet 4.6 — a single run on a small public suite, with all the caveats that implies. Part 3 covers what the skills do; Part 4 covers how much to trust the number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4 — Benchmarking&lt;/strong&gt;: How the benchmark harness works, how to run it, and what the numbers mean. That includes where skills help, where they have no effect, and at least one that appears to &lt;em&gt;hurt&lt;/em&gt; performance when loaded globally — more instructions are not always better. It also covers the limits of a suite this size: contamination risk from public tasks, single-run variance, and why a lift measured on one model says little about another.&lt;/p&gt;




&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/intersystems-community/iris-agentic-dev" rel="noopener noreferrer"&gt;intersystems-community/iris-agentic-dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VS Code extension&lt;/strong&gt;: &lt;a href="https://marketplace.visualstudio.com/items?itemName=intersystems-community.vscode-iris-agentic-dev" rel="noopener noreferrer"&gt;iris-agentic-dev for IRIS on the Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binaries&lt;/strong&gt; (Mac, Linux, Windows): &lt;a href="https://github.com/intersystems-community/iris-agentic-dev/releases/latest" rel="noopener noreferrer"&gt;releases page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Original thread&lt;/strong&gt;: &lt;a href="https://community.intersystems.com/post/frogs-chickens-ai-and-vs-code" rel="noopener noreferrer"&gt;Frogs, Chickens, AI, and VS Code&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Thomas Dyar — Sr. Manager AI Platform &amp;amp; Ecosystem, InterSystems, &lt;/em&gt;&lt;code&gt;iris-agentic-dev&lt;/code&gt;&lt;em&gt; is open source under the intersystems-community GitHub organization.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>ai</category>
      <category>coding</category>
      <category>tooling</category>
    </item>
    <item>
      <title>AI-Assisted Development on IRIS: Beginner tips and guidance</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Thu, 13 Aug 2026 09:32:54 +0000</pubDate>
      <link>https://dev.to/intersystems/ai-assisted-development-on-iris-beginner-tips-and-guidance-15id</link>
      <guid>https://dev.to/intersystems/ai-assisted-development-on-iris-beginner-tips-and-guidance-15id</guid>
      <description>&lt;blockquote&gt;&lt;p&gt;This article provides an AI agent-agnostic view of developing with AI coding assistants, with an IRIS-specific focus. This guidance is based on standards that can be used whether you use Claude Code, GitHub Copilot, Codex, or one of the other coding agents. As such, it doesn't cover setup instructions. Instead, it covers key concepts and how they have improved the performance of AI for my work.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Hi everyone, I wanted to share some thoughts, advice and examples of using AI for IRIS development. Before starting though, I have lots of opinions on AI, far more than I can fit in this article. I am not an AI evangelist. I started as a skeptic and still hold on to a lot of skepticism, along with an intense dislike for slop. I am, however, an absolute believer in the ability of these tools if you can learn to use them correctly.&lt;/p&gt;
&lt;p&gt;I use AI regularly, but make a conscious effort to follow, understand and review any code that isn't a toy project or personal tool. My opinions on AI coding softened dramatically when I first tried vibe-coding for a personal tool (a presentation app which displays web-pages directly alongside slides) and was amazed by success. I recommend next time you think "I wish I had an application/vs code extension/tool which does x", try asking an AI agent for it.&lt;/p&gt;
&lt;p&gt;Of course, the correct approach will vary for each user and each scenario. People working with sensitive data in production database should be far more careful about using AI than I am creating demo projects in isolated containers. As such, I can't claim my experience will be the same as anyone else, but I do suggest taking some time to explore approaches that might work for you.&lt;/p&gt;
&lt;h2&gt;"I tried using AI to write ObjectScript but it hallucinated all the methods"&lt;/h2&gt;
&lt;p&gt;AI agents have improved a lot, even since I started at InterSystems in September 2025. Developer circles often talk about the &lt;a href="https://martinfowler.com/bliki/NovemberInflection.html" rel="noopener nofollow noreferrer"&gt;November inflection point&lt;/a&gt; where the release of Claude Opus 4.5 and GPT-5.2 changed the game with agentic coding. Suddenly models had the ability (and context window) to chain together tool calls to develop, reason, and fix bugs. These models have been incrementally improving with each release cycle since and may well continue to do so.&lt;/p&gt;
&lt;p&gt;In this time, ObjectScript coding from LLMs has improved &lt;strong&gt;dramatically&lt;/strong&gt;. I recommend you re-evaluate any belief about the ability of AI in coding matters that pre-dates these releases, and try again with one of the latest (and most powerful) models in a coding harness.&lt;/p&gt;
&lt;h2&gt;Improving your agent's skillset&lt;/h2&gt;
&lt;p&gt;Frontier AI models (and their lesser counterparts) have a pretty good very good knowledge of most matters, essentially because they have consumed the entire internet. It is true that their ObjectScript knowledge is often less than other languages, primarily because there is less ObjectScript available on the web than other languages.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href="https://agentskills.io/home" rel="noopener nofollow noreferrer"&gt;agent skills&lt;/a&gt; come in.&lt;/p&gt;
&lt;p&gt;Skills are markdown files with some YAML front-matter, it's (basically) that simple. In the metadata, you give the skill a name, and a description, which crucially should say when an agent should use the skill. The name and description are loaded into the agent's context window, and when they need the specific guidance the skill provides, they can activate the skill and use the markdown contents. This is great for specific knowledge, or advice, for example on a lesser used part of a coding language they are a bit ropey at.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/intersystems-community/iris-agentic-dev/" rel="noopener nofollow noreferrer"&gt;iris-agentic-dev&lt;/a&gt;, the MCP server that &lt;span&gt;&lt;span&gt;@tomd&lt;/span&gt;&lt;/span&gt; recently &lt;a href="https://community.intersystems.com/post/iris-agentic-dev-give-your-ai-live-connection-iris-part-1-problem-tool-and-getting-started" rel="noopener nofollow noreferrer"&gt;shared with the community&lt;/a&gt; includes a skill library. One of the skills, &lt;span&gt;&lt;span&gt;&lt;a class="mentioned-user" href="https://dev.to/timothy"&gt;@timothy&lt;/a&gt;.Leavitt&lt;/span&gt;&lt;/span&gt;'s &lt;a href="https://github.com/intersystems-community/iris-agentic-dev/tree/master/skills/skills/objectscript-review" rel="noopener nofollow noreferrer"&gt;objectscript-review&lt;/a&gt;, only includes a checklist of the 10 most common ObjectScript errors, and yet dramatically includes benchmark performance. This skill obviously required extensive ObjectScript knowledge in the first place, alongside experience reviewing agent written ObjectScript to know the most common mistakes. But the benefit is the agent should now correct these mistakes itself, and the future code it produces will be better as a result.&lt;/p&gt;
&lt;p&gt;Skills are reusable pockets of information. If you have a repeatable task an agent struggles with, let it struggle the first time, then when it gets to the right answer (maybe with your guidance) say "write a skill that covers the mistakes you've made here". I recommend reviewing and editing the generated skills carefully because agents tend to put in project specific information which doesn't generalise. But as long as the problems can be generalisable, the solutions can be reusable. Next time, the agents won't make the same mistakes (...&lt;em&gt;probably&lt;/em&gt;, there's no certainty with non-deterministic models).&lt;/p&gt;
&lt;h2&gt;Improving your agent's toolset&lt;/h2&gt;
&lt;p&gt;Skills are great to prevent repeated errors, but they don't help the agent actually do things for you. This is where tools come in. Tools, in the context of AI agents, are functions which the agent can call using structured (JSON) responses. The standard protocol for adding tools to agents is the Model Context Protocol (MCP). This has been written about extensively on the community including articles by &lt;a href="https://community.intersystems.com/post/introduction-ai-hub-part-2-custom-mcp-servers" rel="noopener nofollow noreferrer"&gt;me&lt;/a&gt;, &lt;a href="https://community.intersystems.com/post/model-context-protocol-mcp-intersystems-iris-zero-hero" rel="noopener nofollow noreferrer"&gt;Pietro Di Leo&lt;/a&gt; and &lt;a href="https://community.intersystems.com/post/iris-agentic-dev-give-your-ai-live-connection-iris-part-1-problem-tool-and-getting-started" rel="noopener nofollow noreferrer"&gt;Tom&lt;/a&gt;. I will also once again plug this &lt;a href="https://www.youtube.com/watch?v=pieK0dog66Q" rel="noopener noreferrer"&gt;great intro video&lt;/a&gt; from InterSystems President Don Woodlock.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;MCP servers can be used to expose business logic to external agents or connectors (see my &lt;a href="https://community.intersystems.com/post/introduction-ai-hub-part-2-custom-mcp-servers" rel="noopener nofollow noreferrer"&gt;introduction to MCP servers in AI Hub&lt;/a&gt;). However, to date at least, MCP servers are most commonly used as developer tools. This is the context I want to discuss here.&lt;/p&gt;
&lt;p&gt;If you tell an LLM to write some ObjectScript using an obscure class, it will likely hallucinate how a statistically average version of how the class might look. Instead, if you give an agent with access to a tool to search the class reference documentation, it will likely look up the proper syntax before writing. If you give it access to an ObjectScript shell (or execution environment), it may even test the function syntax before writing.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Try to imagine you were asked to code something from scratch using an obscure library and think about what you would need to do a good job. It would probably include documentation and a feedback loop to test the code you've written. Coding agents require exactly the same. &amp;nbsp;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This is the value of MCP servers. Agents can autonomously search for proper syntax, test functions and code, and try to compile classes. When they see an error, they can read the error trace and investigate. They can execute more code to find the bugs, and search for other classes for proper syntax.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/intersystems-community/iris-agentic-dev" rel="noopener nofollow noreferrer"&gt;iris-agentic-dev&lt;/a&gt; is an MCP server which provides &lt;a href="https://github.com/intersystems-community/iris-agentic-dev/blob/master/docs/tools.md" rel="noopener nofollow noreferrer"&gt;many, many tools&lt;/a&gt; to do different functions in IRIS. If you are a system administrator managing multiple IRIS servers, it has tools to view and edit the roles, servers and logs. If you are a developer, the tools to execute ObjectScript, search existing classes or read production logs, might be more relevant to you.&lt;/p&gt;
&lt;h2&gt;Customisation&lt;/h2&gt;
&lt;p&gt;Many people have their own golden rules for using AI coding agents, like "tests are essential", "create and review specification docs before touching code", "always review the output", "anything intended to be read by a human should be written by a human". These are all valid (and good advice), but for me, there's one golden rule that is more important.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Customisation is key.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;For agents to be useful for your use case, they need to be customised to your use case. Let's look at some ways to do this.&amp;nbsp;&lt;/p&gt;
&lt;h4&gt;Skills&lt;/h4&gt;
&lt;p&gt;Skills are a great way to customise. I have a library of agent skills to cover my own preferences. For example I have skills make code more readable, to write check scripts for interactive tutorials, and to use PyProd to write productions in Python (this one has made it into &lt;a href="" rel="noopener nofollow"&gt;iris-agentic-dev's skill library&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I also have skills which other's have recommended, e.g. &lt;a href="https://github.com/obra/superpowers" rel="noopener nofollow noreferrer"&gt;obra/superpowers&lt;/a&gt;, that have been customised to my preferences. They are just markdown files after all. Take the bits you like, and add/remove sections to get the instructions right for your work.&lt;/p&gt;
&lt;h4&gt;iris-agentic-dev&lt;/h4&gt;
&lt;p&gt;For iris-agentic-dev, this customisation may come in the form of restricting tools that aren't generally useful to you with the &lt;code&gt;IRIS_DISABLED_TOOLS&lt;/code&gt; argument. For example, I have turned off server-side source control tools because I always do source control on the client side, and otherwise agents see it and think that means they should use it. I've also turned off the system admin tools because 95% of my IRIS usage is done with SuperUser so role management is totally not required for my use case. For others, managing different users and using server-side source control will be incredibly valuable whereas giving an agent execution power might be too risky, so they might make the opposite decisions. &amp;nbsp;&lt;/p&gt;
&lt;p&gt;I've also created custom iris-agentic-dev instructions in the form of a skill, with the description "Use this skill when you want to connect to a running IRIS instance". This skill includes the line &lt;code&gt;VS CODE COMPILES THE CLASS ON SAVE, YOU DO NOT NEED TO MANUALLY COMPILE CLASSES&lt;/code&gt;, because I've watched agents go in circles trying to copy a file into a docker container then compile it, only to find out its "suspiciously" already up-to-date. This use case-specific customisation informed by watching agents proves valuable in the long term.&lt;/p&gt;
&lt;h4&gt;AGENTS.MD&lt;/h4&gt;
&lt;p&gt;Finally, you can also customise your agents with AGENTS.MD (or CLAUDE.MD) files, either at a project root or the global config. The contents of these are automatically sent to the agent with every new conversation.&lt;/p&gt;
&lt;p&gt;Project AGENTS.MD are good for project specific information, e.g. &lt;code&gt;This project does x, the source files are in ./src/package, port 52773 on IRIS is mapped to port 62783&lt;/code&gt;. That way, each new conversation I have with an agent doesn't require an introduction, and the agent doesn't have to search through the whole project to find the relevant files.&lt;/p&gt;
&lt;p&gt;Global versions are for rules you want the agent always to abide, for example my global CLAUDE.MD includes &lt;code&gt;NEVER USE EMOJIS ANYWHERE NOT EVEN TICK MARKS&lt;/code&gt; about 3 times. I've also included some more useful guidance, e.g. &lt;code&gt;Give a (very brief) explanation of tool calls or series of tool calls to ensure I can follow what you are trying to do&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;Conclusions&lt;/h4&gt;
&lt;p&gt;For any customisation, you need to try using agents. See what works and what doesn't, what an agent is good at and bad at. If an agent does something you don't like, add instructions not to do it again in one of the places mentioned above.&lt;/p&gt;
&lt;p&gt;Finally, in order to customise, you also need to know what your use case requires. You need to learn with the agent and know what you are directing the agent to do. There are times when letting the agent run autonomously is incredibly powerful, but in general, I prefer keeping a close eye on what the agent is doing, learning from the outputs, and course-correcting when it inevitably does something stupid.&lt;/p&gt;
&lt;h2&gt;Final notes&lt;/h2&gt;
&lt;p&gt;This article has been long, opinionated, and maybe a touch ramble-y, but I hope it has been interesting to some readers. If you would like to hear more on these topics, on opinionated guidance for using AI-assisted coding, or just need help getting started with skills, agents or iris-agentic-dev, feel free to reach out in the comments.&lt;/p&gt;


</description>
      <category>ai</category>
      <category>programming</category>
      <category>ux</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Pure Python Healthcare Interoperability Production with PyProd and the openFDA API</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Fri, 31 Jul 2026 20:45:47 +0000</pubDate>
      <link>https://dev.to/intersystems/building-a-pure-python-healthcare-interoperability-production-with-pyprod-and-the-openfda-api-28nl</link>
      <guid>https://dev.to/intersystems/building-a-pure-python-healthcare-interoperability-production-with-pyprod-and-the-openfda-api-28nl</guid>
      <description>&lt;p&gt;Hi Developers!&lt;/p&gt;
&lt;p&gt;In my previous article, &lt;a href="https://community.intersystems.com/post/45-second-production-testing-chatgpt%E2%80%99s-limits-intersystems-iris-and-pyprod" rel="noopener noreferrer"&gt;&lt;strong&gt;Electric Utility PyProd&lt;/strong&gt;&lt;/a&gt;, we built a simple interoperability production that processed CSV files from a local directory using the PyProd framework. While that example introduced the basic building blocks of a production implemented entirely in Python, real-world interoperability solutions often need to communicate with external systems.&lt;/p&gt;
&lt;p&gt;In this article, we'll build a more advanced production that retrieves healthcare data from the public &lt;a href="https://api.fda.gov/drug/event.json" rel="noopener noreferrer"&gt;&lt;strong&gt;openFDA Drug Adverse Event API&lt;/strong&gt;&lt;/a&gt;, analyzes the returned data, and stores healthcare analytics in InterSystems IRIS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkljhunyf57bgjx8f440e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkljhunyf57bgjx8f440e.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Along the way, we'll see how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;call an external REST API from a PyProd Inbound Adapter&lt;/li&gt;
&lt;li&gt;work with large JSON payloads efficiently&lt;/li&gt;
&lt;li&gt;perform batch-level healthcare analytics&lt;/li&gt;
&lt;li&gt;persist the results in an IRIS SQL table&lt;/li&gt;
&lt;li&gt;implement an entire interoperability production using only Python&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Setting up&lt;/h1&gt;
&lt;p&gt;Clone the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone &lt;a href="https://github.com/Gra-ach/openfda-healthcare-pyprod.git" rel="noopener noreferrer"&gt;https://github.com/Gra-ach/openfda-healthcare-pyprod.git&lt;/a&gt;&lt;br&gt;
cd openfda-healthcare-pyprod&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the container:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up --build -d&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the container is running, load the production:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;intersystems_pyprod src/healthcare-openfda-pyprod/openfda_adverse_events.py&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Step 1 – Create the Inbound Adapter&lt;/h1&gt;
&lt;p&gt;Unlike the previous example, this production doesn't wait for files to appear in a directory. Instead, the custom &lt;code&gt;OpenFDAInboundAdapter&lt;/code&gt; periodically connects to the public &lt;code&gt;openFDA Drug Adverse Event API&lt;/code&gt;. Rather than querying the same medication every time, the adapter rotates through a configurable list of common medications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ASPIRIN&lt;br&gt;
IBUPROFEN&lt;br&gt;
ACETAMINOPHEN&lt;br&gt;
NAPROXEN&lt;br&gt;
LORATADINE&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each polling cycle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;selects the next medication&lt;/li&gt;
&lt;li&gt;builds the REST query&lt;/li&gt;
&lt;li&gt;downloads the latest adverse-event reports&lt;/li&gt;
&lt;li&gt;saves the response as a JSON file&lt;/li&gt;
&lt;li&gt;sends a lightweight production message&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Saving the payload to disk instead of passing it through the production keeps messages small and avoids IRIS string-size limitations.&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;class OpenFDAInboundAdapter(InboundAdapter):
    api_base_url: str = IRISProperty(
        description="openFDA Drug Adverse Event API URL",
        settings="API Settings"
    )
    result_limit: int = IRISProperty(
        description="Number of records to retrieve per poll",
        settings="API Settings"
    )
    api_key: str = IRISProperty(
        description="Optional openFDA API key",
        settings="API Settings"
    )
    payload_dir: str = IRISProperty(
        description="Directory where raw openFDA JSON payloads are saved",
        settings="File Settings"
    )
    medication_names: str = IRISProperty(
        description="Comma-separated medication names to rotate through",
        settings="API Settings"
    )  

    def on_task(self):
        os.makedirs(self.payload_dir, exist_ok=True)

        meds = [
            med.strip().upper()
            for med in str(self.medication_names or "").split(",")
                if med.strip()
        ]

        if not meds:
            meds = ["ASPIRIN", "IBUPROFEN", "ACETAMINOPHEN", "NAPROXEN", "LORATADINE"]

        state_file = f"{OPENFDA_ROOT}/last_med_index.txt"
        os.makedirs(OPENFDA_ROOT, exist_ok=True)

        try:
            with open(state_file, "r", encoding="utf-8") as f:
                last_index = int(f.read().strip())
        except Exception:
            last_index = -1

        next_index = (last_index + 1) % len(meds)
        selected_med = meds[next_index]

        with open(state_file, "w", encoding="utf-8") as f:
            f.write(str(next_index))

        search_query = f'patient.drug.medicinalproduct:"{selected_med}"'

        params = {
            "search": search_query,
            "limit": int(self.result_limit or 50),
            "sort": "receiptdate:desc",
        }
        if self.api_key:
            params["api_key"] = self.api_key

        url = f"{self.api_base_url}?{urllib.parse.urlencode(params)}"
        pulled_at = datetime.utcnow().isoformat(timespec="seconds")
        safe_timestamp = pulled_at.replace(":", "").replace("-", "")
        pulled_at = pulled_at.replace("T", " ")
        payload_file_path = os.path.join(
            self.payload_dir,
            f"openfda_adverse_events_{safe_timestamp}.json",
        )

        IRISLog.Info(f"Calling openFDA API: {url}")

        try:
            with urllib.request.urlopen(url, timeout=30) as response:
                payload_bytes = response.read()

            with open(payload_file_path, "wb") as payload_file:
                payload_file.write(payload_bytes)
        except Exception as ex:
            IRISLog.Error(f"openFDA API call or payload write failed: {ex}")
            return Status.Error()

        msg = AdverseEventFileMessage(
            api_url=url,
            search_query=search_query,
            pulled_at=pulled_at,
            payload_file_path=payload_file_path,
            medicine = selected_med
        )
        self.business_host_process_input(msg)
        return Status.OK()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see the events in the log for the inbound adapter, each time info about a different medicine is requested:&lt;/p&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/yben0tbb988bqfkld7xd.png)

&lt;h1&gt;Step 2 – Create the Business Service&lt;/h1&gt;
&lt;p&gt;The Business Service is intentionally simple. Its only responsibility is forwarding the metadata produced by the adapter to the Business Process.&lt;/p&gt;
&lt;p&gt;Notice that we're no longer passing the entire JSON document between production components. The message &lt;code&gt;AdverseEventFileMessage&lt;/code&gt; that is forwarded between the &lt;code&gt;OpenFDAEventService&lt;/code&gt; and &lt;code&gt;OpenFDAAnalysisProcess&lt;/code&gt; &amp;nbsp;contains only:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API URL&lt;/li&gt;
&lt;li&gt;search query&lt;/li&gt;
&lt;li&gt;selected medication&lt;/li&gt;
&lt;li&gt;timestamp&lt;/li&gt;
&lt;li&gt;payload file path&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class AdverseEventFileMessage(JsonSerialize): 
    api_url: str = Column() 
    search_query: str = Column() 
    pulled_at: str = Column() 
    payload_file_path: str = Column() 
    medicine: str = Column()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps production efficient while preserving access to the original API response.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class OpenFDAEventService(BusinessService): 
    ADAPTER: str = IRISParameter( 
        value="HealthOps.OpenFDAInboundAdapter", 
        description="Pure Python CSV polling adapter" 
    ) 
    process_target: str = IRISProperty( 
        description="Business process target", 
        settings="Target Settings" 
    ) 

    def on_process_input(self, input): 
        return self.send_request_async(self.process_target, input)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are the messages in the Message Viewer:&lt;/p&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/bx3sbri80t9ugrl041q4.png)
&lt;h1&gt;Step 3 – Create the Business Process&lt;/h1&gt;
&lt;p&gt;The Business Process performs the analytics. Using the payload file path, it reloads the JSON response and analyzes the complete batch of adverse-event reports.&lt;/p&gt;
&lt;p&gt;For every API batch it calculates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;total number of reports&lt;/li&gt;
&lt;li&gt;serious adverse-event count&lt;/li&gt;
&lt;li&gt;serious adverse-event percentage&lt;/li&gt;
&lt;li&gt;death count&lt;/li&gt;
&lt;li&gt;hospitalization count&lt;/li&gt;
&lt;li&gt;average patient age&lt;/li&gt;
&lt;li&gt;most frequently reported reaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike the Electric Utility example, which generated one database row per input record, this production generates one analytics message representing the entire batch.&lt;/p&gt;
&lt;p&gt;This greatly reduces message traffic while still preserving all meaningful statistics.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class OpenFDAAnalysisProcess(BusinessProcess): 
    operation_target: str = IRISProperty( 
        description="Operation that persists rows and analysis results", 
        settings="Target Settings" 
    ) 

    def _age_to_years(self, age_value, age_unit) -&amp;gt; Optional[float]: 
        if age_value in ("", None): 
            return None 
        try: 
            age = float(age_value) 
        except Exception: 
            return None 
        unit = str(age_unit or "") 
        if unit == "801": 
            return age 
        if unit == "802": 
            return age / 12.0 
        if unit == "803": 
            return age / 52.0 
        if unit == "804": 
            return age / 365.0 
        if unit == "805": 
            return age / 8760.0 
        return age 

    def _load_records(self, payload_file_path: str): 
        with open(payload_file_path, "r", encoding="utf-8") as payload_file: 
            payload = json.load(payload_file) 
        return payload.get("results", []) 

    def on_request(self, request): 
        try: 
            records = self._load_records(request.payload_file_path) 
        except Exception as ex: 
            IRISLog.Error(f"Failed to read openFDA payload file {request.payload_file_path}: {ex}") 
            return Status.Error() 

        record_count = len(records) 
        serious_count = 0 
        death_count = 0 
        hospitalization_count = 0 
        ages: List[float] = [] 
        reaction_counts: Dict[str, int] = {} 

        for record in records: 
            if str(record.get("serious", "")) == "1": 
                serious_count += 1 
            if str(record.get("seriousnessdeath", "")) == "1": 
                death_count += 1 
            if str(record.get("seriousnesshospitalization", "")) == "1": 
                hospitalization_count += 1 

            patient = record.get("patient", {}) or {} 

            age_years = self._age_to_years( 
                patient.get("patientonsetage"), 
                patient.get("patientonsetageunit"), 
            ) 
            if age_years is not None: 
                ages.append(age_years) 

            for reaction in patient.get("reaction", []) or []: 
                term = reaction.get("reactionmeddrapt") 
                if term: 
                    reaction_counts[term] = reaction_counts.get(term, 0) + 1 

        serious_rate_pct = round((serious_count / record_count) * 100, 2) if record_count else 0.0 
        avg_patient_age_years = round(sum(ages) / len(ages), 2) if ages else 0.0 

        top_reaction = "" 
        top_reaction_count = 0 
        if reaction_counts: 
            top_reaction, top_reaction_count = max(reaction_counts.items(), key=lambda item: item[1]) 

        analysis = AdverseEventAnalysisMessage( 
            api_url=request.api_url, 
            search_query=request.search_query, 
            pulled_at=request.pulled_at, 
            payload_file_path=request.payload_file_path, 
            record_count=record_count, 
            serious_count=serious_count, 
            serious_rate_pct=serious_rate_pct, 
            death_count=death_count, 
            hospitalization_count=hospitalization_count, 
            avg_patient_age_years=avg_patient_age_years, 
            top_reaction=top_reaction, 
            top_reaction_count=top_reaction_count, 
            medicine = request.medicine 
        ) 

        IRISLog.Info( 
            f"Batch analysis complete: " 
            f"medicine={request.medicine}, " 
            f"records={record_count}, " 
            f"serious={serious_count}, " 
            f"serious_rate={serious_rate_pct}%, " 
            f"deaths={death_count}, " 
            f"hospitalizations={hospitalization_count}, " 
            f"avg_age={avg_patient_age_years}, " 
            f"top_reaction={top_reaction}" 
        ) 

        return self.send_request_async(self.operation_target, analysis, response_required=0)&lt;/code&gt;
&lt;/pre&gt;

&lt;p&gt;Here we can see the incoming and outgoing messages for the Business Process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fck26qnrzhm0xv6wkmmco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fck26qnrzhm0xv6wkmmco.png" alt=" " width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the trace of messages for the whole cycle:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzii2razntjrbu93fbk3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzii2razntjrbu93fbk3i.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The message &lt;code&gt;AdverseEventAnalysisMessage&lt;/code&gt; contains aggregated info about the received info about the concrete medicine:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class AdverseEventAnalysisMessage(JsonSerialize): 
    api_url: str = Column() 
    search_query: str = Column() 
    pulled_at: str = Column() 
    payload_file_path: str = Column() 
    record_count: int = Column() 
    serious_count: int = Column() 
    serious_rate_pct: float = Column() 
    death_count: int = Column() 
    hospitalization_count: int = Column() 
    avg_patient_age_years: float = Column() 
    top_reaction: str = Column() 
    top_reaction_count: int = Column() 
    medicine: str = Column()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here's the log of the Business Process with the results of calculations:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv4uuwkfct3i9q5h3mwvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv4uuwkfct3i9q5h3mwvs.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;Step 4 – Create the Business Operation&lt;/h1&gt;
&lt;p&gt;The Business Operation receives the batch analysis and stores it in InterSystems IRIS. If the SQL table doesn't exist yet, it is created automatically.&lt;/p&gt;
&lt;p&gt;Each API request produces one row containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;medication name&lt;/li&gt;
&lt;li&gt;search query&lt;/li&gt;
&lt;li&gt;API URL&lt;/li&gt;
&lt;li&gt;payload file path&lt;/li&gt;
&lt;li&gt;number of retrieved reports&lt;/li&gt;
&lt;li&gt;serious-event statistics&lt;/li&gt;
&lt;li&gt;hospitalization statistics&lt;/li&gt;
&lt;li&gt;average patient age&lt;/li&gt;
&lt;li&gt;top reported reaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the insert succeeds, the JSON payload is moved into the archive directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class OpenFDADBOperation(BusinessOperation): 
    archive_payload_dir: str = IRISProperty( 
        description="Directory where successfully processed JSON payloads are archived", 
        settings="Operation Settings" 
    ) 

    message_map = { 
        f"{iris_package_name}.AdverseEventAnalysisMessage": "save_events" 
    } 

    def _ensure_table(self): 
        ddl = """ 
        CREATE TABLE IF NOT EXISTS HealthOps.OpenFDAAdverseEvents ( 
            id INTEGER IDENTITY PRIMARY KEY,             
            payload_file_path VARCHAR(1000), 
            api_url LONGVARCHAR, 
            search_query VARCHAR(1000), 
            pulled_at TIMESTAMP, 
            batch_record_count INTEGER, 
            batch_serious_count INTEGER, 
            batch_serious_rate_pct NUMERIC(6,2), 
            batch_death_count INTEGER, 
            batch_hospitalization_count INTEGER, 
            batch_avg_patient_age_years NUMERIC(10,2), 
            batch_top_reaction VARCHAR(255), 
            batch_top_reaction_count INTEGER, 
            medicine VARCHAR(60), 
            inserted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 
        ) 
        """ 
        iris.sql.prepare(ddl).execute()     
    
    def save_events(self, request): 
        self._ensure_table()       

        insert_sql = """ 
        INSERT INTO HealthOps.OpenFDAAdverseEvents ( 
            payload_file_path, 
            api_url, search_query, pulled_at, batch_record_count, 
            batch_serious_count, batch_serious_rate_pct, batch_death_count, 
            batch_hospitalization_count, batch_avg_patient_age_years, 
            batch_top_reaction, batch_top_reaction_count, medicine 
        ) 
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 
        """ 

        stmt = iris.sql.prepare(insert_sql) 
        
        stmt.execute( 
            request.payload_file_path, 
            request.api_url, 
            request.search_query, 
            request.pulled_at, 
            int(request.record_count), 
            int(request.serious_count), 
            float(request.serious_rate_pct), 
            int(request.death_count), 
            int(request.hospitalization_count), 
            float(request.avg_patient_age_years), 
            request.top_reaction, 
            int(request.top_reaction_count), 
            request.medicine 
        ) 
        
        os.makedirs(self.archive_payload_dir, exist_ok=True) 
        archive_path = os.path.join(self.archive_payload_dir, os.path.basename(request.payload_file_path)) 
        try: 
            os.replace(request.payload_file_path, archive_path) 
        except Exception as ex: 
            IRISLog.Warning( 
                f"Inserted records but could not archive payload file {request.payload_file_path}: {ex}" 
            ) 

        IRISLog.Info( 
            f"Inserted info about {request.medicine}: " 
            f"serious_rate={request.serious_rate_pct}%, " 
            f"death_count={request.death_count}, " 
            f"hospitalization_count={request.hospitalization_count}, " 
            f"avg_age={request.avg_patient_age_years}, " 
            f"top_reaction={request.top_reaction}" 
        ) 
        return Status.OK()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here's the log of the Business Operation:&lt;/p&gt;


![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xt1y9jjnlei5kkg50es2.png)

&lt;h1&gt;Step 5 – Define the Production&lt;/h1&gt;

&lt;p&gt;As with the previous example, the entire production is assembled directly in Python. It consists of:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OpenFDAInboundAdapter
        ↓
OpenFDAEventService
        ↓
OpenFDAAnalysisProcess
        ↓
OpenFDADBOperation&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because everything is defined in Python, no manual production configuration is required after loading the project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class OpenFDAHealthcareProduction(Production): 
    services = [ 
        ServiceItem( 
            "OpenFDAEventService", 
            "HealthOps.OpenFDAEventService", 
            host_settings={"process_target": "OpenFDAAnalysisProcess"}, 
            adapter_settings={ 
                "api_base_url": "https://api.fda.gov/drug/event.json", 
                "medication_names": "ASPIRIN,IBUPROFEN,ACETAMINOPHEN,NAPROXEN,LORATADINE", 
                "result_limit": 30, 
                "api_key": "", 
                "payload_dir": f"{OPENFDA_ROOT}/payloads" 
            } 
        ) 
    ] 
    processes = [ 
        ProcessItem( 
            "OpenFDAAnalysisProcess", 
            "HealthOps.OpenFDAAnalysisProcess", 
            host_settings={"operation_target": "OpenFDADBOperation"} 
        ) 
    ] 
    operations = [ 
        OperationItem( 
            "OpenFDADBOperation", 
            "HealthOps.OpenFDADBOperation", 
            host_settings={"archive_payload_dir": f"{OPENFDA_ROOT}/archive"} 
        ) 
    ] &lt;/code&gt;&lt;/pre&gt;

&lt;h1&gt;Step 6 – Run the Production&lt;/h1&gt;

&lt;p&gt;Start the production:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;python controls.py start HealthOps.OpenFDAHealthcareProduction
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Or from the IRIS terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zn "ENSEMBLE"
do ##class(Ens.Director).StartProduction("HealthOps.OpenFDAHealthcareProduction")
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or in the UI in Management Portal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkts1of3ei3yv2fh1hmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkts1of3ei3yv2fh1hmr.png" alt=" " width="799" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every polling cycle automatically selects the next medication and downloads a fresh batch of adverse-event reports.&lt;/p&gt;

&lt;h1&gt;Results&lt;/h1&gt;

&lt;p&gt;The production stores one summary row for every API call. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT
    medicine,
    batch_record_count,
    batch_serious_rate_pct,
    batch_death_count,
    batch_hospitalization_count,
    batch_avg_patient_age_years,
    batch_top_reaction
FROM HealthOps.OpenFDAAdverseEvents
ORDER BY inserted_at DESC
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fze5knfkrywb8g8bxsnlr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fze5knfkrywb8g8bxsnlr.png" alt=" " width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This makes it easy to compare medications over time and observe trends without storing thousands of individual adverse-event records.&lt;/p&gt;
&lt;h1&gt;A Note About Large JSON Payloads&lt;/h1&gt;
&lt;p&gt;One interesting challenge when integrating with public APIs is message size. Initially, I passed the complete JSON response between production components. While this works for small payloads, larger responses can exceed IRIS string limits and I got the &amp;lt;MAXSTRING&amp;gt; error. The solution used in this project is to store the API response as a JSON file and send only its location through the production.&lt;/p&gt;
&lt;p&gt;This approach has several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;avoids large interoperability messages&lt;/li&gt;
&lt;li&gt;preserves the original API response&lt;/li&gt;
&lt;li&gt;makes troubleshooting easier&lt;/li&gt;
&lt;li&gt;keeps the production lightweight&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's a useful design pattern whenever you're processing large REST responses or documents.&lt;/p&gt;
&lt;h1&gt;Summary&lt;/h1&gt;
&lt;p&gt;In this article, we've built a complete healthcare interoperability production in pure Python using PyProd.&lt;/p&gt;
&lt;p&gt;Compared to the previous &lt;strong&gt;Electric Utility PyProd&lt;/strong&gt; sample, this project demonstrates a more realistic integration scenario by communicating with an external REST service instead of processing local files.&lt;/p&gt;

&lt;p&gt;Along the way, we've seen how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;integrate with the public openFDA API&lt;/li&gt;
&lt;li&gt;rotate automatically between multiple medications&lt;/li&gt;
&lt;li&gt;process large JSON payloads efficiently&lt;/li&gt;
&lt;li&gt;calculate batch-level healthcare analytics&lt;/li&gt;
&lt;li&gt;archive source payloads&lt;/li&gt;
&lt;li&gt;create SQL tables automatically&lt;/li&gt;
&lt;li&gt;build an entire InterSystems IRIS production using only Python&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Although this example focuses on the openFDA API, the same architecture can easily be adapted to FHIR servers, public health services, hospital APIs, or any REST-based healthcare integration.&lt;/p&gt;

&lt;p&gt;I hope this project serves as a useful starting point for building more advanced Python interoperability solutions with PyProd.&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>sql</category>
    </item>
    <item>
      <title>45-Second Production: Testing ChatGPT’s Limits with InterSystems IRIS and PyProd</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Tue, 28 Jul 2026 19:12:16 +0000</pubDate>
      <link>https://dev.to/intersystems/45-second-production-testing-chatgpts-limits-with-intersystems-iris-and-pyprod-5a34</link>
      <guid>https://dev.to/intersystems/45-second-production-testing-chatgpts-limits-with-intersystems-iris-and-pyprod-5a34</guid>
      <description>&lt;p&gt;It all started on a train ride to visit my parents, while I was chatting with a neighbor in my compartment. As it usually goes, the talk turned to technology, and she threw out a highly specific question: *Could ChatGPT be used to analyze the human genome?* I was highly skeptical that it could pull off something that complex. But the question lingered, burrowing into my mind. By the time I walked through my front door, my skepticism had transformed into a challenge. I didn't have a genome sequencing dataset on hand, but I did want to see if standard ChatGPT could build a functional Interoperability Production from scratch using the &lt;a href="https://github.com/intersystems/pyprod" rel="noopener noreferrer"&gt;PyProd package&lt;/a&gt;. Besides, that would give me the chance to participate in the &lt;a href="https://community.intersystems.com/post/community-bounty-program-idea-application-%E2%80%94-round-1-live" rel="noopener noreferrer"&gt;1st round&lt;/a&gt; of the &lt;a href="https://community.intersystems.com/post/introducing-community-bounty-program-%E2%80%9Cidea-application%E2%80%9D" rel="noopener noreferrer"&gt;&lt;strong&gt;Community Bounty Program "Idea to Application"&lt;/strong&gt;&lt;/a&gt; implementing the &lt;a href="https://ideas.intersystems.com/ideas/DPI-I-955" rel="noopener noreferrer"&gt;third idea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc3v86j0fo640y2dbpjod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc3v86j0fo640y2dbpjod.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided to test it with a multi-step prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using info from the following articles and github repository, write code for a complete InterSystems Production in Python using PyProd package:&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 1: come up with the domain for this production&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 2: create 4 csv files with 30 records in each in the step 1 domain&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 3: write sql create table statement with the structure from csv file&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 4: write inbound adapter in Python using PyProd package that reads the file&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 5: write a business process in Python that analyzes the structure of the step 2 file and makes 1 calculation that makes sense in the step 1 domain&amp;nbsp;&lt;/p&gt;
&lt;p&gt;step 6: write business operation in Python to save all the data read in 4 step and the result of calculation from 5 step to the table created in 3 step&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here are the articles and github repositories you should use:&amp;nbsp;&lt;/p&gt;
&lt;p&gt;https://community.intersystems.com/post/pyprod-pure-python-iris-interoperability&amp;nbsp;&lt;/p&gt;
&lt;p&gt;https://community.intersystems.com/post/pyprod-creating-iris-interoperability-productions-programmatically-python&amp;nbsp;&lt;/p&gt;
&lt;p&gt;https://community.intersystems.com/post/csvgen-pyprod https://github.com/intersystems/pyprod&amp;nbsp;&lt;/p&gt;
&lt;p&gt;https://github.com/gabriel-ing/csvgen-pyprod&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I fed it all to ChatGPT and hit send. Then, I waited. For exactly 45 seconds.&lt;/p&gt;
&lt;h3&gt;The Delivery and the "Gotchas"&lt;/h3&gt;
&lt;p&gt;As a result, I got a &lt;code&gt;smart_grid_pyprod.zip&lt;/code&gt; containing the ready-to-use code. Naturally, I was dying to know whether it actually worked or was just a convincing hallucination. Playing the part of a complete novice, I asked it how to set everything up. ChatGPT promptly walked me through all necessary steps.&lt;/p&gt;
&lt;p&gt;1. Enabling interoperability in the `USER` namespace via the IRIS Terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zn "%SYS"
do ##class(%EnsembleMgr).EnableNamespace("USER")&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2. Configuring the environment variables for Windows 11 and installing the package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set IRISINSTALLDIR=C:\InterSystems\IRIS
set IRISUSERNAME=SuperUser
set IRISPASSWORD=SYS
set IRISNAMESPACE=USER
set PATH=%IRISINSTALLDIR%\mgr\python;%PATH%
python -m pip install intersystems_pyprod --target %IRISINSTALLDIR%\mgr\python --upgrade&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3. Compile the generated code using &lt;code&gt;intersystems_pyprod smart_grid.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;4. Open the Management Portal, and start the production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did it work right out of the box? Well, no.&lt;/strong&gt; Mostly, the problems were about commas where they shouldn’t be and a couple of wrongly written async requests, among other things. It took me about half a day to iron out the creases (granted, I was watching &lt;em&gt;Landman &lt;/em&gt;in parallel while waiting for Docker to build, turned out to be quite apropos - pumping oil and electricity 😉). But, thanks to some basic Python knowledge and a &lt;a href="https://github.com/gabriel-ing/csvgen-pyprod" rel="noopener noreferrer"&gt;crucial reference example&lt;/a&gt; from &lt;span&gt;&lt;span&gt;&lt;a class="mentioned-user" href="https://dev.to/gabriel"&gt;@gabriel&lt;/a&gt;.Ing&lt;/span&gt;&lt;/span&gt;, I got it running.&lt;/p&gt;
&lt;h3&gt;Anatomy of an AI-Generated Production&lt;/h3&gt;
&lt;p&gt;Once the code was fixed, the production (mostly) written by ChatGPT functioned beautifully. It consists of three components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;business service&lt;/strong&gt;&lt;code&gt;SmartMeterFileService&lt;/code&gt; reads the input file&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;business process&lt;/strong&gt;&lt;code&gt;SmartMeterAnalysisProcess&lt;/code&gt; computes per-file total kWh, average kWh, peak meter id, and peak kWh&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;business operation&lt;/strong&gt;&lt;code&gt;SmartMeterDBOperation&lt;/code&gt; persists every CSV row with those calculation results&lt;/li&gt;
&lt;/ul&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/jmn3lraxygfa1sz48m84.png)&lt;p&gt;Looking at the &lt;strong&gt;Visual Trace&lt;/strong&gt; in the Management Portal, you can see messages flowing seamlessly from the service to the process and finally to the operation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhannf4qtab92i0m1dr8n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhannf4qtab92i0m1dr8n.png" alt=" " width="799" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when queried in SQL Explorer&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * 
  FROM EnergyOps.SmartMeterReadings&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the data was there, properly calculated, and perfectly structured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F32qp0nfi9qfz5g2lc6s3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F32qp0nfi9qfz5g2lc6s3.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;h3&gt;The Verdict: Almost a Success&lt;/h3&gt;
&lt;p&gt;I would call this experiment an &lt;strong&gt;almost success&lt;/strong&gt;. Almost, because it required me to know at least something about how it works, so the complete novice would be stumped (or would need to ask a lot of follow-up questions).&amp;nbsp;&lt;/p&gt;
&lt;p&gt;However, if you have a bit of foundational knowledge, a willingness to troubleshoot, and good community examples to lean on, you can make it work. It proves that while AI might not be ready to architect complex, enterprise-grade genome sequencing pipelines entirely on its own just yet, it is an incredible tool for taking an example and expanding on it to get a prototype off the ground.&lt;/p&gt;


</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Introducing the InterSystems IRIS Document Store for Haystack</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:34:15 +0000</pubDate>
      <link>https://dev.to/intersystems/introducing-the-intersystems-iris-document-store-for-haystack-16bf</link>
      <guid>https://dev.to/intersystems/introducing-the-intersystems-iris-document-store-for-haystack-16bf</guid>
      <description>&lt;p&gt;Artificial Intelligence applications are increasingly built around Retrieval-Augmented Generation (RAG), semantic search, and AI agents. As these applications move into production, choosing the right persistence layer becomes just as important as selecting the LLM.&lt;/p&gt;
&lt;p&gt;Today, I'm excited to announce the &lt;strong&gt;InterSystems IRIS Document Store for Haystack&lt;/strong&gt;, a new open-source integration that enables developers to use &lt;strong&gt;InterSystems IRIS&lt;/strong&gt; as a native Document Store within the Haystack AI framework.&lt;/p&gt;
&lt;h2&gt;Why Haystack?&lt;/h2&gt;
&lt;p&gt;Haystack has become one of the leading open-source frameworks for building production-ready AI applications. Its modular pipeline architecture makes it easy to create solutions for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Retrieval-Augmented Generation (RAG)&lt;/li&gt;
&lt;li&gt;Enterprise Search&lt;/li&gt;
&lt;li&gt;Question Answering&lt;/li&gt;
&lt;li&gt;AI Agents&lt;/li&gt;
&lt;li&gt;Semantic Search&lt;/li&gt;
&lt;li&gt;Knowledge Assistants&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Introducing the Integration&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;InterSystems IRIS Document Store&lt;/strong&gt; implements Haystack's Document Store interface, allowing it to integrate naturally into existing Haystack pipelines.&lt;/p&gt;
&lt;p&gt;Whether you're building a small proof of concept or a production RAG system, switching to IRIS as your persistence layer requires minimal changes to your application.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;The package is available on PyPI.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install intersystems-iris-haystack&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Official Haystack Integration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://haystack.deepset.ai/integrations/intersystems-iris-document-store" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://haystack.deepset.ai/integrations/intersystems-iris-document-store" rel="noopener noreferrer"&gt;https://haystack.deepset.ai/integrations/intersystems-iris-document-store&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/s-c-ai/iris-haystack" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://github.com/s-c-ai/iris-haystack" rel="noopener noreferrer"&gt;https://github.com/s-c-ai/iris-haystack&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyPI Package&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pypi.org/project/intersystems-iris-haystack/" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://pypi.org/project/intersystems-iris-haystack/" rel="noopener noreferrer"&gt;https://pypi.org/project/intersystems-iris-haystack/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Example Architecture&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;                 Haystack Pipeline

&lt;p&gt;Converter → Splitter → Embedder&lt;/p&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;            │
            ▼

  InterSystems IRIS Document Store

  • Documents
  • Metadata
  • Vector Search
  • SQL
  • Objects

            │
            ▼

  Retriever → LLM → Answer&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>sql</category>
      <category>github</category>
    </item>
    <item>
      <title>Long Running SQL Queries: a sample exploration</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Fri, 24 Jul 2026 15:59:40 +0000</pubDate>
      <link>https://dev.to/intersystems/long-running-sql-queries-a-sample-exploration-34b4</link>
      <guid>https://dev.to/intersystems/long-running-sql-queries-a-sample-exploration-34b4</guid>
      <description>&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Here at InterSystems, we often deal with massive datasets of structured data. It’s not uncommon to see customers with tables spanning &amp;gt;100 fields and &amp;gt;1 billion rows, each table totaling hundred of GB of data. Now imagine joining two or three of these tables together, with a schema that wasn’t optimized for this specific use case. Just for fun, let’s say you have 10 years worth of EMR data from 20 different hospitals across your state, and you’ve been tasked with finding….&lt;br&gt;&amp;nbsp; &amp;nbsp;every clinician within your network&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; who has administered a specific drug&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; between the years of 2017-2019&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;to patients who reside outside the state&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;and have one of the following conditions [diabetes, hypertension, asthma]&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where the cost was covered by Medicaid&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;I’ve seen our technology handle &lt;a href="https://www.intersystems.com/success-stories/a-billion-data-points-for-innovations-in-care-and-care-coordination/" rel="noopener noreferrer"&gt;these sort of cases just fine&lt;/a&gt;, but the query may still take a while to run. Can it be faster though? Let me walk you through a sample investigation. &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;///////////////////////////////////////////////////////////////////////////////////////////////&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;strong&gt;The Need:&lt;/strong&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Find all patients who have had an outpatient encounter at a facility located in one of these counties in the year 2022 or 2023&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;strong&gt;The Query:&lt;/strong&gt;&lt;br&gt;SELECT DISTINCT enc.Patient-&amp;gt;PatientNumber &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;FROM EMR.Encounter as enc&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;INNER JOIN State_Facility.Address as fa on enc.Facility = fa.FacilityCode&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;INNER JOIN State_Geography.Cities as city ON city.Zip = fa.ZipCode&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;WHERE enc.EncounterTime BETWEEN '2022-01-01' AND '2023-12-31'&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;AND enc.EncounterType IN ('OP','Outpatient','O')&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;AND city.County IN ('Los Angeles County', 'Orange County', 'Riverside County', 'San Bernardino County', 'Ventura County')&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;strong&gt;The Performance:&lt;/strong&gt;&lt;br&gt;The query was taking &amp;gt;24 hours to complete&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;INVESTIGATION STEPS:&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1) Review the tables that you’re querying. What relationships or foreign keys exist between them? What indices already exist? Is your SQL query making good use of the ones that already exist? Do the indices have Status = Selectable?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;We checked each field that was part of a WHERE, AND, or INNER JOIN. Most of them did have indices, including some bitmap indices. [NOTE: Further to the right of the screenshot page, the Status column shows that EncounterTypeIndex is Selectable]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fceqh5hpf4ydnwx2310b8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fceqh5hpf4ydnwx2310b8.png" alt=" " width="800" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2) Review the Query Plan. Does it make sense? Does it make use of the indices and relationships you expected it would? If not, does it seem more or less efficient?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Yes, the Query Plan showed effective use of the indices on EncounterType and StartTime. [NOTE: This screenshot is for a simplified version of the query that does not consider the zip code of the encounter facility]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzskc4vk2isx8kchxlq6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzskc4vk2isx8kchxlq6q.png" alt=" " width="799" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;3) Ensure the table statistics up to date by running &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GSOD_opttable" rel="noopener noreferrer"&gt;Tune Tables&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;strong&gt;4) Check whether the actual Query Plan at runtime matches the one you were shown. &lt;/strong&gt;The "Show Plan" Query Plan does not utilize the &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSOC_rtpc" rel="noopener noreferrer"&gt;Runtime Plan Choice (RTPC)&lt;/a&gt; optimization when it generates a Query Plan, but the RTPC is utilized when the query is actually run. That is why the Show Plan Query Plan and the runtime Query Plan can be different. The RTPC algorithm usually finds an optimal choice, but it can sometimes make a poor choice. If we find that the RTPC algorithm is making the wrong choice, it is possible to suppress the RTPC at runtime by using the &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_select#RSQL_select_args_keyword" rel="noopener noreferrer"&gt;%NORUNTIME keyword&lt;/a&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Once the query was running, we looked at the Processes page and found the process that was running the query. We found the cached query that it was running (the Routine). We went to that cached query and looked at its Query Plan. We found that it was using a Query Plan that was very different from the one we’d seem before, and it looked much less efficient.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fut51eitny82zkwr1pcy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fut51eitny82zkwr1pcy3.png" alt=" " width="799" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs951cou8nur54mt4rmzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs951cou8nur54mt4rmzp.png" alt=" " width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;b&gt;RECOMMENDATIONS:&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;We recommended that the customer take the following actions:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1) Use the &lt;a href="https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_select#RSQL_select_args_keyword" rel="noopener noreferrer"&gt;%NORUNTIME keyword&lt;/a&gt; when executing the query, forcing it to use the more efficient Query Plan&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;2) Build a new bitmap index called EncounterDate based on the EncounterTime field. Date-based indices can be faster than DateTime-based indices, and bitmap indices are often significantly faster than normal indices&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;Once they implemented these two recommendations, their query was now completing in ~6 hours, a 75% improvement.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;FURTHER READING:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check out &lt;a class="mentioned-user" href="https://dev.to/benjamin"&gt;@benjamin&lt;/a&gt;.Spead's excellent collection of resources, which includes links to&amp;nbsp;online documentation, InterSystems online learning courses, presentation slideshows, and Developer Community articles.&lt;br&gt;&lt;a href="https://community.intersystems.com/post/sql-performance-resources" rel="noopener noreferrer"&gt;https://community.intersystems.com/post/sql-performance-resources&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>performance</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Monitoring InterSystems IRIS with Prometheus and Grafana</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Wed, 22 Jul 2026 17:17:48 +0000</pubDate>
      <link>https://dev.to/intersystems/monitoring-intersystems-iris-with-prometheus-and-grafana-37hh</link>
      <guid>https://dev.to/intersystems/monitoring-intersystems-iris-with-prometheus-and-grafana-37hh</guid>
      <description>&lt;p&gt;Monitoring your IRIS deployment is crucial. With the deprecation of&amp;nbsp;&lt;strong&gt;System Alert and Monitoring (SAM),&lt;/strong&gt;&amp;nbsp;a modern, scalable solution is necessary for&amp;nbsp;&lt;strong&gt;real-time insights, early issue detection, and operational efficiency.&lt;/strong&gt;&amp;nbsp;This guide covers setting up&amp;nbsp;&lt;strong&gt;Prometheus and Grafana&lt;/strong&gt;&amp;nbsp;in Kubernetes to monitor&amp;nbsp;&lt;strong&gt;InterSystems IRIS&amp;nbsp;&lt;/strong&gt;effectively.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This guide assumes you already have an IRIS cluster deployed using the InterSystems Kubernetes Operator (IKO), which simplifies deployment, integration and mangement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwn8q07hsv4ubuvwxe6fl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwn8q07hsv4ubuvwxe6fl.png" alt=" " width="800" height="368"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;



&lt;h2&gt;&lt;strong&gt;Why Prometheus and Grafana?&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; and &lt;strong&gt;Grafana&lt;/strong&gt; are widely adopted tools for cloud-native monitoring and visualization. Here’s why they are a fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; Prometheus handles large-scale data ingestion efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting:&lt;/strong&gt; Customizable alerts via &lt;strong&gt;Prometheus Alertmanager.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visualization:&lt;/strong&gt; Grafana offers rich, customizable dashboards for Kubernetes metrics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ease of Integration:&lt;/strong&gt; Seamlessly integrates with &lt;strong&gt;Kubernetes workloads.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Before starting, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of Kubernetes and Linux&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;helm&lt;/code&gt; installed.&lt;/li&gt;
&lt;li&gt;Familiarity with Prometheus concepts (refer to the &lt;a href="https://prometheus.io/docs/introduction/overview/" rel="noopener noreferrer"&gt;Prometheus documantion&lt;/a&gt; for more information).&lt;/li&gt;
&lt;li&gt;A deployed IRIS instance using the &lt;strong&gt;InterSystems Kubernetes Operator (IKO)&lt;/strong&gt;, refer to another article &lt;a href="https://docs.intersystems.com/components/csp/docbook/DocBook.UI.Page.cls?KEY=AIKO" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;Step 1: Enable Metrics in InterSystems IRIS&lt;/h2&gt;

&lt;p&gt;InterSystems IRIS exposes metrics via &lt;code&gt;/api/monitor/&lt;/code&gt;&amp;nbsp;in the Prometheus format. Ensure this endpoint is enabled:&lt;/p&gt;

&lt;ol start="1"&gt;
&lt;li&gt;Open the Management Portal.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;System Administration &amp;gt; Security &amp;gt; Applications &amp;gt; Web Applications&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Ensure &lt;code&gt;/api/monitor/&lt;/code&gt; is enabled and accessible by Prometheus. You can check its status by navigating to the Management Portal, going to &lt;strong&gt;System Administration &amp;gt; Security &amp;gt; Applications &amp;gt; Web Applications&lt;/strong&gt;, and verifying that the endpoint is listed and enabled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify its availability by accessing:&lt;/p&gt;

&lt;pre&gt;http://&amp;lt;IRIS_HOST&amp;gt;:&amp;lt;PORT&amp;gt;/api/monitor/metrics&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xrra3jgxqgg2420kviw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xrra3jgxqgg2420kviw.png" alt=" " width="800" height="333"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;



&lt;h2&gt;&lt;strong&gt;Step 2: Deploy Prometheus Using Helm&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Deploying Prometheus using &lt;strong&gt;Helm&lt;/strong&gt; provides an easy-to-manage monitoring setup. We will use the &lt;code&gt;kube-prometheus-stack&lt;/code&gt; chart that includes Prometheus, Alertmanager, and Grafana.&lt;/p&gt;

&lt;ol start="1"&gt;
&lt;li&gt;
&lt;strong&gt;Prepare the configuration:&lt;/strong&gt; Create a &lt;code&gt;values.yaml&lt;/code&gt; file with the following settings:
&lt;pre&gt;prometheus:
&amp;nbsp; prometheusSpec:
&amp;nbsp; &amp;nbsp; additionalScrapeConfigs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; - job_name: 'intersystems_iris_metrics'
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; metrics_path: '/api/monitor/metrics'
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; static_configs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - targets:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - 'iris-app-compute-0.iris-svc.commerce.svc.cluster.local:80' # Replace with your IRIS service

      # To scrape custom metrics from the REST API created in IRIS
      - job_name: 'custom_iris_metrics'
        metrics_path: '/web/metrics'
        static_configs:
          - targets:
              - 'commerce-app-webgateway-0.iris-svc.commerce.svc.cluster.local:80'
        basic_auth:
          username: '_SYSTEM'
          password: 'SYS'&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explanation:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&lt;strong&gt;iris-app-compute-0.iris-svc.commerce.svc.cluster.local:80&lt;/strong&gt;&lt;/code&gt;: The format of the target should follow this convention: &lt;code&gt;&amp;lt;pod-name&amp;gt;-iris-svc.&amp;lt;namespace&amp;gt;.svc.cluster.local:80&lt;/code&gt;. Replace &lt;code&gt;&amp;lt;pod-name&amp;gt;&lt;/code&gt;&amp;nbsp;with your IRIS pod, specify whether you want to scrape &lt;code&gt;compute&lt;/code&gt; or &lt;code&gt;data&lt;/code&gt; pods, and adjust the namespace as needed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&lt;strong&gt;basic_auth&lt;/strong&gt;&lt;/code&gt;** section**: If authentication is required to access the IRIS metrics endpoint, provide the necessary credentials.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add the Helm repository:&lt;/strong&gt;
&lt;pre&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update&lt;/pre&gt;
&lt;/li&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uyfgv31f7g9hkvfegxoe.png) &amp;nbsp;
&lt;li&gt;
&lt;strong&gt;Install Prometheus using Helm:&lt;/strong&gt;
&lt;pre&gt;helm install monitoring prometheus-community/kube-prometheus-stack -n monitoring --create-namespace -f values.yaml&lt;/pre&gt;
&lt;/li&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/sktubx1bti39hvr574la.png)
&lt;li&gt;
&lt;strong&gt;Verify the deployment:&lt;/strong&gt;
&lt;pre&gt;kubectl get pods -n monitoring&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gw71yjc32t0091v91p4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gw71yjc32t0091v91p4.png" alt=" " width="666" height="194"&gt;&lt;/a&gt; &amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Step 3: Custom Metrics with REST API&lt;/h2&gt;

&lt;p&gt;You can create a&lt;strong&gt; custom metrics CSP&lt;/strong&gt; page that serves your application metrics. In this guide, I provide an example of a simple CSP page that extracts system metrics from IRIS itself, but you can totally build your own CSP page with your own custom metrics—just make sure they are in the &lt;strong&gt;Prometheus format.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;br&gt;
CustomMetrics.REST&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;span class="hljs-keyword"&gt;Class&lt;/span&gt;&amp;nbsp;CustomMetrics.REST&amp;nbsp;&lt;span class="hljs-keyword"&gt;Extends&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;%CSP.REST&lt;/span&gt;
{ &lt;span class="hljs-keyword"&gt;Parameter&lt;/span&gt;&amp;nbsp;HandleCorsRequest = &lt;span class="hljs-number"&gt;1&lt;/span&gt;&lt;span class="hljs-comment"&gt;; ClassMethod&amp;nbsp;Metrics() As&amp;nbsp;%Status&lt;/span&gt;
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Try&lt;/span&gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Do&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;%response.SetHeader&lt;/span&gt;(&lt;span class="hljs-string"&gt;"Content-Type"&lt;/span&gt;, &lt;span class="hljs-string"&gt;"text/plain; version=0.0.4; charset=utf-8"&lt;/span&gt;)
 &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;New&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;$Namespace&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Set&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;$Namespace&lt;/span&gt;&amp;nbsp;= &lt;span class="hljs-string"&gt;"%SYS"&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Set&lt;/span&gt;&amp;nbsp;ref&amp;nbsp;= &lt;span class="hljs-keyword"&gt;##class&lt;/span&gt;(SYS.Stats.Dashboard).Sample()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# HELP iris_license_high Peak number of licenses used"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# TYPE iris_license_high gauge"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"iris_license_high "&lt;/span&gt;, ref.LicenseHigh, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# HELP iris_active_processes Number of active processes"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# TYPE iris_active_processes gauge"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"iris_active_processes "&lt;/span&gt;, ref.Processes, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# HELP iris_application_errors Number of application errors"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"# TYPE iris_application_errors counter"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"iris_application_errors "&lt;/span&gt;, ref.ApplicationErrors, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Return&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;$$$OK&lt;/span&gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;&lt;span class="hljs-keyword"&gt;Catch&lt;/span&gt;&amp;nbsp;ex&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Do&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;%response.SetHeader&lt;/span&gt;(&lt;span class="hljs-string"&gt;"Content-Type"&lt;/span&gt;, &lt;span class="hljs-string"&gt;"text/plain"&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Write&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-string"&gt;"Internal Server Error"&lt;/span&gt;, &lt;span class="hljs-built_in"&gt;$CHAR&lt;/span&gt;(&lt;span class="hljs-number"&gt;10&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Do&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;$System&lt;/span&gt;.Status.DisplayError(ex.AsStatus())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;span class="hljs-keyword"&gt;Return&lt;/span&gt;&amp;nbsp;&lt;span class="hljs-built_in"&gt;$$$ERROR&lt;/span&gt;(&lt;span class="hljs-built_in"&gt;$$$GeneralError&lt;/span&gt;, &lt;span class="hljs-string"&gt;"Internal Server Error"&lt;/span&gt;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
} 
XData&amp;nbsp;UrlMap
{
&amp;lt;Routes&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Route&amp;nbsp;Url=&lt;span class="hljs-string"&gt;"/metrics"&lt;/span&gt;&amp;nbsp;Method=&lt;span class="hljs-string"&gt;"GET"&lt;/span&gt;&amp;nbsp;Call=&lt;span class="hljs-string"&gt;"Metrics"&lt;/span&gt;/&amp;gt;
&amp;lt;/Routes&amp;gt;
} 
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;




&lt;p&gt;Deploy this as a REST service under a new web application called &lt;code&gt;&lt;strong&gt;metrics&lt;/strong&gt;&lt;/code&gt; in IRIS, and add its path to Prometheus for scraping.&lt;/p&gt;





&lt;h2&gt;Step 4: Verify Prometheus Setup&lt;/h2&gt;

&lt;ol start="1"&gt;
&lt;li&gt;Open the Prometheus UI (&lt;code&gt;http://&amp;lt;PROMETHEUS_HOST&amp;gt;:9090&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Status &amp;gt; Targets&lt;/strong&gt; and confirm IRIS metrics are being scraped.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5jutrsrfns2sl4w8z0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh5jutrsrfns2sl4w8z0w.png" alt=" " width="800" height="209"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;



&lt;h3&gt;Step 5: Access Grafana&lt;/h3&gt;

&lt;p&gt;With Prometheus scraping IRIS metrics, the next step is to visualize the data using Grafana.&lt;/p&gt;

&lt;p&gt;1.&amp;nbsp;&lt;strong&gt;Retrieve the Grafana service details:&lt;/strong&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;kubectl get svc -n monitoring&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you’re using an ingress controller, you can access Grafana using the configured hostname (e.g., &lt;code&gt;http://grafana.example.com&lt;/code&gt;). Otherwise, you can use the following options:&lt;/p&gt;

&lt;ol start="1"&gt;
&lt;li&gt;
&lt;strong&gt;Port Forwarding&lt;/strong&gt;: Use &lt;code&gt;kubectl port-forward&lt;/code&gt; to access Grafana locally:
&lt;pre&gt;&lt;code&gt;kubectl port-forward svc/monitoring-grafana -n monitoring 3000:80&lt;/code&gt;&lt;/pre&gt;
Then, access Grafana at &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NodePort or ClusterIP&lt;/strong&gt;: Refer to the &lt;code&gt;NodePort&lt;/code&gt; or &lt;code&gt;ClusterIP&lt;/code&gt; service details from the command output to connect directly.&lt;/li&gt;
&lt;/ol&gt;





&lt;h3&gt;Step 6: Log In to Grafana&lt;/h3&gt;

&lt;p&gt;Use the default credentials to log in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Username&lt;/strong&gt;: &lt;code&gt;admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password&lt;/strong&gt;: &lt;code&gt;prom-operator&lt;/code&gt; (or the password set during installation).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxp4sph9cs735o8zsrioh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxp4sph9cs735o8zsrioh.png" alt=" " width="577" height="553"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;



&lt;h2&gt;&lt;strong&gt;Step 7: Import a Custom Dashboard&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;I’ve created a&lt;strong&gt; custom dashboard&lt;/strong&gt; specifically tailored for &lt;strong&gt;InterSystems IRIS&lt;/strong&gt; metrics, which you can use as a starting point for your monitoring needs. The JSON file for this dashboard is hosted on GitHub for easy access and import:&amp;nbsp;&lt;a href="https://raw.githubusercontent.com/sbendarsky/monitoring-iris/refs/heads/main/grafana-dashboard.json" rel="noopener noreferrer"&gt;Download the Custom Dashboard JSON&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To import the dashboard:&lt;/p&gt;

&lt;ol start="1"&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Dashboards &amp;gt; Import&lt;/strong&gt; in Grafana.&lt;/li&gt;
&lt;li&gt;Paste the URL of the JSON file into the &lt;strong&gt;Import via panel JSON&lt;/strong&gt; field or upload the file directly.&lt;/li&gt;
&lt;li&gt;Assign the dashboard to a folder and Prometheus data source when prompted.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwhwn51qahzayp5trh8b4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwhwn51qahzayp5trh8b4.jpg" alt=" " width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once imported, you can edit the panels to &lt;strong&gt;include additional metrics&lt;/strong&gt;, customize the visualizations, or refine the layout for better insights into your &lt;strong&gt;IRIS &lt;/strong&gt;environment.&lt;/p&gt;





&lt;h3&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;By following this guide, we've successfully set up Prometheus to scrape InterSystems IRIS metrics and visualize them using Grafana. Additionally, you can explore other monitoring tools such as Loki to also monitor logs efficiently and configure alerts using Alertmanager or external services like PagerDuty and Slack. If you have any questions or feedback, feel free to reach out!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>api</category>
      <category>monitoring</category>
      <category>beginners</category>
    </item>
    <item>
      <title>pyprod: Pure Python IRIS Interoperability</title>
      <dc:creator>InterSystems Developer</dc:creator>
      <pubDate>Mon, 20 Jul 2026 15:21:47 +0000</pubDate>
      <link>https://dev.to/intersystems/pyprod-pure-python-iris-interoperability-3kan</link>
      <guid>https://dev.to/intersystems/pyprod-pure-python-iris-interoperability-3kan</guid>
      <description>&lt;p&gt;Intersystems IRIS Productions provide a powerful framework for connecting disparate systems across various protocols and message formats in a reliable, observable, and scalable manner.&amp;nbsp;&lt;strong&gt;intersystems_pyprod&lt;/strong&gt;, short for &lt;em&gt;InterSystems Python Productions&lt;/em&gt;, is a Python library that enables developers to build these interoperability components entirely in Python. Designed for flexibility, it supports a hybrid approach: you can seamlessly mix new Python-based components with existing ObjectScript-based ones, leveraging your established IRIS infrastructure. Once defined, these Python components are managed just like any other; they can be added, configured, and connected using the IRIS Production Configuration page.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;A Quick Primer on InterSystems IRIS Productions&lt;/strong&gt;&lt;/h2&gt;

![ ](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/uxrqudqxu71qyqd568s2.png)
&lt;p&gt;&lt;strong&gt;&lt;u&gt;Key Elements of a Production&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Image from Learning Services training material&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;An IRIS Production generally receives data from external interfaces, processes it through coordinated steps, and routes it to its destination. As messages move through the system, they are automatically persisted, making the entire flow fully traceable through IRIS’s visual trace and logging tools. The architecture relies on certain key elements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Business Hosts:&lt;/strong&gt;&lt;/b&gt; These are the core building blocks—&lt;b&gt;&lt;strong&gt;Services&lt;/strong&gt;&lt;/b&gt;, &lt;b&gt;&lt;strong&gt;Processes&lt;/strong&gt;&lt;/b&gt;, and &lt;b&gt;&lt;strong&gt;Operations&lt;/strong&gt;&lt;/b&gt;—that pass persistable messages between one another.&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Adapters:&lt;/strong&gt;&lt;/b&gt; Inbound and outbound adapters manage the interaction with the external world, handling the specific protocols needed to receive and send data.&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Callbacks:&lt;/strong&gt;&lt;/b&gt; The engine uses specific callback methods to pass messages between hosts, either &lt;b&gt;&lt;strong&gt;synchronously or asynchronously&lt;/strong&gt;&lt;/b&gt;. These callbacks follow strict signatures and return a &lt;code&gt;Status&lt;/code&gt; object to ensure execution integrity.&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Configuration Helpers:&lt;/strong&gt;&lt;/b&gt; Objects such as &lt;b&gt;&lt;strong&gt;Properties&lt;/strong&gt;&lt;/b&gt; and &lt;b&gt;&lt;strong&gt;Parameters&lt;/strong&gt;&lt;/b&gt; expose settings to the Production Configuration UI, allowing users to easily instantiate, configure, and save the state of these components.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;Workflow using pyprod&lt;/h2&gt;
&lt;p&gt;This is essentially a 3&amp;nbsp;step process.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write your production components&lt;/strong&gt; in a regular Python script. In that script, you import the required base classes from &lt;strong&gt;intersystems_pyprod&lt;/strong&gt;&amp;nbsp;and define your own components by subclassing them, just as you would with any other Python library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load them into InterSystems IRIS&lt;/strong&gt; by running the &lt;code&gt;&lt;strong&gt;intersystems_pyprod&lt;/strong&gt;&lt;/code&gt;&amp;nbsp;(same name as the library) command from the terminal and passing it the path to your Python script. This step links the Python classes with IRIS so that they appear as production components and can be configured and wired together using the standard Production Configuration UI.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create the production&lt;/strong&gt; using the Production Configuration page&amp;nbsp;and start the Production&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NOTE: If you create all your components with all their Properties hardcoded within the python script, you only need to add them to the production and start the Production.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;You can connect&amp;nbsp;pyprod to your IRIS instance&amp;nbsp;by doing a&amp;nbsp;&lt;a href="https://github.com/intersystems/pyprod/blob/main/docs/installing.md" rel="noopener noreferrer"&gt;&lt;strong&gt;one time setup&lt;/strong&gt;&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;&lt;strong&gt;Simple Example&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In this example, we demonstrate a synchronous message flow where a request originates from a Service, moves through a Process, and is forwarded to an Operation. The resulting response then travels the same path in reverse, passing from the Operation back through the Process to the Service. Additionally, we showcase how to utilize the &lt;code&gt;IRISLog&lt;/code&gt; utility to write custom log entries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3khatdp059r3gmgs5ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft3khatdp059r3gmgs5ee.png" alt=" " width="742" height="498"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;h3&gt;Step 1&lt;/h3&gt;
&lt;p&gt;Create your Production components using pyprod in the file HelloWorld.py&lt;/p&gt;
&lt;p&gt;Here are some key parts of the code&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Package Naming:&lt;/strong&gt;&lt;/b&gt; We define &lt;code&gt;iris_&lt;/code&gt;&lt;code&gt;package_name&lt;/code&gt;, which prefixes all classes as they appear on the Production Configuration page&amp;nbsp;(If omitted, the script name is used as the default prefix).&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Persistable Messages:&lt;/strong&gt;&lt;/b&gt; We define &lt;code&gt;MyRequest&lt;/code&gt; and &lt;code&gt;MyResponse&lt;/code&gt;. These are the essential data structures for communication, as only persistable objects can be passed between Services, Processes, and Operations.&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;The Inbound Adapter:&lt;/strong&gt;&lt;/b&gt; Our adapter passes a string to the Service using the &lt;code&gt;business_host_process_input&amp;nbsp;&lt;/code&gt;method.&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;The Business Service:&lt;/strong&gt;&lt;/b&gt;&amp;nbsp;Implemented with the help of &lt;strong&gt;OnProcessInput&lt;/strong&gt; callback.&lt;ul&gt;
&lt;li&gt;MyService receives data from the adapter and converts it into a &lt;code&gt;MyRequest&lt;/code&gt; message&lt;/li&gt;
&lt;li&gt;We use the &lt;code&gt;ADAPTER&lt;/code&gt;&lt;b&gt;&lt;strong&gt;IRISParameter&lt;/strong&gt;&lt;/b&gt; to link the Inbound Adapter to the Service. Note that this attribute must be named &lt;code&gt;ADAPTER&lt;/code&gt; in all caps to align with IRIS conventions.&lt;/li&gt;
&lt;li&gt;We define a &lt;code&gt;target&lt;/code&gt;&lt;b&gt;&lt;strong&gt;IRISProperty&lt;/strong&gt;&lt;/b&gt;, which allows users to select the destination component directly via the Configuration UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Business Process:&lt;/strong&gt; Implemented with the help of &lt;strong&gt;OnRequest&lt;/strong&gt; callback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Business Operation&lt;/strong&gt;: Implemented with the help of &lt;strong&gt;OnMessage&lt;/strong&gt; callback. (You can also define a MessageMap)&lt;/li&gt;
&lt;li&gt;
&lt;b&gt;&lt;strong&gt;Logic &amp;amp; Callbacks:&lt;/strong&gt;&lt;/b&gt; Finally, the hosts implement their core logic within standard callbacks like &lt;code&gt;OnProcessInput&lt;/code&gt; and &lt;code&gt;OnRequest&lt;/code&gt;, routing messages using the &lt;code&gt;SendRequestSync&lt;/code&gt; method.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can read more about each of these parts on the pyprod &lt;a href="https://github.com/intersystems/pyprod/blob/main/docs/apireference.md#pyprod--api-reference" rel="noopener noreferrer"&gt;&lt;strong&gt;API Reference page&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;and also using the &lt;a href="https://github.com/intersystems/pyprod/blob/main/docs/quickstart.md#quick-start-guide" rel="noopener noreferrer"&gt;&lt;strong&gt;Quick Start Guide&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="mention"&gt;import&lt;/span&gt; time

&lt;p&gt;&lt;span&gt;from&lt;/span&gt; intersystems_pyprod &lt;span&gt;import&lt;/span&gt; (&lt;br&gt;
    InboundAdapter,BusinessService, BusinessProcess, &lt;br&gt;
    BusinessOperation, OutboundAdapter, JsonSerialize, &lt;br&gt;
    IRISProperty, IRISParameter, IRISLog, Status)&lt;/p&gt;

&lt;p&gt;iris_package_name = &lt;span&gt;"helloworld"&lt;/span&gt;&lt;br&gt;
&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyRequest&lt;/span&gt;&lt;span&gt;(JsonSerialize)&lt;/span&gt;:&lt;br&gt;
    content: str&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyResponse&lt;/span&gt;&lt;span&gt;(JsonSerialize)&lt;/span&gt;:&lt;br&gt;
    content: str&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyInAdapter&lt;/span&gt;&lt;span&gt;(InboundAdapter)&lt;/span&gt;:&lt;br&gt;
    &lt;span&gt;def&lt;/span&gt; &lt;span&gt;OnTask&lt;/span&gt;&lt;span&gt;(self)&lt;/span&gt;:&lt;br&gt;
        time.sleep(&lt;span&gt;0.5&lt;/span&gt;)&lt;br&gt;
        self.business_host_process_input(&lt;span&gt;"request message"&lt;/span&gt;)&lt;br&gt;
        &lt;span&gt;return&lt;/span&gt; Status.OK()&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyService&lt;/span&gt;&lt;span&gt;(BusinessService)&lt;/span&gt;:&lt;br&gt;
    ADAPTER = IRISParameter(&lt;span&gt;"helloworld.MyInAdapter"&lt;/span&gt;)&lt;br&gt;
    target = IRISProperty(settings=&lt;span&gt;"Target"&lt;/span&gt;)&lt;br&gt;
    &lt;span&gt;def&lt;/span&gt; &lt;span&gt;OnProcessInput&lt;/span&gt;&lt;span&gt;(self, input)&lt;/span&gt;:&lt;br&gt;
        persistent_message = MyRequest(input)&lt;br&gt;
        status, response = self.SendRequestSync(self.target, persistent_message)&lt;br&gt;
        IRISLog.Info(response.content)&lt;br&gt;
        &lt;span&gt;return&lt;/span&gt; status&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyProcess&lt;/span&gt;&lt;span&gt;(BusinessProcess)&lt;/span&gt;:&lt;br&gt;
    target = IRISProperty(settings=&lt;span&gt;"Target"&lt;/span&gt;)&lt;br&gt;
    &lt;span&gt;def&lt;/span&gt; &lt;span&gt;on_request&lt;/span&gt;&lt;span&gt;(self, input)&lt;/span&gt;:&lt;br&gt;
        status, response = self.SendRequestSync(self.target,input)&lt;br&gt;
        &lt;span&gt;return&lt;/span&gt; status, response&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyOperation&lt;/span&gt;&lt;span&gt;(BusinessOperation)&lt;/span&gt;:&lt;br&gt;
    ADAPTER = IRISParameter(&lt;span&gt;"helloworld.MyOutAdapter"&lt;/span&gt;)&lt;br&gt;
    &lt;span&gt;def&lt;/span&gt; &lt;span&gt;OnMessage&lt;/span&gt;&lt;span&gt;(self, input)&lt;/span&gt;:&lt;br&gt;
        status = self.ADAPTER.custom_method(input)&lt;br&gt;
        response = MyResponse(&lt;span&gt;"response message"&lt;/span&gt;)&lt;br&gt;
        &lt;span&gt;return&lt;/span&gt; status, response&lt;/p&gt;

&lt;p&gt;&lt;span&gt;class&lt;/span&gt; &lt;span&gt;MyOutAdapter&lt;/span&gt;&lt;span&gt;(OutboundAdapter)&lt;/span&gt;:&lt;br&gt;
    &lt;span&gt;def&lt;/span&gt; &lt;span&gt;custom_method&lt;/span&gt;&lt;span&gt;(self, input)&lt;/span&gt;:&lt;br&gt;
        IRISLog.Info(input.content)&lt;br&gt;
        &lt;span&gt;return&lt;/span&gt; Status.OK()&lt;/p&gt;

&lt;/code&gt;&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2&lt;/h3&gt;
&lt;p&gt;Once your code is ready, load the components to IRIS.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ intersystems_pyprod /full/path/to/HelloWorld.py

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Loading MyRequest to IRIS...
...
Load finished successfully.

Loading MyResponse to IRIS...
...
Load finished successfully.
...
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;h3&amp;gt;Step 3&amp;lt;/h3&amp;gt;&amp;lt;p&amp;gt;Add each host to the Production using the Production Configuration page.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;The image below shows&amp;nbsp;&amp;lt;code data-index-in-node="42" data-path-to-node="3"&amp;gt;MyService&amp;lt;/code&amp;gt;&amp;nbsp;and its &amp;lt;code data-index-in-node="93" data-path-to-node="3"&amp;gt;target&amp;lt;/code&amp;gt; property&amp;nbsp;being configured through the UI. Follow the same process to add &amp;lt;code data-index-in-node="176" data-path-to-node="3"&amp;gt;MyProcess&amp;lt;/code&amp;gt; and &amp;lt;code data-index-in-node="190" data-path-to-node="3"&amp;gt;MyOperation&amp;lt;/code&amp;gt;. Once the setup is complete, simply start the production to see your messages in motion.&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k3972p7c0w53tnajuez.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3k3972p7c0w53tnajuez.png" alt=" " width="552" height="604"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;By combining the flexibility of the Python ecosystem with the industrial-grade reliability of InterSystems IRIS, &lt;b&gt;&lt;strong&gt;pyprod&lt;/strong&gt;&lt;/b&gt; offers a modern path for building interoperability solutions. Whether you are developing entirely new "Pure Python" productions or enhancing existing ObjectScript infrastructures with specialized Python libraries, &lt;b&gt;&lt;strong&gt;pyprod&lt;/strong&gt;&lt;/b&gt; ensures your components remain fully integrated, observable, and easy to configure.&amp;nbsp;We look forward to seeing what you build!&lt;/p&gt;

&lt;h2&gt;Quick Links&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/intersystems/pyprod" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pypi.org/project/intersystems-pyprod/" rel="noopener noreferrer"&gt;PyPi Package&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;b&gt;&lt;strong&gt;Support the Project:&lt;/strong&gt;&lt;/b&gt; If you find this library useful, please&amp;nbsp;consider &lt;b&gt;&lt;strong&gt;giving us a ⭐ on GitHub&amp;nbsp;&lt;/strong&gt;&lt;/b&gt;and suggesting enhancements. It helps the project grow and makes it easier for other developers in the InterSystems community to discover it!&lt;/blockquote&gt;


&lt;/code&gt;&lt;/pre&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
