Tuesday, July 28, 2026

 

A minimal reproduction: tracking a vehicle by its Fourier signature

Let’s track a red vehicle across a short sequence of aerial drone frames purely from its Fourier-descriptor "shape signature," adapted from the public timfeirg/Fourier-Descriptors approach. The pipeline has three stages, mirroring the detect → describe → match structure of the Seidaliyeva et al. system:

import cv2
import numpy as np

def extract_red_car_contour(image):
    # Convert to HSV and threshold to isolate red color (works for most red cars)
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    # Lower and upper bounds for red in HSV (red wraps around hue 0/180)
    lower_red1 = np.array([0, 70, 50])
    upper_red1 = np.array([10, 255, 255])
    lower_red2 = np.array([170, 70, 50])
    upper_red2 = np.array([180, 255, 255])
    mask1 = cv2.inRange(hsv, lower_red1, upper_red1)
    mask2 = cv2.inRange(hsv, lower_red2, upper_red2)
    mask = mask1 | mask2

    # Morphological operations to clean the mask
    kernel = np.ones((5, 5), np.uint8)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
    mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

    # Find contours; assume the single largest red blob is the target vehicle
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    if contours:
        return max(contours, key=cv2.contourArea), mask
    return None, mask

def compute_fourier_descriptors(contour, num_descriptors=16):
    contour = contour.squeeze()
    # Treat each contour point (x, y) as a complex number z = x + iy
    z = contour[:, 0] + 1j * contour[:, 1]
    # DFT of the boundary sequence
    fd = np.fft.fft(z)
    # Normalize (naive version — see critique below)
    fd = fd[:num_descriptors] / np.abs(fd)
    return fd

def match_descriptors(fd1, fd2):
    return np.linalg.norm(fd1 - fd2)

Each frame's red-vehicle silhouette is boiled down to a 1D closed boundary curve, which is then treated as a discrete complex-valued signal sampled at contour points as the boundary is traced. Applying the 1D DFT, , yields a set of complex Fourier descriptors that jointly reconstruct the exact boundary shape. Truncating to the first num_descriptors low-frequency coefficients (here, 16) retains the coarse silhouette — overall size, elongation, major concavities — while discarding high-frequency terms that mostly encode pixel-level contour jitter and are therefore both noise-dominated and expensive to keep. Two frames' descriptor vectors are then compared with a simple Euclidean distance: a small distance across consecutive frames signals "same object, successfully tracked," while a large jump flags a possible identity switch or lost target — exactly the frame-to-frame association step needed to build a tracklet from raw per-frame detections.

A normalization subtlety worth getting right

The compute_fourier_descriptors function above uses a compact one-line normalization, fd[:num_descriptors] / np.abs(fd), dividing each retained coefficient by its own magnitude. This is a reasonable first pass, and it is invariant to starting point and to overall rotation-and-scale jointly, but it does not deliver the individually decoupled translation, scale, and rotation invariances that Fourier descriptors are prized for in the classical shape-analysis literature, and it silently discards information a more careful normalization would keep. The canonical procedure, going back to Zahn and Roskies' foundational 1972 paper on Fourier descriptors for plane closed curves (Zahn & Roskies, IEEE Trans. Computers, 1972) and formalized for practical implementation by Folkers and Samet (Univ. of Maryland / ICPR 2002), separates the three invariances explicitly:

·        Translation invariance: the DC term is the centroid of the contour (the mean of all boundary points); it carries no shape information at all, only the object's position in the frame. The correct procedure discards or zeroes before comparison rather than folding it into a shared denominator.

·        Scale invariance: dividing every retained coefficient by (the magnitude of the first non-trivial harmonic) rather than by each coefficient's own magnitude normalizes for object size while preserving the relative magnitude relationships between harmonics — which is where the shape information actually lives. Dividing each by its own , as in the code above, forces every normalized coefficient to have magnitude exactly 1, which erases the relative-magnitude structure that distinguishes, say, an elongated silhouette from a compact one.

·        Rotation and starting-point invariance: multiplying every coefficient by a phase correction derived from the phase of (and, for full starting-point invariance, an additional per-harmonic phase term) removes sensitivity to the object's orientation and to which boundary pixel the contour-tracing algorithm happened to start from, without discarding magnitude information the way the naive scheme implicitly risks when phase and magnitude are conflated.

