mfcli 0.2.1__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 (138) 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 +200 -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 +144 -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 +470 -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/cline_rules.py +256 -0
  113. mfcli/utils/config.py +33 -0
  114. mfcli/utils/configurator.py +324 -0
  115. mfcli/utils/data_cleaner.py +114 -0
  116. mfcli/utils/datasheet_vectorizer.py +283 -0
  117. mfcli/utils/directory_manager.py +116 -0
  118. mfcli/utils/file_upload.py +298 -0
  119. mfcli/utils/files.py +16 -0
  120. mfcli/utils/http_requests.py +54 -0
  121. mfcli/utils/kb_lister.py +89 -0
  122. mfcli/utils/kb_remover.py +173 -0
  123. mfcli/utils/logger.py +28 -0
  124. mfcli/utils/mcp_configurator.py +394 -0
  125. mfcli/utils/migrations.py +18 -0
  126. mfcli/utils/orm.py +43 -0
  127. mfcli/utils/pdf_splitter.py +63 -0
  128. mfcli/utils/pre_uninstall.py +167 -0
  129. mfcli/utils/query_service.py +22 -0
  130. mfcli/utils/system_check.py +306 -0
  131. mfcli/utils/tools.py +98 -0
  132. mfcli/utils/vectorizer.py +28 -0
  133. mfcli-0.2.1.dist-info/METADATA +956 -0
  134. mfcli-0.2.1.dist-info/RECORD +138 -0
  135. mfcli-0.2.1.dist-info/WHEEL +5 -0
  136. mfcli-0.2.1.dist-info/entry_points.txt +4 -0
  137. mfcli-0.2.1.dist-info/licenses/LICENSE +21 -0
  138. mfcli-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,93 @@
1
+ import asyncio
2
+ from asyncio import Semaphore
3
+ from typing import Type, List, Dict
4
+
5
+ from google.genai.types import File
6
+ from pydantic import BaseModel
7
+
8
+ from mfcli.client.gemini import Gemini
9
+ from mfcli.crud.functional_blocks import create_functional_blocks
10
+ from mfcli.models.functional_blocks import FunctionalBlocks, FunctionalBlockMeta
11
+ from mfcli.models.pipeline_run import PipelineRun
12
+ from mfcli.pipeline.analysis.generators.functional_blocks.instructions import (
13
+ fb_extractor_system_instructions,
14
+ fb_extractor_identify_instructions,
15
+ fb_extractor_fb_meta_instructions
16
+ )
17
+ from mfcli.pipeline.analysis.generators.functional_blocks.validator import validate_functional_blocks_against_netlist
18
+ from mfcli.utils.logger import get_logger
19
+ from mfcli.utils.orm import Session
20
+
21
+ logger = get_logger(__name__)
22
+
23
+ FBExtractedData = FunctionalBlocks | FunctionalBlockMeta
24
+
25
+
26
+ class FBCheatSheetGenerator:
27
+ def __init__(self, gemini: Gemini, files: List[File]):
28
+ self._gemini = gemini
29
+ self._sem = Semaphore(5)
30
+ self._files = files
31
+
32
+ async def _extract_fb_data(self, model: Type[BaseModel], instructions: str) -> FBExtractedData:
33
+ return await self._gemini.generate(
34
+ prompt=instructions,
35
+ instructions=fb_extractor_system_instructions,
36
+ response_model=model,
37
+ files=self._files
38
+ )
39
+
40
+ async def _retrieve_fb_list(self) -> list[str]:
41
+ blocks = await self._extract_fb_data(FunctionalBlocks, fb_extractor_identify_instructions)
42
+ return blocks.functional_blocks
43
+
44
+ async def _generate_fb_data(self, name: str) -> FunctionalBlockMeta:
45
+ block_instructions = fb_extractor_fb_meta_instructions.format(name)
46
+ return await self._extract_fb_data(FunctionalBlockMeta, block_instructions)
47
+
48
+ async def _generate_single_block(self, name: str) -> FunctionalBlockMeta:
49
+ async with self._sem:
50
+ return await self._generate_fb_data(name)
51
+
52
+ async def generate(self) -> List[FunctionalBlockMeta]:
53
+ blocks = await self._retrieve_fb_list()
54
+ tasks = []
55
+ for block in blocks:
56
+ tasks.append(self._generate_single_block(block))
57
+ responses = await asyncio.gather(*tasks)
58
+ block_data = []
59
+ for response in responses:
60
+ if isinstance(response, Exception):
61
+ continue
62
+ block_data.append(response)
63
+ return block_data
64
+
65
+
66
+ def _get_functional_blocks_dict(blocks: List[FunctionalBlockMeta]) -> Dict:
67
+ return {
68
+ "functional_blocks": [block.model_dump() for block in blocks]
69
+ }
70
+
71
+
72
+ async def extract_functional_blocks_from_file(
73
+ db: Session,
74
+ pipeline_run: PipelineRun,
75
+ gemini: Gemini,
76
+ schematic_files: List[File] | None = None,
77
+ user_guide_files: List[File] | None = None,
78
+ netlist_file: File | None = None
79
+ ) -> Dict:
80
+ if not schematic_files and not user_guide_files and not netlist_file:
81
+ error = "At least one of these file types must be specified: [schematic, user_guide, netlist]"
82
+ raise ValueError(error)
83
+ files = []
84
+ if schematic_files:
85
+ files += schematic_files
86
+ if user_guide_files:
87
+ files += user_guide_files
88
+ if netlist_file:
89
+ files.append(netlist_file)
90
+ blocks = await FBCheatSheetGenerator(gemini, files).generate()
91
+ validate_functional_blocks_against_netlist(db, pipeline_run.id, blocks)
92
+ create_functional_blocks(db, pipeline_run, blocks)
93
+ return _get_functional_blocks_dict(blocks)
@@ -0,0 +1,34 @@
1
+ fb_extractor_system_instructions = (
2
+ """
3
+ You are the Functional Block Generator agent. Your role is to analyze PDF schematics
4
+ and break them down into functional blocks that describe how different parts of the
5
+ circuit work together. This information helps firmware developers understand the
6
+ hardware and write code to support each function.
7
+
8
+ A functional block is a group of components that work together for a specific
9
+ purpose. Common examples include:
10
+ - Power Supply (voltage regulators, filtering capacitors)
11
+ - Microcontroller/MCU (main processor and its critical connections)
12
+ - Communication Interfaces (UART, SPI, I2C, USB, CAN, etc.)
13
+ - Sensor Interfaces (ADC inputs, sensor power, signal conditioning)
14
+ - Output Drivers (LED drivers, motor controllers, relay drivers)
15
+ - Debug/Programming Interface (JTAG, SWD, bootloader pins)
16
+ - External Connectors (headers, USB ports, JST connectors)
17
+ - Clock/Crystal circuits
18
+ - Reset circuits
19
+ - Protection circuits
20
+ """
21
+ )
22
+
23
+ fb_extractor_identify_instructions = "Analyze the schematic to identify functional blocks"
24
+
25
+ fb_extractor_fb_meta_instructions = (
26
+ """
27
+ Extract the following information for the {} functional block:
28
+
29
+ BLOCK IDENTIFICATION:
30
+ - block_name: Clear, descriptive name (e.g., "3.3V Power Supply", "USB Interface")
31
+ - description: What this block does and why it's important
32
+ - components: List all reference designators (R1, C1, U1, etc.) in this block
33
+ """
34
+ )
@@ -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
+ )