dialectical-framework 0.4.4__py3-none-any.whl

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.
Files changed (84) hide show
  1. dialectical_framework/__init__.py +43 -0
  2. dialectical_framework/ai_dto/__init__.py +0 -0
  3. dialectical_framework/ai_dto/causal_cycle_assessment_dto.py +16 -0
  4. dialectical_framework/ai_dto/causal_cycle_dto.py +16 -0
  5. dialectical_framework/ai_dto/causal_cycles_deck_dto.py +11 -0
  6. dialectical_framework/ai_dto/dialectical_component_dto.py +16 -0
  7. dialectical_framework/ai_dto/dialectical_components_deck_dto.py +12 -0
  8. dialectical_framework/ai_dto/dto_mapper.py +103 -0
  9. dialectical_framework/ai_dto/reciprocal_solution_dto.py +24 -0
  10. dialectical_framework/analyst/__init__.py +1 -0
  11. dialectical_framework/analyst/audit/__init__.py +0 -0
  12. dialectical_framework/analyst/consultant.py +30 -0
  13. dialectical_framework/analyst/decorator_action_reflection.py +28 -0
  14. dialectical_framework/analyst/decorator_discrete_spiral.py +30 -0
  15. dialectical_framework/analyst/domain/__init__.py +0 -0
  16. dialectical_framework/analyst/domain/assessable_cycle.py +128 -0
  17. dialectical_framework/analyst/domain/cycle.py +133 -0
  18. dialectical_framework/analyst/domain/interpretation.py +16 -0
  19. dialectical_framework/analyst/domain/rationale.py +116 -0
  20. dialectical_framework/analyst/domain/spiral.py +47 -0
  21. dialectical_framework/analyst/domain/transformation.py +24 -0
  22. dialectical_framework/analyst/domain/transition.py +160 -0
  23. dialectical_framework/analyst/domain/transition_cell_to_cell.py +29 -0
  24. dialectical_framework/analyst/domain/transition_segment_to_segment.py +16 -0
  25. dialectical_framework/analyst/strategic_consultant.py +32 -0
  26. dialectical_framework/analyst/think_action_reflection.py +217 -0
  27. dialectical_framework/analyst/think_constructive_convergence.py +87 -0
  28. dialectical_framework/analyst/wheel_builder_transition_calculator.py +157 -0
  29. dialectical_framework/brain.py +81 -0
  30. dialectical_framework/dialectical_analysis.py +14 -0
  31. dialectical_framework/dialectical_component.py +111 -0
  32. dialectical_framework/dialectical_components_deck.py +48 -0
  33. dialectical_framework/dialectical_reasoning.py +105 -0
  34. dialectical_framework/directed_graph.py +419 -0
  35. dialectical_framework/enums/__init__.py +0 -0
  36. dialectical_framework/enums/causality_type.py +10 -0
  37. dialectical_framework/enums/di.py +11 -0
  38. dialectical_framework/enums/dialectical_reasoning_mode.py +9 -0
  39. dialectical_framework/enums/predicate.py +9 -0
  40. dialectical_framework/protocols/__init__.py +0 -0
  41. dialectical_framework/protocols/assessable.py +141 -0
  42. dialectical_framework/protocols/causality_sequencer.py +111 -0
  43. dialectical_framework/protocols/content_fidelity_evaluator.py +16 -0
  44. dialectical_framework/protocols/has_brain.py +18 -0
  45. dialectical_framework/protocols/has_config.py +18 -0
  46. dialectical_framework/protocols/ratable.py +79 -0
  47. dialectical_framework/protocols/reloadable.py +7 -0
  48. dialectical_framework/protocols/thesis_extractor.py +15 -0
  49. dialectical_framework/settings.py +77 -0
  50. dialectical_framework/synthesis.py +7 -0
  51. dialectical_framework/synthesist/__init__.py +1 -0
  52. dialectical_framework/synthesist/causality/__init__.py +0 -0
  53. dialectical_framework/synthesist/causality/causality_sequencer_balanced.py +398 -0
  54. dialectical_framework/synthesist/causality/causality_sequencer_desirable.py +55 -0
  55. dialectical_framework/synthesist/causality/causality_sequencer_feasible.py +55 -0
  56. dialectical_framework/synthesist/causality/causality_sequencer_realistic.py +56 -0
  57. dialectical_framework/synthesist/concepts/__init__.py +0 -0
  58. dialectical_framework/synthesist/concepts/thesis_extractor_basic.py +135 -0
  59. dialectical_framework/synthesist/polarity/__init__.py +0 -0
  60. dialectical_framework/synthesist/polarity/polarity_reasoner.py +700 -0
  61. dialectical_framework/synthesist/polarity/reason_blind.py +62 -0
  62. dialectical_framework/synthesist/polarity/reason_conversational.py +91 -0
  63. dialectical_framework/synthesist/polarity/reason_fast.py +177 -0
  64. dialectical_framework/synthesist/polarity/reason_fast_and_simple.py +52 -0
  65. dialectical_framework/synthesist/polarity/reason_fast_polarized_conflict.py +55 -0
  66. dialectical_framework/synthesist/reverse_engineer.py +401 -0
  67. dialectical_framework/synthesist/wheel_builder.py +337 -0
  68. dialectical_framework/utils/__init__.py +1 -0
  69. dialectical_framework/utils/dc_replace.py +42 -0
  70. dialectical_framework/utils/decompose_probability_uniformly.py +15 -0
  71. dialectical_framework/utils/extend_tpl.py +12 -0
  72. dialectical_framework/utils/gm.py +12 -0
  73. dialectical_framework/utils/is_async.py +13 -0
  74. dialectical_framework/utils/use_brain.py +80 -0
  75. dialectical_framework/validator/__init__.py +1 -0
  76. dialectical_framework/validator/basic_checks.py +75 -0
  77. dialectical_framework/validator/check.py +12 -0
  78. dialectical_framework/wheel.py +395 -0
  79. dialectical_framework/wheel_segment.py +203 -0
  80. dialectical_framework/wisdom_unit.py +185 -0
  81. dialectical_framework-0.4.4.dist-info/LICENSE +21 -0
  82. dialectical_framework-0.4.4.dist-info/METADATA +123 -0
  83. dialectical_framework-0.4.4.dist-info/RECORD +84 -0
  84. dialectical_framework-0.4.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,700 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from abc import abstractmethod