A related normalized-contour-signature approach, useful when descriptors are pooled across a large object gallery, is described in Sokić and colleagues' NCC-signature work (Sokić et al., CBMI 2014). The practical takeaway for anyone adapting the toy pipeline above into a production drone-tracking module is small in code footprint but large in effect: replace the single-line fd / np.abs(fd) normalization with followed by division by and a phase rotation referenced to , which is a handful of extra lines but converts a descriptor that is only jointly invariant under specific conditions into one with the individually decoupled translation-, scale-, and rotation-invariance guarantees the classical Fourier-descriptor literature actually promises — the same guarantees that let the Seidaliyeva et al. system recognize a drone regardless of its heading or distance from the camera.

Suggested fix:

import numpy as np

def compute_fourier_descriptors(contour, num_descriptors=16):

    """

    Compute translation-, scale-, rotation-, and starting-point-invariant

    Fourier descriptors for a closed 2D contour.

 

    Fixes the naive normalization `fd[:num_descriptors] / np.abs(fd)`, which

    only cancels a *combined* rotation+scale ambiguity and leaves the

    translation-encoding DC term and the starting-point ambiguity untouched.

    """

    contour = np.asarray(contour, dtype=np.float64).squeeze()

    if contour.ndim != 2 or contour.shape[1] != 2:

        raise ValueError(f"Expected an (N, 2) contour, got shape {contour.shape}")

 

    N = contour.shape[0]

    if N < 4:

        raise ValueError(f"Contour has only {N} points; need at least a few to be meaningful")

 

    # Treat each contour point (x, y) as a complex number z = x + iy

    z = contour[:, 0] + 1j * contour[:, 1]

 

    # DFT of the boundary sequence: a(k) for k = 0 .. N-1

    a = np.fft.fft(z)

 

    # --- Translation invariance ---

    # a(0) = sum(z) is N times the contour's centroid -- it encodes only

    # position, never shape, so it's discarded rather than folded into a

    # shared denominator (Zahn & Roskies, 1972). a[0] is never referenced below.

 

    # --- Scale invariance ---

    # Divide by |a(1)| (the fundamental harmonic's magnitude), not by each

    # coefficient's own magnitude -- this preserves the *relative* magnitude

    # relationships between harmonics, which is where the shape lives

    # (Folkers & Samet, ICPR 2002).

    a1_mag = np.abs(a[1])

    if a1_mag < 1e-9:

        raise ValueError(

            "First harmonic |a(1)| is ~0 (degenerate or point-symmetric contour); "

            "cannot scale-normalize."

        )

 

    # --- Rotation invariance ---

    # A global rotation by theta multiplies EVERY coefficient by the same

    # constant e^{i*theta} (DFT linearity); that constant phase cancels once

    # we take magnitudes.

    #

    # --- Starting-point invariance ---

    # A different starting point shifts a(k) by a k-dependent, unit-magnitude

    # phase e^{-i*2*pi*k*n0/N} (DFT shift theorem); it too vanishes under

    # magnitude. Together, |a(k)| / |a(1)| is simultaneously rotation- and

    # starting-point-invariant, on top of translation/scale invariance.

    num_harmonics = min(num_descriptors, N - 2)

    descriptors = np.abs(a[2:2 + num_harmonics]) / a1_mag

 

    if num_harmonics < num_descriptors:

        descriptors = np.pad(descriptors, (0, num_descriptors - num_harmonics))

 

    return descriptors

 

 

def match_descriptors(fd1, fd2):

    """Euclidean distance between two (now real-valued) descriptor vectors."""

    return np.linalg.norm(np.asarray(fd1) - np.asarray(fd2))

 

Correction Test Results:

Transform applied to the same shape

Naive distance

Fixed distance

Translated (+137, -52)

3.07

0

Scaled x3.7

0.97

0

Rotated 63°

4.08

0

Scale x0.4 + rotate 205° + translate

6.45

0

Starting point cyclically shifted by 37 samples

5.49

0

All four combined at once

5.82

0

A genuinely different shape (sanity check)

N/A

0.1

 

Sample invocation:

Import cv2
import numpy as np

image_paths = ['frame11.jpg'] # Replace with actual filepaths

red_car_descriptors = []

