fabricatio 0.2.8.dev4__cp312-cp312-win_amd64.whl → 0.2.9__cp312-cp312-win_amd64.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.
- fabricatio/__init__.py +4 -11
- fabricatio/actions/__init__.py +1 -0
- fabricatio/actions/article.py +98 -110
- fabricatio/actions/article_rag.py +15 -10
- fabricatio/actions/output.py +60 -4
- fabricatio/actions/rag.py +2 -1
- fabricatio/actions/rules.py +72 -0
- fabricatio/capabilities/__init__.py +1 -0
- fabricatio/capabilities/censor.py +23 -6
- fabricatio/capabilities/check.py +46 -27
- fabricatio/capabilities/correct.py +35 -16
- fabricatio/capabilities/rag.py +5 -4
- fabricatio/capabilities/rating.py +56 -49
- fabricatio/capabilities/review.py +1 -1
- fabricatio/capabilities/task.py +2 -1
- fabricatio/config.py +5 -3
- fabricatio/fs/readers.py +20 -1
- fabricatio/models/action.py +59 -36
- fabricatio/models/extra/__init__.py +1 -0
- fabricatio/models/extra/advanced_judge.py +4 -4
- fabricatio/models/extra/article_base.py +124 -61
- fabricatio/models/extra/article_main.py +100 -17
- fabricatio/models/extra/article_outline.py +2 -3
- fabricatio/models/extra/article_proposal.py +15 -14
- fabricatio/models/extra/patches.py +17 -4
- fabricatio/models/extra/problem.py +31 -23
- fabricatio/models/extra/rule.py +39 -8
- fabricatio/models/generic.py +369 -78
- fabricatio/models/task.py +1 -1
- fabricatio/models/tool.py +149 -14
- fabricatio/models/usages.py +46 -42
- fabricatio/parser.py +5 -5
- fabricatio/rust.cp312-win_amd64.pyd +0 -0
- fabricatio/{_rust.pyi → rust.pyi} +42 -4
- fabricatio/{_rust_instances.py → rust_instances.py} +1 -1
- fabricatio/utils.py +5 -5
- fabricatio/workflows/__init__.py +1 -0
- fabricatio/workflows/articles.py +3 -5
- fabricatio-0.2.9.data/scripts/tdown.exe +0 -0
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/METADATA +1 -1
- fabricatio-0.2.9.dist-info/RECORD +61 -0
- fabricatio/_rust.cp312-win_amd64.pyd +0 -0
- fabricatio-0.2.8.dev4.data/scripts/tdown.exe +0 -0
- fabricatio-0.2.8.dev4.dist-info/RECORD +0 -56
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/WHEEL +0 -0
- {fabricatio-0.2.8.dev4.dist-info → fabricatio-0.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -3,6 +3,7 @@
|
|
3
3
|
from itertools import chain
|
4
4
|
from typing import Dict, Generator, List, Self, Tuple, override
|
5
5
|
|
6
|
+
from fabricatio.fs.readers import extract_sections
|
6
7
|
from fabricatio.journal import logger
|
7
8
|
from fabricatio.models.extra.article_base import (
|
8
9
|
ArticleBase,
|
@@ -14,22 +15,33 @@ from fabricatio.models.extra.article_base import (
|
|
14
15
|
from fabricatio.models.extra.article_outline import (
|
15
16
|
ArticleOutline,
|
16
17
|
)
|
17
|
-
from fabricatio.models.generic import
|
18
|
+
from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
|
19
|
+
from fabricatio.rust import word_count
|
18
20
|
from fabricatio.utils import ok
|
21
|
+
from pydantic import Field
|
19
22
|
|
23
|
+
PARAGRAPH_SEP = "// - - -"
|
20
24
|
|
21
|
-
|
25
|
+
|
26
|
+
class Paragraph(SketchedAble, WordCount, Described):
|
22
27
|
"""Structured academic paragraph blueprint for controlled content generation."""
|
23
28
|
|
24
|
-
description: str
|
25
|
-
|
29
|
+
description: str = Field(
|
30
|
+
alias="elaboration",
|
31
|
+
description=Described.model_fields["description"].description,
|
32
|
+
)
|
26
33
|
|
27
|
-
|
34
|
+
aims: List[str]
|
28
35
|
"""Specific communicative objectives for this paragraph's content."""
|
29
36
|
|
30
37
|
content: str
|
31
38
|
"""The actual content of the paragraph, represented as a string."""
|
32
39
|
|
40
|
+
@classmethod
|
41
|
+
def from_content(cls, content: str) -> Self:
|
42
|
+
"""Create a Paragraph object from the given content."""
|
43
|
+
return cls(elaboration="", aims=[], expected_word_count=word_count(content), content=content)
|
44
|
+
|
33
45
|
|
34
46
|
class ArticleParagraphSequencePatch(SequencePatch[Paragraph]):
|
35
47
|
"""Patch for `Paragraph` list of `ArticleSubsection`."""
|
@@ -41,11 +53,28 @@ class ArticleSubsection(SubSectionBase):
|
|
41
53
|
paragraphs: List[Paragraph]
|
42
54
|
"""List of Paragraph objects containing the content of the subsection."""
|
43
55
|
|
56
|
+
_max_word_count_deviation: float = 0.3
|
57
|
+
"""Maximum allowed deviation from the expected word count, as a percentage."""
|
58
|
+
|
59
|
+
@property
|
60
|
+
def word_count(self) -> int:
|
61
|
+
"""Calculates the total word count of all paragraphs in the subsection."""
|
62
|
+
return sum(word_count(p.content) for p in self.paragraphs)
|
63
|
+
|
44
64
|
def introspect(self) -> str:
|
45
|
-
"""Introspects the subsection and returns a
|
65
|
+
"""Introspects the subsection and returns a summary of its state."""
|
66
|
+
summary = ""
|
46
67
|
if len(self.paragraphs) == 0:
|
47
|
-
|
48
|
-
|
68
|
+
summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
|
69
|
+
if (
|
70
|
+
abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count
|
71
|
+
> self._max_word_count_deviation
|
72
|
+
):
|
73
|
+
summary += (
|
74
|
+
f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.expected_word_count} words!"
|
75
|
+
)
|
76
|
+
|
77
|
+
return summary
|
49
78
|
|
50
79
|
def update_from_inner(self, other: Self) -> Self:
|
51
80
|
"""Updates the current instance with the attributes of another instance."""
|
@@ -61,20 +90,62 @@ class ArticleSubsection(SubSectionBase):
|
|
61
90
|
Returns:
|
62
91
|
str: Typst code snippet for rendering.
|
63
92
|
"""
|
64
|
-
return f"=== {self.title}\n" + "\n\n".join(p.content for p in self.paragraphs)
|
93
|
+
return f"=== {self.title}\n" + f"\n{PARAGRAPH_SEP}\n".join(p.content for p in self.paragraphs)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
97
|
+
"""Creates an Article object from the given Typst code."""
|
98
|
+
return cls(
|
99
|
+
heading=title,
|
100
|
+
elaboration="",
|
101
|
+
paragraphs=[Paragraph.from_content(p) for p in body.split(PARAGRAPH_SEP)],
|
102
|
+
expected_word_count=word_count(body),
|
103
|
+
aims=[],
|
104
|
+
support_to=[],
|
105
|
+
depend_on=[],
|
106
|
+
)
|
65
107
|
|
66
108
|
|
67
109
|
class ArticleSection(SectionBase[ArticleSubsection]):
|
68
110
|
"""Atomic argumentative unit with high-level specificity."""
|
69
111
|
|
112
|
+
@classmethod
|
113
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
114
|
+
"""Creates an Article object from the given Typst code."""
|
115
|
+
return cls(
|
116
|
+
subsections=[
|
117
|
+
ArticleSubsection.from_typst_code(*pack) for pack in extract_sections(body, level=3, section_char="=")
|
118
|
+
],
|
119
|
+
heading=title,
|
120
|
+
elaboration="",
|
121
|
+
expected_word_count=word_count(body),
|
122
|
+
aims=[],
|
123
|
+
support_to=[],
|
124
|
+
depend_on=[],
|
125
|
+
)
|
126
|
+
|
70
127
|
|
71
128
|
class ArticleChapter(ChapterBase[ArticleSection]):
|
72
129
|
"""Thematic progression implementing research function."""
|
73
130
|
|
131
|
+
@classmethod
|
132
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
133
|
+
"""Creates an Article object from the given Typst code."""
|
134
|
+
return cls(
|
135
|
+
sections=[
|
136
|
+
ArticleSection.from_typst_code(*pack) for pack in extract_sections(body, level=2, section_char="=")
|
137
|
+
],
|
138
|
+
heading=title,
|
139
|
+
elaboration="",
|
140
|
+
expected_word_count=word_count(body),
|
141
|
+
aims=[],
|
142
|
+
support_to=[],
|
143
|
+
depend_on=[],
|
144
|
+
)
|
145
|
+
|
74
146
|
|
75
147
|
class Article(
|
76
|
-
|
77
|
-
CensoredAble,
|
148
|
+
SketchedAble,
|
78
149
|
WithRef[ArticleOutline],
|
79
150
|
PersistentAble,
|
80
151
|
ArticleBase[ArticleChapter],
|
@@ -95,7 +166,7 @@ class Article(
|
|
95
166
|
|
96
167
|
@override
|
97
168
|
def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
|
98
|
-
return super().iter_subsections()
|
169
|
+
return super().iter_subsections() # pyright: ignore [reportReturnType]
|
99
170
|
|
100
171
|
@classmethod
|
101
172
|
def from_outline(cls, outline: ArticleOutline) -> "Article":
|
@@ -108,31 +179,43 @@ class Article(
|
|
108
179
|
Article: The generated article.
|
109
180
|
"""
|
110
181
|
# Set the title from the outline
|
111
|
-
article = Article(**outline.model_dump(exclude={"chapters"}), chapters=[])
|
182
|
+
article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
|
112
183
|
|
113
184
|
for chapter in outline.chapters:
|
114
185
|
# Create a new chapter
|
115
186
|
article_chapter = ArticleChapter(
|
116
187
|
sections=[],
|
117
|
-
**chapter.model_dump(exclude={"sections"}),
|
188
|
+
**chapter.model_dump(exclude={"sections"}, by_alias=True),
|
118
189
|
)
|
119
190
|
for section in chapter.sections:
|
120
191
|
# Create a new section
|
121
192
|
article_section = ArticleSection(
|
122
193
|
subsections=[],
|
123
|
-
**section.model_dump(exclude={"subsections"}),
|
194
|
+
**section.model_dump(exclude={"subsections"}, by_alias=True),
|
124
195
|
)
|
125
196
|
for subsection in section.subsections:
|
126
197
|
# Create a new subsection
|
127
198
|
article_subsection = ArticleSubsection(
|
128
199
|
paragraphs=[],
|
129
|
-
**subsection.model_dump(),
|
200
|
+
**subsection.model_dump(by_alias=True),
|
130
201
|
)
|
131
202
|
article_section.subsections.append(article_subsection)
|
132
203
|
article_chapter.sections.append(article_section)
|
133
204
|
article.chapters.append(article_chapter)
|
134
205
|
return article
|
135
206
|
|
207
|
+
@classmethod
|
208
|
+
def from_typst_code(cls, title: str, body: str) -> Self:
|
209
|
+
"""Generates an article from the given Typst code."""
|
210
|
+
return cls(
|
211
|
+
chapters=[
|
212
|
+
ArticleChapter.from_typst_code(*pack) for pack in extract_sections(body, level=1, section_char="=")
|
213
|
+
],
|
214
|
+
heading=title,
|
215
|
+
expected_word_count=word_count(body),
|
216
|
+
abstract="",
|
217
|
+
)
|
218
|
+
|
136
219
|
def gather_dependencies(self, article: ArticleOutlineBase) -> List[ArticleOutlineBase]:
|
137
220
|
"""Gathers dependencies for all sections and subsections in the article.
|
138
221
|
|
@@ -142,7 +225,7 @@ class Article(
|
|
142
225
|
|
143
226
|
supports = []
|
144
227
|
for a in self.iter_dfs_rev():
|
145
|
-
if article in {ok(b.deref(self)) for b in a.support_to}:
|
228
|
+
if article in {ok(b.deref(self)) for b in a.support_to}: # pyright: ignore [reportUnhashable]
|
146
229
|
supports.append(a)
|
147
230
|
|
148
231
|
return list(set(depends + supports))
|
@@ -9,7 +9,7 @@ from fabricatio.models.extra.article_base import (
|
|
9
9
|
SubSectionBase,
|
10
10
|
)
|
11
11
|
from fabricatio.models.extra.article_proposal import ArticleProposal
|
12
|
-
from fabricatio.models.generic import
|
12
|
+
from fabricatio.models.generic import PersistentAble, SketchedAble, WithRef
|
13
13
|
|
14
14
|
|
15
15
|
class ArticleSubsectionOutline(SubSectionBase):
|
@@ -25,8 +25,7 @@ class ArticleChapterOutline(ChapterBase[ArticleSectionOutline]):
|
|
25
25
|
|
26
26
|
|
27
27
|
class ArticleOutline(
|
28
|
-
|
29
|
-
CensoredAble,
|
28
|
+
SketchedAble,
|
30
29
|
WithRef[ArticleProposal],
|
31
30
|
PersistentAble,
|
32
31
|
ArticleBase[ArticleChapterOutline],
|
@@ -2,21 +2,25 @@
|
|
2
2
|
|
3
3
|
from typing import Dict, List
|
4
4
|
|
5
|
-
from fabricatio.models.generic import
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
from fabricatio.models.generic import (
|
6
|
+
AsPrompt,
|
7
|
+
Described,
|
8
|
+
Language,
|
9
|
+
PersistentAble,
|
10
|
+
SketchedAble,
|
11
|
+
Titled,
|
12
|
+
WithRef,
|
13
|
+
WordCount,
|
14
|
+
)
|
15
|
+
from pydantic import Field
|
16
|
+
|
17
|
+
|
18
|
+
class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble, WordCount, Described, Titled, Language):
|
9
19
|
"""Structured proposal for academic paper development with core research elements.
|
10
20
|
|
11
21
|
Guides LLM in generating comprehensive research proposals with clearly defined components.
|
12
22
|
"""
|
13
23
|
|
14
|
-
language: str
|
15
|
-
"""The language in which the article is written. This should align with the language specified in the article briefing."""
|
16
|
-
|
17
|
-
title: str
|
18
|
-
"""The title of the academic paper, formatted in Title Case."""
|
19
|
-
|
20
24
|
focused_problem: List[str]
|
21
25
|
"""A list of specific research problems or questions that the paper aims to address."""
|
22
26
|
|
@@ -38,12 +42,9 @@ class ArticleProposal(CensoredAble, Display, WithRef[str], AsPrompt, PersistentA
|
|
38
42
|
keywords: List[str]
|
39
43
|
"""A list of keywords that represent the main topics and focus areas of the research."""
|
40
44
|
|
41
|
-
|
45
|
+
description: str = Field(alias="abstract")
|
42
46
|
"""A concise summary of the research proposal, outlining the main points and objectives."""
|
43
47
|
|
44
|
-
min_word_count: int
|
45
|
-
"""The minimum number of words required for the research proposal."""
|
46
|
-
|
47
48
|
def _as_prompt_inner(self) -> Dict[str, str]:
|
48
49
|
return {
|
49
50
|
"ArticleBriefing": self.referenced,
|
@@ -1,7 +1,20 @@
|
|
1
|
-
"""A patch class for updating the description and name of a `WithBriefing` object."""
|
1
|
+
"""A patch class for updating the description and name of a `WithBriefing` object, all fields within this instance will be directly copied onto the target model's field."""
|
2
2
|
|
3
|
-
from
|
3
|
+
from typing import Optional, Type
|
4
4
|
|
5
|
+
from fabricatio.models.extra.rule import RuleSet
|
6
|
+
from fabricatio.models.generic import Language, Patch, WithBriefing
|
7
|
+
from pydantic import BaseModel
|
5
8
|
|
6
|
-
|
7
|
-
|
9
|
+
|
10
|
+
class BriefingMetadata[T: WithBriefing](Patch[T], WithBriefing):
|
11
|
+
"""A patch class for updating the description and name of a `WithBriefing` object, all fields within this instance will be directly copied onto the target model's field."""
|
12
|
+
|
13
|
+
|
14
|
+
class RuleSetMetadata(BriefingMetadata[RuleSet], Language):
|
15
|
+
"""A patch class for updating the description and name of a `RuleSet` object, all fields within this instance will be directly copied onto the target model's field."""
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def ref_cls() -> Optional[Type[BaseModel]]:
|
19
|
+
"""Get the reference class of the model."""
|
20
|
+
return RuleSet
|
@@ -1,11 +1,12 @@
|
|
1
1
|
"""A class representing a problem-solution pair identified during a review process."""
|
2
2
|
|
3
3
|
from itertools import chain
|
4
|
-
from typing import
|
4
|
+
from typing import Any, List, Optional, Self, Tuple, Unpack
|
5
5
|
|
6
6
|
from fabricatio.journal import logger
|
7
7
|
from fabricatio.models.generic import SketchedAble, WithBriefing
|
8
8
|
from fabricatio.utils import ask_edit
|
9
|
+
from pydantic import Field
|
9
10
|
from questionary import Choice, checkbox, text
|
10
11
|
from rich import print as r_print
|
11
12
|
|
@@ -13,36 +14,30 @@ from rich import print as r_print
|
|
13
14
|
class Problem(SketchedAble, WithBriefing):
|
14
15
|
"""Represents a problem identified during review."""
|
15
16
|
|
16
|
-
description: str
|
17
|
-
"""
|
17
|
+
description: str = Field(alias="cause")
|
18
|
+
"""The cause of the problem, including the root cause, the context, and the impact, make detailed enough for engineer to understand the problem and its impact."""
|
18
19
|
|
19
|
-
|
20
|
-
"""Severity level of the problem."""
|
21
|
-
|
22
|
-
category: str
|
23
|
-
"""Category of the problem."""
|
20
|
+
severity_level: int = Field(ge=0, le=10)
|
21
|
+
"""Severity level of the problem, which is a number between 0 and 10, 0 means the problem is not severe, 10 means the problem is extremely severe."""
|
24
22
|
|
25
23
|
location: str
|
26
24
|
"""Location where the problem was identified."""
|
27
25
|
|
28
|
-
recommendation: str
|
29
|
-
"""Recommended solution or action."""
|
30
|
-
|
31
26
|
|
32
27
|
class Solution(SketchedAble, WithBriefing):
|
33
28
|
"""Represents a proposed solution to a problem."""
|
34
29
|
|
35
|
-
description: str
|
30
|
+
description: str = Field(alias="mechanism")
|
36
31
|
"""Description of the solution, including a detailed description of the execution steps, and the mechanics, principle or fact."""
|
37
32
|
|
38
33
|
execute_steps: List[str]
|
39
|
-
"""A list of steps to execute to implement the solution, which is expected to be able to finally solve the corresponding problem."""
|
34
|
+
"""A list of steps to execute to implement the solution, which is expected to be able to finally solve the corresponding problem, and which should be an Idiot-proof tutorial."""
|
40
35
|
|
41
|
-
|
42
|
-
"""Feasibility level of the solution."""
|
36
|
+
feasibility_level: int = Field(ge=0, le=10)
|
37
|
+
"""Feasibility level of the solution, which is a number between 0 and 10, 0 means the solution is not feasible, 10 means the solution is complete feasible."""
|
43
38
|
|
44
|
-
|
45
|
-
"""Impact level of the solution."""
|
39
|
+
impact_level: int = Field(ge=0, le=10)
|
40
|
+
"""Impact level of the solution, which is a number between 0 and 10, 0 means the solution is not impactful, 10 means the solution is extremely impactful."""
|
46
41
|
|
47
42
|
|
48
43
|
class ProblemSolutions(SketchedAble):
|
@@ -51,7 +46,12 @@ class ProblemSolutions(SketchedAble):
|
|
51
46
|
problem: Problem
|
52
47
|
"""The problem identified in the review."""
|
53
48
|
solutions: List[Solution]
|
54
|
-
"""A collection of potential solutions."""
|
49
|
+
"""A collection of potential solutions, spread the thought, add more solution as possible.Do not leave this as blank"""
|
50
|
+
|
51
|
+
def model_post_init(self, context: Any, /) -> None:
|
52
|
+
"""Initialize the problem-solution pair with a problem and a list of solutions."""
|
53
|
+
if len(self.solutions) == 0:
|
54
|
+
logger.warning(f"No solution found for problem {self.problem.name}, please add more solutions manually.")
|
55
55
|
|
56
56
|
def update_from_inner(self, other: Self) -> Self:
|
57
57
|
"""Update the current instance with another instance's attributes."""
|
@@ -69,6 +69,10 @@ class ProblemSolutions(SketchedAble):
|
|
69
69
|
self.solutions = solutions
|
70
70
|
return self
|
71
71
|
|
72
|
+
def has_solutions(self) -> bool:
|
73
|
+
"""Check if the problem-solution pair has any solutions."""
|
74
|
+
return len(self.solutions) > 0
|
75
|
+
|
72
76
|
async def edit_problem(self) -> Self:
|
73
77
|
"""Interactively edit the problem description."""
|
74
78
|
self.problem = Problem.model_validate_strings(
|
@@ -87,11 +91,11 @@ class ProblemSolutions(SketchedAble):
|
|
87
91
|
"""Check if the improvement is decided."""
|
88
92
|
return len(self.solutions) == 1
|
89
93
|
|
90
|
-
def final_solution(self) -> Optional[Solution]:
|
94
|
+
def final_solution(self, always_use_first: bool = False) -> Optional[Solution]:
|
91
95
|
"""Get the final solution."""
|
92
|
-
if not self.decided():
|
96
|
+
if not always_use_first and not self.decided():
|
93
97
|
logger.error(
|
94
|
-
f"There is
|
98
|
+
f"There is {len(self.solutions)} solutions for problem {self.problem.name}, please decide which solution is eventually adopted."
|
95
99
|
)
|
96
100
|
return None
|
97
101
|
return self.solutions[0]
|
@@ -106,6 +110,10 @@ class Improvement(SketchedAble):
|
|
106
110
|
problem_solutions: List[ProblemSolutions]
|
107
111
|
"""Collection of problems identified during review along with their potential solutions."""
|
108
112
|
|
113
|
+
def all_problems_have_solutions(self) -> bool:
|
114
|
+
"""Check if all problems have solutions."""
|
115
|
+
return all(ps.has_solutions() for ps in self.problem_solutions)
|
116
|
+
|
109
117
|
async def supervisor_check(self, check_solutions: bool = True) -> Self:
|
110
118
|
"""Perform an interactive review session to filter problems and solutions.
|
111
119
|
|
@@ -145,9 +153,9 @@ class Improvement(SketchedAble):
|
|
145
153
|
return all(ps.decided() for ps in self.problem_solutions)
|
146
154
|
|
147
155
|
@classmethod
|
148
|
-
def gather(cls, *improvements:
|
156
|
+
def gather(cls, *improvements: Unpack[Tuple["Improvement", ...]]) -> Self:
|
149
157
|
"""Gather multiple improvements into a single instance."""
|
150
158
|
return cls(
|
151
|
-
focused_on="
|
159
|
+
focused_on=";".join(imp.focused_on for imp in improvements),
|
152
160
|
problem_solutions=list(chain(*(imp.problem_solutions for imp in improvements))),
|
153
161
|
)
|
fabricatio/models/extra/rule.py
CHANGED
@@ -1,21 +1,52 @@
|
|
1
|
-
"""A module containing classes related to rule sets and rules.
|
1
|
+
"""A module containing classes related to rule sets and rules.
|
2
2
|
|
3
|
-
|
3
|
+
This module provides the `Rule` and `RuleSet` classes, which are used to define and manage
|
4
|
+
individual rules and collections of rules, respectively. These classes are designed to
|
5
|
+
facilitate the creation, organization, and application of rules in various contexts,
|
6
|
+
ensuring clarity, consistency, and enforceability. The module supports detailed
|
7
|
+
descriptions, examples, and metadata for each rule and rule set, making it suitable for
|
8
|
+
complex rule management systems.
|
9
|
+
"""
|
4
10
|
|
5
|
-
from
|
11
|
+
from typing import List, Self, Tuple, Unpack
|
6
12
|
|
13
|
+
from fabricatio.models.generic import Language, PersistentAble, SketchedAble, WithBriefing
|
14
|
+
from more_itertools import flatten
|
7
15
|
|
8
|
-
|
16
|
+
|
17
|
+
class Rule(WithBriefing, Language, SketchedAble, PersistentAble):
|
9
18
|
"""Represents a rule or guideline for a specific topic."""
|
10
19
|
|
11
20
|
violation_examples: List[str]
|
12
|
-
"""
|
21
|
+
"""A list of concrete examples demonstrating violations of this rule. Each example should
|
22
|
+
be a clear scenario or case that illustrates how the rule can be broken, including the
|
23
|
+
context, actions, and consequences of the violation. These examples should help in
|
24
|
+
understanding the boundaries of the rule."""
|
25
|
+
|
13
26
|
compliance_examples: List[str]
|
14
|
-
"""
|
27
|
+
"""A list of concrete examples demonstrating proper compliance with this rule. Each example
|
28
|
+
should be a clear scenario or case that illustrates how to correctly follow the rule,
|
29
|
+
including the context, actions, and positive outcomes of compliance. These examples should
|
30
|
+
serve as practical guidance for implementing the rule correctly."""
|
15
31
|
|
16
32
|
|
17
|
-
class RuleSet(
|
33
|
+
class RuleSet(SketchedAble, PersistentAble, WithBriefing, Language):
|
18
34
|
"""Represents a collection of rules and guidelines for a particular topic."""
|
19
35
|
|
20
36
|
rules: List[Rule]
|
21
|
-
"""The rules and guidelines contained in
|
37
|
+
"""The collection of rules and guidelines contained in this rule set. Each rule should be
|
38
|
+
a well-defined, specific guideline that contributes to the overall purpose of the rule set.
|
39
|
+
The rules should be logically organized and consistent with each other, forming a coherent
|
40
|
+
framework for the topic or domain covered by the rule set."""
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def gather(cls, *rulesets: Unpack[Tuple["RuleSet",...]]) -> Self:
|
44
|
+
"""Gathers multiple rule sets into a single rule set."""
|
45
|
+
if not rulesets:
|
46
|
+
raise ValueError("No rulesets provided")
|
47
|
+
return cls(
|
48
|
+
language=rulesets[0].language,
|
49
|
+
name=";".join(ruleset.name for ruleset in rulesets),
|
50
|
+
description=";".join(ruleset.description for ruleset in rulesets),
|
51
|
+
rules=list(flatten(r.rules for r in rulesets)),
|
52
|
+
)
|