fabricatio 0.2.8.dev3__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.
Files changed (48) hide show
  1. fabricatio/__init__.py +4 -11
  2. fabricatio/actions/__init__.py +1 -0
  3. fabricatio/actions/article.py +128 -165
  4. fabricatio/actions/article_rag.py +62 -46
  5. fabricatio/actions/output.py +60 -4
  6. fabricatio/actions/rag.py +2 -1
  7. fabricatio/actions/rules.py +72 -0
  8. fabricatio/capabilities/__init__.py +1 -0
  9. fabricatio/capabilities/censor.py +104 -0
  10. fabricatio/capabilities/check.py +148 -32
  11. fabricatio/capabilities/correct.py +162 -100
  12. fabricatio/capabilities/rag.py +5 -4
  13. fabricatio/capabilities/rating.py +109 -54
  14. fabricatio/capabilities/review.py +1 -1
  15. fabricatio/capabilities/task.py +2 -1
  16. fabricatio/config.py +14 -6
  17. fabricatio/fs/readers.py +20 -1
  18. fabricatio/models/action.py +63 -41
  19. fabricatio/models/adv_kwargs_types.py +25 -0
  20. fabricatio/models/extra/__init__.py +1 -0
  21. fabricatio/models/extra/advanced_judge.py +7 -4
  22. fabricatio/models/extra/article_base.py +125 -79
  23. fabricatio/models/extra/article_main.py +101 -19
  24. fabricatio/models/extra/article_outline.py +2 -3
  25. fabricatio/models/extra/article_proposal.py +15 -14
  26. fabricatio/models/extra/patches.py +20 -0
  27. fabricatio/models/extra/problem.py +64 -23
  28. fabricatio/models/extra/rule.py +39 -10
  29. fabricatio/models/generic.py +405 -75
  30. fabricatio/models/kwargs_types.py +23 -17
  31. fabricatio/models/task.py +1 -1
  32. fabricatio/models/tool.py +149 -14
  33. fabricatio/models/usages.py +55 -56
  34. fabricatio/parser.py +12 -13
  35. fabricatio/rust.cp312-win_amd64.pyd +0 -0
  36. fabricatio/{_rust.pyi → rust.pyi} +42 -4
  37. fabricatio/{_rust_instances.py → rust_instances.py} +1 -1
  38. fabricatio/utils.py +5 -5
  39. fabricatio/workflows/__init__.py +1 -0
  40. fabricatio/workflows/articles.py +3 -5
  41. fabricatio-0.2.9.data/scripts/tdown.exe +0 -0
  42. {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/METADATA +1 -1
  43. fabricatio-0.2.9.dist-info/RECORD +61 -0
  44. fabricatio/_rust.cp312-win_amd64.pyd +0 -0
  45. fabricatio-0.2.8.dev3.data/scripts/tdown.exe +0 -0
  46. fabricatio-0.2.8.dev3.dist-info/RECORD +0 -53
  47. {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/WHEEL +0 -0
  48. {fabricatio-0.2.8.dev3.dist-info → fabricatio-0.2.9.dist-info}/licenses/LICENSE +0 -0
@@ -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
- CensoredAble,
11
- Display,
10
+ Described,
12
11
  FinalizedDumpAble,
13
12
  Introspect,
13
+ Language,
14
14
  ModelHash,
15
15
  PersistentAble,
16
16
  ProposedUpdateAble,
17
17
  ResolveUpdateConflict,
18
+ SequencePatch,
19
+ SketchedAble,
20
+ Titled,
21
+ WordCount,
18
22
  )
23
+ from pydantic import Field
19
24
 
20
25
 
21
26
  class ReferringType(StrEnum):
@@ -29,51 +34,51 @@ class ReferringType(StrEnum):
29
34
  type RefKey = Tuple[str, Optional[str], Optional[str]]
30
35
 
31
36
 
32
- class ArticleRef(CensoredAble, Display, ProposedUpdateAble):
37
+ class ArticleRef(ProposedUpdateAble):
33
38
  """Reference to a specific chapter, section or subsection within the article. You SHALL not refer to an article component that is external and not present within our own article.
34
39
 
35
40
  Examples:
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,60 +90,44 @@ class ArticleRef(CensoredAble, Display, 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(CensoredAble, Display):
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
-
122
-
123
- class Patch[T](ProposedUpdateAble, Display):
124
- """Base class for patches."""
125
123
 
126
- tweaked: List[T]
127
- """Tweaked content list"""
128
-
129
- def update_from_inner(self, other: Self) -> Self:
130
- """Updates the current instance with the attributes of another instance."""
131
- self.tweaked.clear()
132
- self.tweaked.extend(other.tweaked)
133
- return self
134
-
135
- @classmethod
136
- def default(cls) -> Self:
137
- """Defaults to empty list."""
138
- return cls(tweaked=[])
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."""
139
128
 
140
129
 
141
- class ArticleRefPatch(Patch[ArticleRef]):
130
+ class ArticleRefSequencePatch(SequencePatch[ArticleRef]):
142
131
  """Patch for article refs."""
143
132
 
144
133
 
@@ -163,8 +152,8 @@ class ArticleOutlineBase(
163
152
  self.support_to.extend(other.support_to)
164
153
  self.depend_on.clear()
165
154
  self.depend_on.extend(other.depend_on)
166
- self.writing_aim.clear()
167
- self.writing_aim.extend(other.writing_aim)
155
+ self.aims.clear()
156
+ self.aims.extend(other.aims)
168
157
  self.description = other.description
169
158
  return self
170
159
 
@@ -288,25 +277,19 @@ class ChapterBase[T: SectionBase](ArticleOutlineBase):
288
277
  return ""
289
278
 
290
279
 
291
- class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
280
+ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, WordCount, Described, Titled, Language, ABC):
292
281
  """Base class for article outlines."""
293
282
 
294
- language: str
295
- """Written language of the article. SHALL be aligned to the language of the article proposal provided."""
296
-
297
- title: str
298
- """Title of the academic paper."""
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."""
299
292
 
300
- prospect: str
301
- """Consolidated research statement with four pillars:
302
- 1. Problem Identification: Current limitations
303
- 2. Methodological Response: Technical approach
304
- 3. Empirical Validation: Evaluation strategy
305
- 4. Scholarly Impact: Field contributions
306
- """
307
-
308
- abstract: str
309
- """The abstract is a concise summary of the academic paper's main findings."""
310
293
  chapters: List[T]
311
294
  """Chapters of the article. Contains at least one chapter. You can also add more as needed."""
312
295
 
@@ -393,34 +376,97 @@ class ArticleBase[T: ChapterBase](FinalizedDumpAble, AsPrompt, ABC):
393
376
  return component, summary
394
377
  return None
395
378
 
396
- @overload
397
- def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]: ...
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
+
383
+
384
+ def iter_chap_title(self) -> Generator[str, None, None]:
385
+ """Iterates through all chapter titles in the article."""
386
+ for chap in self.chapters:
387
+ yield chap.title
388
+
389
+ def iter_section_title(self) -> Generator[str, None, None]:
390
+ """Iterates through all section titles in the article."""
391
+ for _, sec in self.iter_sections():
392
+ yield sec.title
393
+
394
+ def iter_subsection_title(self) -> Generator[str, None, None]:
395
+ """Iterates through all subsection titles in the article."""
396
+ for _, _, subsec in self.iter_subsections():
397
+ yield subsec.title
398
398
 