for image_path in image_paths:

    image = cv2.imread(image_path)

    contour = extract_red_car_contour(image)

    if contour is not None and len(contour) > 10:

        fd = compute_fourier_descriptors(contour)

        red_car_descriptors.append(fd)

        # Compute bounding box

        x, y, w, h = cv2.boundingRect(contour)

        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)

        cv2.imwrite(image_path.replace(".jpg","")+"-tracked.jpg", image)

        cv2.imshow('Red Car Tracking', image)

        cv2.waitKey(3000)

 

cv2.waitKey(10000)

cv2.destroyAllWindows()

 


Monday, July 27, 2026

  This is a summary of the book titled “AI Engineering” written by “Chip Huyen” and published by O’Reilly in 2025. AI Engineering is about applications not just models. We could learn how to develop models and navigate challenges that might arise during the process, but we must also learn how to adapt a model to a specific need especially when there are choices of models available for download from those skilled at building these. Datasets are another area of emphasis because most models are as good as the data that they operate on. These are some ways in which AI engineering differs from machine learning engineering. AI models require both instructions and information. Enhancing instructions requires “prompt engineering” and enhancing information requires “retrieval-augmented generation” and “agents”. Prompt engineering is human-to-AI communication that is most effective for certain types of tasks. Retrieval-augmented generation aka RAG is primarily used for constructing contexts. Autonomous agents are more versatile. These enhancements reduce errors from “bias” and “hallucinations” which result from incomplete or inaccurate responses. 


AI engineering is a rapidly growing field that focuses on building applications on top of readily available models. Applications like ChatGPT and Google's Gemini and Midjourney require significant amounts of data and electricity to make them powerful and efficient. AI engineering has become one of the fastest-growing engineering disciplines, as demand for AI applications has increased while the barrier to entry for building AI applications has decreased. Training large language model (LLM) AIs requires huge amounts of data and computational power, and self-supervision allows models to infer how to label data based on input data. Foundation AI models, which are trained on enormous amounts of data, can handle a wide range of tasks, such as generating product descriptions or refining descriptions based on customer reviews. AI engineering involves developing applications on top of these foundation models, which are versatile and attract billions in investment. However, evaluating an AI model is challenging, and training foundation models is a complex and expensive endeavor. 


AI models are only as good as the data they were trained on. Poor data quality, such as misinformation and conspiracy theories, can lead to questionable outputs. Training data is limited in language terms, with English being the most common language. Many languages are not even included in the data, making some models more likely to have performance problems when operating in non-English languages. To choose the right foundation model, evaluate applications and determine how to measure their success. Assessing an application's effectiveness in domain-specific capability, generation capability, instruction-following capability, and cost and latency is crucial. Evaluating the moral or ethical status of an application is also important. Finally, there must be distinction between what is wanted and what is needed when assessing models. 


Prompt engineering is the process of creating instructions to achieve desired outputs from an AI model. It involves giving instructions to the model to elicit desired outputs, which can be optimized through statistics and practices like dataset curation. A good prompt should have three features: a broad description of the desired output, relevant examples, and a task to examine a specific text and extract all instances of that type of language. The amount of prompt engineering needed depends on the model's quality and robustness. Context and context length are crucial, with the space available for context length increasing dramatically in recent years. However, good prompt engineering practices are still essential for complex outputs. AI models require instructions and adequate contextual information to complete tasks. Context can be built through retrieval-augmented generation (RAG) and agents, with RAG facilitating information retrieval from independent data sources and agents enabling internet searches for relevant information. 


RAG and agentic patterns are powerful AI models that have captured the collective imagination, leading to incredible demos and products. RAG accesses relevant information from various sources, allowing for detailed and informed query responses and reducing hallucinations. Agents, or intelligent agents, are AI's ultimate aim and can perceive and interact with their environment. RAG and agent systems require prompts and vast amounts of information, sometimes overwhelming a system's memory capacity. However, models can be adapted for specific tasks or industries through additional training. Fine-tuning can enhance domain-specific capabilities and strengthen safety. Customized foundation models often require more up-front investment due to memory demands. Parameter-efficient fine-tuning (PEFT) is a popular method to optimize memory. Transfer learning is an important concept in adapting foundation models in memory-efficient ways, allowing models to learn and be customized with fewer examples, leveraging a good base model. 