5
+ from typing import Self
6
+
7
+ from dependency_injector.wiring import Provide
8
+ from mirascope import BaseMessageParam, Messages, prompt_template
9
+ from mirascope.integrations.langfuse import with_langfuse
10
+
11
+ from dialectical_framework import Rationale
12
+ from dialectical_framework.ai_dto.dialectical_component_dto import \
13
+ DialecticalComponentDto
14
+ from dialectical_framework.ai_dto.dialectical_components_deck_dto import \
15
+ DialecticalComponentsDeckDto
16
+ from dialectical_framework.ai_dto.dto_mapper import (map_from_dto,
17
+ map_list_from_dto)
18
+ from dialectical_framework.dialectical_analysis import DialecticalAnalysis
19
+ from dialectical_framework.dialectical_component import DialecticalComponent
20
+ from dialectical_framework.dialectical_components_deck import \
21
+ DialecticalComponentsDeck
22
+ from dialectical_framework.enums.di import DI
23
+ from dialectical_framework.enums.dialectical_reasoning_mode import \
24
+ DialecticalReasoningMode
25
+ from dialectical_framework.protocols.has_brain import HasBrain
26
+ from dialectical_framework.protocols.reloadable import Reloadable
27
+ from dialectical_framework.settings import Settings
28
+ from dialectical_framework.synthesist.reverse_engineer import ReverseEngineer
29
+ from dialectical_framework.utils.dc_replace import dc_safe_replace
30
+ from dialectical_framework.utils.extend_tpl import extend_tpl
31
+ from dialectical_framework.utils.use_brain import use_brain
32
+ from dialectical_framework.validator.basic_checks import (check,
33
+ is_negative_side,
34
+ is_positive_side,
35
+ is_strict_opposition,
36
+ is_valid_opposition)
37
+ from dialectical_framework.wheel_segment import (ALIAS_T, ALIAS_T_MINUS,
38
+ ALIAS_T_PLUS)
39
+ from dialectical_framework.wisdom_unit import (ALIAS_A, ALIAS_A_MINUS,
40
+ ALIAS_A_PLUS, WisdomUnit)
41
+
42
+
43
+ class PolarityReasoner(HasBrain, Reloadable):
44
+ def __init__(
45
+ self,
46
+ *,
47
+ text: str = "",
48
+ ):
49
+ self._text = text
50
+ self._wisdom_unit = None
51
+
52
+ self._mode: DialecticalReasoningMode = DialecticalReasoningMode.GENERAL_CONCEPTS
53
+
54
+ self._analysis = DialecticalAnalysis(corpus=self._text)
55
+
56
+ @property
57
+ def text(self) -> str:
58
+ return self._text
59
+
60
+ def reload(
61
+ self, *, text: str, perspectives: WisdomUnit | list[WisdomUnit] = None
62
+ ) -> Self:
63
+ self._text = text
64
+ if not perspectives:
65
+ perspectives = []
66
+ if isinstance(perspectives, WisdomUnit):
67
+ perspectives = [perspectives]
68
+
69
+ self._analysis = DialecticalAnalysis(
70
+ corpus=text,
71
+ perspectives=perspectives,
72
+ )
73
+
74
+ if perspectives:
75
+ # Take first perspective as active
76
+ self._wisdom_unit = perspectives[-1]
77
+ else:
78
+ self._wisdom_unit = None
79
+ return self
80
+
81
+ @prompt_template(
82
+ """
83
+ USER:
84
+ {start}
85
+
86
+ USER:
87
+ Extract the central idea or the primary thesis (denote it as T) of the context with minimal distortion. If already concise (single word/phrase/clear thesis), keep it intact; only condense verbose messages while preserving original meaning.
88
+
89
+ Output the dialectical component T within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T" in the explanation.
90
+ """
91
+ )
92
+ def prompt_thesis(
93
+ self, text: str = None, config: Settings = Provide[DI.settings]
94
+ ) -> Messages.Type:
95
+ return {
96
+ "computed_fields": {
97
+ # Sometimes we don't want the whole user input again, as we're calculating the thesis within a longer analysis
98
+ "start": (
99
+ "<context>" + inspect.cleandoc(text) + "</context>"
100
+ if text
101
+ else "Ok."
102
+ ),
103
+ "component_length": config.component_length,
104
+ },
105
+ }
106
+
107
+ @prompt_template(
108
+ """
109
+ A dialectical opposition presents the conceptual or functional antithesis of the original statement that creates direct opposition, while potentially still allowing their mutual coexistence. For instance, Love vs. Hate or Indifference; Science vs. Superstition, Faith/Belief; Human-caused Global Warming vs. Natural Cycles.
110
+
111
+ Generate a dialectical opposition (A) of the thesis "{thesis}" (T). Be detailed enough to show deep understanding, yet concise enough to maintain clarity. Generalize all of them using up to 6 words.
112
+
113
+ Output the dialectical component A within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T" or "A" in the explanation.
114
+ """
115
+ )
116
+ def prompt_antithesis(
117
+ self, thesis: str | DialecticalComponent, config: Settings = Provide[DI.settings]
118
+ ) -> Messages.Type:
119
+ if isinstance(thesis, DialecticalComponent):
120
+ thesis = thesis.statement
121
+ return {
122
+ "computed_fields": {
123
+ "thesis": thesis,
124
+ "component_length": config.component_length,
125
+ },
126
+ }
127
+
128
+ @prompt_template(
129
+ """
130
+ Generate a negative side (T-) of a thesis "{thesis}" (T), representing its strict semantic exaggeration and overdevelopment, as if the author of T lost his inner control. Make sure that T- is not the same as: "{not_like_this}".
131
+
132
+ For instance, if T = Courage, then T- = Foolhardiness. If T = Love, then T- = Obsession, Fixation, Loss of Mindfulness. If T = Fear, then T- = Paranoia. If T = Hate and Indifference then T- = Malevolence and Apathy.
133
+
134
+ If more than one T- exists, provide a generalized representation that encompasses their essence. Be detailed enough to show deep understanding, yet concise enough to maintain clarity. For instance, T- = "Obsession, Fixation, Loss of Mindfulness" can be generalized into T- = Mental Preoccupation
135
+
136
+ Output the dialectical component T- within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T-" or "A-" in the explanation.
137
+ """
138
+ )
139
+ def prompt_thesis_negative_side(
140
+ self,
141
+ thesis: str | DialecticalComponent,
142
+ not_like_this: str | DialecticalComponent = "",
143
+ config: Settings = Provide[DI.settings],
144
+ ) -> Messages.Type:
145
+ if isinstance(thesis, DialecticalComponent):
146
+ thesis = thesis.statement
147
+ if isinstance(not_like_this, DialecticalComponent):
148
+ not_like_this = not_like_this.statement
149
+ return {
150
+ "computed_fields": {
151
+ "thesis": thesis,
152
+ "not_like_this": not_like_this,
153
+ "component_length": config.component_length,
154
+ },
155
+ }
156
+
157
+ @prompt_template()
158
+ def prompt_antithesis_negative_side(
159
+ self,
160
+ antithesis: str | DialecticalComponent,
161
+ not_like_this: str | DialecticalComponent = "",
162
+ ) -> Messages.Type:
163
+ tpl: list[BaseMessageParam] = self.prompt_thesis_negative_side(
164
+ antithesis, not_like_this
165
+ )
166
+ # Replace the technical terms in the prompt, so that it makes sense when passed in the history
167
+ for i in range(len(tpl)):
168
+ if tpl[i].content:
169
+ tpl[i].content = dc_safe_replace(
170
+ tpl[i].content,
171
+ {
172
+ ALIAS_T: ALIAS_A,
173
+ ALIAS_T_MINUS: ALIAS_A_MINUS,
174
+ ALIAS_A_MINUS: ALIAS_T_MINUS,
175
+ },
176
+ )
177
+ return tpl
178
+
179
+ @prompt_template(
180
+ """
181
+ A contradictory/semantic opposition presents a direct semantic opposition and/or contradiction to the original statement that excludes their mutual coexistence. For instance, Happiness vs. Unhappiness; Truthfulness vs. Lie/Deceptiveness; Dependence vs. Independence.
182
+
183
+ Generate a positive side or outcome (T+) of a thesis "{thesis}" (T), representing its constructive (balanced) form/side, that is also the contradictory/semantic opposition of "{antithesis_negative}" (A-).
184
+
185
+ Make sure that T+ is truly connected to the semantic T, representing its positive and constructive side or outcome that is also highly perceptive, nuanced, gentle, evolving, and instrumental in solving problems and creating friendships. For instance, T+ = Trust can be seen as the constructive side of T = Courage. T+ = Kindness and Empathy are natural constructive outcomes of T = Love.
186
+
187
+ If more than one T+ exists, provide a generalized representation that encompasses their essence. Be detailed enough to show deep understanding, yet concise enough to maintain clarity.
188
+
189
+ Output the dialectical component T+ within {component_length} word(s), the shorter, the better. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T+" or "A-" in the explanation.
190
+ """
191
+ )
192
+ def prompt_thesis_positive_side(
193
+ self,
194
+ thesis: str | DialecticalComponent,
195
+ antithesis_negative: str | DialecticalComponent,
196
+ config: Settings = Provide[DI.settings],
197
+ ) -> Messages.Type:
198
+ if isinstance(thesis, DialecticalComponent):
199
+ thesis = thesis.statement
200
+ if isinstance(antithesis_negative, DialecticalComponent):
201
+ antithesis_negative = antithesis_negative.statement
202
+ return {
203
+ "computed_fields": {
204
+ "thesis": thesis,
205
+ "antithesis_negative": antithesis_negative,
206
+ "component_length": config.component_length,
207
+ },
208
+ }
209
+
210
+ @prompt_template()
211
+ def prompt_antithesis_positive_side(
212
+ self,
213
+ antithesis: str | DialecticalComponent,
214
+ thesis_negative: str | DialecticalComponent,
215
+ ) -> Messages.Type:
216
+ tpl: list[BaseMessageParam] = self.prompt_thesis_positive_side(
217
+ antithesis, thesis_negative
218
+ )
219
+ # Replace the technical terms in the prompt, so that it makes sense when passed in the history
220
+ for i in range(len(tpl)):
221
+ if tpl[i].content:
222
+ tpl[i].content = dc_safe_replace(
223
+ tpl[i].content,
224
+ {
225
+ ALIAS_T: ALIAS_A,
226
+ ALIAS_T_PLUS: ALIAS_A_PLUS,
227
+ ALIAS_A_MINUS: ALIAS_T_MINUS,
228
+ },
229
+ )
230
+ return tpl
231
+
232
+ @prompt_template(
233
+ """
234
+ MESSAGES:
235
+ {wu_construction}
236
+
237
+ USER:
238
+ Identifying Positive and Negative Syntheses
239
+
240
+ USER:
241
+ Consider the dialectical components identified in previous analysis:
242
+ {wu_dcs:list}
243
+
244
+ For the pair of thesis and antithesis, identify both positive synthesis (S+) and negative synthesis (S-):
245
+
246
+ S+ (Positive Synthesis): The emergent quality that arises when the positive/constructive aspects (T+ and A+) are combined in complementary harmony. This represents a new dimension where 1+1>2, expanding possibilities while preserving the unique value of each component. Consider the birth of a child from two parents, or binocular vision producing depth perception.
247
+
248
+ S- (Negative Synthesis): The uniformity that results when negative/exaggerated aspects (T- and A-) reinforce each other. This represents reduction of dimensionality where 1+1<2, increasing intensity along limited axes at the expense of diversity. Examples include pendulums aligning their rhythms, or centralized rules that suppress variation.
249
+
250
+ Output the dialectical components S+ and S-. Compose the explanation how it was derived in the passive voice. Don't mention any special denotations such as "T", "T+" or "A-" in the explanation. To the explanation add a concrete real life example.
251
+ """
252
+ )
253
+ def prompt_synthesis(self, wisdom_unit: WisdomUnit) -> Messages.Type:
254
+ tpl = ReverseEngineer.till_wisdom_units(
255
+ wisdom_units=[wisdom_unit], text=self._analysis.corpus
256
+ )
257
+ wu_dcs = []
258
+ for field, alias in wisdom_unit.field_to_alias.items():
259
+ dc = wisdom_unit.get(alias)
260
+ if not dc:
261
+ continue
262
+ wu_dcs.append(f"{alias} = {dc.statement}")
263
+ return {
264
+ "computed_fields": {
265
+ "wu_construction": tpl,
266
+ "wu_dcs": wu_dcs,
267
+ },
268
+ }
269
+
270
+ @prompt_template()
271
+ @abstractmethod
272
+ def prompt_next(self, wu_so_far: WisdomUnit) -> Messages.Type: ...
273
+
274
+ @with_langfuse()
275
+ @use_brain(response_model=DialecticalComponentDto)
276
+ async def find_thesis(self) -> DialecticalComponentDto:
277
+ return self.prompt_thesis(self._text)
278
+
279
+ @with_langfuse()
280
+ @use_brain(response_model=DialecticalComponentDto)
281
+ async def find_antithesis(
282
+ self,
283
+ thesis: str,
284
+ ) -> DialecticalComponentDto:
285
+ return self.prompt_antithesis(thesis)
286
+
287
+ @with_langfuse()
288
+ @use_brain(response_model=DialecticalComponentDto)
289
+ async def find_thesis_negative_side(
290
+ self,
291
+ thesis: str,
292
+ not_like_this: str = "",
293
+ ) -> DialecticalComponentDto:
294
+ return self.prompt_thesis_negative_side(thesis, not_like_this)
295
+
296
+ @with_langfuse()
297
+ @use_brain(response_model=DialecticalComponentDto)
298
+ async def find_antithesis_negative_side(
299
+ self,
300
+ thesis: str,
301
+ not_like_this: str = "",
302
+ ) -> DialecticalComponentDto:
303
+ return self.prompt_antithesis_negative_side(thesis, not_like_this)
304
+
305
+ @with_langfuse()
306
+ @use_brain(response_model=DialecticalComponentDto)
307
+ async def find_thesis_positive_side(
308
+ self,
309
+ thesis: str,
310
+ antithesis_negative: str,
311
+ ) -> DialecticalComponentDto:
312
+ return self.prompt_thesis_positive_side(thesis, antithesis_negative)
313
+
314
+ @with_langfuse()
315
+ @use_brain(response_model=DialecticalComponentDto)
316
+ async def find_antithesis_positive_side(
317
+ self,
318
+ thesis: str,
319
+ antithesis_negative: str,
320
+ ) -> DialecticalComponentDto:
321
+ return self.prompt_antithesis_positive_side(thesis, antithesis_negative)
322
+
323
+ @with_langfuse()
324
+ @use_brain(response_model=DialecticalComponentsDeckDto)
325
+ async def find_next(
326
+ self,
327
+ wu_so_far: WisdomUnit,
328
+ ) -> DialecticalComponentsDeckDto:
329
+ """
330
+ Raises:
331
+ StopIteration: if nothing needs to be found anymore
332
+ """
333
+ prompt = self.prompt_next(wu_so_far)
334
+ if self._analysis.perspectives:
335
+ tpl = ReverseEngineer.till_wisdom_units(
336
+ wisdom_units=self._analysis.perspectives, text=self._analysis.corpus
337
+ )
338
+ else:
339
+ tpl = ReverseEngineer().prompt_input_text(text=self.text)
340
+
341
+ return extend_tpl(tpl, prompt)
342
+
343
+ @with_langfuse()
344
+ @use_brain(response_model=DialecticalComponentsDeckDto)
345
+ async def find_synthesis(
346
+ self,
347
+ wu: WisdomUnit,
348
+ ) -> DialecticalComponentsDeckDto:
349
+ return self.prompt_synthesis(wu)
350
+
351
+ async def think(self, thesis: str | DialecticalComponent = None) -> WisdomUnit:
352
+ # TODO: when thesis is a simple str, we need to actualize it with AI, where it comes from
353
+ wu = WisdomUnit(reasoning_mode=self._mode)
354
+
355
+ if thesis is not None:
356
+ if isinstance(thesis, DialecticalComponent):
357
+ if thesis.alias != ALIAS_T:
358
+ raise ValueError(
359
+ f"The thesis cannot be a dialectical component with alias '{thesis.alias}'"
360
+ )
361
+ wu.t = thesis
362
+ else:
363
+ wu.t = DialecticalComponent(
364
+ alias=ALIAS_T, statement=thesis,
365
+ # explanation="Provided as string"
366
+ )
367
+ else:
368
+ wu.t = map_from_dto(await self.find_thesis(), DialecticalComponent)
369
+
370
+ self._wisdom_unit = await self._fill_with_reason(wu)
371
+ self._analysis.perspectives.append(self._wisdom_unit)
372
+ return self._wisdom_unit
373
+
374
+ async def _fill_with_reason(self, wu: WisdomUnit) -> WisdomUnit:
375
+ empty_count = len(wu.alias_to_field)
376
+ for alias in wu.alias_to_field:
377
+ if wu.is_set(alias):
378
+ empty_count -= 1
379
+
380
+ try:
381
+ ci = 0
382
+ while ci < empty_count:
383
+ if wu.is_complete():
384
+ break
385
+ """
386
+ We assume here, that with every iteration we will find a new dialectical component(s).
387
+ If we keep finding the same ones (or not find at all), we will still avoid the infinite loop - that's good.
388
+ """
389
+ dc_deck_dto = await self.find_next(wu)
390
+ dc_deck: DialecticalComponentsDeck = DialecticalComponentsDeck(dialectical_components=map_list_from_dto(dc_deck_dto.dialectical_components, DialecticalComponent))
391
+ for dc in dc_deck.dialectical_components:
392
+ alias = dc.alias
393
+ if wu.get(alias):
394
+ # Don't override if we already have it
395
+ continue
396
+ else:
397
+ setattr(wu, alias, dc)
398
+ ci += 1
399
+ except StopIteration:
400
+ pass
401
+
402
+ return wu
403
+
404
+ async def redefine(
405
+ self,
406
+ *, # ← everything after * is keyword-only
407
+ original: WisdomUnit | None = None,
408
+ **modified_dialectical_components,
409
+ ) -> WisdomUnit:
410
+ """
411
+ This method doesn't mutate the original WisdomUnit. It returns a fresh instance.
412
+ """
413
+
414
+ warnings: dict[str, list[str]] = {}
415
+
416
+ if original is None:
417
+ original = self._wisdom_unit
418
+
419
+ if original is None:
420
+ raise ValueError("Wisdom unit is not generated yet.")
421
+
422
+ # Replace it in case the parameter "original" was given
423
+ self._wisdom_unit = original
424
+
425
+ changed: dict[str, str] = {
426
+ k: str(v)
427
+ for k, v in modified_dialectical_components.items()
428
+ if k in WisdomUnit.__pydantic_fields__
429
+ }
430
+
431
+ new_wu: WisdomUnit = WisdomUnit(reasoning_mode=original.reasoning_mode)
432
+
433
+ # ==
434
+ # Redefine opposition
435
+ # ==
436
+ base = "t"
437
+ other = "a" if base == "t" else "t"
438
+
439
+ for dialectical_component in [base, other]:
440
+ if changed.get(dialectical_component):
441
+ setattr(
442
+ new_wu,
443
+ dialectical_component,
444
+ DialecticalComponent(
445
+ alias=new_wu.__pydantic_fields__.get(
446
+ dialectical_component
447
+ ).alias,
448
+ statement=changed.get(dialectical_component),
449
+ rationales=[
450
+ Rationale(
451
+ text=f"{new_wu.__pydantic_fields__.get(dialectical_component).alias} redefined."
452
+ )
453
+ ]
454
+ ),
455
+ )
456
+ else:
457
+ new_wu.set_dialectical_component_as_copy_from_another_segment(original, dialectical_component)
458
+
459
+ alias_base = "T" if base == "t" else "A"
460
+ alias_other = "A" if base == "t" else "T"
461
+
462
+ if changed.get(base) or changed.get(other):
463
+ check1 = check(
464
+ is_valid_opposition,
465
+ self,
466
+ getattr(new_wu, base).statement,
467
+ getattr(new_wu, other).statement,
468
+ )
469
+
470
+ if not check1.valid:
471
+ if changed.get(base) and not changed.get(other):
472
+ # base side changed
473
+ o = map_from_dto(await self.find_antithesis(getattr(new_wu, base).statement), DialecticalComponent)
474
+ assert isinstance(o, DialecticalComponent)
475
+ if o.best_rationale and o.best_rationale.text:
476
+ o.best_rationale.text = f"REGENERATED. {o.best_rationale.text}"
477
+ setattr(new_wu, other, o)
478
+ changed[other] = o.statement
479
+ check1.valid = 1
480
+ check1.explanation = "Regenerated, therefore must be valid."
481
+ elif changed.get(other) and not changed.get(base):
482
+ # other side changed
483
+ bm = map_from_dto(await self.find_antithesis(getattr(new_wu, other).statement), DialecticalComponent)
484
+ assert isinstance(bm, DialecticalComponent)
485
+ if bm.best_rationale and bm.best_rationale.text:
486
+ bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
487
+ setattr(new_wu, base, bm)
488
+ changed[base] = bm.statement
489
+ check1.valid = 1
490
+ check1.explanation = "Regenerated, therefore must be valid."
491
+
492
+ if not check1.valid:
493
+ getattr(new_wu, base).statement = (
494
+ f"ERROR: {getattr(new_wu, base).statement}"
495
+ )
496
+ getattr(new_wu, other).statement = (
497
+ f"ERROR: {getattr(new_wu, other).statement}"
498
+ )
499
+ warnings.setdefault(alias_base, []).append(check1.explanation)
500
+ warnings.setdefault(alias_other, []).append(check1.explanation)
501
+ raise AssertionError(f"{alias_base}, {alias_other}", warnings, new_wu)
502
+
503
+ else:
504
+ # Keep originals
505
+ pass
506
+
507
+ # NOTE: At this point we are sure that T and A are present and valid in the new wheel
508
+
509
+ # ==
510
+ # Redefine diagonal relations
511
+ # ==
512
+ for side in ["t", "a"]:
513
+ base = side
514
+ other = "a" if base == "t" else "t"
515
+
516
+ base_negative_side_fn = (
517
+ self.find_thesis_negative_side
518
+ if side == "t"
519
+ else self.find_antithesis_negative_side
520
+ )
521
+ other_positive_side_fn = (
522
+ self.find_antithesis_positive_side
523
+ if side == "t"
524
+ else self.find_thesis_positive_side
525
+ )
526
+
527
+ base_minus = "t_minus" if side == "t" else "a_minus"
528
+ base_plus = "t_plus" if side == "t" else "a_plus"
529
+ other_plus = "a_plus" if side == "t" else "t_plus"
530
+ other_minus = "a_minus" if side == "t" else "t_minus"
531
+
532
+ alias_base_minus = "T-" if side == "t" else "A-"
533
+ alias_other_plus = "A+" if side == "t" else "T+"
534
+
535
+ for dialectical_component in [base_minus, other_plus]:
536
+ if changed.get(dialectical_component):
537
+ setattr(
538
+ new_wu,
539
+ dialectical_component,
540
+ DialecticalComponent(
541
+ alias=new_wu.__pydantic_fields__.get(
542
+ dialectical_component
543
+ ).alias,
544
+ statement=changed.get(dialectical_component),
545
+ rationales=[
546
+ Rationale(
547
+ text=f"{new_wu.__pydantic_fields__.get(dialectical_component).alias} redefined."
548
+ )
549
+ ]
550
+ ),
551
+ )
552
+ else:
553
+ new_wu.set_dialectical_component_as_copy_from_another_segment(
554
+ original, dialectical_component
555
+ )
556
+
557
+ if (changed.get(base) or changed.get(base_minus)) or (
558
+ changed.get(other) or changed.get(other_plus)
559
+ ):
560
+ if changed.get(base_minus) or changed.get(base):
561
+ check2 = check(
562
+ is_negative_side,
563
+ self,
564
+ getattr(new_wu, base_minus).statement,
565
+ getattr(new_wu, base).statement,
566
+ )
567
+
568
+ if not check2.valid:
569
+ if changed.get(base) and not changed.get(base_minus):
570
+ not_like_other_minus = ""
571
+ if hasattr(new_wu, other_minus):
572
+ if getattr(new_wu, other_minus):
573
+ not_like_other_minus = getattr(
574
+ new_wu, other_minus
575
+ ).statement
576
+ bm = map_from_dto(await base_negative_side_fn(
577
+ getattr(new_wu, base).statement, not_like_other_minus
578
+ ), DialecticalComponent)
579
+ assert isinstance(bm, DialecticalComponent)
580
+ if bm.best_rationale and bm.best_rationale.text:
581
+ bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
582
+ setattr(new_wu, base_minus, bm)
583
+ changed[base_minus] = bm.statement
584
+ check2.valid = True
585
+ check2.explanation = "Regenerated, therefore must be valid."
586
+
587
+ if not check2.valid:
588
+ getattr(new_wu, base_minus).statement = (
589
+ f"ERROR: {getattr(new_wu, base_minus).statement}"
590
+ )
591
+ warnings.setdefault(alias_base_minus, []).append(
592
+ check2.explanation
593
+ )
594
+ raise AssertionError(f"{alias_base_minus}", warnings, new_wu)
595
+
596
+ # NOTE: At this point we are sure that BASE and BASE- are present and valid between themselves in the new wheel
597
+
598
+ other_plus_regenerated = False
599
+ if changed.get(other_plus) or changed.get(other):
600
+ check3 = check(
601
+ is_positive_side,
602
+ self,
603
+ getattr(new_wu, other_plus).statement,
604
+ getattr(new_wu, other).statement,
605
+ )
606
+
607
+ if not check3.valid:
608
+ if changed.get(other) and not changed.get(other_plus):
609
+ op = map_from_dto(await other_positive_side_fn(
610
+ getattr(new_wu, other).statement,
611
+ getattr(new_wu, base_minus).statement,
612
+ ), DialecticalComponent)
613
+ assert isinstance(op, DialecticalComponent)
614
+ if op.best_rationale and op.best_rationale.text:
615
+ op.best_rationale.text = f"REGENERATED. {op.best_rationale.text}"
616
+ setattr(new_wu, other_plus, op)
617
+ changed[other_plus] = op.statement
618
+ check3.valid = True
619
+ check3.explanation = "Regenerated, therefore must be valid."
620
+ other_plus_regenerated = True
621
+
622
+ if not check3.valid:
623
+ getattr(new_wu, other_plus).statement = (
624
+ f"ERROR: {getattr(new_wu, other_plus).statement}"
625
+ )
626
+ warnings.setdefault(alias_other_plus, []).append(
627
+ check3.explanation
628
+ )
629
+ raise AssertionError(f"{alias_other_plus}", warnings, new_wu)
630
+
631
+ # NOTE: At this point we are sure that OTHER and OTHER- are present and valid between themselves in the new wheel
632
+
633
+ additional_diagonal_check_skip = other_plus_regenerated or (
634
+ not changed.get(base_minus) and not changed.get(other_plus)
635
+ )
636
+
637
+ if not additional_diagonal_check_skip:
638
+ check4 = check(
639
+ is_strict_opposition,
640
+ self,
641
+ getattr(new_wu, base_minus).statement,
642
+ getattr(new_wu, other_plus).statement,
643
+ )
644
+ if not check4.valid:
645
+ if changed.get(base_minus) and not changed.get(other_plus):
646
+ # base side changed
647
+ op = map_from_dto(await other_positive_side_fn(
648
+ getattr(new_wu, other).statement,
649
+ getattr(new_wu, base_minus).statement,
650
+ ), DialecticalComponent)
651
+ assert isinstance(op, DialecticalComponent)
652
+ if op.best_rationale and op.best_rationale.text:
653
+ op.best_rationale.text = f"REGENERATED. {op.best_rationale.text}"
654
+ setattr(new_wu, other_plus, op)
655
+ changed[other_plus] = op.statement
656
+ check4.valid = True
657
+ check4.explanation = "Regenerated, therefore must be valid."
658
+ elif changed.get(other_plus) and not changed.get(base_minus):
659
+ # other side changed
660
+ not_like_other_minus = ""
661
+ if hasattr(new_wu, other_minus):
662
+ if getattr(new_wu, other_minus):
663
+ not_like_other_minus = getattr(
664
+ new_wu, other_minus
665
+ ).statement
666
+ bm = map_from_dto(await base_negative_side_fn(
667
+ getattr(new_wu, base).statement, not_like_other_minus
668
+ ), DialecticalComponent)
669
+ assert isinstance(bm, DialecticalComponent)
670
+ if bm.best_rationale and bm.best_rationale.text:
671
+ bm.best_rationale.text = f"REGENERATED. {bm.best_rationale.text}"
672
+ setattr(new_wu, base_minus, bm)
673
+ changed[base_minus] = bm.statement
674
+ check4.valid = True
675
+ check4.explanation = "Regenerated, therefore must be valid."
676
+
677
+ if not check4.valid:
678
+ getattr(new_wu, base_minus).statement = (
679
+ f"ERROR: {getattr(new_wu, base_minus).statement}"
680
+ )
681
+ getattr(new_wu, other_plus).statement = (
682
+ f"ERROR: {getattr(new_wu, other_plus).statement}"
683
+ )
684
+ warnings.setdefault(alias_base_minus, []).append(
685
+ check4.explanation
686
+ )
687
+ warnings.setdefault(alias_other_plus, []).append(
688
+ check4.explanation
689
+ )
690
+ raise AssertionError(
691
+ f"{alias_base_minus}, {alias_other_plus}", warnings, new_wu
692
+ )
693
+
694
+ # NOTE: At this point we are sure that diagonals are present and valid in the new wheel
695
+
696
+ else:
697
+ # Keep originals
698
+ pass
699
+
700
+ return new_wu