399
399
  @overload
400
- def find_illegal_ref(self) -> Optional[Tuple[ArticleRef, str]]: ...
400
+ def find_illegal_ref(self, gather_identical: bool) -> Optional[Tuple[ArticleOutlineBase,ArticleRef | List[ArticleRef], str]]: ...
401
401
 
402
- def find_illegal_ref(self, gather_identical: bool = False) -> Optional[Tuple[ArticleRef | List[ArticleRef], str]]:
402
+ @overload
403
+ def find_illegal_ref(self) -> Optional[Tuple[ArticleOutlineBase,ArticleRef, str]]: ...
404
+ def find_illegal_ref(
405
+ self, gather_identical: bool = False
406
+ ) -> Optional[Tuple[ArticleOutlineBase, ArticleRef | List[ArticleRef], str]]:
403
407
  """Finds the first illegal component in the outline.
404
408
 
405
409
  Returns:
406
410
  Tuple[ArticleOutlineBase, str]: A tuple containing the illegal component and an error message.
407
411
  """
408
412
  summary = ""
413
+ chap_titles_set = set(self.iter_chap_title())
414
+ sec_titles_set = set(self.iter_section_title())
415
+ subsec_titles_set = set(self.iter_subsection_title())
416
+
409
417
  for component in self.iter_dfs_rev():
