fabricatio 0.2.9.dev2__cp312-cp312-win_amd64.whl → 0.2.9.dev4__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.
@@ -7,15 +7,20 @@ from typing import Generator, List, Optional, Self, Tuple, overload
7
7
 
8
8
  from fabricatio.models.generic import (
9
9
  AsPrompt,
10
+ Described,
10
11
  FinalizedDumpAble,
11
12
  Introspect,
13
+ Language,
12
14
  ModelHash,
13
15
  PersistentAble,
14
16
  ProposedUpdateAble,
15
17
  ResolveUpdateConflict,
16
18
  SequencePatch,
17
19
  SketchedAble,
20
+ Titled,
21
+ WordCount,
18
22
  )
23
+ from pydantic import Field
19
24
 
20
25
 
21
26
  class ReferringType(StrEnum):
@@ -36,44 +41,44 @@ class ArticleRef(ProposedUpdateAble):
36
41
  - Referring to a chapter titled `Introduction`:
37
42
  Using Python
38
43
  ```python
39
- ArticleRef(referred_chapter_title="Introduction")
44
+ ArticleRef(chap="Introduction")
40
45
  ```
41
46
  Using JSON
42
47
  ```json
43
- {referred_chapter_title="Introduction"}
48
+ {chap="Introduction"}
44
49
  ```
45
50
  - Referring to a section titled `Background` under the `Introduction` chapter:
46
51
  Using Python
47
52
  ```python
48
- ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background")
53
+ ArticleRef(chap="Introduction", sec="Background")
49
54
  ```
50
55
  Using JSON
51
56
  ```json
52
- {referred_chapter_title="Introduction", referred_section_title="Background"}
57
+ {chap="Introduction", sec="Background"}
53
58
  ```
54
59
  - Referring to a subsection titled `Related Work` under the `Background` section of the `Introduction` chapter:
55
60
  Using Python
56
61
  ```python
57
- ArticleRef(referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work")
62
+ ArticleRef(chap="Introduction", sec="Background", subsec="Related Work")
58
63
  ```
59
64
  Using JSON
60
65
  ```json
61
- {referred_chapter_title="Introduction", referred_section_title="Background", referred_subsection_title="Related Work"}
66
+ {chap="Introduction", sec="Background", subsec="Related Work"}
62
67
  ```
63
68
  """
64
69
 
65
- referred_chapter_title: str
70
+ chap: str
66
71
  """`title` Field of the referenced chapter"""
67
- referred_section_title: Optional[str] = None
72
+ sec: Optional[str] = None
68
73
  """`title` Field of the referenced section."""
69
- referred_subsection_title: Optional[str] = None
74
+ subsec: Optional[str] = None
70
75
  """`title` Field of the referenced subsection."""
71
76
 
72
77
  def update_from_inner(self, other: Self) -> Self:
73
78
  """Updates the current instance with the attributes of another instance."""
74
- self.referred_chapter_title = other.referred_chapter_title
75
- self.referred_section_title = other.referred_section_title
76
- self.referred_subsection_title = other.referred_subsection_title
79
+ self.chap = other.chap
80
+ self.sec = other.sec
81
+ self.subsec = other.subsec
77
82
  return self
78
83
 
79
84
  def deref(self, article: "ArticleBase") -> Optional["ArticleOutlineBase"]:
@@ -85,42 +90,41 @@ class ArticleRef(ProposedUpdateAble):
85
90
  Returns:
86
91
  ArticleMainBase | ArticleOutline | None: The dereferenced section or subsection, or None if not found.
87
92
  """
88
- chap = next((chap for chap in article.chapters if chap.title == self.referred_chapter_title), None)
89
- if self.referred_section_title is None or chap is None:
93
+ chap = next((chap for chap in article.chapters if chap.title == self.chap), None)
94
+ if self.sec is None or chap is None:
90
95
  return chap
91
- sec = next((sec for sec in chap.sections if sec.title == self.referred_section_title), None)
92
- if self.referred_subsection_title is None or sec is None:
96
+ sec = next((sec for sec in chap.sections if sec.title == self.sec), None)
97
+ if self.subsec is None or sec is None:
93
98
  return sec
