{"id":2824,"date":"2026-05-20T11:39:03","date_gmt":"2026-05-20T11:39:03","guid":{"rendered":"https:\/\/oka.how\/?p=2824"},"modified":"2026-07-01T06:15:41","modified_gmt":"2026-07-01T06:15:41","slug":"extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library","status":"publish","type":"post","link":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/","title":{"rendered":"Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library"},"content":{"rendered":"<div class=\"fusion-fullwidth fullwidth-box fusion-builder-row-1 fusion-flex-container nonhundred-percent-fullwidth non-hundred-percent-height-scrolling\" style=\"--awb-border-radius-top-left:0px;--awb-border-radius-top-right:0px;--awb-border-radius-bottom-right:0px;--awb-border-radius-bottom-left:0px;--awb-flex-wrap:wrap;\" ><div class=\"fusion-builder-row fusion-row fusion-flex-align-items-flex-start fusion-flex-content-wrap\" style=\"max-width:1206.4px;margin-left: calc(-4% \/ 2 );margin-right: calc(-4% \/ 2 );\"><div class=\"fusion-layout-column fusion_builder_column fusion-builder-column-0 fusion_builder_column_1_1 1_1 fusion-flex-column\" style=\"--awb-bg-size:cover;--awb-width-large:100%;--awb-margin-top-large:0px;--awb-spacing-right-large:1.92%;--awb-margin-bottom-large:0px;--awb-spacing-left-large:1.92%;--awb-width-medium:100%;--awb-spacing-right-medium:1.92%;--awb-spacing-left-medium:1.92%;--awb-width-small:100%;--awb-spacing-right-small:1.92%;--awb-spacing-left-small:1.92%;\"><div class=\"fusion-column-wrapper fusion-flex-justify-content-flex-start fusion-content-layout-column\"><div class=\"fusion-text fusion-text-1\"><div class=\"wp-block-spacer\" style=\"height: 100px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h1 class=\"wp-block-heading\">Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library<\/h1>\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n<p class=\"wp-block-paragraph\">Managing an HPC cluster without proper observability is like flying blind. You know jobs are running, but you don&#8217;t know what they cost, how much energy they consume, or what their carbon footprint looks like. <a href=\"https:\/\/oka.how\">OKA<\/a>, developed by UCit, was built to solve exactly this problem. It is an all-in-one HPC analytics platform that ships with over 20 native dashboards covering resource utilization, user behavior, job efficiency, sustainability reporting, and cost tracking \u2014 all without requiring custom development from your team.<\/p>\n<p class=\"wp-block-paragraph\">Out of the box, OKA ingests data from your job scheduler (Slurm, PBS, LSF, \u2026) and immediately surfaces actionable insights. But no two HPC centers are identical. Your energy monitoring stack, your pricing model, your project accounting conventions, and your sustainability commitments are unique to your organization. That is where <strong>Data Enhancers<\/strong> come in.<\/p>\n<p class=\"wp-block-paragraph\">Data Enhancers are lightweight Python plugins that run inside OKA&#8217;s processing pipeline and add custom columns to your job data, or allow you to compute important metrics such as cost or carbon footprint. They can pull data from external APIs, query internal databases, invoke CLI tools, or simply reformat existing fields. The result is that OKA becomes tailored to your environment, surfacing metrics that matter specifically to you \u2014 carbon footprint per project, real electricity cost per user, power draw per job \u2014 without modifying OKA&#8217;s core.<\/p>\n<p class=\"wp-block-paragraph\">This article walks through the open-source Data Enhancer library we maintain at UCit. Each enhancer addresses a concrete use case. Whether you are ready to adopt one as-is or use it as a starting point for your own implementation, these examples cover the most common enrichment needs we encounter at HPC centers.<\/p>\n<blockquote class=\"wp-block-quote quote-blue-border is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">The source code for all enhancers discussed here is available in the <a href=\"https:\/\/bitbucket.org\/ucit\/oka_dataenhancers\" target=\"_blank\" rel=\"noreferrer noopener\"><strong><code>oka_dataenhancers<\/code> repository<\/strong><\/a>.<\/p>\n<\/blockquote>\n<div class=\"wp-block-image blue-captions\">\n<figure class=\"aligncenter size-large is-resized\"><img decoding=\"async\" width=\"700\" height=\"510\" class=\"lazyload wp-image-2832\" style=\"width: 700px;\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1024x746.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1024x746.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27700%27%20height%3D%27510%27%20viewBox%3D%270%200%20700%20510%27%3E%3Crect%20width%3D%27700%27%20height%3D%27510%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-200x146.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-300x219.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-400x292.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-600x437.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-768x560.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-800x583.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1024x746.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1200x875.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards.webp 1520w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 700px) 100vw, 700px\" \/><figcaption class=\"wp-element-caption\">OKA provides 20+ HPC-native dashboards, built by cluster operations experts,<br \/>giving both IT staff and end-users instant visibility into the cluster\u2019s behavior.<\/figcaption><\/figure>\n<\/div>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h2 class=\"wp-block-heading\">Anatomy of a Data Enhancer<\/h2>\n<p class=\"wp-block-paragraph\">Before diving into specific examples, let&#8217;s understand what a Data Enhancer looks like and how OKA manages it.<\/p>\n<p class=\"wp-block-paragraph\">At its core, a Data Enhancer is a Python class that inherits from OKA&#8217;s <code>DataEnhancer<\/code> base class and implements a single <code>run<\/code> method. The method receives a Pandas DataFrame \u2014 one row per job, one column per scheduler field \u2014 and returns it enriched with whatever additional columns you add.<\/p>\n<p class=\"wp-block-paragraph\">Here is the skeleton of a complete, working enhancer:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; --cbp-line-number-width: calc(2 * 0.6 * .875rem); line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">import pandas as pd\r\nfrom applications.data_manager.lib.enhancer import DataEnhancer\r\nfrom pydantic import BaseModel, Field\r\n\r\nclass MyCostCalculatorParams(BaseModel):\r\n    CPU_HOUR_COST: float = Field(default=0.02, ge=0.0)\r\n    GPU_HOUR_COST: float = Field(default=0.04, ge=0.0)\r\n    GPU_PARTITION: str = Field(default=\"gpu\")\r\n\r\nclass MyCostCalculator(DataEnhancer):\r\n    params_model = MyCostCalculatorParams\r\n\r\n    def run(self, data: pd.DataFrame, **kwargs) -&gt; pd.DataFrame:\r\n        data[\"COST\"] = data[\"COREHOURS\"] * self.params.CPU_HOUR_COST\r\n        gpu_mask = data[\"PARTITION\"] == self.params.GPU_PARTITION\r\n        data.loc[gpu_mask, \"COST\"] += data.loc[gpu_mask, \"GPUHOURS\"] * self.params.GPU_HOUR_COST\r\n        return data<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f286c4;\">import<\/span><span style=\"color: #f6f6f4;\"> pandas <\/span><span style=\"color: #f286c4;\">as<\/span><span style=\"color: #f6f6f4;\"> pd<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f286c4;\">from<\/span><span style=\"color: #f6f6f4;\"> applications.data_manager.lib.enhancer <\/span><span style=\"color: #f286c4;\">import<\/span><span style=\"color: #f6f6f4;\"> DataEnhancer<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f286c4;\">from<\/span><span style=\"color: #f6f6f4;\"> pydantic <\/span><span style=\"color: #f286c4;\">import<\/span><span style=\"color: #f6f6f4;\"> BaseModel, Field<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">MyCostCalculatorParams<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">BaseModel<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">CPU_HOUR_COST<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">float<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.02<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">ge<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.0<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">GPU_HOUR_COST<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">float<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.04<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">ge<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.0<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">GPU_PARTITION<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">gpu<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">MyCostCalculator<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">DataEnhancer<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    params_model <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> MyCostCalculatorParams<\/span><\/span>\r\n\r\n<span class=\"line\">    <span style=\"color: #f286c4;\">def<\/span> <span style=\"color: #62e884;\">run<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">data<\/span><span style=\"color: #f286c4;\">:<\/span><span style=\"color: #f6f6f4;\"> pd.DataFrame, <\/span><span style=\"color: #f286c4;\">**<\/span><span style=\"color: #ffb86c; font-style: italic;\">kwargs<\/span><span style=\"color: #f6f6f4;\">) <\/span><span style=\"color: #f286c4;\">-&gt;<\/span><span style=\"color: #f6f6f4;\"> pd.DataFrame:<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">        data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COST<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COREHOURS<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">*<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">CPU_HOUR_COST<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">        gpu_mask <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">PARTITION<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">==<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">GPU_PARTITION<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">        data.loc[gpu_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COST<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">+=<\/span><span style=\"color: #f6f6f4;\"> data.loc[gpu_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">GPUHOURS<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">*<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">GPU_HOUR_COST<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #f286c4;\">return<\/span><span style=\"color: #f6f6f4;\"> data<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">A few things are worth noting:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Pydantic for parameters.<\/strong> The <code>params_model<\/code> class declares every tunable parameter with its type, default, and validation rules. OKA reads these in the admin panel and exposes them as a form, so operators can adjust pricing or thresholds without touching code.<\/li>\n<li><strong>One file, no package.<\/strong> Each enhancer lives in a single <code>.py<\/code> file. There is no <code>__init__.py<\/code>, no package structure to set up.<\/li>\n<li><strong><code>DataEnhancer<\/code> is provided by OKA.<\/strong> The base class is bundled with OKA and is not installed in your local development environment. It provides the <code>self.params<\/code> attribute (a validated instance of your <code>params_model<\/code>) and integrates with OKA&#8217;s logging infrastructure.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\"><strong>Writing and deploying an enhancer through the OKA UI.<\/strong><\/p>\n<figure class=\"wp-block-image size-full blue-captions\"><img decoding=\"async\" width=\"2560\" height=\"1591\" class=\"lazyload wp-image-2874\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-scaled.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-scaled.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272560%27%20height%3D%271591%27%20viewBox%3D%270%200%202560%201591%27%3E%3Crect%20width%3D%272560%27%20height%3D%271591%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-200x124.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-300x186.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-400x249.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-600x373.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-768x477.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-800x497.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-1024x636.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-1200x746.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-1536x954.webp 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/1-data-enhancers-scaled.webp 2560w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">Use the built-in IDE to create your own Data Enhancers, you can also use available templates and library components<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\">The latest version of OKA includes an integrated code editor directly in the <strong>Management \u2192 Data Enhancers<\/strong> admin panel. The workflow is entirely UI-driven:<\/p>\n<ol class=\"wp-block-list\">\n<li><strong>Create<\/strong> \u2014 click &#8220;Create Data Enhancer&#8221;, give it a name and description, then write or paste your Python code into the built-in editor (CodeMirror with syntax highlighting, autocomplete, and the option to import a <code>.py<\/code> file directly). The first save creates a <strong>draft<\/strong>.<\/li>\n<li><strong>Test in the sandbox<\/strong> \u2014 before committing to production, OKA lets you run the draft against a subset of real cluster jobs and inspect the output columns and logs side by side. This is the right moment to verify that your formula, API call, or field transformation behaves as expected on actual data.<\/li>\n<li><strong>Publish<\/strong> \u2014 once satisfied, publish the draft. This creates an immutable, timestamped snapshot. No server restart is needed: OKA loads published enhancers dynamically at pipeline execution time.<\/li>\n<li><strong>Assign to a cluster<\/strong> \u2014 navigate to the cluster&#8217;s configuration page, select the published enhancer in the Data Enhancers tab, and choose its position in the execution sequence (order matters when one enhancer produces a column that another consumes).<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\"><strong>Versioning and auditability.<\/strong> Every save produces a new version. Published versions are immutable \u2014 editing an existing enhancer always creates a new draft, which must go through the publish step before it becomes active. This matters in practice: the way job cost or carbon footprint is computed can change over time as pricing models or grid data sources evolve, and the version history gives you a clear audit trail of which formula was active during which period.<\/p>\n<figure class=\"wp-block-image size-full blue-captions\"><img decoding=\"async\" width=\"2560\" height=\"1591\" class=\"lazyload wp-image-2873\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-scaled.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-scaled.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272560%27%20height%3D%271591%27%20viewBox%3D%270%200%202560%201591%27%3E%3Crect%20width%3D%272560%27%20height%3D%271591%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-200x124.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-300x186.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-400x249.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-600x373.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-768x477.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-800x497.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-1024x636.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-1200x746.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-1536x954.webp 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/3-data-enhancers-scaled.webp 2560w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">The &#8216;update&#8217; panel enables you to modify, version and release updated Data Enhancers<\/figcaption><\/figure>\n<p class=\"wp-block-paragraph\"><strong>Pipeline stages.<\/strong> Enhancers can run at two points: during ingestion (enriching jobs as they are recorded) or retroactively to processe already-stored jobs in chunks. The retroactive mode is useful when you want to backfill new metrics on historical data without re-ingesting from the scheduler.<\/p>\n<p class=\"wp-block-paragraph\">Full documentation is available at <a href=\"https:\/\/doc.oka.how\" target=\"_blank\" rel=\"noreferrer noopener\">doc.oka.how<\/a> under the admin guide \u2192 Data Enhancers section.<\/p>\n<figure class=\"wp-block-image size-full blue-captions\"><img decoding=\"async\" width=\"2560\" height=\"1591\" class=\"lazyload wp-image-2872\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-scaled.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-scaled.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272560%27%20height%3D%271591%27%20viewBox%3D%270%200%202560%201591%27%3E%3Crect%20width%3D%272560%27%20height%3D%271591%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-200x124.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-300x186.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-400x249.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-600x373.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-768x477.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-800x497.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-1024x636.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-1200x746.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-1536x954.webp 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/0-data-enhancers-scaled.webp 2560w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">The management panel gives you an overview of all existing data enhancers, and their usage<\/figcaption><\/figure>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h2 class=\"wp-block-heading\">The Enhancers \u2014 A Use-Case Tour<\/h2>\n<h3 id=\"energy\" class=\"wp-block-heading\">Measuring Energy Consumption: EAR Integration<\/h3>\n<p class=\"wp-block-paragraph\">The scheduler knows when a job ran and on which nodes. It does not know how much electricity that job actually consumed. Bridging that gap requires an energy monitoring layer, and <a href=\"https:\/\/gitlab.bsc.es\/ear_team\/ear\" target=\"_blank\" rel=\"noreferrer noopener\">EAR (Energy Aware Runtime)<\/a> is one of the most widely deployed solutions on FLOPS-intensive clusters.<\/p>\n<p class=\"wp-block-paragraph\">The repository provides two enhancers for pulling EAR data into OKA. They add the same four columns \u2014 <code>Energy<\/code> (Joules), <code>Power<\/code> (average watts), <code>Minimum_Power<\/code>, and <code>Maximum_Power<\/code> \u2014 but source the data differently depending on your infrastructure setup.<\/p>\n<h4 class=\"wp-block-heading\">Via the EAR database (<code>ear_from_db.py<\/code>)<\/h4>\n<p class=\"wp-block-paragraph\">This variant connects directly to the EAR MySQL database and aggregates per-job power metrics:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">class EnhancerEARFromDBParams(BaseModel):\r\n    host: str = Field(default=\"localhost\", description=\"EAR database host address\")\r\n    port: int = Field(default=3306, description=\"EAR database port number\")\r\n    user: str = Field(description=\"EAR database username (must have read access to the Applications and Power_signatures tables)\")\r\n    password: str = Field(description=\"Password for the configured database user\")\r\n    database: str = Field(default=\"EAR\", description=\"EAR database name\")<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">EnhancerEARFromDBParams<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">BaseModel<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    host: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">localhost<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR database host address<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    port: <\/span><span style=\"color: #97e1f1; font-style: italic;\">int<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">3306<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR database port number<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    user: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR database username (must have read access to the Applications and Power_signatures tables)<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    password: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Password for the configured database user<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    database: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR database name<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">It issues a single query that joins EAR&#8217;s <code>Applications<\/code> and <code>Power_signatures<\/code> tables, computes average, minimum and maximum DC power, and calculates total energy as <code>Power \u00d7 Time<\/code>. Only jobs whose IDs are at or above the minimum ID in the current batch are queried, keeping the database load proportional to the batch size rather than to the full job history.<\/p>\n<p class=\"wp-block-paragraph\">This approach is ideal when OKA has direct network access to the EAR database and you are comfortable providing read credentials.<\/p>\n<h4 class=\"wp-block-heading\">Via the <code>eacct<\/code> CLI tool (<code>ear_from_eacct.py<\/code>)<\/h4>\n<p class=\"wp-block-paragraph\">If you would rather not expose database credentials to the OKA process, <code>eacct<\/code> \u2014 EAR&#8217;s built-in accounting command \u2014 provides the same information through a command-line interface:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">class EnhancerEARFromEacctParams(BaseModel):\r\n    eacct_path: str = Field(\r\n        default=\"eacct\",\r\n        description=\"Path to the eacct executable; use a full path if eacct is not on the system PATH\",\r\n    )\r\n    batch_size: int = Field(\r\n        default=100,\r\n        description=\"Maximum number of job IDs per eacct invocation; reduce if hitting shell argument-length limits\",\r\n    )\r\n    ear_etc: str | None = Field(\r\n        default=None,\r\n        description=\"Path to the EAR configuration directory; sets the EAR_ETC environment variable when provided\",\r\n    )\r\n    ear_install_path: str | None = Field(\r\n        default=None,\r\n        description=\"EAR installation prefix; sets EAR_INSTALL_PATH when provided\",\r\n    )\r\n    env_vars: dict[str, str] = Field(\r\n        default_factory=dict,\r\n        description=\"Additional environment variables forwarded to the eacct process\",\r\n    )<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">EnhancerEARFromEacctParams<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">BaseModel<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    eacct_path: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">eacct<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Path to the eacct executable; use a full path if eacct is not on the system PATH<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    )<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    batch_size: <\/span><span style=\"color: #97e1f1; font-style: italic;\">int<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">100<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Maximum number of job IDs per eacct invocation; reduce if hitting shell argument-length limits<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    )<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    ear_etc: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">|<\/span> <span style=\"color: #bf9eee;\">None<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">None<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Path to the EAR configuration directory; sets the EAR_ETC environment variable when provided<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    )<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    ear_install_path: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">|<\/span> <span style=\"color: #bf9eee;\">None<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">None<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">EAR installation prefix; sets EAR_INSTALL_PATH when provided<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    )<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    env_vars: dict[<\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">default_factory<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #97e1f1; font-style: italic;\">dict<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\">        <span style=\"color: #ffb86c; font-style: italic;\">description<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Additional environment variables forwarded to the eacct process<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    )<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">The enhancer runs <code>eacct -c no_file -n all -j <\/code> in batches of up to <code>batch_size<\/code> IDs (to stay within command-line argument limits), parses the semicolon-separated CSV output, and calculates energy as <code>Power \u00d7 Time<\/code>. The &#8220;No jobs found&#8221; case is handled gracefully, and each batch failure is logged as a warning rather than aborting the whole run.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Without energy data, OKA can show you <em>when<\/em> jobs ran but not <em>what they cost in electricity<\/em>. Once <code>Energy<\/code> is populated, the carbon and cost enhancers described below can immediately turn it into actionable sustainability and financial metrics.<\/p>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h3 class=\"wp-block-heading\">Carbon Footprint and Electricity Cost: RTE \u00e9CO2mix + ENTSO-E<\/h3>\n<p class=\"wp-block-paragraph\">Sustainability reporting and energy cost attribution are two of the most frequent requests we receive from HPC finance and sustainability teams. The <code>carbon_cost_RTE.py<\/code> enhancer addresses both in a single plugin.<\/p>\n<p class=\"wp-block-paragraph\">It enriches each job with four new columns:<\/p>\n<figure class=\"wp-block-table\">\n<table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Column<\/th>\n<th>Unit<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>CO2<\/code><\/td>\n<td>kgCO2eq<\/td>\n<td>Carbon footprint of the job<\/td>\n<\/tr>\n<tr>\n<td><code>elec_cost_eur<\/code><\/td>\n<td>\u20ac<\/td>\n<td>Wholesale electricity cost<\/td>\n<\/tr>\n<tr>\n<td><code>avg_carbon_intensity_gco2_kwh<\/code><\/td>\n<td>gCO2eq\/kWh<\/td>\n<td>Time-weighted mean grid intensity over the job<\/td>\n<\/tr>\n<tr>\n<td><code>avg_spot_price_eur_mwh<\/code><\/td>\n<td>\u20ac\/MWh<\/td>\n<td>Time-weighted mean day-ahead spot price over the job<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p class=\"wp-block-paragraph\">The two data sources are:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong><a href=\"https:\/\/www.rte-france.com\/donnees-publications\/eco2mix-donnees-temps-reel\" target=\"_blank\" rel=\"noreferrer noopener\">RTE \u00e9CO2mix<\/a><\/strong> (via the ODR\u00c9 open-data API) \u2014 carbon intensity of the French electricity grid, updated every 15 minutes, no API key required. It reflects the direct combustion emissions of the power mix as published by the French grid operator RTE.<\/li>\n<li><strong><a href=\"https:\/\/transparency.entsoe.eu\/\" target=\"_blank\" rel=\"noreferrer noopener\">ENTSO-E Transparency Platform<\/a><\/strong> \u2014 wholesale day-ahead spot prices for the French bidding zone, at 1-hour granularity. This requires a free API key (obtainable by emailing <code>transparency@entsoe.eu<\/code>).<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">The calculation is straightforward in concept but careful in implementation:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\"># Energy in kWh\r\nelec_kwh = data[oka_constants.ENERGY] \/ 3_600_000\r\n\r\n# One API call covers the entire batch [min(START), max(END)]\r\nt_min = data[\"START\"].min()\r\nt_max = data[\"END\"].max()\r\nci_series = RTEClient().get_carbon_intensity(t_min, t_max, cache)\r\n\r\n# Per-job time-weighted mean of the carbon intensity series\r\navg_ci = data.apply(\r\n    lambda r: _time_weighted_mean(ci_series, r[\"START\"], r[\"END\"]),\r\n    axis=1,\r\n)\r\n# kWh \u00d7 gCO2\/kWh \u00f7 1000 = kgCO2\r\ndata[\"CO2\"] = elec_kwh * avg_ci \/ 1000<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7b7f8b;\"># Energy in kWh<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">elec_kwh <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[oka_constants.<\/span><span style=\"color: #bf9eee;\">ENERGY<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">\/<\/span> <span style=\"color: #bf9eee;\">3_600_000<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># One API call covers the entire batch [min(START), max(END)]<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">t_min <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">START<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">].min()<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">t_max <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">END<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">].max()<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">ci_series <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> RTEClient().get_carbon_intensity(t_min, t_max, cache)<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># Per-job time-weighted mean of the carbon intensity series<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">avg_ci <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data.apply(<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #f286c4;\">lambda<\/span> <span style=\"color: #ffb86c; font-style: italic;\">r<\/span><span style=\"color: #f6f6f4;\">: _time_weighted_mean(ci_series, r[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">START<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">], r[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">END<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">]),<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #ffb86c; font-style: italic;\">axis<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">1<\/span><span style=\"color: #f6f6f4;\">,<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># kWh \u00d7 gCO2\/kWh \u00f7 1000 = kgCO2<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">CO2<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> elec_kwh <\/span><span style=\"color: #f286c4;\">*<\/span><span style=\"color: #f6f6f4;\"> avg_ci <\/span><span style=\"color: #f286c4;\">\/<\/span> <span style=\"color: #bf9eee;\">1000<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">The <code>_time_weighted_mean<\/code> function handles a subtle but important edge case: jobs shorter than the data source&#8217;s granularity (e.g. a 5-minute job against 15-minute RTE data). In that case, <code>Series.asof()<\/code> is used to return the value of the active bucket directly, rather than trying to average over an empty slice.<\/p>\n<p class=\"wp-block-paragraph\">To avoid hammering public APIs on every pipeline run, the enhancer maintains a local <strong>SQLite cache<\/strong> (<code>timeseries.db<\/code>) with a 24-hour TTL. All time series are stored as UTC ISO 8601 strings so that lexicographic ordering equals chronological ordering, enabling simple range queries without any date parsing.<\/p>\n<p class=\"wp-block-paragraph\">The <code>enable_carbon<\/code> and <code>enable_cost<\/code> parameters let you switch each feature on or off independently \u2014 useful when, for example, you want carbon tracking but have not yet obtained an ENTSO-E API key:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">class EnhancerCarbonCostParams(BaseModel):\r\n    country_code: str = Field(default=\"FR\")\r\n    enable_carbon: bool = Field(default=True)\r\n    enable_cost: bool = Field(default=True)<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">EnhancerCarbonCostParams<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">BaseModel<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    country_code: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">FR<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    enable_carbon: <\/span><span style=\"color: #97e1f1; font-style: italic;\">bool<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">True<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    enable_cost: <\/span><span style=\"color: #97e1f1; font-style: italic;\">bool<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">True<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">&nbsp;<\/p>\n<blockquote class=\"wp-block-quote quote-blue-border is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Current scope.<\/strong> This enhancer currently supports French grid data (RTE and the French ENTSO-E bidding zone). The architecture is designed to be extended: <code>country_code<\/code> is a reserved parameter, and the <code>RTEClient<\/code> \/ <code>ENTSOEClient<\/code> classes can be supplemented with equivalents for other countries&#8217; grid operators. Contributions are welcome.<\/p>\n<p class=\"wp-block-paragraph\"><strong>What this estimate covers \u2014 and what it does not.<\/strong> The <code>CO2<\/code> column computed with this Data Enhancer reflects the operational emissions of the computing nodes alone: measured energy \u00d7 grid carbon intensity. It deliberately excludes several contributors that can represent a significant share of an HPC system&#8217;s total carbon footprint:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Lifecycle emissions<\/strong> \u2014 manufacturing, transport, and end-of-life disposal of servers, networking hardware, and storage.<\/li>\n<li><strong>Infrastructure overhead<\/strong> \u2014 storage systems, service nodes, management infrastructure, and cooling that are not captured by per-job IPMI\/RAPL measurements.<\/li>\n<li><strong>PUE and datacenter losses<\/strong> \u2014 the energy consumed by cooling, power distribution, and UPS systems is not factored in unless the raw energy readings already account for it.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">For a complete and auditable carbon assessment of an HPC system \u2014 one that covers the full lifecycle and the entire datacenter footprint \u2014 a dedicated analysis is required. <a href=\"https:\/\/kyanopi.com\" target=\"_blank\" rel=\"noreferrer noopener\">Kyanopi<\/a> provides this kind of in-depth environmental assessment for HPC infrastructures.<\/p>\n<\/blockquote>\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Multiplying measured energy by grid carbon intensity turns raw compute time into a sustainability KPI \u2014 per-project CO2 budgets, cluster-level carbon reporting, and eventually, carbon-aware scheduling decisions. The electricity cost column adds a financial dimension: organizations can cross-reference actual spot prices with their internal chargeback rates, or expose the variable cost of compute to users to incentivize off-peak submissions.<\/p>\n<figure class=\"wp-block-image size-full blue-captions\"><img decoding=\"async\" width=\"2560\" height=\"1591\" class=\"lazyload wp-image-2870\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-scaled.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-scaled.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272560%27%20height%3D%271591%27%20viewBox%3D%270%200%202560%201591%27%3E%3Crect%20width%3D%272560%27%20height%3D%271591%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-200x124.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-300x186.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-400x249.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-600x373.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-768x477.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-800x497.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-1024x636.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-1200x746.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-1536x954.webp 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/5-data-enhancers-scaled.webp 2560w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">Thanks to the &#8216;Carbon&#8217; data enhancer we implemented, we can now toggle to carbon footprint metrics in supported OKA dashboards<\/figcaption><\/figure>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h3 class=\"wp-block-heading\">Job Cost Accounting<\/h3>\n<p class=\"wp-block-paragraph\">Internal chargebacks are a universal requirement: research computing centers need to allocate costs to departments, projects, or grants. The <code>costs_demo.py<\/code> enhancer provides a ready-to-adapt template for exactly this use case.<\/p>\n<p class=\"wp-block-paragraph\">It implements a realistic three-tier pricing model:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\"># 1. Default cost for all jobs\r\ndata[\"COST\"] = data[\"COREHOURS\"] * self.params.CPU_HOUR_COST\r\n\r\n# 2. Discounted rate for contract accounts (regex match)\r\naccount_mask = data[\"ACCOUNT\"].str.match(self.params.DISC_ACCOUNT)\r\ndata.loc[account_mask, \"COST\"] = (\r\n    data.loc[account_mask, \"COREHOURS\"] * self.params.DISC_ACCOUNT_CPU_HOURS_COST\r\n)\r\n\r\n# 3. GPU surcharge on top for jobs in the GPU partition\r\ngpu_mask = data[\"PARTITION\"] == self.params.GPU_PARTITION\r\ndata.loc[gpu_mask, \"COST\"] += (\r\n    data.loc[gpu_mask, \"GPUHOURS\"] * self.params.GPU_HOUR_COST\r\n)<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7b7f8b;\"># 1. Default cost for all jobs<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COST<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COREHOURS<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">*<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">CPU_HOUR_COST<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># 2. Discounted rate for contract accounts (regex match)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">account_mask <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">ACCOUNT<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">].str.match(<\/span><span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">DISC_ACCOUNT<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data.loc[account_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COST<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> (<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    data.loc[account_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COREHOURS<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">*<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">DISC_ACCOUNT_CPU_HOURS_COST<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># 3. GPU surcharge on top for jobs in the GPU partition<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">gpu_mask <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">PARTITION<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">==<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">GPU_PARTITION<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data.loc[gpu_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">COST<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">+=<\/span><span style=\"color: #f6f6f4;\"> (<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">    data.loc[gpu_mask, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">GPUHOURS<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">*<\/span> <span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.<\/span><span style=\"color: #bf9eee;\">GPU_HOUR_COST<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">)<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">All pricing constants are Pydantic fields with defaults:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">class MyCostCalculatorParams(BaseModel):\r\n    CPU_HOUR_COST: float = Field(default=0.02, ge=0.0)   # \u20ac\/core-hour\r\n    GPU_HOUR_COST: float = Field(default=0.04, ge=0.0)   # \u20ac\/GPU-hour (surcharge)\r\n    GPU_PARTITION: str = Field(default=\"gpu\")\r\n    DISC_ACCOUNT_CPU_HOURS_COST: float = Field(default=0.01, ge=0.0)\r\n    DISC_ACCOUNT: str = Field(default=r\"contract_fusion_.*\")<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f286c4;\">class<\/span> <span style=\"color: #97e1f1;\">MyCostCalculatorParams<\/span><span style=\"color: #f6f6f4;\">(<\/span><span style=\"color: #97e1f1; font-style: italic;\">BaseModel<\/span><span style=\"color: #f6f6f4;\">):<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">CPU_HOUR_COST<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">float<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.02<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">ge<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.0<\/span><span style=\"color: #f6f6f4;\">)   <\/span><span style=\"color: #7b7f8b;\"># \u20ac\/core-hour<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">GPU_HOUR_COST<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">float<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.04<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">ge<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.0<\/span><span style=\"color: #f6f6f4;\">)   <\/span><span style=\"color: #7b7f8b;\"># \u20ac\/GPU-hour (surcharge)<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">GPU_PARTITION<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">gpu<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">DISC_ACCOUNT_CPU_HOURS_COST<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">float<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.01<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">ge<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">0.0<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\">    <span style=\"color: #bf9eee;\">DISC_ACCOUNT<\/span><span style=\"color: #f6f6f4;\">: <\/span><span style=\"color: #97e1f1; font-style: italic;\">str<\/span> <span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> Field(<\/span><span style=\"color: #ffb86c; font-style: italic;\">default<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f286c4;\">r<\/span><span style=\"color: #ee6666;\">\"<\/span><span style=\"color: #e7ee98;\">contract_fusion_<\/span><span style=\"color: #97e1f1; font-style: italic;\">.<\/span><span style=\"color: #f286c4;\">*<\/span><span style=\"color: #ee6666;\">\"<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">Because the parameters are declared this way, an operator can update the CPU rate or add a new discount pattern directly from the OKA admin panel \u2014 no code change, no server restart required (only the pipeline re-run).<\/p>\n<p class=\"wp-block-paragraph\">The enhancer is entirely self-contained: no external APIs, no additional dependencies. It is the fastest enhancer to adopt from this library, and it covers the most common chargeback patterns we encounter at HPC centers. From here, you can extend it to read pricing tables from a database, apply time-of-day rates, or integrate with your institution&#8217;s financial system.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> Cost visibility changes user behavior. When researchers can see what their jobs cost, they tend to right-size allocations, avoid holding idle reservations, and shift exploratory workloads to off-peak hours. A chargeback system \u2014 even a simple one \u2014 is often the single highest-leverage change an HPC center can make to improve overall cluster efficiency.<\/p>\n<figure class=\"wp-block-image size-full blue-captions\"><img decoding=\"async\" width=\"2560\" height=\"1591\" class=\"lazyload wp-image-2879\" src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-scaled.webp\" data-orig-src=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-scaled.webp\" alt=\"\" srcset=\"data:image\/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%272560%27%20height%3D%271591%27%20viewBox%3D%270%200%202560%201591%27%3E%3Crect%20width%3D%272560%27%20height%3D%271591%27%20fill-opacity%3D%220%22%2F%3E%3C%2Fsvg%3E\" data-srcset=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-200x124.webp 200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-300x186.webp 300w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-400x249.webp 400w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-600x373.webp 600w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-768x477.webp 768w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-800x497.webp 800w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-1024x636.webp 1024w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-1200x746.webp 1200w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-1536x954.webp 1536w, https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/6-data-enhancers-scaled.webp 2560w\" data-sizes=\"auto\" data-orig-sizes=\"(max-width: 2560px) 100vw, 2560px\" \/><figcaption class=\"wp-element-caption\">Thanks to the &#8216;Cost&#8217; data enhancer we implemented, we can now toggle to cost metrics in supported OKA dashboards<\/figcaption><\/figure>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h3 class=\"wp-block-heading\">Cleaning and Enriching Job Metadata: The Misc Toolkit<\/h3>\n<p class=\"wp-block-paragraph\">Analytics quality depends on data quality. The three utility enhancers in the <code>misc\/<\/code> directory tackle the most common metadata cleanup tasks that every HPC site eventually writes some ad-hoc script to solve. Having them as named, tested, configurable enhancers integrates the cleanup directly into the OKA pipeline.<\/p>\n<h4 class=\"wp-block-heading\">Normalizing User Names: <code>capitalize_user.py<\/code><\/h4>\n<p class=\"wp-block-paragraph\">When multiple scheduler plugins, LDAP connectors, or import scripts coexist, user names can arrive in mixed case \u2014 <code>alice<\/code>, <code>Alice<\/code>, and <code>ALICE<\/code> are three distinct values to a GROUP BY query but represent the same person. This enhancer fixes it in one line:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">data[self.params.field] = data[self.params.field].str.upper()<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f6f6f4;\">data[<\/span><span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.field] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #bf9eee; font-style: italic;\">self<\/span><span style=\"color: #f6f6f4;\">.params.field].str.upper()<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">The target column defaults to the OKA <code>User<\/code> field but is configurable, so the same class can normalize any string column \u2014 account names, group names, or project codes \u2014 simply by changing the <code>field<\/code> parameter in the admin panel.<\/p>\n<h4 class=\"wp-block-heading\">Exploding Structured Metadata: <code>split_field.py<\/code><\/h4>\n<p class=\"wp-block-paragraph\">Many sites encode structured metadata in free-text scheduler fields. A common pattern in the <code>Account<\/code> or <code>Comment<\/code> field looks like:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">app=namd,nproc=128,cutoff=10,timestep=1.1,numsteps=2500<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f6f6f4;\">app<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\">namd,nproc<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">128<\/span><span style=\"color: #f6f6f4;\">,cutoff<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">10<\/span><span style=\"color: #f6f6f4;\">,timestep<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">1.1<\/span><span style=\"color: #f6f6f4;\">,numsteps<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">2500<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">The <code>SplitField<\/code> enhancer parses this into individual columns:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\"># Input: Account = \"app=namd,nproc=128,cutoff=10\"\r\n# Enhancer produces: app, nproc, cutoff\r\n# OKA then automatically prefixes all new columns \u2192 Cust_app, Cust_nproc, Cust_cutoff\r\nexploded = data[\"Account\"].str.split(\",\").explode()\r\nkv = exploded.str.split(\"=\", expand=True)\r\nkv.columns = [\"field_name\", \"field_value\"]\r\nsplit_df = kv.pivot_table(columns=\"field_name\", values=\"field_value\", ...)<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #7b7f8b;\"># Input: Account = \"app=namd,nproc=128,cutoff=10\"<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># Enhancer produces: app, nproc, cutoff<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #7b7f8b;\"># OKA then automatically prefixes all new columns \u2192 Cust_app, Cust_nproc, Cust_cutoff<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">exploded <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Account<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">].str.split(<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">,<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">).explode()<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">kv <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> exploded.str.split(<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">expand<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">True<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">kv.columns <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> [<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">field_name<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">field_value<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">]<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">split_df <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> kv.pivot_table(<\/span><span style=\"color: #ffb86c; font-style: italic;\">columns<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">field_name<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">values<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">field_value<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #bf9eee;\">...<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">Once these columns exist in OKA, you can filter by application, group by <code>Cust_nproc<\/code>, or build dashboards that break down resource usage by scientific domain \u2014 all without changing anything in the scheduler configuration.<\/p>\n<h4 class=\"wp-block-heading\">Parsing Slurm WCKeys: <code>wckeys_split.py<\/code><\/h4>\n<p class=\"wp-block-paragraph\">Slurm&#8217;s WCKey mechanism lets users tag jobs with a <code>project:software<\/code> identifier. Many centers use it for fine-grained accounting, but OKA ingests WCKey as a single opaque string. This enhancer splits it:<\/p>\n<div class=\"wp-block-kevinbatdorf-code-block-pro\" style=\"font-size: .875rem; font-family: Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; line-height: 1.25rem; --cbp-tab-width: 2; tab-size: var(--cbp-tab-width, 2);\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\"><span style=\"display: flex; align-items: center; padding: 10px 0px 10px 16px; margin-bottom: -2px; width: 100%; text-align: left; background-color: #333545; color: #ebebe6;\">Python<\/span><\/p>\n<pre class=\"code-block-pro-copy-button-pre\" aria-hidden=\"true\"><textarea class=\"code-block-pro-copy-button-textarea\" tabindex=\"-1\" readonly=\"readonly\" aria-hidden=\"true\">parts = data[\"WCKey\"].str.split(\":\", n=1, expand=True)\r\ndata[\"Project\"] = parts[0]\r\ndata[\"Software\"] = parts[1] if 1 in parts.columns else pd.NA<\/textarea><\/pre>\n<pre class=\"shiki dracula-soft\" style=\"background-color: #282a36;\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #f6f6f4;\">parts <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">WCKey<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">].str.split(<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">:<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">n<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">1<\/span><span style=\"color: #f6f6f4;\">, <\/span><span style=\"color: #ffb86c; font-style: italic;\">expand<\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #bf9eee;\">True<\/span><span style=\"color: #f6f6f4;\">)<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Project<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> parts[<\/span><span style=\"color: #bf9eee;\">0<\/span><span style=\"color: #f6f6f4;\">]<\/span><\/span>\r\n<span class=\"line\"><span style=\"color: #f6f6f4;\">data[<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #e7ee98;\">Software<\/span><span style=\"color: #dee492;\">\"<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">=<\/span><span style=\"color: #f6f6f4;\"> parts[<\/span><span style=\"color: #bf9eee;\">1<\/span><span style=\"color: #f6f6f4;\">] <\/span><span style=\"color: #f286c4;\">if<\/span> <span style=\"color: #bf9eee;\">1<\/span> <span style=\"color: #f286c4;\">in<\/span><span style=\"color: #f6f6f4;\"> parts.columns <\/span><span style=\"color: #f286c4;\">else<\/span><span style=\"color: #f6f6f4;\"> pd.<\/span><span style=\"color: #bf9eee;\">NA<\/span><\/span><\/code><\/pre>\n<\/div>\n<p class=\"wp-block-paragraph\">Rows where WCKey is missing or lacks the separator produce <code>pd.NA<\/code> in both output columns rather than an error string, so downstream aggregations behave correctly without additional null handling. OKA automatically prefixes the new columns, so they appear in dashboards as <code>Cust_Project<\/code> and <code>Cust_Software<\/code> \u2014 first-class filter and group-by dimensions available across every OKA view.<\/p>\n<p class=\"wp-block-paragraph\"><strong>Why this matters.<\/strong> You cannot analyze what you cannot query. These enhancers convert opaque text fields into structured, filterable columns. The investment is minimal \u2014 typically five minutes of configuration in the OKA admin panel \u2014 and the payoff is immediate: richer dashboards, better anomaly detection, and cleaner chargeback reports.<\/p>\n<div class=\"wp-block-spacer\" style=\"height: 50px;\" aria-hidden=\"true\">\u00a0<\/div>\n<h2 class=\"wp-block-heading\">Conclusion: Make OKA Yours<\/h2>\n<p class=\"wp-block-paragraph\">Data Enhancers are what turn OKA from a powerful generic platform into a precision instrument tuned for your cluster. The examples in this library cover the most common enrichment needs across HPC centers:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Energy measurement<\/strong> via EAR, whether through a direct database connection or through the <code>eacct<\/code> command-line tool.<\/li>\n<li><strong>Carbon footprint and electricity cost<\/strong> by crossing job energy with real-time French grid data, enabling sustainability reporting and variable-cost attribution.<\/li>\n<li><strong>Cost accounting<\/strong> through a flexible chargeback model that handles CPU hours, GPU surcharges, and contract discounts \u2014 all configurable from the admin panel.<\/li>\n<li><strong>Metadata normalization<\/strong> to make scheduler fields queryable, filterable, and aggregatable in OKA dashboards.<\/li>\n<\/ul>\n<p class=\"wp-block-paragraph\">Getting started is straightforward:<\/p>\n<ol class=\"wp-block-list\">\n<li><strong>Browse the repository<\/strong> and identify the enhancer closest to your use case.<\/li>\n<li><strong>Open Management \u2192 Data Enhancers<\/strong> in OKA and paste the code into the integrated editor.<\/li>\n<li><strong>Test in the sandbox<\/strong> against a real job subset, then publish the draft.<\/li>\n<li><strong>Assign to your cluster<\/strong> and adjust parameters \u2014 pricing constants, database credentials, API keys \u2014 directly in the admin panel.<\/li>\n<li><strong>Run the enhancers pipeline<\/strong> to backfill historical data with the new metrics.<\/li>\n<\/ol>\n<p class=\"wp-block-paragraph\">If you build something new, consider contributing it back. The library grows through the collective experience of HPC centers, and a well-documented enhancer you write today might save another team days of work tomorrow.<\/p>\n<p class=\"wp-block-paragraph\">OKA ships with powerful defaults. The real power is in making it yours.<\/p>\n<blockquote class=\"wp-block-quote quote-blue-border is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Questions, feature requests, or contributions? Open an issue or a pull request in the <a href=\"https:\/\/bitbucket.org\/ucit\/oka_dataenhancers\" target=\"_blank\" rel=\"noreferrer noopener\"><code>oka_dataenhancers<\/code> repository<\/a>, or <a href=\"\/#contact\">contact the UCit team<\/a>.<\/p>\n<\/blockquote>\n<\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38],"tags":[],"class_list":["post-2824","post","type-post","status-publish","format-standard","hentry","category-use-case"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Extending OKA with Data Enhancers: real-world examples from OKA&#039;s open-source library - OKA by UCit<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Extending OKA with Data Enhancers: real-world examples from OKA&#039;s open-source library - OKA by UCit\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/\" \/>\n<meta property=\"og:site_name\" content=\"OKA by UCit\" \/>\n<meta property=\"article:published_time\" content=\"2026-05-20T11:39:03+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-01T06:15:41+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"1520\" \/>\n\t<meta property=\"og:image:height\" content=\"1108\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"okaadmin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"okaadmin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/\"},\"author\":{\"name\":\"okaadmin\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\"},\"headline\":\"Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library\",\"datePublished\":\"2026-05-20T11:39:03+00:00\",\"dateModified\":\"2026-07-01T06:15:41+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/\"},\"wordCount\":2842,\"image\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/oka-dashboards-1024x746.webp\",\"articleSection\":[\"Use-case\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/\",\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/\",\"name\":\"Extending OKA with Data Enhancers: real-world examples from OKA's open-source library - OKA by UCit\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/oka-dashboards-1024x746.webp\",\"datePublished\":\"2026-05-20T11:39:03+00:00\",\"dateModified\":\"2026-07-01T06:15:41+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[[\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/\"]]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#primaryimage\",\"url\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/oka-dashboards.webp\",\"contentUrl\":\"https:\\\/\\\/oka.how\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/oka-dashboards.webp\",\"width\":1520,\"height\":1108,\"caption\":\"OKA provides 20+ HPC-native dashboards, built by cluster operations experts, giving both IT staff and end-users instant visibility into the cluster\u2019s behavior.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/2026\\\/05\\\/20\\\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/oka.how\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/\",\"name\":\"OKA by UCit\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/oka.how\\\/index.php\\\/en\\\/#\\\/schema\\\/person\\\/884ebbecc2bba75633dbba352cf7860d\",\"name\":\"okaadmin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g\",\"caption\":\"okaadmin\"},\"sameAs\":[\"https:\\\/\\\/oka.how\"],\"url\":\"https:\\\/\\\/oka.how\\\/index.php\\\/author\\\/author-1\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Extending OKA with Data Enhancers: real-world examples from OKA's open-source library - OKA by UCit","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/","og_locale":"en_US","og_type":"article","og_title":"Extending OKA with Data Enhancers: real-world examples from OKA's open-source library - OKA by UCit","og_url":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/","og_site_name":"OKA by UCit","article_published_time":"2026-05-20T11:39:03+00:00","article_modified_time":"2026-07-01T06:15:41+00:00","og_image":[{"width":1520,"height":1108,"url":"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards.webp","type":"image\/webp"}],"author":"okaadmin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"okaadmin","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#article","isPartOf":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/"},"author":{"name":"okaadmin","@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d"},"headline":"Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library","datePublished":"2026-05-20T11:39:03+00:00","dateModified":"2026-07-01T06:15:41+00:00","mainEntityOfPage":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/"},"wordCount":2842,"image":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#primaryimage"},"thumbnailUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1024x746.webp","articleSection":["Use-case"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/","url":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/","name":"Extending OKA with Data Enhancers: real-world examples from OKA's open-source library - OKA by UCit","isPartOf":{"@id":"https:\/\/oka.how\/index.php\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#primaryimage"},"image":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#primaryimage"},"thumbnailUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards-1024x746.webp","datePublished":"2026-05-20T11:39:03+00:00","dateModified":"2026-07-01T06:15:41+00:00","author":{"@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d"},"breadcrumb":{"@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":[["https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/"]]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#primaryimage","url":"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards.webp","contentUrl":"https:\/\/oka.how\/wp-content\/uploads\/2026\/05\/oka-dashboards.webp","width":1520,"height":1108,"caption":"OKA provides 20+ HPC-native dashboards, built by cluster operations experts, giving both IT staff and end-users instant visibility into the cluster\u2019s behavior."},{"@type":"BreadcrumbList","@id":"https:\/\/oka.how\/index.php\/2026\/05\/20\/extending-oka-with-data-enhancers-real-world-examples-from-okas-open-source-library\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oka.how\/"},{"@type":"ListItem","position":2,"name":"Extending OKA with Data Enhancers: real-world examples from OKA&#8217;s open-source library"}]},{"@type":"WebSite","@id":"https:\/\/oka.how\/index.php\/en\/#website","url":"https:\/\/oka.how\/index.php\/en\/","name":"OKA by UCit","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oka.how\/index.php\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/oka.how\/index.php\/en\/#\/schema\/person\/884ebbecc2bba75633dbba352cf7860d","name":"okaadmin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/650f9c77adc16f83f98d0627d5b5c4440cf72fc718329d74e32372820253cc3d?s=96&d=mm&r=g","caption":"okaadmin"},"sameAs":["https:\/\/oka.how"],"url":"https:\/\/oka.how\/index.php\/author\/author-1\/"}]}},"_links":{"self":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/2824","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/comments?post=2824"}],"version-history":[{"count":5,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/2824\/revisions"}],"predecessor-version":[{"id":3041,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/posts\/2824\/revisions\/3041"}],"wp:attachment":[{"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/media?parent=2824"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/categories?post=2824"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oka.how\/index.php\/wp-json\/wp\/v2\/tags?post=2824"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}