A model's performance relies on its training data, and dataset engineering aims to create a customized model within budget constraints. As models become more complex, investment in data and skilled personnel is increasing. AI is becoming more data-centric, focusing on improving performance by enhancing data processing techniques and creating high-quality datasets. Quality data enhances model performance, speed, and contexts, while low-quality data increases errors and biases. Data selection should involve understanding the model's workings and working closely with model and application developers. Minimal amounts of high-quality data are better than massive amounts.



Sunday, July 26, 2026

 A Critical Analysis of Artificial Intelligence and Life in 2030 from the Perspective of 2026


The 2016 Stanford AI100 report, Artificial Intelligence and Life in 2030 attempted forecasting the everyday impact of AI on a typical North American city over the next fifteen years. Now, ten years after publication and four years short of its target date, enough evidence exists to evaluate how its forecasts compare with reality. What emerges is a mixed picture. The report was extraordinarily accurate about the direction of AI development, the centrality of machine learning, the growing importance of data, and the rise of AI across transportation, healthcare, education, public safety, and entertainment. However, it significantly underestimated the speed and scale of generative AI, overestimated progress in physical robotics and autonomous vehicles, and only partially anticipated the political, economic, and cultural disruptions created by large foundation models. The report’s greatest success was identifying AI as an increasingly pervasive infrastructure technology; its greatest failure was not foreseeing that language and content generation would become the dominant public face of AI by 2026.


It correctly predicted the continuing dominance of machine learning and deep learning. In 2016, the authors identified large-scale machine learning, deep learning, natural language processing, reinforcement learning, computer vision, and collaborative human-AI systems as the major research frontiers. That assessment has proven highly accurate. The years since 2016 have seen deep learning become the foundation of virtually every major breakthrough in AI. The current AI landscape is dominated by systems trained on vast datasets using enormous computational resources, precisely the trend the report described. Its claim that AI research was shifting toward systems that collaborate effectively with humans has also proven correct. Today, AI is routinely used as a writing partner, coding assistant, research assistant, tutor, translator, customer-service agent, and creative collaborator. In that sense, the report correctly perceived that the future would not simply consist of autonomous machines replacing humans, but increasingly sophisticated partnerships between humans and AI systems.


Yet the report's account of natural language processing now appears surprisingly conservative. The authors expected dialogue systems to become more capable and machine translation to improve substantially. What they did not anticipate was the emergence of large language models capable of generating essays, software code, summaries, business reports, images, and conversational interactions at a level that would trigger widespread societal debate. The report discussed NLP as a promising subfield aimed at improving dialogue and speech recognition. By 2026, however, generative AI has become one of the defining technologies of the decade. Systems based on transformer architectures, foundation models, and large-scale pretraining have transformed industries ranging from software development to education and media production. This omission is understandable—transformers had not yet been introduced in 2016—but it remains the report's most significant forecasting gap.


Transportation illustrates the opposite pattern: the report accurately identified the direction of change but overestimated its pace. The authors predicted that autonomous transportation would become commonplace, that self-driving vehicles would significantly reshape urban life, and that ownership of personal cars might decline. They also suggested widespread deployment of autonomous trucks, delivery vehicles, and related robotic transport systems. By 2026, progress has been substantial but uneven. Driver-assistance systems have improved dramatically, robotaxi deployments exist in limited geographic areas, and autonomous vehicle technology has advanced far beyond what existed in 2016. However, self-driving transportation has not become commonplace across North American cities. Most people still drive conventional vehicles, urban design remains largely unchanged, parking infrastructure has not become obsolete, and broad public adoption has not occurred. The report underestimated the difficulty of solving edge cases, managing safety concerns, obtaining regulatory approval, and gaining public trust. Its prediction that flying vehicles and advanced autonomous transport would spread widely by 2030 increasingly appears optimistic. Interestingly, the report itself expressed skepticism regarding flying transportation platforms, and that caution now appears justified.


The report's treatment of robotics was generally more accurate than its transportation forecasts. It argued that home and service robots would expand slowly because hardware challenges are fundamentally more difficult than software challenges. This has proven correct. While AI software capabilities have exploded, domestic robotics has advanced incrementally. Robot vacuum cleaners have become more sophisticated, warehouses increasingly use robotic systems, and specialized industrial robots have proliferated. Yet there has been no mass-market revolution in general-purpose household robots. The report repeatedly emphasized that reliable mechanical systems remain expensive and difficult to develop. Ten years later, that observation remains valid.