94
- return next((subsec for subsec in sec.subsections if subsec.title == self.referred_subsection_title), None)
99
+ return next((subsec for subsec in sec.subsections if subsec.title == self.subsec), None)
95
100
 
96
101
  @property
97
102
  def referring_type(self) -> ReferringType:
98
103
  """Determine the type of reference based on the presence of specific attributes."""
99
- if self.referred_subsection_title is not None:
104
+ if self.subsec is not None:
100
105
  return ReferringType.SUBSECTION
101
- if self.referred_section_title is not None:
106
+ if self.sec is not None:
102
107
  return ReferringType.SECTION
103
108
  return ReferringType.CHAPTER
104
109
 
105
110
 
106
- class ArticleMetaData(SketchedAble):
111
+ class ArticleMetaData(SketchedAble, Described, WordCount, Titled, Language):
107
112
  """Metadata for an article component."""
108
113
 
109
- description: str
110
- """Description of the research component in academic style."""
114
+ description: str = Field(
115
+ alias="elaboration",
116
+ description=Described.model_fields["description"].description,
117
+ )
111
118
 
112
- support_to: List[ArticleRef]
113
- """List of references to other component of this articles that this component supports."""
114
- depend_on: List[ArticleRef]
115
- """List of references to other component of this articles that this component depends on."""
119
+ title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
116
120
 
117
- writing_aim: List[str]
121
+ aims: List[str]
118
122
  """List of writing aims of the research component in academic style."""
119
- title: str
120
- """Do not add any prefix or suffix to the title. should not contain special characters."""
121
123
 
122
- expected_word_count: int
123
- """Expected word count of this research component."""
124
+ support_to: List[ArticleRef]
125
+ """List of references to other future components in this article that this component supports to."""
126
+ depend_on: List[ArticleRef]
127
+ """List of references to other previous components in this article that this component depends on."""
124
128
 
125
129
 
126
130
  class ArticleRefSequencePatch(SequencePatch[ArticleRef]):
@@ -148,8 +152,8 @@ class ArticleOutlineBase(
148
152
  self.support_to.extend(other.support_to)
149
153
  self.depend_on.clear()
150
154
  self.depend_on.extend(other.depend_on)
151
- self.writing_aim.clear()
152
- self.writing_aim.extend(other.writing_aim)
155
+ self.aims.clear()
156
+ self.aims.extend(other.aims)
153
157
  self.description = other.description
154
158
  return self
155
159
 
@@ -273,25 +277,19 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
273
277
  return ""
274
278
 
275
279
 
276
- class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
280
+ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Described, Titled, Language, ABC):
277
281
  """Base class for article outlines."""
278
282
 
279
- language: str
280
- """Written language of the article. SHALL be aligned to the language of the article proposal provided."""
283
+ title: str = Field(alias="heading", description=Titled.model_fields["title"].description)
284
+ description: str = Field(alias="abstract")
285
+ """The abstract serves as a concise summary of an academic article, encapsulating its core purpose, methodologies, key results,
286
+ and conclusions while enabling readers to rapidly assess the relevance and significance of the study.
287
+ Functioning as the article's distilled essence, it succinctly articulates the research problem, objectives,
288
+ and scope, providing a roadmap for the full text while also facilitating database indexing, literature reviews,
289
+ and citation tracking through standardized metadata. Additionally, it acts as an accessibility gateway,
290
+ allowing scholars to gauge the study's contribution to existing knowledge, its methodological rigor,
291
+ and its broader implications without engaging with the entire manuscript, thereby optimizing scholarly communication efficiency."""
281
292
 
282
- title: str
283
- """Title of the academic paper."""
284
-
285
- prospect: str
286
- """Consolidated research statement with four pillars:
287
- 1. Problem Identification: Current limitations
288
- 2. Methodological Response: Technical approach
289
- 3. Empirical Validation: Evaluation strategy
290
- 4. Scholarly Impact: Field contributions
291
- """
292
-
293
- abstract: str
294
- """The abstract is a concise summary of the academic paper's main findings."""
295
293
  chapters: List[T]