410
418
  for ref in chain(component.depend_on, component.support_to):
411
419
  if not ref.deref(self):
412
420
  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"
413
- if summary and not gather_identical:
414
- return ref, summary
421
+
422
+ if ref.chap not in (chap_titles_set):
423
+ summary += f"Chapter titled `{ref.chap}` is not any of {chap_titles_set}\n"
424
+ if ref.sec and ref.sec not in (sec_titles_set):
425
+ summary += f"Section Titled `{ref.sec}` is not any of {sec_titles_set}\n"
426
+ if ref.subsec and ref.subsec not in (subsec_titles_set):
427
+ summary += f"Subsection Titled `{ref.subsec}` is not any of {subsec_titles_set}"
428
+
415
429
  if summary and gather_identical:
416
- return [
417
- identical_ref
418
- for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
419
- if identical_ref == ref
420
- ], summary
430
+ return (
431
+ component,
432
+ [
433
+ identical_ref
434
+ for identical_ref in chain(self.iter_depend_on(), self.iter_support_on())
435
+ if identical_ref == ref
436
+ ],
437
+ summary,
438
+ )
439
+ if summary:
440
+ return component, ref, summary
421
441
 
422
442
  return None
423
443
 
444
+ def gather_illegal_ref(self) -> Tuple[List[ArticleRef], str]:
445
+ """Gathers all illegal references in the article."""
446
+ summary = []
447
+ chap_titles_set = set(self.iter_chap_title())
448
+ sec_titles_set = set(self.iter_section_title())
449
+ subsec_titles_set = set(self.iter_subsection_title())
450
+ res_seq = []
451
+
452
+ for component in self.iter_dfs():
453
+ for ref in (
454
+ r for r in chain(component.depend_on, component.support_to) if not r.deref(self) and r not in res_seq
455
+ ):
456
+ res_seq.append(ref)
457
+ if ref.chap not in chap_titles_set:
458
+ summary.append(
459
+ f"Chapter titled `{ref.chap}` is not exist, since it is not any of {chap_titles_set}."
460
+ )
461
+ if ref.sec and (ref.sec not in sec_titles_set):
462
+ summary.append(f"Section Titled `{ref.sec}` is not exist, since it is not any of {sec_titles_set}")
463
+ if ref.subsec and (ref.subsec not in subsec_titles_set):
464
+ summary.append(
465
+ f"Subsection Titled `{ref.subsec}` is not exist, since it is not any of {subsec_titles_set}"
466
+ )
467
+
468
+ return res_seq, "\n".join(summary)
469
+
424
470
  def finalized_dump(self) -> str:
425
471
  """Generates standardized hierarchical markup for academic publishing systems.
426
472
 
@@ -3,36 +3,47 @@
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,
9
10
  ArticleOutlineBase,
10
11
  ChapterBase,
11
- Patch,
12
12
  SectionBase,
13
13
  SubSectionBase,
14
14
  )
15
15
  from fabricatio.models.extra.article_outline import (
16
16
  ArticleOutline,
17
17
  )
18
- from fabricatio.models.generic import CensoredAble, Display, PersistentAble, WithRef
18
+ from fabricatio.models.generic import Described, PersistentAble, SequencePatch, SketchedAble, WithRef, WordCount
19
+ from fabricatio.rust import word_count
19
20
  from fabricatio.utils import ok
21
+ from pydantic import Field
20
22
 
23
+ PARAGRAPH_SEP = "// - - -"
21
24
 
22
- class Paragraph(CensoredAble):
25
+
26
+ class Paragraph(SketchedAble, WordCount, Described):
23
27
  """Structured academic paragraph blueprint for controlled content generation."""
24
28
 
25
- description: str
26
- """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
+ )
27
33
 
28
- writing_aim: List[str]
34
+ aims: List[str]
29
35
  """Specific communicative objectives for this paragraph's content."""
30
36
 
31
37
  content: str
32
38
  """The actual content of the paragraph, represented as a string."""
33
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
+
34
45
 
35
- class ArticleParagraphPatch(Patch[Paragraph]):
46
+ class ArticleParagraphSequencePatch(SequencePatch[Paragraph]):
36
47
  """Patch for `Paragraph` list of `ArticleSubsection`."""
