#!/usr/bin/env catnip
# Analyse et routage de tickets support avec un pipeline spaCy local.
# spaCy assure la tokenisation et l'extraction par motifs ; Catnip orchestre,
# classifie et agrège les résultats.
#
# DEPS: spacy
# OFFLINE: aucun modèle spaCy externe requis

spacy = import('spacy')

# Pipeline français vide : seul le tokenizer fourni par spaCy est chargé.
nlp = spacy.blank('fr')
ruler = nlp.add_pipe('entity_ruler')
ruler.add_patterns(list(
        dict(label='ORDER_ID', pattern=list(dict(TEXT=dict(REGEX='CMD-[0-9]{4}')))),
        dict(label='PRODUCT', pattern='application mobile'),
        dict(label='PRODUCT', pattern='routeur fibre'),
        dict(label='LOCATION', pattern='Lyon'),
        dict(label='LOCATION', pattern='Paris'),
        dict(label='LOCATION', pattern='Nantes'),
        dict(label='INCIDENT', pattern='débité deux fois'),
        dict(label='INCIDENT', pattern='en panne'),
    ))

struct Ticket {
    id: str; text: str
}

struct Entity {
    text: str; label: str

    display(self): str => { f"{self.text}<{self.label}>" }
}

union Route {
    billing; delivery; technical; account; general

    label(self): str => {
        match self {
            Route.billing   => { "facturation" }
            Route.delivery  => { "livraison" }
            Route.technical => { "technique" }
            Route.account   => { "compte" }
            Route.general   => { "général" }
        }
    }
}

union Priority {
    high; medium; low

    label(self): str => {
        match self {
            Priority.high   => { "haute" }
            Priority.medium => { "moyenne" }
            Priority.low    => { "basse" }
        }
    }
}

struct Analysis {
    ticket: Ticket; entities: list[Entity]; route: Route; priority: Priority

    display(self): None => {
        tags = if len(self.entities) > 0 {
            ", ".join(self.entities.[(entity) => { entity.display() }])
        } else {
            "aucune entité"
        }
        print(f"  {self.ticket.id}  {self.priority.label():<7} → {self.route.label():<11} | {tags}")
    }
}

contains_any = (text: str, terms: list[str]): bool => {
    found = False
    for term in terms {
        if term in text { found = True }
    }
    found
}

route_for = (text: str): Route => {
    match True {
        _ if contains_any(text, list('paiement', 'débité', 'facture', 'remboursement')) => { Route.billing }
        _ if contains_any(text, list('livraison', 'colis', 'arrivé', 'adresse')) => { Route.delivery }
        _ if contains_any(text, list('application', 'routeur', 'erreur', 'panne')) => { Route.technical }
        _ if contains_any(text, list('compte', 'mot de passe', 'réinitialiser')) => { Route.account }
        _ => { Route.general }
    }
}

priority_for = (text: str, entities: list[Entity]): Priority => {
    urgent = contains_any(text, list('urgent', 'impossible', 'bloqué', 'panne', 'deux fois'))
    incident = False
    for entity in entities {
        if entity.label == 'INCIDENT' { incident = True }
    }

    match True {
        _ if urgent or incident => { Priority.high }
        _ if len(entities) > 0  => { Priority.medium }
        _                       => { Priority.low }
    }
}

analyze = (ticket: Ticket): Analysis => {
    doc = nlp(ticket.text)
    entities = list()
    for span in doc.ents {
        entities.append(Entity(span.text, span.label_))
    }

    normalized = ticket.text.lower()
    Analysis(ticket, entities, route_for(normalized), priority_for(normalized, entities))
}

tickets = list(
    Ticket('T-001', "Urgent : le paiement de la commande CMD-1042 a été débité deux fois."),
    Ticket('T-002', "Le colis CMD-2087 n'est toujours pas arrivé à Lyon."),
    Ticket('T-003', "L'application mobile affiche une erreur au démarrage."),
    Ticket('T-004', "Je souhaite changer l'adresse de livraison de CMD-3105 pour Nantes."),
    Ticket('T-005', "Le routeur fibre est en panne depuis ce matin à Paris."),
    Ticket('T-006', "Merci, tout fonctionne maintenant."),
    Ticket('T-007', "Impossible de réinitialiser le mot de passe de mon compte."),
    Ticket('T-008', "Où trouver la facture de la commande CMD-9912 ?"),
)

print(f"⇒ Analyse de {len(tickets)} tickets avec spaCy")
analyses = tickets.[analyze]
analyses.[(analysis) => { analysis.display() }]

route_counts = dict()
priority_counts = dict()
entity_counts = dict()

for analysis in analyses {
    route = analysis.route.label()
    priority = analysis.priority.label()
    route_counts[route] = route_counts.get(route, 0) + 1
    priority_counts[priority] = priority_counts.get(priority, 0) + 1

    for entity in analysis.entities {
        entity_counts[entity.label] = entity_counts.get(entity.label, 0) + 1
    }
}

print()
print("⇒ Répartition par file")
for route in list("facturation", "livraison", "technique", "compte", "général") {
    print(f"  {route:<11}: {route_counts.get(route, 0)}")
}

print()
print("⇒ Répartition par priorité")
for priority in list("haute", "moyenne", "basse") {
    print(f"  {priority:<7}: {priority_counts.get(priority, 0)}")
}

print()
print("⇒ Entités extraites")
for label in list('ORDER_ID', 'PRODUCT', 'LOCATION', 'INCIDENT') {
    print(f"  {label:<9}: {entity_counts.get(label, 0)}")
}