296
294
  """Chapters of the article. Contains at least one chapter. You can also add more as needed."""
297
295
 
@@ -378,12 +376,31 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
378
376
  return component, summary
379
377
  return None
380
378
 
379
+ def gather_introspected(self) -> Optional[str]:
380
+ """Gathers all introspected components in the article structure."""
381
+ return "\n".join([i for component in self.chapters if (i := component.introspect())])
382
+
381
383
  @overload
382
384
  def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]: ...
383
385
 
384
386
  @overload
385
387
  def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
386
388
 
389
+ def iter_chap_title(self) -> Generator[str, None, None]:
390
+ """Iterates through all chapter titles in the article."""
391
+ for chap in self.chapters:
392
+ yield chap.title
393
+
394
+ def iter_section_title(self) -> Generator[str, None, None]:
395
+ """Iterates through all section titles in the article."""
396
+ for _, sec in self.iter_sections():
397
+ yield sec.title
398
+
399
+ def iter_subsection_title(self) -> Generator[str, None, None]:
400
+ """Iterates through all subsection titles in the article."""
401
+ for _, _, subsec in self.iter_subsections():
402
+ yield subsec.title
403
+
387
404
  def find_illegal_ref(self, gather_identical: bool = False) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]:
388
405
  """Finds the first illegal component in the outline.
389
406
 
@@ -391,21 +408,64 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
391
408
  Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
392
409
  """
393
410
  summary = ""
411
+ chap_titles_set = set(self.iter_chap_title())
412
+ sec_titles_set = set(self.iter_section_title())
413
+ subsec_titles_set = set(self.iter_subsection_title())
414
+
394
415
  for component in self.iter_dfs_rev():
395
416
  for ref in chain(component.depend_on, component.support_to):
396
417
  if not ref.deref(self):
397
418
  summary += f"Invalid internal reference in `{component.__class__.__name__}` titled `{component.title}`, because the referred {ref.referring_type} is not exists within the article, see the original obj dump: {ref.model_dump()}\n"
398
- if summary and not gather_identical:
399
- return ref, summary
400
- if summary and gather_identical:
401
- return [
402
- identical_ref
403
- for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
404
- if identical_ref == ref
405
- ], summary
419
+
420
+ if ref.chap not in (chap_titles_set):
421
+ summary += f"Chapter titled `{ref.chap}` is not any of {chap_titles_set}\n"
422
+ if ref.sec and ref.sec not in (sec_titles_set):
423
+ summary += f"Section Titled `{ref.sec}` is not any of {sec_titles_set}\n"
424
+ if ref.subsec and ref.subsec not in (subsec_titles_set):
425
+ summary += f"Subsection Titled `{ref.subsec}` is not any of {subsec_titles_set}"
426
+
427
+ if summary:
428
+ return (
429
+ (
430
+ [
431
+ identical_ref
432
+ for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
433
+ if identical_ref == ref
434
+ ],
435
+ summary,
436
+ )
437
+ if gather_identical
438
+ else (ref, summary)
439
+ )
406
440
 
407
441
  return None
408
442
 
443
+ def gather_illegal_ref(self) -> Tuple[List[ArticleRef], str]:
444
+ """Gathers all illegal references in the article."""
445
+ summary = []
446
+ chap_titles_set = set(self.iter_chap_title())
447
+ sec_titles_set = set(self.iter_section_title())
448
+ subsec_titles_set = set(self.iter_subsection_title())
449
+ res_seq = []
450
+
451
+ for component in self.iter_dfs():
452
+ for ref in (
453
+ r for r in chain(component.depend_on, component.support_to) if not r.deref(self) and r not in res_seq
454
+ ):
455
+ res_seq.append(ref)
456
+ if ref.chap not in chap_titles_set:
457
+ summary.append(
458
+ f"Chapter titled `{ref.chap}` is not exist, since it is not any of {chap_titles_set}."
459
+ )
460
+ if ref.sec and (ref.sec not in sec_titles_set):
461
+ summary.append(f"Section Titled `{ref.sec}` is not exist, since it is not any of {sec_titles_set}")
462
+ if ref.subsec and (ref.subsec not in subsec_titles_set):
463
+ summary.append(
464
+ f"Subsection Titled `{ref.subsec}` is not exist, since it is not any of {subsec_titles_set}"
465
+ )
466
+
467
+ return res_seq, "\n".join(summary)
468
+
409
469
  def finalized_dump(self) -> str:
410
470
  """Generates standardized hierarchical markup for academic publishing systems.
411
471
 
@@ -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 PersistentAble, SequencePatch, SketchedAble, WithRef
18
+ from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
19
+ from fabricatio.rust import detect_language, word_count
18
20
  from fabricatio.utils import ok
21
+ from pydantic import Field
19
22
 
23
+ PARAGRAPH_SEP = "// - - -"
20
24
 
21
- class Paragraph(SketchedAble):
25
+
26
+ class Paragraph(SketchedAble, WordCount, Described):
22
27
  """Structured academic paragraph blueprint for controlled content generation."""
23
28
 
24
- description: str
25
- """Functional summary of the paragraph's role in document structure."""
29
+ description: str = Field(
30
+ alias="elaboration",
31
+ description=Described.model_fields["description"].description,
32
+ )
26
33
 
27
- writing_aim: List[str]
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,25 @@ 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 message if it has no paragraphs."""
65
+ """Introspects the subsection and returns a summary of its state."""
66
+ summary = ""
46
67
  if len(self.paragraphs) == 0:
47
- return f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, to achieve the goal of `{self.writing_aim}`."
48
- return ""
68
+ summary += f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, You should add some!\n"
69
+ if abs((wc := self.word_count) - self.expected_word_count) / self.expected_word_count > self._max_word_count_deviation:
70
+ summary += (
71
+ f"`{self.__class__.__name__}` titled `{self.title}` have {wc} words, expected {self.word_count} words!"
72
+ )
73
+
74
+ return summary
49
75
 
50
76
  def update_from_inner(self, other: Self) -> Self:
51
77
  """Updates the current instance with the attributes of another instance."""
@@ -61,16 +87,64 @@ class ArticleSubsection(SubSectionBase):
61
87
  Returns:
62
88
  str: Typst code snippet for rendering.
63
89
  """
64
- return f"=== {self.title}\n" + "\n\n".join(p.content for p in self.paragraphs)
90
+ return f"=== {self.title}\n" + f"\n{PARAGRAPH_SEP}\n".join(p.content for p in self.paragraphs)
91
+
92
+ @classmethod
93
+ def from_typst_code(cls, title: str, body: str, language: str) -> Self:
94
+ """Creates an Article object from the given Typst code."""
95
+ return cls(
96
+ heading=title,
97
+ elaboration="",
98
+ paragraphs=[Paragraph.from_content(p) for p in body.split(PARAGRAPH_SEP)],
99
+ expected_word_count=word_count(body),
100
+ language=language,
101
+ aims=[],
102
+ support_to=[],
103
+ depend_on=[],
104
+ )
65
105
 
66
106
 
67
107
  class ArticleSection(SectionBase[ArticleSubsection]):
68
108
  """Atomic argumentative unit with high-level specificity."""
69
109
 
110
+ @classmethod
111
+ def from_typst_code(cls, title: str, body: str, language: str) -> Self:
112
+ """Creates an Article object from the given Typst code."""
113
+ return cls(
114
+ subsections=[
115
+ ArticleSubsection.from_typst_code(*pack, language=language)
116
+ for pack in extract_sections(body, level=3, section_char="=")
117
+ ],
118
+ heading=title,
119
+ elaboration="",
120
+ expected_word_count=word_count(body),
121
+ language=language,
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, language: str) -> Self:
133
+ """Creates an Article object from the given Typst code."""
134
+ return cls(
135
+ sections=[
136
+ ArticleSection.from_typst_code(*pack, language=language)
137
+ for pack in extract_sections(body, level=2, section_char="=")
138
+ ],
139
+ heading=title,
140
+ elaboration="",
141
+ expected_word_count=word_count(body),
142
+ language=language,
143
+ aims=[],
144
+ support_to=[],
145
+ depend_on=[],
146
+ )
147
+
74
148
 
