mfcli 0.2.0__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 (136) hide show
  1. mfcli/.env.example +72 -0
  2. mfcli/__init__.py +0 -0
  3. mfcli/agents/__init__.py +0 -0
  4. mfcli/agents/controller/__init__.py +0 -0
  5. mfcli/agents/controller/agent.py +19 -0
  6. mfcli/agents/controller/config.yaml +27 -0
  7. mfcli/agents/controller/tools.py +42 -0
  8. mfcli/agents/tools/general.py +118 -0
  9. mfcli/alembic/env.py +61 -0
  10. mfcli/alembic/script.py.mako +28 -0
  11. mfcli/alembic/versions/6ccc0c7c397c_added_fields_to_pdf_parts_model.py +39 -0
  12. mfcli/alembic/versions/769019ef4870_added_gemini_file_path_to_pdf_part_model.py +33 -0
  13. mfcli/alembic/versions/7a2e3a779fdc_added_functional_block_and_component_.py +54 -0
  14. mfcli/alembic/versions/7d5adb2a47a7_added_pdf_parts_model.py +41 -0
  15. mfcli/alembic/versions/7fcb7d6a5836_init.py +167 -0
  16. mfcli/alembic/versions/e0f2b5765c72_added_cascade_delete_for_models_that_.py +32 -0
  17. mfcli/alembic.ini +147 -0
  18. mfcli/cli/__init__.py +0 -0
  19. mfcli/cli/dependencies.py +59 -0
  20. mfcli/cli/main.py +192 -0
  21. mfcli/client/__init__.py +0 -0
  22. mfcli/client/chroma_db.py +184 -0
  23. mfcli/client/docling.py +44 -0
  24. mfcli/client/gemini.py +252 -0
  25. mfcli/client/llama_parse.py +38 -0
  26. mfcli/client/vector_db.py +93 -0
  27. mfcli/constants/__init__.py +0 -0
  28. mfcli/constants/base_enum.py +18 -0
  29. mfcli/constants/directory_names.py +1 -0
  30. mfcli/constants/file_types.py +189 -0
  31. mfcli/constants/gemini.py +1 -0
  32. mfcli/constants/openai.py +6 -0
  33. mfcli/constants/pipeline_run_status.py +3 -0
  34. mfcli/crud/__init__.py +0 -0
  35. mfcli/crud/file.py +42 -0
  36. mfcli/crud/functional_blocks.py +26 -0
  37. mfcli/crud/netlist.py +18 -0
  38. mfcli/crud/pipeline_run.py +17 -0
  39. mfcli/crud/project.py +99 -0
  40. mfcli/digikey/__init__.py +0 -0
  41. mfcli/digikey/digikey.py +105 -0
  42. mfcli/main.py +5 -0
  43. mfcli/mcp/__init__.py +0 -0
  44. mfcli/mcp/configs/cline_mcp_settings.json +11 -0
  45. mfcli/mcp/configs/mfcli.mcp.json +7 -0
  46. mfcli/mcp/mcp_instance.py +6 -0
  47. mfcli/mcp/server.py +37 -0
  48. mfcli/mcp/state_manager.py +51 -0
  49. mfcli/mcp/tools/__init__.py +0 -0
  50. mfcli/mcp/tools/query_knowledgebase.py +108 -0
  51. mfcli/models/__init__.py +10 -0
  52. mfcli/models/base.py +10 -0
  53. mfcli/models/bom.py +71 -0
  54. mfcli/models/datasheet.py +10 -0
  55. mfcli/models/debug_setup.py +64 -0
  56. mfcli/models/file.py +43 -0
  57. mfcli/models/file_docket.py +94 -0
  58. mfcli/models/file_metadata.py +19 -0
  59. mfcli/models/functional_blocks.py +94 -0
  60. mfcli/models/llm_response.py +5 -0
  61. mfcli/models/mcu.py +97 -0
  62. mfcli/models/mcu_errata.py +26 -0
  63. mfcli/models/netlist.py +59 -0
  64. mfcli/models/pdf_parts.py +25 -0
  65. mfcli/models/pipeline_run.py +34 -0
  66. mfcli/models/project.py +27 -0
  67. mfcli/models/project_metadata.py +15 -0
  68. mfcli/pipeline/__init__.py +0 -0
  69. mfcli/pipeline/analysis/__init__.py +0 -0
  70. mfcli/pipeline/analysis/bom_netlist_mapper.py +28 -0
  71. mfcli/pipeline/analysis/generators/__init__.py +0 -0
  72. mfcli/pipeline/analysis/generators/bom/__init__.py +0 -0
  73. mfcli/pipeline/analysis/generators/bom/bom.py +74 -0
  74. mfcli/pipeline/analysis/generators/debug_setup/__init__.py +0 -0
  75. mfcli/pipeline/analysis/generators/debug_setup/debug_setup.py +71 -0
  76. mfcli/pipeline/analysis/generators/debug_setup/instructions.py +150 -0
  77. mfcli/pipeline/analysis/generators/functional_blocks/__init__.py +0 -0
  78. mfcli/pipeline/analysis/generators/functional_blocks/functional_blocks.py +93 -0
  79. mfcli/pipeline/analysis/generators/functional_blocks/instructions.py +34 -0
  80. mfcli/pipeline/analysis/generators/functional_blocks/validator.py +94 -0
  81. mfcli/pipeline/analysis/generators/generator.py +258 -0
  82. mfcli/pipeline/analysis/generators/generator_base.py +18 -0
  83. mfcli/pipeline/analysis/generators/mcu/__init__.py +0 -0
  84. mfcli/pipeline/analysis/generators/mcu/instructions.py +156 -0
  85. mfcli/pipeline/analysis/generators/mcu/mcu.py +84 -0
  86. mfcli/pipeline/analysis/generators/mcu_errata/__init__.py +1 -0
  87. mfcli/pipeline/analysis/generators/mcu_errata/instructions.py +77 -0
  88. mfcli/pipeline/analysis/generators/mcu_errata/mcu_errata.py +95 -0
  89. mfcli/pipeline/analysis/generators/summary/__init__.py +0 -0
  90. mfcli/pipeline/analysis/generators/summary/summary.py +47 -0
  91. mfcli/pipeline/classifier.py +93 -0
  92. mfcli/pipeline/data_enricher.py +15 -0
  93. mfcli/pipeline/extractor.py +34 -0
  94. mfcli/pipeline/extractors/__init__.py +0 -0
  95. mfcli/pipeline/extractors/pdf.py +12 -0
  96. mfcli/pipeline/parser.py +120 -0
  97. mfcli/pipeline/parsers/__init__.py +0 -0
  98. mfcli/pipeline/parsers/netlist/__init__.py +0 -0
  99. mfcli/pipeline/parsers/netlist/edif.py +93 -0
  100. mfcli/pipeline/parsers/netlist/kicad_legacy_net.py +326 -0
  101. mfcli/pipeline/parsers/netlist/kicad_spice.py +135 -0
  102. mfcli/pipeline/parsers/netlist/pads.py +185 -0
  103. mfcli/pipeline/parsers/netlist/protel.py +166 -0
  104. mfcli/pipeline/parsers/netlist/protel_detector.py +29 -0
  105. mfcli/pipeline/pipeline.py +419 -0
  106. mfcli/pipeline/preprocessors/__init__.py +0 -0
  107. mfcli/pipeline/preprocessors/user_guide.py +127 -0
  108. mfcli/pipeline/run_context.py +32 -0
  109. mfcli/pipeline/schema_mapper.py +89 -0
  110. mfcli/pipeline/sub_classifier.py +115 -0
  111. mfcli/utils/__init__.py +0 -0
  112. mfcli/utils/config.py +33 -0
  113. mfcli/utils/configurator.py +324 -0
  114. mfcli/utils/data_cleaner.py +82 -0
  115. mfcli/utils/datasheet_vectorizer.py +281 -0
  116. mfcli/utils/directory_manager.py +96 -0
  117. mfcli/utils/file_upload.py +298 -0
  118. mfcli/utils/files.py +16 -0
  119. mfcli/utils/http_requests.py +54 -0
  120. mfcli/utils/kb_lister.py +89 -0
  121. mfcli/utils/kb_remover.py +173 -0
  122. mfcli/utils/logger.py +28 -0
  123. mfcli/utils/mcp_configurator.py +311 -0
  124. mfcli/utils/migrations.py +18 -0
  125. mfcli/utils/orm.py +43 -0
  126. mfcli/utils/pdf_splitter.py +63 -0
  127. mfcli/utils/query_service.py +22 -0
  128. mfcli/utils/system_check.py +306 -0
  129. mfcli/utils/tools.py +31 -0
  130. mfcli/utils/vectorizer.py +28 -0
  131. mfcli-0.2.0.dist-info/METADATA +841 -0
  132. mfcli-0.2.0.dist-info/RECORD +136 -0
  133. mfcli-0.2.0.dist-info/WHEEL +5 -0
  134. mfcli-0.2.0.dist-info/entry_points.txt +3 -0
  135. mfcli-0.2.0.dist-info/licenses/LICENSE +21 -0
  136. mfcli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ from typing import List, Dict, Tuple