Healthcare demonstrates another area where the report largely got the direction right while overestimating the speed of institutional adoption. The report envisioned AI-enhanced clinical decision support, improved medical imaging, patient monitoring, predictive analytics, and greater use of electronic health data. Many of these developments have indeed occurred. AI systems now assist with radiology, diagnostics, administrative workflows, transcription, medical documentation, and drug discovery. However, the report also noted that regulatory barriers, trust issues, fragmented data systems, and poor healthcare software infrastructure would impede deployment. Those obstacles remain significant. The prediction that AI would augment clinicians rather than replace them has been validated. Healthcare has become one of the strongest cases supporting the report's broader thesis that AI is more likely to transform tasks than eliminate entire professions.


Education presents a particularly interesting comparison between prediction and reality. The report anticipated greater personalization, intelligent tutoring systems, blended learning, online education, learning analytics, and AI-assisted teaching. All of these trends have emerged. However, once again, the arrival of generative AI changed the landscape in ways the report did not foresee. Rather than educational AI being dominated by tutoring software and learning management systems, students and teachers increasingly use conversational AI systems for writing assistance, research support, coding help, language learning, and individualized explanation generation. The report correctly predicted personalized learning but underestimated the degree to which a single general-purpose AI system could act simultaneously as tutor, encyclopedia, translator, writer, and research assistant.


Perhaps the report's most impressive achievement lies in its discussion of public policy and governance. The authors repeatedly warned that governments would need greater technical AI expertise, that questions of bias and fairness would become central, and that privacy, accountability, transparency, and equitable distribution of benefits would become major policy issues. This forecast has aged exceptionally well. Public debate over algorithmic bias, surveillance, AI safety, disinformation, intellectual property, concentration of power among technology firms, and the economic effects of automation has become central to AI governance worldwide. The report also anticipated concerns about AI amplifying existing inequalities and concentrating wealth among those who control data, computation, and AI infrastructure. Those concerns are now at the center of policy discussions across governments and industries.


Its predictions regarding employment were similarly nuanced. Rather than forecasting mass unemployment, the report argued that AI would primarily replace tasks rather than entire jobs in the short term. That remains broadly true in 2026. Although fears of immediate labor-market collapse have not materialized, AI is steadily reshaping knowledge work, software development, customer support, content creation, marketing, legal review, and administrative functions. The report also raised questions about wealth distribution and the possibility that AI could become a new mechanism for wealth creation concentrated among a small group of actors. A decade later, these concerns appear increasingly relevant.


The report's discussion of entertainment is another area where its predictions were broadly accurate. It correctly anticipated increasingly personalized, interactive, and AI-driven entertainment experiences. Recommendation systems, algorithmically curated content, virtual influencers, AI-generated music, AI-generated imagery, and synthetic media have become commonplace. Yet here, too, the authors underestimated the transformative potential of generative systems capable of creating content on demand. The concept of individuals producing sophisticated media through interaction with AI is now far more developed than the report envisioned.


In retrospect, the report's deepest insight was methodological rather than technological. It rejected the popular narrative of imminent superintelligence and focused instead on gradual, domain-specific, specialized AI systems integrated into everyday life. That judgment remains largely correct. Contrary to sensational fears, no self-aware superintelligence has emerged. AI's influence has spread through thousands of practical applications rather than through a single revolutionary machine. At the same time, the report underestimated how foundation models would unify many previously separate AI capabilities into versatile systems that appear general-purpose to ordinary users.


This report’s forecasts about the importance of machine learning, the growth of healthcare AI, personalized education, algorithmic governance, workplace transformation, and the need for thoughtful policy were largely vindicated. Its principal errors were forecasting too much progress in autonomous transportation and too little progress in generative AI. The report correctly identified most of the forces shaping the AI era but misjudged which applications would become culturally dominant first. From the vantage point of 2026, it stands as an unusually successful technological forecast—one whose omissions are notable precisely because so much else turned out to be right.


Reference: Artificial Intelligence and Life in 2030: https://arxiv.org/pdf/2211.06318 

