power bi reports
9 TopicsAI-Powered Power BI reporting: From design to deployment with agent skills (Preview)
AI powered Power BI reporting skills is a collection of multiple skills that enables AI agents to automate report creation, from designing pages to publishing to Fabric. Now available through the Power BI authoring plugin in Skills for Fabric —a first-party catalog of agent skills for Microsoft Fabric optimized for GitHub Copilot CLI—this capability allows agents to author reports through natural language, write schema-correct PBIR files, reload Power BI Desktop, capture screenshots, and iteratively refine reports for a seamless, end-to-end authoring experience.38KViews4likes9CommentsThere are bugs in the landing page settings and drill down menu
The option to set the homepage in the Power BI desktop version of the Power BI report server does not work. After setting up the login page, when I publish the report to the Power BI report server, the homepage is not the login page set by the user. Additionally, the new drill down option does not work in the Power BI desktop version of the Power BI report server. When I hover over a chart in the report server, the drill down option does not automatically pop up, and I still need to right-click to see the drill down option. When will the personalized visual object feature be launched on the Power BI desktop version of the Power BI report server?876Views0likes3CommentsTime Series Visualization in Real-Time Dashboard (Preview)
Time series analysis is at the heart of understanding how data behaves over time. Whether you’re tracking sensor readings, monitoring system performance, or analyzing business trends, the ability to visualize and explore time-based data is essential for making informed decisions.1.8KViews1like0CommentsAnalyze Your Business Events in Eventhouse and Real-Time Dashboards (Preview)
Your business events are now automatically stored and queryable in Eventhouse. Run historical analyses with KQL, build live Real-Time Dashboards, and turn every business signal into actionable intelligence, all without extra configuration.
1.8KViews0likes0CommentsEnrich Power BI reports with machine learning in Microsoft Fabric
Many organizations want to go beyond descriptive Power BI reports and start answering forward‑looking questions with machine learning—identifying emerging trends, at-risk accounts, and where to focus effort to maximize impact. In practice, this is hard. Adding machine learning to Power BI often means moving data out of semantic models, rebuilding logic, managing separate storage and security, and assembling custom pipelines. As teams cross these boundaries, definitions drift, refreshes break, and insights rarely return to the tools business users rely on. Microsoft Fabric takes a different approach. Fabric unifies data engineering, data science, and business intelligence on a single, unified platform built on OneLake. Instead of breaking apart your BI stack, Fabric lets you extend it—reusing governed semantic models, training models where the data lives, and operationalize predictions in the same flow without duplicating logic or rebuilding pipelines. This post shows an end‑to‑end pattern for enriching a Power BI report with machine learning in Fabric. We start with a governed semantic model, train a churn-prediction model, and operationalize predictions with batch and real‑time scoring. The result is predictive insight aligned with business logic, reliably refreshed, and surfaced directly in Power BI for monitoring, sharing, and action. Scenario: Predicting bank customer churn Across industries, teams use Power BI to understand what has already happened. Dashboards show trends, highlight performance, and keep organizations aligned around a shared view of the business. But leaders are asking new questions—not just what happened, but what is likely next and how outcomes might change if they act. They want insights that help teams prioritize, intervene earlier, and focus effort where it matters. This is why many organizations look to enrich Power BI reports with machine learning. This challenge is especially common in financial services. Consider a bank that uses Power BI to track customer activity, balances, and service usage. Historical analysis shows that around 20% of customers churn, with churn tied to factors such as customer tenure, product usage, service interactions, and balance changes. Figure 1 Bank customer churn overview in Power BI At this point, descriptive reporting is no longer enough. The real questions become: Which customers are most likely to churn next? How confident are those predictions? Can churn risk update automatically as customer data changes? Can accounts teams monitor high‑risk customers and act early? Microsoft Fabric makes it possible to answer those questions with a fully integrated, end-to-end workflow. Architecture Figure 2 Architecture to enrich Power BI reports with ML Model First, let's look briefly at the architecture: Semantic model defines business logic and metrics. Semantic Link to access that model directly from notebooks. Fabric ML experiment to train and evaluate a churn prediction model. Batch scoring applies to the model at scale and saves predictions to OneLake. Real-time scoring endpoints support low-latency inference where needed. Dataflow Gen2 calls the real-time scoring endpoint to enrich data during ingestion. Power BI Report visualizing churn risk and driving action. All components live natively in Fabric and share the same security, governance, and storage layer. Prerequisites Before starting, you should have: A Fabric-enabled workspace. A “Bank Customer Churn Analysis” semantic model published in the workspace. You can find a sample semantic model and notebooks in this repository. Step 1: Explore the semantic model with Semantic Link A common challenge in analytics organizations is logic duplication. Business concepts like active customer, average monthly balance, or churned user are first defined in DAX for reporting, then redefined again in SQL or Spark for pipelines, and rebuilt once more in Python during feature engineering. Over time, those definitions drift, numbers diverge, and teams spend more time reconciling metrics than using them. In Fabric, the Power BI semantic model becomes the contract. Instead of exporting data and reimplementing logic, Semantic Link lets you query the semantic model directly from a Fabric notebook. Measures, filters, and calculated columns behave exactly as they do in Power BI. There is no separate authentication step, and no risk of logic drifting over time—the Fabric security context applies automatically. With one line of Python code, you can explore all semantic models, tables, and columns. # Semantic Link uses your Fabric identity — no separate credential handling. import sempy.fabric as fabric # Discover semantic models (datasets) you can access df_datasets = fabric.list_datasets() display(df_datasets) # Inspect tables in a specific semantic model dataset_name = "Bank Customer Churn Analysis" df_tables = fabric.list_tables(dataset_name) display(df_tables) # Inspect columns and metadata (useful for feature planning) df_columns = fabric.list_columns(dataset_name) display(df_columns) Figure 3 Semantic model discovery When you query a Power BI semantic model through Semantic Link, you work with the same logic that already powers your reports, not a parallel copy for data science. The result is a FabricDataFrame, which looks and feels like a pandas DataFrame but carries much more value. It preserves semantic context—such as measures, calculations, and lineage—directly from the semantic model into the data science environment. Because it subclasses pandas, you can use it with familiar libraries and workflows, while also calling Power BI measures directly from your notebook. # Measures are the business logic. Keeping them centralized prevents drift. df_measures = fabric.list_measures(dataset_name) display(df_measures) Figure 4 Inspect measure definitions # Pull governed data from the semantic model. # This keeps your ML features aligned with what Power BI shows. df_raw = fabric.read_table(dataset=dataset_name, table="churn") display(df_raw) Figure 5 Pull Semantic model table as a SparkDataFrame This lets you build features, explore data, and train models on the same business definitions your stakeholders trust in Power BI—without rewriting logic, managing credentials, or worrying about drift over time. Once the data is available in the notebook, you can perform exploratory analysis with familiar tools such as pandas, seaborn, or matplotlib. Any additional features you derive build on centrally governed definitions instead of replacing them. That foundation makes everything that follows safe, repeatable, and trustworthy. import seaborn as sns sns.set_theme(style="whitegrid", palette="tab10", rc = {'figure.figsize':(9,6)}) import matplotlib.pyplot as plt import matplotlib.ticker as mticker from matplotlib import rc, rcParams import numpy as np import pandas as pd import itertools Drop the duplicated rows, rows with missing data and drop the columns that you do not need. def clean_data(df): # Drop rows with missing data across all columns df = df.dropna() # Drop duplicate rows in columns: 'CustomerId', 'RowNumber' df = df.drop_duplicates(subset=['CustomerId', 'RowNumber']) # Drop columns: 'RowNumber', 'Surname' return df df_clean = clean_data(df_raw.copy()) df_clean.head() Figure 6 Cleaned training dataset Data exploration Display some summaries and visualizations of the cleaned data. Use this code to determine categorical, numerical, and target attributes. # Determine the dependent (target) attribute dependent_variable_name = "Exited" print(dependent_variable_name) # Determine the categorical attributes categorical_variables = [col for col in df_clean.columns if col in "O" or df_clean[col].nunique() <=5 and col not in "Exited"] print(categorical_variables) # Determine the numerical attributes numeric_variables = [col for col in df_clean.columns if df_clean[col].dtype != "object" and df_clean[col].nunique() >5 and col not in "CustomerId"] print(numeric_variables) Show the five-number summary (the minimum score, first quartile, median, third quartile, the maximum score) for the numerical attributes, using box plots. df_num_cols = df_raw[numeric_variables] sns.set(font_scale = 0.7) fig, axes = plt.subplots(nrows = 2, ncols = 3, gridspec_kw = dict(hspace=0.3), figsize = (17,8)) fig.tight_layout() for ax,col in zip(axes.flatten(), df_num_cols.columns): sns.boxplot(x = df_num_cols[col], color='green', ax = ax) fig.delaxes(axes[1,2]) Figure 7 Five-number summary for numeric attributes Show the distribution of exited versus non-exited customers across the categorical attributes. df_raw['Exited'] = df_raw['Exited'].astype(str) attr_list = ['Geography', 'Gender', 'HasCrCard', 'IsActiveMember', 'NumOfProducts', 'Tenure'] df_raw['Exited'] = df_raw['Exited'].astype(str) fig, axarr = plt.subplots(2, 3, figsize=(15, 4)) for ind, item in enumerate (attr_list): print(ind, item) sns.countplot(x = item, hue = 'Exited', data = df_raw, ax = axarr[ind%2][ind//2]) fig.subplots_adjust(hspace=0.7) Figure 8 Distribution of exited versus non-exited customers across the categorical attributes Show the frequency distribution of numerical attributes using histogram. columns = df_num_cols.columns[: len(df_num_cols.columns)] fig = plt.figure() fig.set_size_inches(18, 8) length = len(columns) for i,j in itertools.zip_longest(columns, range(length)): plt.subplot((length // 2), 3, j+1) plt.subplots_adjust(wspace = 0.2, hspace = 0.5) df_num_cols[i].hist(bins = 20, edgecolor = 'black') plt.title(i) plt.show() Figure 9 Frequency distribution of numerical attributes The exploratory analysis highlights a few clear patterns. Most customers are based in France, while Spain—despite having fewer customers—shows the lowest churn rate compared to France and Germany. Product adoption is limited, with very few customers using more than two bank products. Customer activity stands out as a strong signal: inactive customers are significantly more likely to churn. In contrast, gender and tenure length show little influence on whether a customer decides to close their account. Feature engineering Now let us perform feature engineering to generate new attributes based on current attributes: df_clean['Tenure'] = df_clean['Tenure'].astype(int) df_clean["NewTenure"] = df_clean["Tenure"]/df_clean["Age"] df_clean["NewCreditsScore"] = pd.qcut(df_clean['CreditScore'], 6, labels = [1, 2, 3, 4, 5, 6]) df_clean["NewAgeScore"] = pd.qcut(df_clean['Age'], 8, labels = [1, 2, 3, 4, 5, 6, 7, 8]) df_clean["NewBalanceScore"] = pd.qcut(df_clean['Balance'].rank(method="first"), 5, labels = [1, 2, 3, 4, 5]) df_clean["NewEstSalaryScore"] = pd.qcut(df_clean['EstimatedSalary'], 10, labels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) Perform one-hot encoding for geography and gender columns: import pandas as pd def clean_data(df_clean): # One-hot encode columns: 'Geography', 'Gender' df_clean = pd.get_dummies(df_clean, columns=['Geography', 'Gender']) return df_clean df_clean_1 = clean_data(df_clean.copy()) df_clean_1.head() Create a delta table for the cleaned data to get ready for model training. table_name = "df_clean" # Create Spark DataFrame from pandas sparkDF=spark.createDataFrame(df_clean_1) sparkDF.write.mode("overwrite").option("overwriteSchema", "true").format("delta").save(f"Tables/{table_name}") print(f"Spark dataframe saved to delta table: {table_name}") Step 2: Build a machine learning model in Fabric With governed data in hand, the focus shifts from alignment to prediction: learning which customers are most likely to leave. Model training To predict churn, we train a LightGBM classifier, a strong fit for large tabular datasets like customer records. LightGBM captures non‑linear patterns with minimal tuning and integrates natively with Fabric’s MLflow experience, making experiments easy to track and models easy to operationalize. Because churn data is naturally imbalanced, we apply SMOTE during training to ensure the model learns equally from both churned and retained customers. This combination keeps the notebook focused on insight and outcomes—building a reliable model that can be registered once and reused consistently across batch and real‑time scoring—rather than on managing infrastructure or custom training code. # Install imblearn for SMOTE using pip %pip install imblearn Prior to training any machine learning model, you need to load the delta table from the Lakehouse to read the cleaned data you created in the previous step. import pandas as pd SEED = 12345 df_clean = spark.read.format("delta").load("Tables/df_clean").toPandas() Now let us generate experiments for tracking and set the experiment. import mlflow # Setup experiment name EXPERIMENT_NAME = "bank-churn-experiment" mlflow.set_experiment(EXPERIMENT_NAME) Import the required libraries for model training from sklearn.model_selection import train_test_split from lightgbm import LGBMClassifier from sklearn.metrics import accuracy_score, f1_score, precision_score, confusion_matrix, recall_score, roc_auc_score, classification_report Use the train_test_split function from scikit-learn to split the data into training, validation, and test sets. #Split the dataset to 60%, 20%, 20% for training, validation, and test datasets y = df_clean["Exited"] X = df_clean.drop("Exited",axis=1) #Train-Test Separation X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=SEED) #Train-Validation Separation X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.25, random_state=SEED) And save the test data to a delta table # Save the test data to a delta table table_name = "df_test" df_test=spark.createDataFrame(X_test) df_test.write.mode("overwrite").option("overwriteSchema", "true").format("delta").save(f"Tables/{table_name}") print(f"Spark test DataFrame saved to delta table: {table_name}") The data exploration in Step 1 showed that of 10,000 customers, only 2,037 customers (around 20%) left the bank, indicating a highly imbalanced dataset. With so few examples of the minority class, a model struggles to learn the decision boundary. SMOTE is the most widely used approach for synthesizing new minority-class samples. Apply SMOTE to the training data to synthesize new samples for the minority class. from collections import Counter from imblearn.over_sampling import SMOTE sm = SMOTE(random_state=1234) X_res, y_res = sm.fit_resample(X_train, y_train) new_train = pd.concat([X_res, y_res], axis=1) Train the model using LightGBM and register the trained model as a ML model artifact. At the time of this writing, Fabric Real-Time Scoring Endpoint did not support tensor-based models, so we created a non-tensor-based output schema for the model. import mlflow.pyfunc import pandas as pd import numpy as np class Predictor(mlflow.pyfunc.PythonModel): def __init__(self, base_model): self.base_model = base_modeldef predict(self, context, model_input: pd.DataFrame) -> pd.DataFrame: # prediction pred = self.base_model.predict(model_input) pred = np.asarray(pred, dtype=np.int64) # probability (positive class) proba = self.base_model.predict_proba(model_input) proba = np.asarray(proba[:, 1], dtype=np.float64) return pd.DataFrame({ "prediction": pred, "probability": proba }) import mlflow from mlflow.models.signature import infer_signature import numpy as np import pandas as pd from lightgbm import LGBMClassifier # Define the LightGMB model lgbm_sm_model = LGBMClassifier( learning_rate=0.07, max_delta_step=2, n_estimators=100, max_depth=10, eval_metric="logloss", objective='binary', random_state=42 ) with mlflow.start_run(run_name="lgbm_sm_non_tensor") as run: lgbm_non_tensor_sm_run_id = run.info.run_id # Train on balanced data lgbm_sm_model.fit(X_res, y_res.ravel()) # Validation predictions (convert to pandas for non-tensor signature) y_pred = lgbm_sm_model.predict(X_val) # Optionally include probability for positive class as a tabular output example for signature y_proba = lgbm_sm_model.predict_proba(X_val)[:, 1] y_proba_series = pd.Series(y_proba.astype(float), name="probability") # Build output example for signature (TWO cols) pred_ex = np.asarray(lgbm_sm_model.predict(X_val), dtype=np.int64) proba_ex = np.asarray(lgbm_sm_model.predict_proba(X_val)[:, 1], dtype=np.float64) y_example = pd.DataFrame({"prediction": pred_ex, "probability": proba_ex}) signature = infer_signature(X_val, y_example) # Compute metrics accuracy = accuracy_score(y_val, y_pred) cr_lgbm_sm = classification_report(y_val, y_pred) cm_lgbm_sm = confusion_matrix(y_val, y_pred) roc_auc_lgbm_sm = roc_auc_score(y_res, lgbm_sm_model.predict_proba(X_res)[:, 1]) # Log metrics mlflow.log_metric("val_accuracy", accuracy) mlflow.log_metric("train_roc_auc", roc_auc_lgbm_sm) # Classification report mlflow.log_text(cr_lgbm_sm, "classification_report.txt") # Confusion matrix cm_df = pd.DataFrame( cm_lgbm_sm, index=["Actual_0", "Actual_1"], columns=["Pred_0", "Pred_1"]) mlflow.log_table(cm_df, "confusion_matrix.json") # Log model mlflow.pyfunc.log_model( artifact_path="model", python_model=Predictor(lgbm_sm_model), signature=signature, input_example=X_val.head(5), registered_model_name="lgbm_sm_non_tensor" ) View the machine learning experiment The experiment runs are automatically saved in the experiment artifact that can be found from the workspace. You can select the link from the cell output and open the experiment view. Parameters, metrics, and artifacts are all captured in the experiment, making every run reproducible and auditable. Figure 10 Fabric machine learning experiment view Validate the model performance Once machine learning model training is complete, you can assess its performance. Open the saved experiment, load the machine learning models, and evaluate them on the validation dataset. import mlflow.pyfunc # Fetch the model load_model_lgbm1_sm = mlflow.pyfunc.load_model(f"runs:/{lgbm_non_tensor_sm_run_id}/model") # Assess the performance of the loaded model on validation dataset ypred_lgbm1_sm_v1 = load_model_lgbm1_sm.predict(X_val) Next, you will develop a script to plot the confusion matrix to evaluate the accuracy of the classification using the validation dataset. import seaborn as sns sns.set_theme(style="whitegrid", palette="tab10", rc = {'figure.figsize':(9,6)}) import matplotlib.pyplot as plt import matplotlib.ticker as mticker from matplotlib import rc, rcParams import numpy as np import itertools def plot_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues): print(cm) plt.figure(figsize=(4,4)) plt.rcParams.update({'font.size': 10}) plt.imshow(cm, interpolation='nearest', cmap=cmap) plt.title(title) plt.colorbar() tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=45, color="blue") plt.yticks(tick_marks, classes, color="blue") fmt = '.2f' if normalize else 'd' thresh = cm.max() / 2. for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): plt.text(j, i, format(cm[i, j], fmt), horizontalalignment="center", color="red" if cm[i, j] > thresh else "black") plt.tight_layout() plt.ylabel('True label') plt.xlabel('Predicted label') cfm = confusion_matrix(y_val, y_pred=ypred_lgbm1_sm_v1["prediction"]) plot_confusion_matrix(cfm, classes=['Non Churn','Churn'], title='LightGBM-non-tensor') tn, fp, fn, tp = cfm.ravel() Figure 11 Confusion matrix for the churn prediction model Once performance is validated, the model is registered in the MLflow Model Registry. Moving forward, the model becomes a managed Fabric asset with a stable name and version, ready to be consumed by downstream pipelines. Step 3: Batch scoring on active customers Once the model is registered, the fastest way to make it usable is to score data at Spark scale and persist the results in OneLake—in our case, scoring active customers to identify who is likely to churn next. Fabric’s PREDICT capability is built for this: you point to a named model version in the Fabric registry and run inference where the data lives. That model name and version keeps scoring repeatable, letting you rerun the job with the same model and upgrade only when ready. Start by loading the latest snapshot of active customers from OneLake and applying the same feature-engineering logic to the active users. # Load the active users active_users = spark.read.format("delta").load("Tables/active") display(active_users) from pyspark.sql import functions as F from pyspark.sql.window import Window # Start with Spark DataFrame df = active_users.drop("RowNumber", "Surname") # Tenure as integer df = df.withColumn("Tenure", F.col("Tenure").cast("int")) # NewTenure = Tenure / Age df = df.withColumn("NewTenure", F.col("Tenure") / F.col("Age")) # CreditScore qcut into 6 bins credit_window = Window.orderBy("CreditScore") df = df.withColumn("CreditIdx", F.row_number().over(credit_window)) credit_count = df.count() df = df.withColumn("NewCreditsScore", F.ceil(F.lit(6) * F.col("CreditIdx") / F.lit(credit_count))) # Age qcut into 8 bins age_window = Window.orderBy("Age") df = df.withColumn("AgeIdx", F.row_number().over(age_window)) age_count = df.count() df = df.withColumn("NewAgeScore", F.ceil(F.lit(8) * F.col("AgeIdx") / F.lit(age_count))) # Balance qcut with rank(method="first") into 5 bins balance_window = Window.orderBy("Balance") df = df.withColumn("BalanceRank", F.row_number().over(balance_window)) balance_count = df.count() df = df.withColumn("NewBalanceScore", F.ceil(F.lit(5) * F.col("BalanceRank") / F.lit(balance_count))) # EstimatedSalary qcut into 10 bins salary_window = Window.orderBy("EstimatedSalary") df = df.withColumn("SalaryIdx", F.row_number().over(salary_window)) salary_count = df.count() df = df.withColumn("NewEstSalaryScore", F.ceil(F.lit(10) * F.col("SalaryIdx") / F.lit(salary_count))) # Cleanup temporary columns df_clean = df.drop("CreditIdx","AgeIdx","BalanceRank","SalaryIdx") from pyspark.sql import functions as F def clean_data_spark(df): df = ( df # One-hot Geography .withColumn("Geography_France", F.when(F.col("Geography") == "France", 1).otherwise(0)) .withColumn("Geography_Germany", F.when(F.col("Geography") == "Germany", 1).otherwise(0)) .withColumn("Geography_Spain", F.when(F.col("Geography") == "Spain", 1).otherwise(0)) # One-hot Gender .withColumn("Gender_Male", F.when(F.col("Gender") == "Male", 1).otherwise(0)) .withColumn("Gender_Female", F.when(F.col("Gender") == "Female", 1).otherwise(0)) # Drop original categorical columns .drop("Geography", "Gender") ) return df df_clean_1 = clean_data_spark(df_clean) display(df_clean_1) In practice, you have a few equally supported ways to invoke PREDICT, depending on how you like to work. If you prefer a notebook‑first workflow, the Transformer API gives you a clean wrapper around your registered MLflow model. You create an MLFlowTransformer, tell it which input columns to use, and choose an output column name (for example, predictions). from synapse.ml.predict import MLFlowTransformer model = MLFlowTransformer( inputCols=list(df_clean_1.columns), outputCol='predictions', modelName='lgbm_sm_non_tensor', modelVersion=1 ) From there, scoring is a standard Spark transform: you call transform() and you get back a DataFrame that includes predicted labels and probabilities—ready to save as a Delta table. The payoff is simple: the scoring step feels like any other Spark pipeline step, and it scales with your data. import pandas from pyspark.sql.functions import col predictions = model.transform(df_clean_1) predictions = predictions.select( "*", col("predictions.prediction").alias("prediction"), col("predictions.probability").alias("probability") ).drop("predictions") ay(predictions) If your team prefers more declarative pipelines—or you want scoring that reads like a query—Fabric also supports calling PREDICT through Spark SQL. That is useful when you want inference to sit alongside familiar SQLstyle transformations, or when you are handing off a scoring step to engineers who live in SQL. style transformations, or from pyspark.ml.feature import SQLTransformer # Substitute "model_name", "model_version", and "features" below with values for your own model name, model version, and feature columns model_name = 'lgbm_sm_non_tensor' model_version = 1 features = df_clean_1.columns sqlt = SQLTransformer().setStatement( f"SELECT PREDICT('{model_name}/{model_version}', {','.join(features)}) as predictions FROM __THIS__") # Substitute "X_test" below with your own test dataset display(sqlt.transform(df_clean_1)) And if you need maximum flexibility inside PySpark, you can convert the registered model into a PySpark UDF and call it like any other function over columns. This is handy when scoring needs to be embedded inside more customized DataFrame logic, while keeping the model reference centralized in the registry. from pyspark.sql.functions import col, pandas_udf, udf, lit # Substitute "model" and "features" below with values for your own model name and feature columns my_udf = model.to_udf() features = df_clean_1.columns display(df_clean_1.withColumn("predictions", my_udf(*[col(f) for f in features]))) Whatever you choose, the goal is the same: write predictions back to OneLake as a durable table. At that point, churn risk stops being “ML output in a notebook” and becomes governed data you can join, track over time, and surface directly in Power BI. # Save predictions to lakehouse to be used for generating a Power BI report table_name = "active_predictions" predictions.write.format('delta').mode("overwrite").save(f"Tables/{table_name}") print(f"Spark DataFrame saved to delta table: {table_name}") Step 4: Enable real-time scoring Batch scoring supports many reporting scenarios, but some decisions need predictions as soon as data changes—for example, a customer updating their profile, opening a support ticket, or onboarding. Waiting for the next scheduled refresh is often too late. Microsoft Fabric addresses this with real-time scoring endpoints. The same model used for batch scoring can be activated as an online endpoint without rewriting or redeploying it. ‑time scoring endpoints‑deploying An endpoint loads a specific model version once and serves low latency predictions through a secure REST interface. It uses the same feature schema and versioning model as batch inference, keeping training and serving cleanly separated. You can iterate on models without disrupting live scoring and always know which version is serving predictions.‑latency predictions through a secure REST interface. It uses the same feature schema and versioning model as batch inference, Fabric makes this simple. Endpoints are a built-in property of ML models. You can enable them directly from the Fabric UI with a low code experience, preview sample predictions immediately, and then integrate them wherever needed—inside Fabric or from external systems—through a scalable, managed API.‑in property of ML models. You can enable them directly from the Fabric UI with a low‑code experience, preview sample predictions immediately, and then integrate them wherever Figure 12 Activate real-time scoring endpoint for a ML model version Step 5: Enrich data during ingestion with Dataflow Gen2 Realtime scoring becomes most powerful when it is embedded in the data ingestion pipeline. Instead of scoring after the fact, predictions are generated as data arrives or changes.‑time scoring becomes most powerful when it is embedded With Dataflow Gen2, you can call the real-time scoring endpoint as part of a transformation step. New or updated customer records are sent to the model, enriched with churn prediction and probability, and written back to OneLake as part of the ingestion flow.‑time scoring endpoint as part of a transformation step. New or updated customer records are sent to the model, enriched with churn prediction and probability, and written back to OneLake as part of the ingestion flow. This keeps your data continuously ML enriched without custom orchestration or brittle glue code. Downstream consumers—reports, dashboards, and analysts—always see the latest predictions, and the scoring logic stays centralized around the managed model, not scattered across pipelines. Below is the sample M code for calling the ML endpoint from Dataflow Gen2. Use a service principal as credentials, and ensure it has the necessary permissions on the workspace hosting your ML model endpoint. To maintain workflow reliability, turn off the auto-sleep feature for endpoints. (input as table) as table => let // Feature engineering features = fnFeatureEngineering(input), // Build JSON request body jsonBody = "{""inputs"":" & Text.FromBinary( Json.FromValue( List.Transform( Table.ToRows(features), each _ ) ) ) & "}", // OAuth parameters TenantId = "<Your Tenant ID>", ClientId = "<Your Client ID>", ClientSecret = "<Your Client Secret>", Scope = "https://api.fabric.microsoft.com/.default", TokenUrl = "https://login.microsoftonline.com/" & TenantId & "/oauth2/v2.0/token", FormEncode = (fields as record) as text => Text.Combine( List.Transform( Record.FieldNames(fields), (k) => Uri.EscapeDataString(k) & "=" & Uri.EscapeDataString(Text.From(Record.Field(fields, k))) ), "&" ), TokenBodyText = FormEncode([ client_id = ClientId, client_secret = ClientSecret, grant_type = "client_credentials", scope = Scope ]), TokenResponse = Json.Document( Web.Contents( TokenUrl, [ Headers = [#"Content-Type" = "application/x-www-form-urlencoded"], Content = Text.ToBinary(TokenBodyText) ] ) ), AccessToken = TokenResponse[access_token], // Call ML model endpoint ScoreUrl = "<Your ML model endpoint url>", response = Json.Document( Web.Contents( ScoreUrl, [ Headers = [ #"Content-Type" = "application/json", #"Authorization" = "Bearer " & AccessToken ], Content = Text.ToBinary(jsonBody) ] ) ), // Parse orientation=values output PredictionsRaw = response[predictions], PredictionValues = List.Transform(PredictionsRaw, each _{0}), ProbabilityValues = List.Transform(PredictionsRaw, each _{1}), // Append columns to table Result = Table.FromColumns( Table.ToColumns(features) & { PredictionValues, ProbabilityValues }, Table.ColumnNames(features) & { "prediction", "probability" } ) in Result Step 6: Visualize and act in Power BI Once predictions are stored in OneLake, they behave like any other curated dataset in Fabric. You bring them into the semantic model, join them with customer dimensions, and shape them using the same governance patterns you already apply to business data. You can enrich your Power BI report by adding a dedicated view that highlights customers with a high risk of churn. Figure 13 Customers with high churn risk From the Power BI user’s perspective, churn risk becomes a clear, refreshed metric that can be filtered, segmented, and tracked over time. Account teams can monitor high-risk customers, prioritize outreach, and act before customers leave. Under the hood, this experience is powered by a full machine learning pipeline. On the surface, it feels like standard BI—reliable, explainable, and easy to consume. Closing thoughts The outcome of this pattern isn’t just a trained model—it’s machine learning insight that behaves like data. Predictions are written to OneLake as governed; refreshable tables aligned to the Power BI semantic model and surfaced directly in reports. As data changes, insights update. As definitions evolve, logic stays consistent. For business teams, churn risk appears as a trusted metric—filterable, explainable, and ready to act on. Microsoft Fabric makes this practical by keeping data, models, and BI on the same platform. There’s no need to export data, rebuild business logic, or maintain custom scoring pipelines. Teams can move from descriptive reporting to predictive insight without adding operational complexity. The same approach extends naturally to forecasting, anomaly detection, and propensity modeling. The principle stays the same: start from governed semantics, train models in place, and operationalize predictions where the business already works. In Fabric, machine learning isn’t adjacent to BI—it’s embedded in the analytics experience. Try it out Explore the sample repository and try it out. If you’re already using Power BI and Fabric, you can apply this pattern directly to your own data—starting with a single semantic model, one report, and one prediction, then expand as value becomes clear. Check out Fabric data science document for more information. If you’d like to learn more or discuss how this fits your organization, you can book time with author.57KViews0likes0CommentsPower BI Calculate Working Days For Every Month Dynamically
Hi Community Members, Since I am new to Power BI, I don't know much about it, though I am learning. I have a simple requirement where I need to use Power BI DAX to display the current working day number out of the overall working day number and that should appear dynamically every month. For example, 2020 April month has a total of 22 working days. And suppose today is the 20th working day out of 22days. So in Power BI Report, I need to display it as 20 of 22 days. Furthermore, I need to display "As of April 28, 2020" above it (Using DAX only and this one should likewise alter dynamically). Refer to the screenshot link below: Power-BI-Calculate-Working-Days-Dynamically Can anyone please help me out to achieve this? Thanks in Advance!!713Views0likes1CommentPower BI Report Data Source at Single Place While Moving to Different Environments.
While developing the Power BI Report, data source (DB Server Name and DB Name) is specified. But while moving to other environments need to open each and every report and change the data source like DEV, STG, UAT etc. Would like to see if there is any option by which data source is configured at one place and all reports will get data source from that location. Basically, have to change data source at single place while moving to different environments.Solved842Views0likes1CommentWhere are we with Shared Data Sources for Power BI Reports?
We are going through updating some documentation for deploying paginated reports and Power BI reports to Power BI Report Server, and I'm being reminded of how clunky the situation still is with managing data sources for PBI reports. I noticed in the January 2020 release there is a new PBIDS format, and it got me wondering if this might be a path to getting pubished shared data source that can be used for multiple reports? It's just one more difference between the paginated and Power BI reports that makes things feel disjointed.630Views1like0Comments