2
+ from difflib import SequenceMatcher
3
+
4
+ from mfcli.models.functional_blocks import FunctionalBlockMeta
5
+ from mfcli.models.netlist import NetlistComponent, Netlist
6
+ from mfcli.utils.logger import get_logger
7
+ from mfcli.utils.orm import Session
8
+
9
+ logger = get_logger(__name__)
10
+
11
+
12
+ def validate_functional_blocks_against_netlist(
13
+ db: Session,
14
+ pipeline_run_id: int,
15
+ functional_blocks: List[FunctionalBlockMeta]
16
+ ) -> Dict:
17
+ """
18
+ Validate functional block components against netlist.
19
+ Returns corrected blocks and validation report.
20
+ """
21
+
22
+ # Get all valid component ref_des from netlist
23
+ valid_components = (
24
+ db.query(NetlistComponent.ref_des)
25
+ .join(NetlistComponent.netlist)
26
+ .filter(Netlist.pipeline_run_id == pipeline_run_id)
27
+ .all()
28
+ )
29
+ valid_refs = {comp.ref_des for comp in valid_components}
30
+
31
+ report = {
32
+ "total_components": 0,
33
+ "valid_components": 0,
34
+ "invalid_components": [],
35
+ "corrected_components": [],
36
+ "blocks_validated": 0
37
+ }
38
+
39
+ for block in functional_blocks:
40
+ corrected_components = []
41
+
42
+ for component_ref in block.components:
43
+ report["total_components"] += 1
44
+
45
+ if component_ref in valid_refs:
46
+ # Valid component
47
+ report["valid_components"] += 1
48
+ corrected_components.append(component_ref)
49
+ else:
50
+ # Invalid - try fuzzy matching
51
+ best_match = find_best_match(component_ref, valid_refs)
52
+
53
+ if best_match and similarity_ratio(component_ref, best_match) > 0.8:
54
+ # High confidence match - autocorrect
55
+ logger.warning(f"Auto-correcting '{component_ref}' -> '{best_match}'")
56
+ corrected_components.append(best_match)
57
+ report["corrected_components"].append({
58
+ "original": component_ref,
59
+ "corrected": best_match,
60
+ "block": block.name
61
+ })
62
+ else:
63
+ # No good match - log and skip
64
+ logger.error(f"Invalid component '{component_ref}' in block '{block.name}'")
65
+ report["invalid_components"].append({
66
+ "ref": component_ref,
67
+ "block": block.name,
68
+ "suggestion": best_match if best_match else None
69
+ })
70
+
71
+ block.components = corrected_components
72
+
73
+ report["blocks_validated"] += 1
74
+
75
+ return report
76
+
77
+
78
+ def find_best_match(ref: str, valid_refs: set) -> str:
79
+ """Find nearest matching reference designator."""
80
+ best_match = None
81
+ best_ratio = 0
82
+
83
+ for valid_ref in valid_refs:
84
+ ratio = SequenceMatcher(None, ref.upper(), valid_ref.upper()).ratio()
85
+ if ratio > best_ratio:
86
+ best_ratio = ratio
87
+ best_match = valid_ref
88
+
89
+ return best_match if best_ratio > 0.6 else None
90
+
91
+
92
+ def similarity_ratio(str1: str, str2: str) -> float:
93
+ """Calculate similarity between two strings."""
94
+ return SequenceMatcher(None, str1.upper(), str2.upper()).ratio()
@@ -0,0 +1,258 @@
1
+ import csv
2
+ import json
3
+ from pathlib import Path
4
+ from typing import Dict, Any, List, Type
5
+
6
+ from google.genai.types import File as GeminiFile
7
+
8
+ from mfcli.client.gemini import GeminiFileEntity
9
+ from mfcli.constants.file_types import FileSubtypes
10
+ from mfcli.models.bom import BOMSchema
11
+ from mfcli.models.file import File
12
+ from mfcli.models.file_docket import FileDocketEntry
13
+ from mfcli.pipeline.analysis.generators.bom.bom import BOMGenerator
14
+ from mfcli.pipeline.analysis.generators.debug_setup.debug_setup import DSCheatSheetGenerator
15
+ from mfcli.pipeline.analysis.generators.functional_blocks.functional_blocks import (
16
+ extract_functional_blocks_from_file,
17
+ FBCheatSheetGenerator
18
+ )
19
+ from mfcli.pipeline.analysis.generators.generator_base import GeneratorBase
20
+ from mfcli.pipeline.analysis.generators.mcu.mcu import MCUCheatSheetGenerator
21
+ from mfcli.pipeline.analysis.generators.mcu_errata.mcu_errata import ErrataCheatSheetGenerator
22
+ from mfcli.pipeline.analysis.generators.summary.summary import SummaryCheatSheetGenerator
23
+ from mfcli.pipeline.run_context import PipelineRunContext
24
+ from mfcli.utils.directory_manager import app_dirs
25
+ from mfcli.utils.logger import get_logger
26
+
27
+ logger = get_logger(__name__)
28
+
29
+ FileSubtypeGeneratorMap: Dict[FileSubtypes, List[Type[GeneratorBase]]] = {
30
+ FileSubtypes.ERRATA: [ErrataCheatSheetGenerator],
31
+ FileSubtypes.MCU_DATASHEET: [MCUCheatSheetGenerator],
32
+ FileSubtypes.SCHEMATIC: [DSCheatSheetGenerator],
33
+ FileSubtypes.USER_GUIDE: [SummaryCheatSheetGenerator],
34
+ FileSubtypes.REFERENCE_MANUAL: [SummaryCheatSheetGenerator]
35
+ }
36
+
37
+ # Mapping of generator classes to friendly output names
38
+ GeneratorNameMap: Dict[Any, str] = {
39
+ ErrataCheatSheetGenerator: "mcu_errata",
40
+ MCUCheatSheetGenerator: "mcu",
41
+ DSCheatSheetGenerator: "debug_setup",
42
+ FBCheatSheetGenerator: "functional_blocks",
43
+ SummaryCheatSheetGenerator: "summary"
44
+ }
45
+
46
+
47
+ class Generator:
48
+ def __init__(self, context: PipelineRunContext, processed_file_types: set[str] = None):
49
+ self._context = context
50
+ self._processed_file_types = processed_file_types or set()
51
+
52
+ @staticmethod
53
+ def _create_cheat_sheet_json_file(file_path: str, cheat_sheet_data: Dict) -> str:
54
+ # Ensure parent directory exists
55
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
56
+ with open(file_path, 'w', encoding='utf-8', errors='replace') as jsonfile:
57
+ data = json.dumps(cheat_sheet_data, indent=2, ensure_ascii=False)
58
+ jsonfile.write(data)
59
+ return data
60
+
61
+ def _add_to_file_docket(self, file_name: str, file_path: str, vectorize: bool, sub_type: str):
62
+ entry = FileDocketEntry(
63
+ name=file_name,
64
+ path=str(file_path),
65
+ vectorize=vectorize,
66
+ sub_type=sub_type
67
+ )
68
+ self._context.docket.add(entry)
69
+
70
+ def _file_subtype_query(self, subtype: FileSubtypes):
71
+ return (
72
+ self._context.db.query(File)
73
+ .filter(File.pipeline_run_id == self._context.run.id)
74
+ .filter(File.sub_type == subtype)
75
+ .filter(File.is_datasheet == 0)
76
+ )
77
+
78
+ def _query_first_file_subtype(self, subtype: FileSubtypes) -> File | None:
79
+ return self._file_subtype_query(subtype).first()
80
+
81
+ def _query_all_file_subtype(self, subtype: FileSubtypes) -> List[File]:
82
+ return self._file_subtype_query(subtype).all()
83
+
84
+ @staticmethod
85
+ def _create_bom_csv(file_path: str, bom_entries: List[Dict]):
86
+ # Ensure parent directory exists
87
+ Path(file_path).parent.mkdir(parents=True, exist_ok=True)
88
+ columns = list(BOMSchema.model_fields.keys())
89
+ with open(file_path, "w", newline="", encoding="utf-8", errors="ignore") as f:
90
+ writer = csv.DictWriter(f, fieldnames=columns)
91
+ writer.writeheader()
92
+ writer.writerows(bom_entries)
93
+
94
+ def _vectorize_bom_csv(self, file_path: str):
95
+ with open(file_path, "r") as f:
96
+ self._context.vectorizer.vectorize_text_content(f.read(), "bom.csv", "BOM")
97
+
98
+ def _get_gemini_file(self, file: File) -> GeminiFile | None:
99
+ if not self._context.gemini_file_cache.get(file.gemini_file_id):
100
+ logger.warn(f"No Gemini file found for file: {file.id}")
101
+ return
102
+ return self._context.gemini_file_cache[file.gemini_file_id]
103
+
104
+ async def _generate_bom(self):
105
+ bom_file = self._query_first_file_subtype(FileSubtypes.BOM)
106
+ if bom_file:
107
+ return
108
+ logger.info("Generation BOM")
109
+ generator = BOMGenerator(self._context.gemini)
110
+ schematic_file = self._query_first_file_subtype(FileSubtypes.SCHEMATIC)
111
+ if not schematic_file:
112
+ return
113
+ gemini_file = self._get_gemini_file(schematic_file)
114
+ if not gemini_file:
115
+ return
116
+ bom_entries = await generator.generate(gemini_file)
117
+ file_path = app_dirs.generated_files_dir / "bom.csv"
118
+ self._create_bom_csv(file_path, bom_entries)
119
+ if self._context.config.vectorize_hw_files:
120
+ self._vectorize_bom_csv(file_path)
121
+ self._add_to_file_docket("bom.csv", file_path, self._context.config.vectorize_hw_files, "BOM")
122
+
123
+ @staticmethod
124
+ def _get_generator_name(generator_type: Any) -> str:
125
+ """Get the friendly name for a generator class."""
126
+ return GeneratorNameMap.get(generator_type, generator_type.__name__.lower())
127
+
128
+ def _get_cs_vectorization_setting(self, cs_type: Any) -> bool:
129
+ vectorize_config = self._context.config.vectorize_cheat_sheets_config
130
+ should_vectorize = False
131
+ if cs_type == ErrataCheatSheetGenerator:
132
+ should_vectorize = vectorize_config.vectorize_errata
133
+ elif cs_type == MCUCheatSheetGenerator:
134
+ should_vectorize = vectorize_config.vectorize_mcu
135
+ elif cs_type == DSCheatSheetGenerator:
136
+ should_vectorize = vectorize_config.vectorize_debug_setup
137
+ elif cs_type == FBCheatSheetGenerator:
138
+ should_vectorize = vectorize_config.vectorize_functional_blocks
139
+ return should_vectorize
140
+
141
+ def _get_gemini_files_from_pdf(self, file: File) -> List[GeminiFile]:
142
+ if not file.pdf_parts:
143
+ return [self._get_gemini_file(file)]
144
+ return [self._get_gemini_file(part) for part in file.pdf_parts]
145
+
146
+ async def _generate_functional_blocks(self):
147
+ # TODO: HANDLE CASE OF MULTIPLE SCHEMATIC/USER GUIDES/NETLISTS
148
+ schematic = self._query_first_file_subtype(FileSubtypes.SCHEMATIC)
149
+ schematic_files = self._get_gemini_files_from_pdf(schematic) if schematic else None
150
+ user_guide = self._query_first_file_subtype(FileSubtypes.USER_GUIDE)
151
+ user_guide_files = self._get_gemini_files_from_pdf(user_guide) if user_guide else None
152
+
153
+ # TODO: QUERY DIFFERENT NETLISTS
154
+ netlist = self._query_first_file_subtype(FileSubtypes.PROTEL_ALTIUM)
155
+
156
+ # Upload netlist to Gemini because it's not a PDF, hasn't been uploaded yet
157
+ netlist_file = None
158
+ if netlist:
159
+ netlist_gemini_file_meta = GeminiFileEntity(
160
+ path=Path(netlist.path),
161
+ mime_type="text/plain"
162
+ )
163
+ netlist_file = await self._context.gemini.upload(netlist_gemini_file_meta)
164
+
165
+ if schematic_files or user_guide_files or netlist_file:
166
+ logger.info("Generating functional blocks cheat sheet")
167
+
168
+ # Generate functional blocks from uploaded files
169
+ block_data = await extract_functional_blocks_from_file(
170
+ db=self._context.db,
171
+ pipeline_run=self._context.run,
172
+ gemini=self._context.gemini,
173
+ schematic_files=schematic_files,
174
+ user_guide_files=user_guide_files,
175
+ netlist_file=netlist_file
176
+ )
177
+
178
+ # Create cheat sheet JSON file
179
+ generator_name = self._get_generator_name(FBCheatSheetGenerator)
180
+ file_name = f"{schematic.name}_{generator_name}_cheat_sheet.json"
181
+ file_path = app_dirs.cheat_sheets_dir / file_name
182
+ text = self._create_cheat_sheet_json_file(file_path, block_data)
183
+
184
+ # Vectorize file and add to file docket
185
+ self._vectorize_and_add_to_docket(FBCheatSheetGenerator, file_path, file_name, text)
186
+
187
+ # Commit functional blocks to DB
188
+ self._context.db.commit()
189
+
190
+ def _vectorize_and_add_to_docket(self, cs_type: Any, file_path: str, file_name: str, text: str):
191
+ logger.debug(f"Vectorizing and adding cheat sheet to docket: {file_name}")
192
+ should_vectorize = self._get_cs_vectorization_setting(cs_type)
193
+ logger.debug(f"Should vectorize: {should_vectorize}")
194
+ if should_vectorize:
195
+ logger.debug(f"Vectorizing cheat sheet: {file_name}")
196
+ self._context.vectorizer.vectorize_text_content(text, file_name, "CHEAT_SHEET")
197
+ logger.debug(f"Adding cheat sheet to docket: {file_name}")
198
+ self._add_to_file_docket(file_name, file_path, should_vectorize, "CHEAT_SHEET")
199
+
200
+ async def _generate_for_subtype(self, subtype: FileSubtypes, generators: List[Type[GeneratorBase]]):
201
+ files = self._query_all_file_subtype(subtype)
202
+ for file in files:
203
+ gemini_files = self._get_gemini_files_from_pdf(file)
204
+ if not gemini_files:
205
+ continue
206
+ for generator in generators:
207
+ gen = generator(self._context, file, gemini_files)
208
+ generator_name = self._get_generator_name(generator)
209
+ try:
210
+ logger.info(f"Generating {generator_name} cheat sheet")
211
+ cheat_sheet_data = await gen.generate()
212
+ if not cheat_sheet_data:
213
+ continue
214
+ file_name = f"{file.name}_{generator_name}_cheat_sheet.json"
215
+ file_path = app_dirs.cheat_sheets_dir / file_name
216
+ text = self._create_cheat_sheet_json_file(file_path, cheat_sheet_data)
217
+ self._vectorize_and_add_to_docket(generator, file_path, file_name, text)
218
+ except Exception as e:
219
+ logger.exception(e)
220
+ logger.error(f"Error generating cheat sheet from {generator_name}")
221
+
222
+ async def generate_cheat_sheets(self):
223
+ # Only generate BOM if schematic was processed
224
+ if "SCHEMATIC" in self._processed_file_types:
225
+ logger.info("Generating BOM (schematic was processed)")
226
+ try:
227
+ await self._generate_bom()
228
+ except Exception as e:
229
+ logger.exception(e)
230
+ logger.error(f"Error generating BOM: {self._context.run.id}")
231
+ else:
232
+ logger.info("Skipping BOM generation (schematic not processed)")
233
+
234
+ # Only generate functional blocks if schematic, user guide, or netlist was processed
235
+ netlist_types = {"PROTEL_ALTIUM", "KICAD_LEGACY_NET", "KICAD_SPICE", "PADS", "EDIF"}
236
+ should_generate_fb = (
237
+ "SCHEMATIC" in self._processed_file_types or
238
+ "USER_GUIDE" in self._processed_file_types or
239
+ any(nt in self._processed_file_types for nt in netlist_types)
240
+ )
241
+
242
+ if should_generate_fb:
243
+ logger.info("Generating functional blocks (schematic/user guide/netlist was processed)")
244
+ try:
245
+ await self._generate_functional_blocks()
246
+ except Exception as e:
247
+ logger.exception(e)
248
+ logger.error(f"Error generating functional blocks: {self._context.run.id}")
249
+ else:
250
+ logger.info("Skipping functional blocks generation (no relevant files processed)")
251
+
252
+ # Generate cheat sheets for processed file types only
253
+ for subtype, generators in FileSubtypeGeneratorMap.items():
254
+ if subtype.name in self._processed_file_types:
255
+ logger.info(f"Generating cheat sheets for {subtype.name} (file was processed)")
256
+ await self._generate_for_subtype(subtype, generators)
257
+ else:
258
+ logger.info(f"Skipping cheat sheet generation for {subtype.name} (file not processed)")
@@ -0,0 +1,18 @@
1
+ from abc import abstractmethod
2
+ from typing import List, Dict
3
+
4
+ from google.genai.types import File as GeminiFile
5
+
6
+ from mfcli.models.file import File
7
+ from mfcli.pipeline.run_context import PipelineRunContext
8
+
9
+
10
+ class GeneratorBase:
11
+ def __init__(self, context: PipelineRunContext, db_file: File, uploads: List[GeminiFile]):
12
+ self._context = context
13
+ self._file = db_file
14
+ self._uploads: List[GeminiFile] = uploads
15
+
16
+ @abstractmethod
17
+ async def generate(self) -> Dict:
18
+ pass
File without changes
@@ -0,0 +1,156 @@
1
+ mcu_instructions_base = (
2
+ """
3
+ You are the MCU Cheat Sheet Generator agent. Your role is to analyze microcontroller
4
+ datasheets and user manuals to extract critical information needed for creating initial
5
+ firmware boot code, linker scripts, and basic "hello world" applications.
6
+ """
7
+ )
8
+
9
+ mcu_data_instruction = (
10
+ """
11
+ Extract the following information systematically:
12
+ MCU IDENTIFICATION:
13
+ - mcu_name: Full part number (e.g., "STM32F407VGT6", "ATMEGA328P", "MSPM0L1306")
14
+ - mcu_family: Family or series name
15
+ - architecture: CPU core architecture (ARM Cortex-M4, AVR, RISC-V, etc.)
16
+ - core_frequency_max: Maximum operating frequency
17
+ """
18
+ )
19
+
20
+ mcu_memory_map_instructions = (
21
+ """
22
+ Extract the following information systematically:
23
+ MEMORY MAP (CRITICAL for linker script!):
24
+ For each memory region, extract:
25
+ - name: FLASH, RAM, ROM, SRAM, etc.
26
+ - start_address: Starting address in hex (e.g., "0x08000000")
27
+ - size: Size with unit (e.g., "128KB", "0x20000")
28
+ - type: flash, ram, rom, peripheral, reserved
29
+ - description: Any important notes about this region
30
+
31
+ Look in: Memory organization, memory map tables, address space layout sections
32
+ """
33
+ )
34
+
35
+ mcu_clock_config_instructions = (
36
+ """
37
+ Extract the following information systematically:
38
+ CLOCK CONFIGURATION:
39
+ - clock_source: Available clock sources (HSI, HSE, PLL, Internal RC, etc.)
40
+ - frequency_range: Typical or maximum frequencies
41
+ - configuration_steps: How to set up the system clock
42
+ - registers: Key clock control registers (RCC, CCR, etc.)
43
+ - notes: Important clock-related information
44
+
45
+ Look in: Clock tree diagrams, RCC chapter, system clock sections
46
+ """
47
+ )
48
+
49
+ mcu_interrupt_vectors_instructions = (
50
+ """
51
+ Extract the following information systematically:
52
+ INTERRUPT VECTORS:
53
+ Extract the key interrupt vectors (at least the essential ones):
54
+ - vector_number: Position in vector table
55
+ - name: Interrupt name (Reset, NMI, HardFault, SysTick, etc.)
56
+ - address: Vector table address if specified
57
+ - priority: Default or configurable priority
58
+ - description: What triggers this interrupt
59
+
60
+ Look in: Interrupt vector table, exception handling, NVIC sections
61
+
62
+ Priority: Focus on:
63
+ - Reset vector (most important!)
64
+ - NMI, HardFault, SysTick (ARM Cortex)
65
+ - Common peripheral interrupts
66
+ """
67
+ )
68
+
69
+ mcu_startup_req_instructions = (
70
+ """
71
+ Extract the following information systematically:
72
+ STARTUP REQUIREMENTS:
73
+ - boot_mode: How boot mode is selected
74
+ - startup_code_location: Where startup code must be placed
75
+ - vector_table_offset: VTOR register info (ARM Cortex)
76
+ - stack_pointer_init: How to set up stack pointer
77
+ - minimum_init_sequence: Bare minimum steps to boot successfully
78
+ - watchdog_handling: Watchdog timer at startup
79
+
80
+ Look in: Boot sequence, startup procedure, reset behavior sections
81
+ """
82
+ )
83
+
84
+ mcu_peripherals_instructions = (
85
+ """
86
+ Extract the following information systematically:
87
+ ESSENTIAL PERIPHERALS:
88
+ For GPIO, UART, or other basic peripherals:
89
+ - name: Peripheral name
90
+ - base_address: Base address in memory map
91
+ - clock_enable: How to enable peripheral clock
92
+ - initialization_steps: Basic init procedure
93
+ - key_registers: Important registers
94
+
95
+ Focus on GPIO and UART for a basic hello world app
96
+ """
97
+ )
98
+
99
+ mcu_linker_script_instructions = (
100
+ """
101
+ Extract the following information systematically:
102
+ LINKER SCRIPT NOTES:
103
+ Provide guidance for creating a linker script:
104
+ - Memory region definitions (FLASH, RAM, etc.)
105
+ - Entry point specification
106
+ - Section placement (.text, .data, .bss)
107
+ - Stack and heap allocation
108
+ - Vector table placement
109
+ - Any special considerations
110
+ """
111
+ )
112
+
113
+ mcu_hello_world_instructions = (
114
+ """
115
+ Extract the following information systematically:
116
+ HELLO WORLD STEPS:
117
+ List the steps to create a minimal working program:
118
+ 1. Set up linker script
119
+ 2. Create startup code
120
+ 3. Initialize system clock
121
+ 4. Configure GPIO/UART
122
+ 5. Write main() function
123
+ 6. Build and flash
124
+
125
+ Be specific about what needs to be done at each step
126
+ """
127
+ )
128
+
129
+ mcu_debugging_interface_instructions = (
130
+ """
131
+ Extract the following information systematically:
132
+ DEBUGGING/PROGRAMMING INTERFACE:
133
+ - debugging_interface: JTAG, SWD, ICSP, etc.
134
+ - programming_interface: How to program the device
135
+ """
136
+ )
137
+
138
+ mcu_programming_interface_instructions = (
139
+ """
140
+ Extract the following information systematically:
141
+ DEBUGGING/PROGRAMMING INTERFACE:
142
+ - debugging_interface: JTAG, SWD, ICSP, etc.
143
+ - programming_interface: How to program the device
144
+ """
145
+ )
146
+
147
+ mcu_datasheet_references = (
148
+ """
149
+ Extract the following information systematically:
150
+ DATASHEET REFERENCES:
151
+ List specific sections/pages that were most useful in this document:
152
+ - "Memory Map - Section 2.2, Page 45"
153
+ - "Clock Tree - Figure 15, Page 78"
154
+ etc.
155
+ """
156
+ )
@@ -0,0 +1,84 @@
1
+ import asyncio
2
+ from typing import Dict, Type, List
3
+
4
+ from google.genai.types import File as GeminiFile
5
+ from pydantic import BaseModel
6
+
7
+ from mfcli.models.file import File
8
+ from mfcli.models.mcu import (
9
+ MCUData,
10
+ MemoryMap,
11
+ ClockConfig,
12
+ InterruptVectors,
13
+ StartupRequirementsModel,
14
+ EssentialPeripherals,
15
+ MCULinkerScriptInfo,
16
+ MCUHelloWorldSteps,
17
+ MCUDebuggingInterface,
18
+ MCUProgrammingInterface,
19
+ MCUDatasheetReferences
20
+ )
21
+ from mfcli.pipeline.analysis.generators.generator_base import GeneratorBase
22
+ from mfcli.pipeline.analysis.generators.mcu.instructions import (
23
+ mcu_data_instruction,
24
+ mcu_instructions_base,
25
+ mcu_memory_map_instructions,
26
+ mcu_clock_config_instructions,
27
+ mcu_interrupt_vectors_instructions,
28
+ mcu_startup_req_instructions,
29
+ mcu_peripherals_instructions,
30
+ mcu_linker_script_instructions,
31
+ mcu_hello_world_instructions,
32
+ mcu_debugging_interface_instructions,
33
+ mcu_programming_interface_instructions,
34
+ mcu_datasheet_references
35
+ )
36
+ from mfcli.pipeline.run_context import PipelineRunContext
37
+ from mfcli.utils.logger import get_logger
38
+
39
+ logger = get_logger(__name__)
40
+
41
+ MCUModelInstructionsMap: Dict[Type[BaseModel], str] = {
42
+ MCUData: mcu_data_instruction,
43
+ MemoryMap: mcu_memory_map_instructions,
44
+ ClockConfig: mcu_clock_config_instructions,
45
+ InterruptVectors: mcu_interrupt_vectors_instructions,
46
+ StartupRequirementsModel: mcu_startup_req_instructions,
47
+ EssentialPeripherals: mcu_peripherals_instructions,
48
+ MCULinkerScriptInfo: mcu_linker_script_instructions,
49
+ MCUHelloWorldSteps: mcu_hello_world_instructions,
50
+ MCUDebuggingInterface: mcu_debugging_interface_instructions,
51
+ MCUProgrammingInterface: mcu_programming_interface_instructions,
52
+ MCUDatasheetReferences: mcu_datasheet_references
53
+ }
54
+
55
+
56
+ class MCUCheatSheetGenerator(GeneratorBase):
57
+ def __init__(self, context: PipelineRunContext, db_file: File, uploads: List[GeminiFile]):
58
+ super().__init__(context, db_file, uploads)
59
+
60
+ async def _extract_mcu_data(self, model: Type[BaseModel], instructions: str) -> BaseModel:
61
+ return await self._context.gemini.generate(
62
+ prompt=instructions,
63
+ instructions=mcu_instructions_base,
64
+ response_model=model,
65
+ files=self._uploads
66
+ )
67
+
68
+ @staticmethod
69
+ def _parse_mcu_generated_data(results: list[BaseModel]) -> Dict:
70
+ response_json = {}
71
+ for result in results:
72
+ response_json.update(result.model_dump())
73
+ return {
74
+ "mcu_cheat_sheet": response_json
75
+ }
76
+
77
+ async def generate(self) -> Dict:
78
+ logger.debug(f"Generating MCU data cheat sheet")
79
+ tasks = []
80
+ for model, instructions in MCUModelInstructionsMap.items():
81
+ tasks.append(self._extract_mcu_data(model, instructions))
82
+ results: list[BaseModel] = await asyncio.gather(*tasks)
83
+ logger.debug(f"MCU data cheat sheet generated")
84
+ return self._parse_mcu_generated_data(results)
@@ -0,0 +1 @@
1
+ """MCU Errata Cheat Sheet Generator for firmware-relevant silicon errata."""