guineapular 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guineapular-1.0.0/MANIFEST.in +2 -0
- guineapular-1.0.0/PKG-INFO +16 -0
- guineapular-1.0.0/README.md +36 -0
- guineapular-1.0.0/data/auth_registry.json +12 -0
- guineapular-1.0.0/guineapular.egg-info/PKG-INFO +16 -0
- guineapular-1.0.0/guineapular.egg-info/SOURCES.txt +16 -0
- guineapular-1.0.0/guineapular.egg-info/dependency_links.txt +1 -0
- guineapular-1.0.0/guineapular.egg-info/requires.txt +3 -0
- guineapular-1.0.0/guineapular.egg-info/top_level.txt +1 -0
- guineapular-1.0.0/pular_core/__init__.py +1 -0
- guineapular-1.0.0/pular_core/api.py +126 -0
- guineapular-1.0.0/pular_core/auth.py +65 -0
- guineapular-1.0.0/pular_core/context.py +89 -0
- guineapular-1.0.0/pular_core/engine.py +100 -0
- guineapular-1.0.0/pular_core/knowledge.py +83 -0
- guineapular-1.0.0/pular_core/validator.py +41 -0
- guineapular-1.0.0/setup.cfg +4 -0
- guineapular-1.0.0/setup.py +20 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guineapular
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Le cerveau déterministe de l'IA Pular-Learning
|
|
5
|
+
Author: Keit Xellker & User
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Requires-Dist: setuptools
|
|
10
|
+
Requires-Dist: fastapi
|
|
11
|
+
Requires-Dist: uvicorn
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# guineapular 🌍
|
|
2
|
+
|
|
3
|
+
Le cerveau déterministe de l'IA Pular-Learning.
|
|
4
|
+
|
|
5
|
+
`guineapular` est une bibliothèque Python spécialisée dans la traduction et la discussion en Pular (Guinée), basée sur une architecture de racines courtes et un moteur morphologique précis.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install guineapular
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Utilisation Rapide
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pular_core.api import PularCoreAPI
|
|
17
|
+
|
|
18
|
+
# Initialisation
|
|
19
|
+
api = PularCoreAPI()
|
|
20
|
+
|
|
21
|
+
# Mode Traduction
|
|
22
|
+
print(api.translate_simple("cle-api", 0, "manger", "present"))
|
|
23
|
+
|
|
24
|
+
# Mode Discussion (Sarah)
|
|
25
|
+
response = api.chat("cle-api", "Comment ça va ?")
|
|
26
|
+
print(response['sarah_response'])
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Fonctionnalités
|
|
30
|
+
- **Précision Chirurgicale** : Pas de LLM, pas d'hallucinations.
|
|
31
|
+
- **Morpho Engine** : Gestion automatique des suffixes (-ugol, -gol, -agol).
|
|
32
|
+
- **Sarah Persona** : Module d'émotions et d'opinions intégré.
|
|
33
|
+
- **Sécurité** : Gatekeeper avec système de crédits.
|
|
34
|
+
|
|
35
|
+
## Auteurs
|
|
36
|
+
Développé par Sarah Xellker & User.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guineapular
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Le cerveau déterministe de l'IA Pular-Learning
|
|
5
|
+
Author: Keit Xellker & User
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.6
|
|
9
|
+
Requires-Dist: setuptools
|
|
10
|
+
Requires-Dist: fastapi
|
|
11
|
+
Requires-Dist: uvicorn
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: requires-dist
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
data/auth_registry.json
|
|
5
|
+
guineapular.egg-info/PKG-INFO
|
|
6
|
+
guineapular.egg-info/SOURCES.txt
|
|
7
|
+
guineapular.egg-info/dependency_links.txt
|
|
8
|
+
guineapular.egg-info/requires.txt
|
|
9
|
+
guineapular.egg-info/top_level.txt
|
|
10
|
+
pular_core/__init__.py
|
|
11
|
+
pular_core/api.py
|
|
12
|
+
pular_core/auth.py
|
|
13
|
+
pular_core/context.py
|
|
14
|
+
pular_core/engine.py
|
|
15
|
+
pular_core/knowledge.py
|
|
16
|
+
pular_core/validator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pular_core
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# pular_core package initialization
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from .knowledge import PularKnowledge
|
|
2
|
+
from .engine import PularEngine
|
|
3
|
+
from .validator import PularValidator
|
|
4
|
+
from .context import PularContextAnalyzer
|
|
5
|
+
from .auth import AuthPaywall
|
|
6
|
+
|
|
7
|
+
class PularCoreAPI:
|
|
8
|
+
"""
|
|
9
|
+
Interface unifiée du service Xellker Pular Core (v1.0).
|
|
10
|
+
Fait le pont entre les 4 Sous-Programmes.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, dictionary_path=None):
|
|
14
|
+
self.knowledge = PularKnowledge(dictionary_path)
|
|
15
|
+
self.engine = PularEngine(self.knowledge)
|
|
16
|
+
self.context = PularContextAnalyzer(self.engine, self.knowledge)
|
|
17
|
+
self.auth = AuthPaywall()
|
|
18
|
+
self.validator = PularValidator()
|
|
19
|
+
|
|
20
|
+
def _gatekeeper(self, api_key, word_count=1):
|
|
21
|
+
"""
|
|
22
|
+
Gatekeeper (Sous-Programme 4) :
|
|
23
|
+
Refuse l'exécution si l'utilisateur n'a pas payé ou n'a plus de crédits.
|
|
24
|
+
"""
|
|
25
|
+
if not self.auth.validate_key(api_key):
|
|
26
|
+
return {"error": "Invalid or inactive API Key. Access Denied."}
|
|
27
|
+
|
|
28
|
+
if not self.auth.deduct_credits(api_key, word_count):
|
|
29
|
+
return {"error": "Insufficient credits. Please top up your account."}
|
|
30
|
+
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
def translate_simple(self, api_key, subject_idx, verb_french, tense_key, is_negative=False):
|
|
34
|
+
"""Traduction simple avec Gatekeeper."""
|
|
35
|
+
gate_status = self._gatekeeper(api_key, 1)
|
|
36
|
+
if gate_status: return gate_status
|
|
37
|
+
|
|
38
|
+
raw_translation = self.engine.translate_simple_sentence(
|
|
39
|
+
subject_idx, verb_french, tense_key, is_negative
|
|
40
|
+
)
|
|
41
|
+
return self.validator.normalize_typography(raw_translation)
|
|
42
|
+
|
|
43
|
+
def translate_dual(self, api_key, subject_idx, v1_french, v2_french, tense_key):
|
|
44
|
+
"""Traduction double verbe (Successivité) avec Gatekeeper."""
|
|
45
|
+
gate_status = self._gatekeeper(api_key, 2)
|
|
46
|
+
if gate_status: return gate_status
|
|
47
|
+
|
|
48
|
+
raw_translation = self.context.resolve_double_verb(
|
|
49
|
+
subject_idx, v1_french, v2_french, tense_key
|
|
50
|
+
)
|
|
51
|
+
return self.validator.normalize_typography(raw_translation)
|
|
52
|
+
|
|
53
|
+
def translate_with_object(self, api_key, subject_idx, verb_french, tense_key, object_key):
|
|
54
|
+
"""Traduction avec pronom objet (-lan, -ma) avec Gatekeeper."""
|
|
55
|
+
gate_status = self._gatekeeper(api_key, 2)
|
|
56
|
+
if gate_status: return gate_status
|
|
57
|
+
|
|
58
|
+
raw_translation = self.context.apply_object_suffix(
|
|
59
|
+
verb_french, subject_idx, tense_key, object_key
|
|
60
|
+
)
|
|
61
|
+
return self.validator.normalize_typography(raw_translation)
|
|
62
|
+
|
|
63
|
+
def get_identity(self, api_key, term, is_negative=False):
|
|
64
|
+
"""Identité & Beauté avec Gatekeeper."""
|
|
65
|
+
gate = self._gatekeeper(api_key, 1)
|
|
66
|
+
if gate: return gate
|
|
67
|
+
return self.context.handle_identity(term, is_negative)
|
|
68
|
+
|
|
69
|
+
def get_ethical_eval(self, api_key, is_good=True):
|
|
70
|
+
"""Éthique (Sarah) avec Gatekeeper."""
|
|
71
|
+
gate = self._gatekeeper(api_key, 1)
|
|
72
|
+
if gate: return gate
|
|
73
|
+
return self.context.evaluate_ethics(is_good)
|
|
74
|
+
|
|
75
|
+
def chat(self, api_key, message_french):
|
|
76
|
+
"""
|
|
77
|
+
Porte DISCUSSION (/chat) : Sarah Xellker v1.0.
|
|
78
|
+
Gère les intentions et simule une mémoire contextuelle.
|
|
79
|
+
"""
|
|
80
|
+
gate = self._gatekeeper(api_key, 1)
|
|
81
|
+
if gate: return gate
|
|
82
|
+
|
|
83
|
+
# 1. Normalisation
|
|
84
|
+
message = message_french.lower()
|
|
85
|
+
for char in "?!.,": message = message.replace(char, "")
|
|
86
|
+
key = message.strip().replace(" ", "_")
|
|
87
|
+
|
|
88
|
+
# 2. Logique Contextuelle (Action-Réaction)
|
|
89
|
+
# On vérifie d'abord les correspondances directes (Salutations, Accords)
|
|
90
|
+
response = self.context.get_conversation_pair(key)
|
|
91
|
+
if response:
|
|
92
|
+
return {
|
|
93
|
+
"status": "success",
|
|
94
|
+
"intent": "conversation",
|
|
95
|
+
"sarah_response": self.validator.normalize_typography(response),
|
|
96
|
+
"mood": "friendly"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# 3. Logique d'Emotion/Opinion (Sarah vivante)
|
|
100
|
+
for section in ["opinion", "emotion", "duration"]:
|
|
101
|
+
data = self.knowledge.find_in_lexicon(section, key)
|
|
102
|
+
if data:
|
|
103
|
+
return {
|
|
104
|
+
"status": "success",
|
|
105
|
+
"intent": section,
|
|
106
|
+
"sarah_response": self.validator.normalize_typography(data),
|
|
107
|
+
"mood": "vibrant"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# 4. Fallback (Tentative de traduction ou aveu d'ignorance)
|
|
111
|
+
return {
|
|
112
|
+
"status": "success",
|
|
113
|
+
"intent": "unknown",
|
|
114
|
+
"sarah_response": self.validator.normalize_typography(self.knowledge.find_in_lexicon("conversation", "je_ne_sais_pas")),
|
|
115
|
+
"mood": "puzzled"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def get_lexicon_term(self, api_key, category, term):
|
|
119
|
+
"""Récupération de terme lexique avec Gatekeeper."""
|
|
120
|
+
gate_status = self._gatekeeper(api_key, 1)
|
|
121
|
+
if gate_status: return gate_status
|
|
122
|
+
|
|
123
|
+
data = self.knowledge.find_in_lexicon(category, term)
|
|
124
|
+
if isinstance(data, dict):
|
|
125
|
+
return data.get("pular", data.get("nom", str(data)))
|
|
126
|
+
return data
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import secrets
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
class AuthPaywall:
|
|
6
|
+
"""
|
|
7
|
+
La Couche Business & Sécurité (Sub-Programme 4).
|
|
8
|
+
Gère la monétisation, les clés API et le décompte des crédits.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, auth_file="data/auth_registry.json"):
|
|
12
|
+
self.auth_file = auth_file
|
|
13
|
+
self.registry = self._load_registry()
|
|
14
|
+
|
|
15
|
+
def _load_registry(self):
|
|
16
|
+
if not os.path.exists(self.auth_file):
|
|
17
|
+
# Création d'un registre par défaut pour les tests
|
|
18
|
+
os.makedirs(os.path.dirname(self.auth_file), exist_ok=True)
|
|
19
|
+
default_registry = {
|
|
20
|
+
"admin-key-99": {"owner": "Admin", "credits": 9999, "active": True},
|
|
21
|
+
"user-key-test": {"owner": "Tester", "credits": 50, "active": True}
|
|
22
|
+
}
|
|
23
|
+
with open(self.auth_file, "w") as f:
|
|
24
|
+
json.dump(default_registry, f, indent=2)
|
|
25
|
+
return default_registry
|
|
26
|
+
|
|
27
|
+
with open(self.auth_file, "r") as f:
|
|
28
|
+
return json.load(f)
|
|
29
|
+
|
|
30
|
+
def save_registry(self):
|
|
31
|
+
with open(self.auth_file, "w") as f:
|
|
32
|
+
json.dump(self.registry, f, indent=2)
|
|
33
|
+
|
|
34
|
+
def generate_key(self, owner, initial_credits=100):
|
|
35
|
+
"""Génère un identifiant unique pour un nouveau client."""
|
|
36
|
+
new_key = f"xell-{secrets.token_hex(4)}"
|
|
37
|
+
self.registry[new_key] = {
|
|
38
|
+
"owner": owner,
|
|
39
|
+
"credits": initial_credits,
|
|
40
|
+
"active": True
|
|
41
|
+
}
|
|
42
|
+
self.save_registry()
|
|
43
|
+
return new_key
|
|
44
|
+
|
|
45
|
+
def validate_key(self, api_key):
|
|
46
|
+
"""Vérifie si la clé est valide et active."""
|
|
47
|
+
return api_key in self.registry and self.registry[api_key]["active"]
|
|
48
|
+
|
|
49
|
+
def deduct_credits(self, api_key, word_count):
|
|
50
|
+
"""Décompte chaque mot traduit du crédit de l'utilisateur."""
|
|
51
|
+
if not self.validate_key(api_key):
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
current_credits = self.registry[api_key]["credits"]
|
|
55
|
+
if current_credits < word_count:
|
|
56
|
+
return False # Solde insuffisant
|
|
57
|
+
|
|
58
|
+
self.registry[api_key]["credits"] -= word_count
|
|
59
|
+
self.save_registry()
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
def get_remaining_credits(self, api_key):
|
|
63
|
+
if self.validate_key(api_key):
|
|
64
|
+
return self.registry[api_key]["credits"]
|
|
65
|
+
return 0
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
logger = logging.getLogger(__name__)
|
|
4
|
+
|
|
5
|
+
class PularContextAnalyzer:
|
|
6
|
+
"""
|
|
7
|
+
L'Analyseur de Contexte (Sub-Programme 3).
|
|
8
|
+
Gère les règles de successivité, les objets directes et les nuances sémantiques.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, engine, knowledge):
|
|
12
|
+
self.engine = engine
|
|
13
|
+
self.knowledge = knowledge
|
|
14
|
+
|
|
15
|
+
def apply_object_suffix(self, verb_french, person_idx_subject, tense_key, object_person_key):
|
|
16
|
+
"""
|
|
17
|
+
Gère l'objet : Coller les suffixes -lan (moi) ou -ma (toi) directement après la racine.
|
|
18
|
+
"""
|
|
19
|
+
verb_data = self.knowledge.get_verb_data(verb_french)
|
|
20
|
+
if not verb_data:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
root = verb_data["root"]
|
|
24
|
+
obj_suffix = self.knowledge.data.get("grammar", {}).get("pronoms_objet", {}).get(object_person_key)
|
|
25
|
+
|
|
26
|
+
if not obj_suffix:
|
|
27
|
+
return self.engine.conjugate(verb_french, tense_key, person_idx_subject)
|
|
28
|
+
|
|
29
|
+
# Le pronom sujet reste celui de la règle générale
|
|
30
|
+
# Le suffixe de conjugaison vient APRES l'objet?
|
|
31
|
+
# En Pular, c'est Racine + Objet + Suffixe (souvent)
|
|
32
|
+
# Mais le user dit: "Coller les suffixes -lan (moi) ou -ma (toi) directement après la racine."
|
|
33
|
+
|
|
34
|
+
pronom_sujet = self.engine.conjugate(verb_french, tense_key, person_idx_subject).split(" ")[0]
|
|
35
|
+
conj_suffix = self.knowledge.get_suffix(tense_key if tense_key != "present" else "present")
|
|
36
|
+
|
|
37
|
+
# Simplification selon instruction user: Racine + Objet
|
|
38
|
+
# Ex: Mi gnam-lan (Je me mange?) -> Plus réaliste: A gnam-lan (Tu me manges)
|
|
39
|
+
# On va suivre l'instruction: "Coller ... directement après la racine"
|
|
40
|
+
return f"{pronom_sujet} {root}{obj_suffix}{conj_suffix if conj_suffix else ''}"
|
|
41
|
+
|
|
42
|
+
def resolve_double_verb(self, subject_idx, v1_french, v2_french, tense_key):
|
|
43
|
+
"""Règle du Double Verbe : Verbe 1 conjugué, Verbe 2 en -gol."""
|
|
44
|
+
return self.engine.apply_successivity([v1_french, v2_french], subject_idx, tense_key)
|
|
45
|
+
|
|
46
|
+
def handle_identity(self, term, is_negative=False):
|
|
47
|
+
"""Gère le module Identité et la logique de classe adjectivale."""
|
|
48
|
+
data = self.knowledge.find_in_lexicon("identity", term)
|
|
49
|
+
if not data: return None
|
|
50
|
+
|
|
51
|
+
if isinstance(data, dict) and "root" in data:
|
|
52
|
+
# Logique d'adjectif (ex: Labba-)
|
|
53
|
+
root = data["root"]
|
|
54
|
+
if is_negative:
|
|
55
|
+
return f"{root}{data.get('negation', 'ta')}"
|
|
56
|
+
return f"{root}" # En Pular, la racine peut être l'état
|
|
57
|
+
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
def evaluate_ethics(self, is_good=True):
|
|
61
|
+
"""Module Éthique (Sarah's Ethics)."""
|
|
62
|
+
section = self.knowledge.find_in_lexicon("ethics", "bien_bon" if is_good else "mal_mauvais")
|
|
63
|
+
return section
|
|
64
|
+
|
|
65
|
+
def get_conversation_pair(self, word_french):
|
|
66
|
+
"""Action-Réaction logic for conversation module."""
|
|
67
|
+
data = self.knowledge.find_in_lexicon("conversation", word_french)
|
|
68
|
+
if not data: return None
|
|
69
|
+
|
|
70
|
+
if isinstance(data, dict):
|
|
71
|
+
return data.get("pular")
|
|
72
|
+
return data
|
|
73
|
+
|
|
74
|
+
def handle_nuance(self, word_french):
|
|
75
|
+
"""
|
|
76
|
+
Gestion des Nuances (Paires Critiques).
|
|
77
|
+
Ex: Weela (Faim), Welii (Doux), Welaa (Pas doux).
|
|
78
|
+
"""
|
|
79
|
+
# On cherche d'abord dans les verbes, puis dans le lexique
|
|
80
|
+
# Cette partie sera enrichie selon les retours utilisateur
|
|
81
|
+
if word_french.lower() == "faim":
|
|
82
|
+
return "weela"
|
|
83
|
+
if word_french.lower() == "doux":
|
|
84
|
+
return "welii"
|
|
85
|
+
if word_french.lower() == "pas doux":
|
|
86
|
+
return "welaa"
|
|
87
|
+
|
|
88
|
+
return self.knowledge.find_in_lexicon("quantities", word_french) \
|
|
89
|
+
or self.knowledge.find_in_lexicon("meteo", word_french)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
class PularEngine:
|
|
7
|
+
"""
|
|
8
|
+
Le moteur déterministe de transformation linguistique.
|
|
9
|
+
Applique la matrice de conjugaison, les pronoms, la successivité verbale
|
|
10
|
+
et les règles syntaxiques (mots fantômes).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
class PularEngine:
|
|
14
|
+
"""
|
|
15
|
+
Le moteur déterministe de transformation linguistique.
|
|
16
|
+
Applique la matrice de conjugaison, les pronoms, la successivité verbale
|
|
17
|
+
et les règles syntaxiques.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, knowledge):
|
|
21
|
+
self.knowledge = knowledge
|
|
22
|
+
|
|
23
|
+
def get_infinitive(self, verb_french):
|
|
24
|
+
"""Calcule l'infinitif selon l'Algorithme de l'Infinitif."""
|
|
25
|
+
verb_data = self.knowledge.get_verb_data(verb_french)
|
|
26
|
+
if not verb_data:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
root = verb_data["root"]
|
|
30
|
+
v_type = verb_data["type"] # type_1, type_2, type_3
|
|
31
|
+
|
|
32
|
+
if v_type == "type_1":
|
|
33
|
+
return f"{root}ugol"
|
|
34
|
+
elif v_type == "type_2":
|
|
35
|
+
return f"{root}gol"
|
|
36
|
+
elif v_type == "type_3":
|
|
37
|
+
return f"{root}agol"
|
|
38
|
+
return f"{root}gol" # Default
|
|
39
|
+
|
|
40
|
+
def conjugate(self, verb_french, tense_key, person_index, is_negative=False):
|
|
41
|
+
"""
|
|
42
|
+
Applique les règles de conjugaison sur un verbe.
|
|
43
|
+
person_index: 0(1S) Ă 5(3P)
|
|
44
|
+
"""
|
|
45
|
+
verb_data = self.knowledge.get_verb_data(verb_french)
|
|
46
|
+
if not verb_data:
|
|
47
|
+
logger.warning(f"Données inconnues pour le verbe : {verb_french}")
|
|
48
|
+
return verb_french
|
|
49
|
+
|
|
50
|
+
root = verb_data["root"]
|
|
51
|
+
v_type = verb_data["type"]
|
|
52
|
+
is_reflexive = (v_type == "type_3")
|
|
53
|
+
|
|
54
|
+
# Configuration par défaut
|
|
55
|
+
set_key = "set_A"
|
|
56
|
+
suffix_key = tense_key
|
|
57
|
+
|
|
58
|
+
if is_negative:
|
|
59
|
+
set_key = "set_A"
|
|
60
|
+
suffix_key = "negation"
|
|
61
|
+
elif tense_key == "present":
|
|
62
|
+
set_key = "set_B"
|
|
63
|
+
suffix_key = "present_reflexive" if is_reflexive else "present"
|
|
64
|
+
elif tense_key == "past":
|
|
65
|
+
set_key = "set_A"
|
|
66
|
+
suffix_key = "past_reflexive" if is_reflexive else "past"
|
|
67
|
+
elif tense_key == "futur":
|
|
68
|
+
set_key = "set_A"
|
|
69
|
+
suffix_key = "futur"
|
|
70
|
+
|
|
71
|
+
pronom = self.knowledge.get_subject_pronom(set_key, person_index)
|
|
72
|
+
suffix = self.knowledge.get_suffix(suffix_key)
|
|
73
|
+
|
|
74
|
+
if not pronom or suffix is None:
|
|
75
|
+
return f"{root}"
|
|
76
|
+
|
|
77
|
+
return f"{pronom} {root}{suffix}"
|
|
78
|
+
|
|
79
|
+
def apply_successivity(self, verbs_french, subject_idx, tense_key):
|
|
80
|
+
"""
|
|
81
|
+
Applique la règle de successivité verbale :
|
|
82
|
+
V1 conjugué, V2+ à l'infinitif.
|
|
83
|
+
"""
|
|
84
|
+
if not verbs_french:
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
results = []
|
|
88
|
+
# Premier verbe conjugué
|
|
89
|
+
results.append(self.conjugate(verbs_french[0], tense_key, subject_idx))
|
|
90
|
+
|
|
91
|
+
# Suivants Ă l'infinitif
|
|
92
|
+
for v in verbs_french[1:]:
|
|
93
|
+
inf = self.get_infinitive(v)
|
|
94
|
+
results.append(inf if inf else v)
|
|
95
|
+
|
|
96
|
+
return " ".join(results)
|
|
97
|
+
|
|
98
|
+
def translate_simple_sentence(self, subject_idx, verb_french, tense_key, is_negative=False):
|
|
99
|
+
"""Traduit une phrase simple Sujet + Verbe."""
|
|
100
|
+
return self.conjugate(verb_french, tense_key, subject_idx, is_negative)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
class PularKnowledge:
|
|
8
|
+
"""
|
|
9
|
+
Gère la Source de Vérité (Master Dictionary).
|
|
10
|
+
Responsable du chargement, de la sauvegarde, de la recherche de racines,
|
|
11
|
+
suffixes, pronoms et de la gestion de la polysémie.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, data_path=None):
|
|
15
|
+
if data_path is None:
|
|
16
|
+
# Recherche relative au fichier (pour support pip install)
|
|
17
|
+
base_dir = os.path.dirname(__file__)
|
|
18
|
+
data_path = os.path.join(base_dir, "data", "dictionary.json")
|
|
19
|
+
self.data_path = data_path
|
|
20
|
+
self.data = self._load_data()
|
|
21
|
+
|
|
22
|
+
def _load_data(self):
|
|
23
|
+
"""Charge le dictionnaire JSON."""
|
|
24
|
+
if not os.path.exists(self.data_path):
|
|
25
|
+
logger.error(f"Fichier de données introuvable : {self.data_path}")
|
|
26
|
+
return {}
|
|
27
|
+
try:
|
|
28
|
+
with open(self.data_path, "r", encoding="utf-8") as f:
|
|
29
|
+
return json.load(f)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.error(f"Erreur lors du chargement du JSON : {e}")
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def save_data(self):
|
|
35
|
+
"""Sauvegarde les modifications dans le fichier JSON."""
|
|
36
|
+
try:
|
|
37
|
+
with open(self.data_path, "w", encoding="utf-8") as f:
|
|
38
|
+
json.dump(self.data, f, indent=2, ensure_ascii=False)
|
|
39
|
+
return True
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logger.error(f"Erreur lors de la sauvegarde du JSON : {e}")
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def get_verb_data(self, french_verb):
|
|
45
|
+
"""Récupère les données complètes d'un verbe (racine, type, catégorie)."""
|
|
46
|
+
return self.data.get("verbs", {}).get(french_verb.lower())
|
|
47
|
+
|
|
48
|
+
def get_verb_root(self, french_verb):
|
|
49
|
+
"""Récupère la racine Pular d'un verbe français."""
|
|
50
|
+
data = self.get_verb_data(french_verb)
|
|
51
|
+
return data.get("root") if data else None
|
|
52
|
+
|
|
53
|
+
def get_suffix(self, suffix_key):
|
|
54
|
+
"""Récupère un suffixe de grammaire."""
|
|
55
|
+
return self.data.get("grammar", {}).get("suffixes", {}).get(suffix_key)
|
|
56
|
+
|
|
57
|
+
def get_pronom_set(self, set_key):
|
|
58
|
+
"""Récupère une liste de pronoms (set_A ou set_B)."""
|
|
59
|
+
return self.data.get("grammar", {}).get("pronoms_sujet", {}).get(set_key)
|
|
60
|
+
|
|
61
|
+
def get_subject_pronom(self, set_key, person_index):
|
|
62
|
+
"""
|
|
63
|
+
Récupère le pronom spécifique pour une personne.
|
|
64
|
+
person_index: 0=1S, 1=2S, 2=3S, 3=1P, 4=2P, 5=3P
|
|
65
|
+
"""
|
|
66
|
+
pronoms = self.get_pronom_set(set_key)
|
|
67
|
+
if pronoms and 0 <= person_index < len(pronoms):
|
|
68
|
+
return pronoms[person_index]
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def find_in_lexicon(self, section, key):
|
|
72
|
+
"""Recherche dans le lexique (quantities, numbers, meteo, etc.)."""
|
|
73
|
+
return self.data.get("lexicon", {}).get(section, {}).get(key.lower())
|
|
74
|
+
|
|
75
|
+
def update_word(self, section, french_word, pular_data):
|
|
76
|
+
"""Ajoute ou met Ă jour un mot dans le lexique."""
|
|
77
|
+
if "lexicon" not in self.data:
|
|
78
|
+
self.data["lexicon"] = {}
|
|
79
|
+
if section not in self.data["lexicon"]:
|
|
80
|
+
self.data["lexicon"][section] = {}
|
|
81
|
+
|
|
82
|
+
self.data["lexicon"][section][french_word.lower()] = pular_data
|
|
83
|
+
return self.save_data()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class PularValidator:
|
|
4
|
+
"""
|
|
5
|
+
Assure la conformité typographique et le système de confiance.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
# Caractères Pular spécifiques
|
|
10
|
+
self.pular_chars = {
|
|
11
|
+
'b_impl': 'É“',
|
|
12
|
+
'd_impl': 'É—',
|
|
13
|
+
'y_impl': 'Ć´',
|
|
14
|
+
'n_nasal': 'Ĺ‹',
|
|
15
|
+
'ny': 'ñ'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def normalize_typography(self, text):
|
|
19
|
+
"""
|
|
20
|
+
Remplace les tentatives de caractères Pular approximatifs par les officiels.
|
|
21
|
+
Note: Cette logique peut être étendue selon les besoins utilisateurs.
|
|
22
|
+
"""
|
|
23
|
+
if not text:
|
|
24
|
+
return text
|
|
25
|
+
|
|
26
|
+
# Exemple de normalisation (Ă enrichir via apprentissage)
|
|
27
|
+
# Ici on pourrait intégrer l'enforcement Mi do -> Mi ɗo
|
|
28
|
+
normalized = text.replace(" do", " É—o")
|
|
29
|
+
# On pourrait aussi mapper des combinaisons de touches spécifiques
|
|
30
|
+
return normalized
|
|
31
|
+
|
|
32
|
+
def validate_pular_entry(self, word):
|
|
33
|
+
"""Vérifie si un mot contient les caractères attendus (aide à l'apprentissage)."""
|
|
34
|
+
# Logic pour suggérer ɗ au lieu de d si le dictionnaire l'indique
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def check_confidence(self, current_score, conflict=False):
|
|
38
|
+
"""Gère l'évolution du score de confiance."""
|
|
39
|
+
if conflict:
|
|
40
|
+
return max(1, current_score - 1)
|
|
41
|
+
return min(5, current_score + 1)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="guineapular",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
include_package_data=True,
|
|
8
|
+
install_requires=[
|
|
9
|
+
"setuptools",
|
|
10
|
+
"fastapi",
|
|
11
|
+
"uvicorn"
|
|
12
|
+
],
|
|
13
|
+
author="Keit Xellker & User",
|
|
14
|
+
description="Le cerveau déterministe de l'IA Pular-Learning",
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
],
|
|
19
|
+
python_requires='>=3.6',
|
|
20
|
+
)
|