37
48
 
38
49
 
@@ -42,11 +53,28 @@ class ArticleSubsection(SubSectionBase):
42
53
  paragraphs: List[Paragraph]
43
54
  """List of Paragraph objects containing the content of the subsection."""
44
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
+
45
64
  def introspect(self) -> str:
46
- """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 = ""
47
67
  if len(self.paragraphs) == 0:
48
- return f"`{self.__class__.__name__}` titled `{self.title}` have no paragraphs, to achieve the goal of `{self.writing_aim}`."
49
- return ""
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
50
78
 
51
79
  def update_from_inner(self, other: Self) -> Self:
52
80
  """Updates the current instance with the attributes of another instance."""
@@ -62,20 +90,62 @@ class ArticleSubsection(SubSectionBase):
62
90
  Returns:
63
91
  str: Typst code snippet for rendering.
64
92
  """
65
- 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
+ )
66
107
 
67
108
 
68
109
  class ArticleSection(SectionBase[ArticleSubsection]):
69
110
  """Atomic argumentative unit with high-level specificity."""
70
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
+
71
127
 
72
128
  class ArticleChapter(ChapterBase[ArticleSection]):
73
129
  """Thematic progression implementing research function."""
74
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
+
75
146
 
76
147
  class Article(
77
- Display,
78
- CensoredAble,
148
+ SketchedAble,
79
149
  WithRef[ArticleOutline],
80
150
  PersistentAble,
81
151
  ArticleBase[ArticleChapter],
@@ -96,7 +166,7 @@ class Article(
96
166
 
97
167
  @override
98
168
  def iter_subsections(self) -> Generator[Tuple[ArticleChapter, ArticleSection, ArticleSubsection], None, None]:
99
- return super().iter_subsections()
169
+ return super().iter_subsections() # pyright: ignore [reportReturnType]
100
170
 
101
171
  @classmethod
102
172
  def from_outline(cls, outline: ArticleOutline) -> "Article":
@@ -109,31 +179,43 @@ class Article(
109
179
  Article: The generated article.
110
180
  """
111
181
  # Set the title from the outline
112
- article = Article(**outline.model_dump(exclude={"chapters"}), chapters=[])
182
+ article = Article(**outline.model_dump(exclude={"chapters"}, by_alias=True), chapters=[])
113
183
 
114
184
  for chapter in outline.chapters:
115
185
  # Create a new chapter
116
186
  article_chapter = ArticleChapter(
117
187
  sections=[],
118
- **chapter.model_dump(exclude={"sections"}),
188
+ **chapter.model_dump(exclude={"sections"}, by_alias=True),
119
189
  )
120
190
  for section in chapter.sections:
121
191
  # Create a new section
122
192
  article_section = ArticleSection(
123
193
  subsections=[],
124
- **section.model_dump(exclude={"subsections"}),
194
+ **section.model_dump(exclude={"subsections"}, by_alias=True),
125
195
  )
126
196
  for subsection in section.subsections:
127
197
  # Create a new subsection
128
198
  article_subsection = ArticleSubsection(
129
199
  paragraphs=[],
130
- **subsection.model_dump(),
200
+ **subsection.model_dump(by_alias=True),
131
201
  )
132
202
  article_section.subsections.append(article_subsection)
133
203
  article_chapter.sections.append(article_section)
134
204
  article.chapters.append(article_chapter)
135
205
  return article
136
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
+
137
219
  def gather_dependencies(self, article: ArticleOutlineBase) -> List[ArticleOutlineBase]:
138
220
  """Gathers dependencies for all sections and subsections in the article.
139
221
 
@@ -143,7 +225,7 @@ class Article(
143
225
 
144
226
  supports = []
145
227
  for a in self.iter_dfs_rev():
146
- 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]
147
229
  supports.append(a)
148
230
 
149
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 CensoredAble, Display, PersistentAble, WithRef
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
- Display,
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 AsPrompt, CensoredAble, Display, PersistentAble, WithRef
6
-
7
-
8
- class ArticleProposal(CensoredAble, Display, 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(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
- 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
- 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,
@@ -0,0 +1,20 @@
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
+
3
+ from typing import Optional, Type
4
+
5
+ from fabricatio.models.extra.rule import RuleSet
6
+ from fabricatio.models.generic import Language, Patch, WithBriefing
7
+ from pydantic import BaseModel
8
+
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