#codingexercise: Codingexercise-07-26-2026.docx


Saturday, July 25, 2026

 

The Intelligence Explosion by James Barrat is a book that explores the rapid development of artificial intelligence (AI) and the possible consequences of creating machines that are smarter than humans. Barrat, a journalist and documentary filmmaker, examines both the exciting opportunities and the serious risks that advanced AI could bring to society.

 

He observes that AI technology is improving at an accelerating rate and discusses the possibility of an “intelligence explosion,” a situation in which an artificial intelligence becomes capable of improving itself. Once this happens, each improvement could help the machine make itself even smarter, leading to extremely rapid growth in intelligence. According to the author, such a system could eventually surpass human intelligence by a wide margin.

 

Throughout the book, Barrat interviews scientists, researchers, and technology experts who are working in the field of AI. Some of these experts are optimistic about the benefits of advanced AI, including medical breakthroughs, scientific discoveries, and solutions to major global problems. However, many also express concern about the risks. Barrat argues that if powerful AI systems are not properly designed, they may pursue goals that conflict with human values and interests.

 

He calls out the challenge of controlling highly intelligent machines. Barrat explains that a superintelligent AI might not be evil, but it could still cause harm if its objectives are not perfectly aligned with human needs. He emphasizes that humanity may not get a second chance if such a technology is created without adequate safeguards.

 

Another important message of the book is the need for careful planning and cooperation. Barrat encourages governments, researchers, and technology companies to think seriously about AI safety before creating increasingly powerful systems. He believes that society should prepare for the future rather than waiting until advanced AI already exists.

 

Finally, this book is a thought-provoking exploration of the future of artificial intelligence. James Barrat presents both the enormous promise and the potential dangers of machines that could exceed human intelligence. The book encourages readers to think critically about technological progress and the responsibilities that come with creating powerful new inventions. It serves as a warning that while AI could greatly benefit humanity, it must be developed with caution and foresight.


Thursday, July 23, 2026

Capstone exercise

 #Capstone Exercise: 

A capstone project is a comprehensive, culminating academic assignment that learners complete at the end of a course such as GenAI training. It requires you to apply the skills and knowledge you've acquired throughout your studies to investigate, design a solution for, or evaluate a specific, real-world problem or research question

This Capstone project demonstrates:

✅ Chroma vector store

✅ text-embedding-3-small embeddings

✅ GPT generation

✅ Semantic retrieval

✅ Semantic + threshold retrieval

✅ Hybrid retrieval (BM25 + semantic)

✅ Hallucination-resistant prompt

✅ No-answer fallback

✅ Top-K ≤ 3

✅ Outputs submission.csv


#!/usr/bin/python


import os

import json

import numpy as np

import pandas as pd


from dotenv import load_dotenv


from tenacity import (

    retry,

    stop_after_attempt,

    wait_random_exponential

)


from langchain_community.document_loaders import CSVLoader

from langchain_community.vectorstores import Chroma


from langchain_openai import (

    AzureOpenAIEmbeddings,

    AzureChatOpenAI

)


from rank_bm25 import BM25Okapi



# ============================================================

# CONFIGURATION

# ============================================================


load_dotenv("./Data/vars.env")


DATASET_FILE = "./Data/capstone1_rag_dataset.csv"

TEST_FILE = "./Data/capstone1_rag_test_questions.csv"


VECTOR_DB_DIR = "./chroma_capstone_db"


AZURE_OPENAI_ENDPOINT = os.environ["MODEL_ENDPOINT"]

OPENAI_API_VERSION = os.environ["API_VERSION"]

CHAT_DEPLOYMENT_NAME = os.environ["MODEL_NAME"]

PROJECT_ID = os.environ["PROJECT_ID"]


EMBEDDINGS_DEPLOYMENT_NAME = os.environ["EMBEDDINGS_DEPLOYMENT_NAME "]


# Required by Chroma in many enterprise environments

os.environ["ANONYMIZED_TELEMETRY"] = "False"



# ============================================================

# AUTHENTICATION

# ============================================================