75
149
  class Article(
76
150
  SketchedAble,
@@ -94,7 +168,7 @@ class Article(
94
168
 
95
169
  @override
96
170
  def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
97
- return super().iter_subsections()
171
+ return super().iter_subsections() # pyright: ignore [reportReturnType]
98
172
 
99
173
  @classmethod
100
174
  def from_outline(cls, outline: ArticleOutline) -> "Article":
@@ -107,31 +181,45 @@ class Article(
107
181
  Article: The generated article.
108
182
  """
109
183
  # Set the title from the outline
110
- article = Article(**outline.model_dump(exclude={"chapters"}), chapters=[])
184
+ article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
111
185
 
112
186
  for chapter in outline.chapters:
113
187
  # Create a new chapter
114
188
  article_chapter = ArticleChapter(
115
189
  sections=[],
116
- **chapter.model_dump(exclude={"sections"}),
190
+ **chapter.model_dump(exclude={"sections"}, by_alias=True),
117
191
  )
118
192
  for section in chapter.sections:
119
193
  # Create a new section
120
194
  article_section = ArticleSection(
121
195
  subsections=[],
122
- **section.model_dump(exclude={"subsections"}),
196
+ **section.model_dump(exclude={"subsections"}, by_alias=True),
123
197
  )
124
198
  for subsection in section.subsections:
125
199
  # Create a new subsection
126
200
  article_subsection = ArticleSubsection(
127
201
  paragraphs=[],
128
- **subsection.model_dump(),
202
+ **subsection.model_dump(by_alias=True),
129
203
  )
130
204
  article_section.subsections.append(article_subsection)
131
205
  article_chapter.sections.append(article_section)
132
206
  article.chapters.append(article_chapter)
133
207
  return article
134
208
 
209
+ @classmethod
210
+ def from_typst_code(cls, title: str, body: str) -> Self:
211
+ """Generates an article from the given Typst code."""
212
+ return cls(
213
+ language=(lang := detect_language(body)),
214
+ chapters=[
215
+ ArticleChapter.from_typst_code(*pack, language=lang)
216
+ for pack in extract_sections(body, level=1, section_char="=")
217
+ ],
218
+ heading=title,
219
+ expected_word_count=word_count(body),
220
+ abstract="",
221
+ )
222
+
135
223
  def gather_dependencies(self, article: ArticleOutlineBase) -> List[ArticleOutlineBase]:
136
224
  """Gathers dependencies for all sections and subsections in the article.
137
225
 
@@ -2,21 +2,25 @@
2
2
 
3
3
  from typing import Dict, List
4
4
 
5
- from fabricatio.models.generic import AsPrompt, SketchedAble, PersistentAble, WithRef
6
-
7
-
8
- class ArticleProposal(SketchedAble, WithRef[str], AsPrompt, PersistentAble):
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(SketchedAble, WithRef[str], AsPrompt, PersistentAble):
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
- abstract: str
45
+ description: str = Field(alias="abstract")
42
46
  """A concise summary of the research proposal, outlining the main points and objectives."""
43
47
 
44
- expected_word_count: int
45
- """The estimated word count of the final academic paper."""
46
-
47
48
  def _as_prompt_inner(self) -> Dict[str, str]:
48
49
  return {
49
50
  "ArticleBriefing": self.referenced,
@@ -3,17 +3,17 @@
3
3
  from typing import Optional, Type
4
4
 
5
5
  from fabricatio.models.extra.rule import RuleSet
6
- from fabricatio.models.generic import Patch, WithBriefing
6
+ from fabricatio.models.generic import Language, Patch, WithBriefing
7
7
  from pydantic import BaseModel
8
8
 
9
9
 
10
- class BriefingPatch[T: WithBriefing](Patch[T], WithBriefing):
11
- """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."""
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
12
 
13
13
 
14
- class RuleSetBriefingPatch(BriefingPatch[RuleSet]):
15
- """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
- language: str
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
17
  @staticmethod
18
18
  def ref_cls() -> Optional[Type[BaseModel]]:
19
19
  """Get the reference class of the model."""
@@ -1,7 +1,7 @@
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 List, Literal, Optional, Self
4
+ from typing import Any, List, Literal, Optional, Self, Tuple, Unpack
5
5
 
6
6
  from fabricatio.journal import logger
7
7
  from fabricatio.models.generic import SketchedAble, WithBriefing
@@ -51,7 +51,12 @@ class ProblemSolutions(SketchedAble):
51
51
  problem: Problem
52
52
  """The problem identified in the review."""
53
53
  solutions: List[Solution]
54
- """A collection of potential solutions."""
54
+ """A collection of potential solutions, spread the thought, add more solution as possible.Do not leave this as blank"""
55
+
56
+ def model_post_init(self, context: Any, /) -> None:
57
+ """Initialize the problem-solution pair with a problem and a list of solutions."""
58
+ if len(self.solutions) == 0:
59
+ logger.warning(f"No solution found for problem {self.problem.name}, please add more solutions manually.")
55
60
 
56
61
  def update_from_inner(self, other: Self) -> Self:
57
62
  """Update the current instance with another instance's attributes."""
@@ -69,6 +74,10 @@ class ProblemSolutions(SketchedAble):
69
74
  self.solutions = solutions
70
75
  return self
71
76
 
77
+ def has_solutions(self) -> bool:
78
+ """Check if the problem-solution pair has any solutions."""
79
+ return len(self.solutions) > 0
80
+
72
81
  async def edit_problem(self) -> Self:
73
82
  """Interactively edit the problem description."""
74
83
  self.problem = Problem.model_validate_strings(
@@ -87,11 +96,11 @@ class ProblemSolutions(SketchedAble):
87
96
  """Check if the improvement is decided."""
88
97
  return len(self.solutions) == 1
89
98
 
90
- def final_solution(self) -> Optional[Solution]:
99
+ def final_solution(self, always_use_first: bool = False) -> Optional[Solution]:
91
100
  """Get the final solution."""
92
- if not self.decided():
101
+ if not always_use_first and not self.decided():
93
102
  logger.error(
94
- f"There is more than one solution for problem {self.problem.name}, please decide which solution is eventually adopted."
103
+ f"There is {len(self.solutions)} solutions for problem {self.problem.name}, please decide which solution is eventually adopted."
95
104
  )
96
105
  return None
97
106
  return self.solutions[0]
@@ -106,6 +115,10 @@ class Improvement(SketchedAble):
106
115
  problem_solutions: List[ProblemSolutions]
107
116
  """Collection of problems identified during review along with their potential solutions."""
108
117
 
118
+ def all_problems_have_solutions(self) -> bool:
119
+ """Check if all problems have solutions."""
120
+ return all(ps.has_solutions() for ps in self.problem_solutions)
121
+
109
122
  async def supervisor_check(self, check_solutions: bool = True) -> Self:
110
123
  """Perform an interactive review session to filter problems and solutions.
111
124
 
@@ -145,9 +158,9 @@ class Improvement(SketchedAble):
145
158
  return all(ps.decided() for ps in self.problem_solutions)
146
159
 
147
160
  @classmethod
148
- def gather(cls, *improvements: Self) -> Self:
161
+ def gather(cls, *improvements: Unpack[Tuple["Improvement", ...]]) -> Self:
149
162
  """Gather multiple improvements into a single instance."""
150
163
  return cls(
151
- focused_on="\n".join(imp.focused_on for imp in improvements),
164
+ focused_on=";".join(imp.focused_on for imp in improvements),
152
165
  problem_solutions=list(chain(*(imp.problem_solutions for imp in improvements))),
153
166
  )