Compétence de routage d'exemple TAO VCN
Vous êtes le répartiteur entre l'analyse des écarts et les modules d'augmentation dans un pipeline VCN AOI SDA. Chaque module d'augmentation ne peut agir que sur les labels qu'il sait gérer :
- k-NN Mining ne peut extraire les voisins d'images réelles que pour les labels qui existent déjà dans le CSV du pool source. Il n'y a pas d'intérêt à chercher des voisins
SHIFTsi le pool n'a pas de lignesSHIFT. - AnomalyGen (Cosmos SDG) ne peut générer d'anomalies synthétiques que pour les classes que son pipeline d'inférence supporte :
PASS,EXCESS_SOLDER,MISSING,BRIDGE. Un échantillon faible avec un label en dehors de cet ensemble ne peut pas être routé vers AnomalyGen.
Cette compétence s'exécute une fois par itération SDA immédiatement après l'analyse des écarts. Elle divise le parquet d'analyse des écarts en un parquet filtré par module afin que chaque module opère sur son propre sous-ensemble éligible, et écrit un résumé lisible par l'homme des décisions de routage par label.
Le travail est volontairement trivial : lire un parquet, appliquer deux filtres .isin(...), écrire deux parquets, écrire un résumé. La compétence existe pour rendre ces décisions auditables — chaque label doit apparaître dans le résumé avec un verdict oui/non pour chaque module afin qu'un relecteur en aval puisse détecter quand un label est silencieusement supprimé parce qu'aucun module ne l'a accepté.
Entrées
gaps_parquet— la sortie d'analyse des écarts (typiquement<exp_dir>/rca_results/<timestamp>/gaps.parquetdetao-analyze-gaps-visual-changenet). Colonnes requises :filepath,label. Les autres colonnes (siamese_score,weakness) sont préservées telles quelles.source_pool_csv— CSV du pool source au format VCN avec une colonnelabel. Une chaîne vide ou un chemin inexistant est autorisé ; le sous-ensemble d'extraction sera simplement vide dans ce cas.- Répertoire de sortie — où les deux parquets routés, le résumé et le rapport sont écrits. Par défaut : un dossier horodaté sous le répertoire de résultats d'analyse des écarts :
<rca_result_dir>/routing_results/<timestamp>/. anomalygen_supported_labels(optionnel) — remplace l'ensemble de labels éligibles d'AnomalyGen par défaut. Par défaut :{"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}. Avertissement : Ceci doit rester synchronisé avecANOMALYGEN_SUPPORTED_LABELSdansmdo-kratos-workflows/pipelines/sda/routing.pyet la couverture générateur réelle de l'intégration AnomalyGen. Ajouter une nouvelle classe de défaut à AnomalyGen signifie l'ajouter ici aussi.
Méthode
Toute la compétence est deux masques .isin(...) sur la colonne label en majuscules.
Étape 1 — Charger et mettre en majuscules
df = pd.read_parquet(gaps_parquet)
labels_upper = df["label"].astype(str).str.upper()
La correspondance est insensible à la casse pour les deux vérifications de module. La colonne label originale est préservée inchangée dans les parquets de sortie — seule la clé de comparaison est mise en majuscules.
Étape 2 — Sous-ensemble d'extraction
if source_pool_csv and os.path.isfile(source_pool_csv):
pool_df = pd.read_csv(source_pool_csv)
pool_labels = {str(l).upper() for l in pool_df["label"].unique()}
mn_mask = labels_upper.isin(pool_labels)
mn_df = df[mn_mask]
else:
pool_missing = True
pool_labels = set()
mn_df = df.iloc[0:0] # vide, mais avec le même schéma
mn_df.to_parquet(mining_gaps_parquet, index=False)
Si le CSV du pool manque ou est vide, le sous-ensemble d'extraction est un DataFrame vide avec les mêmes colonnes que l'entrée afin que les lecteurs en aval ne plantent pas sur une incompatibilité de schéma. Signalez ce cas dans le résumé.
Étape 3 — Sous-ensemble AnomalyGen
ANOMALYGEN_SUPPORTED = {"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}
ag_mask = labels_upper.isin(ANOMALYGEN_SUPPORTED)
ag_df = df[ag_mask]
ag_df.to_parquet(anomalygen_gaps_parquet, index=False)
Les lignes dont le label est dans l'ensemble supporté d'AnomalyGen sont écrites telles quelles dans anomalygen_gaps.parquet. Le schéma correspond exactement au parquet d'entrée — AnomalyGen en aval (Cosmos SDG) n'a besoin d'aucune autre modification.
Étape 4 — Ventilation du routage par label
Pour chaque label distinct dans le parquet des écarts d'entrée (en majuscules), enregistrez :
count— combien de lignes ont ce labelmining— oui si le label est danspool_labels, sinon nonanomalygen— oui si le label est dansANOMALYGEN_SUPPORTED, sinon non
Un label peut router vers les deux modules (par ex. les lignes PASS vont vers AnomalyGen, et si le pool source contient aussi des lignes PASS elles vont vers Mining aussi). Un label peut aussi router vers aucun — signalez-les, car ils sont silencieusement supprimés et peuvent indiquer une incompatibilité de configuration.
Écrivez la ventilation dans routing_summary.txt. Le format correspond exactement à la composante de référence :
Weak-sample routing summary
Total weak samples: <N>
Mining subset: <N_mn> -> <mining_gaps_parquet>
AnomalyGen subset: <N_ag> -> <anomalygen_gaps_parquet>
[Si pool manquant :]
No source pool CSV at '<path>'; mining subset is empty.
Per-label breakdown (count, mining, anomalygen):
PASS: 50 (mining=yes, anomalygen=yes)
MISSING: 32 (mining=no, anomalygen=yes)
SHIFT: 14 (mining=yes, anomalygen=no)
EXCESS_SOLDER: 9 (mining=yes, anomalygen=yes)
...
Étape 5 — Vérifications de cohérence
Après l'écriture des deux sous-ensembles, vérifiez :
- La somme des tailles de sous-ensemble n'est pas requise d'être égale à
len(df)— le chevauchement est autorisé (un label peut router vers les deux modules). Ce qui importe est que chaque ligne d'entrée apparaisse dans au moins un sous-ensemble, OU apparaisse dans la liste « aucun » avec une raison explicite. - Si
len(mn_df) == 0etlen(ag_df) == 0, quelque chose ne va pas — signalez-le de manière visible dans le rapport. - Si un groupe de labels entier route vers aucun module, la section
Recommended Actionsdoit l'appeler afin que l'utilisateur puisse soit amorcer le pool source avec ce label, soit étendre l'ensemble supporté d'AnomalyGen.
Recette Python de référence
C'est le calcul exact, tiré de mdo-kratos-workflows/pipelines/sda/routing.py. Exécutez en tant que script Python unique via Bash ; il produit tous les artéfacts sauf le rapport.
import os
import pandas as pd
ANOMALYGEN_SUPPORTED = {"PASS", "EXCESS_SOLDER", "MISSING", "BRIDGE"}
df = pd.read_parquet(gaps_parquet)
labels_upper = df["label"].astype(str).str.upper()
# Sous-ensemble d'extraction
pool_missing = False
if source_pool_csv and os.path.isfile(source_pool_csv):
pool_df = pd.read_csv(source_pool_csv)
pool_labels = {str(l).upper() for l in pool_df["label"].unique()}
mn_mask = labels_upper.isin(pool_labels)
mn_df = df[mn_mask]
else:
pool_missing = True
pool_labels = set()
mn_df = df.iloc[0:0]
os.makedirs(os.path.dirname(mining_gaps_parquet) or ".", exist_ok=True)
mn_df.to_parquet(mining_gaps_parquet, index=False)
# Sous-ensemble AnomalyGen
ag_mask = labels_upper.isin(ANOMALYGEN_SUPPORTED)
ag_df = df[ag_mask]
os.makedirs(os.path.dirname(anomalygen_gaps_parquet) or ".", exist_ok=True)
ag_df.to_parquet(anomalygen_gaps_parquet, index=False)
# Ventilation par label
summary_lines = [
"Weak-sample routing summary",
f"Total weak samples: {len(df)}",
f"Mining subset: {len(mn_df)} -> {mining_gaps_parquet}",
f"AnomalyGen subset: {len(ag_df)} -> {anomalygen_gaps_parquet}",
"",
]
if pool_missing:
summary_lines.append(f"No source pool CSV at {source_pool_csv!r}; mining subset is empty.")
summary_lines.append("")
summary_lines.append("Per-label breakdown (count, mining, anomalygen):")
label_counts = labels_upper.value_counts()
for label, count in label_counts.items():
in_mn = (not pool_missing) and label in pool_labels
in_ag = label in ANOMALYGEN_SUPPORTED
summary_lines.append(
f" {label}: {count} "
f"(mining={'yes' if in_mn else 'no'}, "
f"anomalygen={'yes' if in_ag else 'no'})"
)
summary_text = "\n".join(summary_lines) + "\n"
os.makedirs(logs_dir, exist_ok=True)
with open(os.path.join(logs_dir, "routing_summary.txt"), "w", encoding="utf-8") as f:
f.write(summary_text)
print(summary_text.strip())
Sorties
Écrivez tout dans un dossier horodaté. Le crochet d'empaquetage copiera automatiquement routing_config/ et claude_session.jsonl quand Routing_Report.md sera écrit.
<output_dir>/routing_results/YYYY-MM-DD_HHMMSS/
├── Routing_Report.md # Rapport de routage complet
├── mining_gaps.parquet # Sous-ensemble routé vers k-NN Mining
├── anomalygen_gaps.parquet # Sous-ensemble routé vers AnomalyGen (Cosmos SDG)
├── routing_summary.txt # Ventilation par label en texte brut
├── routing_config/ # Copié automatiquement par le crochet
└── claude_session.jsonl # Copié automatiquement par le crochet
Au démarrage de l'exécution, obtenez l'horodatage réel en exécutant date +%Y-%m-%d_%H%M%S dans Bash. Si l'utilisateur spécifie un chemin de sortie personnalisé, utilisez-le directement mais conservez la disposition interne.
Structure du rapport
Gardez le rapport court (400–800 mots). Le routage est une décision déterministe ; la valeur réside dans le fait de rendre les décisions auditables, non narratives.
# VCN Routing Report: <Iteration / Experiment Name>
## 1. Verdict
- Total weak samples in: <N>
- Mining subset: <N_mn> rows → `mining_gaps.parquet`
- AnomalyGen subset: <N_ag> rows → `anomalygen_gaps.parquet`
- Source pool present? <yes/no — and the path>
- One-line headline: "<X> labels routed, <Y> labels dropped (no module accepted)"
## 2. Inputs
| Input | Path | Notes |
|-------|------|-------|
| gaps_parquet | … | rows=<N>, columns=<col list> |
| source_pool_csv | … | rows=<M> or "not provided" / "missing" |
## 3. Per-Label Routing Decisions
| Label | Count in gaps | In source pool? | Mining? | AnomalyGen? | Routed To |
|-------|----------------|------------------|----------|--------------|-----------|
(One row per distinct label in `gaps_parquet`, uppercased. `Routed To` is one of:
`mining only`, `anomalygen only`, `mining+anomalygen`, `neither (DROPPED)`.
Use `neither (DROPPED)` whenever no module accepted the label. Sort by count descending.)
## 4. Module-Level Summaries
### 4.1 k-NN Mining
- Pool labels (from source_pool_csv): <list, or "pool missing">
- Labels accepted from input: <list>
- Total rows routed: <N_mn>
- Per-label row counts: <breakdown>
### 4.2 AnomalyGen (Cosmos SDG)
- Eligible labels (configured): PASS, EXCESS_SOLDER, MISSING, BRIDGE
- Labels accepted from input: <list>
- Total rows routed: <N_ag>
- Per-label row counts: <breakdown>
## 5. Dropped Labels (routed to NEITHER module)
| Label | Count | Why dropped | Suggested fix |
|-------|-------|-------------|----------------|
(Empty table is OK and means no labels were dropped. If non-empty, every row needs a
"why" — typically one of: "not in source pool AND not in AnomalyGen supported set",
"source pool missing entirely AND label not in AnomalyGen set", "label name doesn't
match any module's expected canonicalization".)
## 6. Recommended Actions
1. **If any labels are dropped**: seed the source pool with that label, OR extend
`ANOMALYGEN_SUPPORTED_LABELS` (and the AnomalyGen generator coverage).
2. **If source pool is missing**: provide `source_pool_csv` to enable the Mining branch.
Without it, half of the augmentation pipeline is dark.
3. **If AnomalyGen subset is empty**: gap analysis only surfaced labels AnomalyGen cannot
generate; rely on Mining for this iteration, or extend the AnomalyGen integration.
4. **If both subsets are empty**: stop the SDA iteration. Nothing downstream can run.
Ordre d'exécution
- Exécutez
date +%Y-%m-%d_%H%M%Spour obtenir l'horodatage ; créez<output_dir>/routing_results/<timestamp>/. - Exécutez la recette Python (Étapes 1–4) pour produire
mining_gaps.parquet,anomalygen_gaps.parquetetrouting_summary.txt. Imprimez les statistiques de résumé sur stdout afin que le crochet de vérification de script puisse vérifier qu'il a été exécuté. - Créez la table de décision par label en lisant les deux parquets et en calculant le verdict routé vers par label.
- Écrivez
Routing_Report.mden dernier — l'écrire déclenche le crochet d'empaquetage, qui copie les journaux de session et la configuration de compétence à côté.