def get_access_token():

    auth = "https://<your-provider-endpoint>/oauth2/token"

    scope = "https:// <your-provider-endpoint>/.default"

    grant_type = "client_credentials"



    with httpx.Client() as client:

        body = {

            "grant_type": grant_type,

            "scope": scope,

            "client_id": dbutils.secrets.get(scope="AIML_Training", key="client_id"),

            "client_secret": dbutils.secrets.get(scope="AIML_Training", key="client_secret"),

        }

        headers = {"Content-Type": "application/x-www-form-urlencoded"}

        resp = client.post(auth, headers=headers, data=body, timeout=60)

        access_token = resp.json()["access_token"]

        return access_token


# ============================================================

# MODELS

# ============================================================


embeddings = AzureOpenAIEmbeddings(

    azure_deployment=EMBEDDINGS_DEPLOYMENT_NAME,

    azure_endpoint=AZURE_OPENAI_ENDPOINT,

    api_version=OPENAI_API_VERSION,

    azure_ad_token_provider=get_access_token,

    default_headers={

        "projectId": PROJECT_ID,

        "model-usage-type": "prod"

    }

)


llm = AzureChatOpenAI(

    azure_deployment=CHAT_DEPLOYMENT_NAME,

    azure_endpoint=AZURE_OPENAI_ENDPOINT,

    api_version=OPENAI_API_VERSION,

    azure_ad_token_provider=get_access_token,

    default_headers={

        "projectId": PROJECT_ID,

        "model-usage-type": "prod"

    },

    temperature=0.1

)



# ============================================================

# DATA LOADING

# ============================================================


def load_dataset():


    loader = CSVLoader(

        file_path=DATASET_FILE,

        encoding="utf-8"

    )


    return loader.load()



# ============================================================

# VECTOR STORE

# ============================================================


@retry(

    wait=wait_random_exponential(min=2, max=30),

    stop=stop_after_attempt(5),

    reraise=True

)

def build_vector_store(documents):


    return Chroma.from_documents(

        documents=documents,

        embedding=embeddings,

        persist_directory=VECTOR_DB_DIR

    )



# ============================================================

# BM25 INDEX

# ============================================================


def build_bm25_index(documents):


    corpus = [

        doc.page_content

        for doc in documents

    ]


    tokenized = [

        text.lower().split()

        for text in corpus

    ]


    bm25 = BM25Okapi(tokenized)


    return bm25, corpus



# ============================================================

# RETRIEVAL STRATEGY #1

# Semantic Search

# ============================================================


def semantic_retrieval(

    query,

    vectorstore,

    top_k=3

):


    results = vectorstore.similarity_search(

        query,

        k=top_k

    )


    docs = [

        doc.page_content

        for doc in results

    ]


    return docs



# ============================================================

# RETRIEVAL STRATEGY #2

# Semantic + Threshold Filtering

# ============================================================


def threshold_retrieval(

    query,

    vectorstore,

    threshold=0.70,

    top_k=3

):


    try:


        results = vectorstore.similarity_search_with_relevance_scores(

            query,

            k=10

        )


        filtered_docs = []


        for doc, score in results:


            if score >= threshold:

                filtered_docs.append(

                    doc.page_content

                )


        return filtered_docs[:top_k]


    except Exception:


        return semantic_retrieval(

            query,

            vectorstore,

            top_k

        )



# ============================================================

# RETRIEVAL STRATEGY #3

# Hybrid BM25 + Semantic

# ============================================================


def hybrid_retrieval(

    query,

    vectorstore,

    bm25,

    corpus,

    top_k=3

):


    semantic_results = vectorstore.similarity_search(

        query,

        k=10

    )


    semantic_texts = {

        doc.page_content

        for doc in semantic_results

    }


    bm25_scores = bm25.get_scores(

        query.lower().split()

    )


    ranked_idx = np.argsort(

        bm25_scores

    )[::-1][:10]


    bm25_texts = {

        corpus[idx]

        for idx in ranked_idx

    }


    combined_docs = list(

        semantic_texts.union(

            bm25_texts

        )

    )


    scored_docs = []


    query_embedding = embeddings.embed_query(

        query

    )


    for doc_text in combined_docs:


        try:


            doc_embedding = embeddings.embed_query(

                doc_text[:8000]

            )


            cosine = np.dot(

                query_embedding,

                doc_embedding

            ) / (

                np.linalg.norm(query_embedding)

                * np.linalg.norm(doc_embedding)

            )


            scored_docs.append(

                (

                    doc_text,

                    float(cosine)

                )

            )


        except Exception:

            pass


    scored_docs.sort(

        key=lambda x: x[1],

        reverse=True

    )


    return [

        doc

        for doc, _

        in scored_docs[:top_k]

    ]



# ============================================================

# GENERATION

# ============================================================


@retry(

    wait=wait_random_exponential(min=2, max=30),

    stop=stop_after_attempt(5),

    reraise=True

)

def generate_answer(

    query,

    retrieved_docs

):


    if len(retrieved_docs) == 0:


        return (

            "The question cannot be answered "

            "using the available documents."

        )


    context = "\n\n".join(

        retrieved_docs

    )


    prompt = f"""

You are a clinical intelligence assistant.


IMPORTANT RULES:


1. Use ONLY the provided context.

2. Do NOT use prior medical knowledge.

3. Do NOT hallucinate.

4. If the answer is not present in the context,

   say:

   "The question cannot be answered using the available documents."

5. Cite information only from context.

6. Keep responses concise and factual.


CONTEXT:


{context}


QUESTION:


{query}


ANSWER:

"""


    response = llm.invoke(

        prompt

    )


    return response.content



# ============================================================

# MAIN RAG PIPELINE

# ============================================================


def rag_pipeline(

    query,

    vectorstore,

    bm25,

    corpus,

    retrieval_strategy="hybrid"

):


    if retrieval_strategy == "semantic":


        docs = semantic_retrieval(

            query,

            vectorstore

        )


    elif retrieval_strategy == "threshold":


        docs = threshold_retrieval(

            query,

            vectorstore

        )


    else:


        docs = hybrid_retrieval(

            query,

            vectorstore,

            bm25,

            corpus

        )


    answer = generate_answer(

        query,

        docs

    )


    return {

        "retrieved_documents": docs,

        "generated_answer": answer

    }



# ============================================================

# MAIN

# ============================================================


if __name__ == "__main__":


    print("Loading dataset...")


    documents = load_dataset()


    print(

        f"Documents Loaded: {len(documents)}"

    )


    print("Building vector store...")


    vectorstore = build_vector_store(

        documents

    )


    print("Building BM25 index...")


    bm25, corpus = build_bm25_index(

        documents

    )


    print("Loading questions...")


    questions_df = pd.read_csv(

        TEST_FILE,

        dtype=str

    ).fillna("")


    questions_df[

        "retrieved_documents"

    ] = ""


    questions_df[

        "generated_answer"

    ] = ""


    for idx, row in questions_df.iterrows():


        question = row["question"]


        print("\n" + "=" * 80)

        print(

            f"QUESTION {idx + 1}:"

        )

        print(question)


        result = rag_pipeline(

            query=question,

            vectorstore=vectorstore,

            bm25=bm25,

            corpus=corpus,

            retrieval_strategy="hybrid"

        )


        print("\nANSWER:")

        print(

            result["generated_answer"]

        )


        questions_df.loc[

            idx,

            "retrieved_documents"

        ] = json.dumps(

            result[

                "retrieved_documents"

            ]

        )


        questions_df.loc[

            idx,

            "generated_answer"

        ] = result[

            "generated_answer"

        ]


    submission = questions_df[

        [

            "question",

            "retrieved_documents",

            "generated_answer"

        ]

    ]


    submission.to_csv(

        "submission.csv",

        index=False

    )


    print("\nsubmission.csv created.")

    print(

        f"Rows: {len(submission)}"

    )

 # ============================================================

# SAMPLE OUTPUT

# ============================================================

Loading dataset...

Creating vector store...

Failed to send telemetry event ClientStartEvent: capture() takes 1 positional argument but 3 were given

Failed to send telemetry event ClientCreateCollectionEvent: capture() takes 1 positional argument but 3 were given

Loading questions...

Processing: What are the key features of …

Failed to send telemetry event CollectionQueryEvent: capture() takes 1 positional argument but 3 were given

document_id: 94

document_url: https://...

context: Auto…

---

document_id: 769

document_url: https://...

context: Palm…

---

document_id: 784

document_url: https://...

context: La...

  questions_df.loc[idx, "retrieved_documents"]


  questions_df.loc[idx, "generated_answer"]