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,59 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ from pydantic import BaseModel
5
+ from sqlalchemy import Column, TIMESTAMP, func
6
+ from sqlmodel import Field, Relationship, SQLModel
7
+
8
+ from mfcli.models.bom import BOM
9
+ from mfcli.models.pipeline_run import PipelineRun
10
+
11
+
12
+ class Pin(BaseModel):
13
+ """Represents a single pin connection."""
14
+ pin: str = Field(..., description="Pin number or name")
15
+ net: str = Field(..., description="Net name this pin connects to")
16
+
17
+
18
+ class Component(BaseModel):
19
+ """Represents a component with its pins and connections."""
20
+ ref_des: str = Field(..., description="Reference designator (C1, R5, U2, etc.)")
21
+ part_number: str = Field(..., description="Part number or footprint")
22
+ pins: List[Pin] = Field(default_factory=list, description="List of pin connections")
23
+
24
+
25
+ class NetlistSchema(BaseModel):
26
+ """Top-level schema for parsed netlist."""
27
+ components: List[Component] = Field(default_factory=list, description="List of components")
28
+
29
+
30
+ class Netlist(SQLModel, table=True):
31
+ __tablename__ = "netlists"
32
+ id: Optional[int] = Field(default=None, primary_key=True)
33
+ pipeline_run_id: int = Field(foreign_key='pipeline_runs.id', index=True, nullable=False, ondelete="CASCADE")
34
+ pipeline_run: Optional["PipelineRun"] = Relationship()
35
+ components: List["NetlistComponent"] = Relationship(back_populates='netlist', cascade_delete=True)
36
+ created_at: datetime = Field(
37
+ sa_column=Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False)
38
+ )
39
+
40
+
41
+ class NetlistComponent(SQLModel, table=True):
42
+ __tablename__ = "netlist_components"
43
+ id: Optional[int] = Field(default=None, primary_key=True)
44
+ netlist_id: int = Field(foreign_key='netlists.id', index=True, nullable=False, ondelete="CASCADE")
45
+ netlist: Optional["Netlist"] = Relationship(back_populates='components')
46
+ pins: List["NetlistPin"] = Relationship(back_populates='netlist_component', cascade_delete=True)
47
+ ref_des: str = Field(..., max_length=100, nullable=False)
48
+ part_number: str = Field(max_length=255, nullable=False, index=True)
49
+ bom_entry_id: Optional[int] = Field(foreign_key='bom.id', index=True, ondelete='CASCADE')
50
+ bom_entry: Optional["BOM"] = Relationship()
51
+
52
+
53
+ class NetlistPin(SQLModel, table=True):
54
+ __tablename__ = "netlist_pins"
55
+ id: Optional[int] = Field(default=None, primary_key=True)
56
+ netlist_component_id: int = Field(foreign_key='netlist_components.id', index=True, nullable=False, ondelete="CASCADE")
57
+ netlist_component: NetlistComponent = Relationship()
58
+ pin: str = Field(..., max_length=100, nullable=False)
59
+ net: str = Field(..., max_length=100, nullable=False)
@@ -0,0 +1,25 @@
1
+ from typing import Optional
2
+
3
+ from sqlalchemy import Column, Integer, ForeignKey
4
+ from sqlmodel import SQLModel, Field, Relationship
5
+
6
+
7
+ class PDFPart(SQLModel, table=True):
8
+ __tablename__ = "pdf_parts"
9
+
10
+ id: Optional[int] = Field(default=None, primary_key=True)
11
+ file_id: int = Field(
12
+ sa_column=Column(
13
+ Integer,
14
+ ForeignKey("files.id", ondelete="CASCADE"),
15
+ nullable=False,
16
+ index=True
17
+ )
18
+ )
19
+ file: Optional["File"] = Relationship(back_populates='pdf_parts')
20
+ path: str = Field(max_length=600, nullable=False)
21
+ gemini_file_id: str = Field(max_length=40, nullable=True)
22
+ start_page: int = Field(nullable=True)
23
+ end_page: int = Field(nullable=True)
24
+ title: str = Field(nullable=True)
25
+ section_no: int = Field(nullable=True)
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+ from typing import Optional
3
+
4
+ from sqlalchemy import Integer, func, Text, Column, DateTime, ForeignKey
5
+ from sqlmodel import SQLModel, Field, Relationship
6
+
7
+ from mfcli.constants.pipeline_run_status import PIPELINE_STATUS_IN_PROGRESS
8
+ from mfcli.models.project import Project
9
+
10
+
11
+ class PipelineRun(SQLModel, table=True):
12
+ __tablename__ = "pipeline_runs"
13
+
14
+ id: Optional[int] = Field(default=None, primary_key=True)
15
+ project_id: int = Field(
16
+ sa_column=Column(
17
+ Integer,
18
+ ForeignKey("projects.id", ondelete="CASCADE"),
19
+ nullable=True,
20
+ index=True
21
+ )
22
+ )
23
+ project: Optional["Project"] = Relationship(back_populates='runs')
24
+ status: int = Field(
25
+ sa_column=Column(Integer, nullable=False, server_default=str(PIPELINE_STATUS_IN_PROGRESS))
26
+ )
27
+ errors: Optional[str] = Field(default=None, sa_column=Column(Text, nullable=True))
28
+
29
+ created_at: datetime = Field(
30
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
31
+ )
32
+ updated_at: datetime = Field(
33
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
34
+ )
@@ -0,0 +1,27 @@
1
+ from datetime import datetime
2
+ from typing import Optional, List
3
+
4
+ from sqlalchemy import Column, DateTime, func
5
+ from sqlmodel import SQLModel, Field, Relationship
6
+
7
+ project_name_regex = r"^[A-Za-z0-9_-]+$"
8
+
9
+
10
+ class Project(SQLModel, table=True):
11
+ __tablename__ = "projects"
12
+
13
+ id: Optional[int] = Field(default=None, primary_key=True)
14
+ runs: List["PipelineRun"] = Relationship(back_populates='project', cascade_delete=True)
15
+ name: str = Field(
16
+ nullable=False,
17
+ unique=True,
18
+ index=True,
19
+ min_length=3,
20
+ max_length=45,
21
+ regex=project_name_regex
22
+ )
23
+ index_id: str = Field(index=True, unique=True)
24
+ repo_dir: str = Field(index=True, unique=True)
25
+ created_at: datetime = Field(
26
+ sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
27
+ )
@@ -0,0 +1,15 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class VectorizeCheatSheetsConfig(BaseModel):
5
+ vectorize_errata: bool = Field(default=False)
6
+ vectorize_mcu: bool = Field(default=False)
7
+ vectorize_debug_setup: bool = Field(default=False)
8
+ vectorize_functional_blocks: bool = Field(default=False)
9
+
10
+
11
+ class ProjectConfig(BaseModel):
12
+ name: str
13
+ vectorize_hw_files: bool = Field(default=True)
14
+ vectorize_datasheets: bool = Field(default=True)
15
+ vectorize_cheat_sheets_config: VectorizeCheatSheetsConfig = Field(default_factory=VectorizeCheatSheetsConfig)
File without changes
File without changes
@@ -0,0 +1,28 @@
1
+ from mfcli.models.bom import BOM
2
+ from mfcli.models.file import File
3
+ from mfcli.models.netlist import NetlistComponent, Netlist
4
+ from mfcli.utils.logger import get_logger
5
+ from mfcli.utils.orm import Session
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ def map_netlist_to_bom_entries(db: Session, pipeline_run_id: int):
11
+ logger.debug(f"Mapping netlist to BOM entries for pipeline: {pipeline_run_id}")
12
+ components: list[NetlistComponent] = (
13
+ db.query(NetlistComponent)
14
+ .join(NetlistComponent.netlist)
15
+ .filter(Netlist.pipeline_run_id == pipeline_run_id)
16
+ .all()
17
+ )
18
+ results = (
19
+ db.query(BOM.id, BOM.value)
20
+ .join(BOM.file)
21
+ .filter(File.pipeline_run_id == pipeline_run_id)
22
+ .all()
23
+ )
24
+ bom_value_id_map = {result[1]: result[0] for result in results}
25
+ for component in components:
26
+ if bom_value_id_map.get(component.part_number):
27
+ component.bom_entry_id = bom_value_id_map[component.part_number]
28
+ logger.debug(f"All netlist entries mapped: {pipeline_run_id}")
File without changes
File without changes
@@ -0,0 +1,74 @@
1
+ import asyncio
2
+ from asyncio import Semaphore
3
+ from typing import Dict, Type, List
4
+
5
+ from google.genai.types import File
6
+ from pydantic import BaseModel, Field
7
+
8
+ from mfcli.client.gemini import Gemini
9
+ from mfcli.models.bom import BOMSchema
10
+ from mfcli.utils.logger import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ system_instructions = (
15
+ """
16
+ You are the BOM Generator agent. Your role is to extract components from PDF schematics
17
+ and generate a Bill of Materials (BOM) in CSV format.
18
+ """
19
+ )
20
+
21
+ identify_bom_entries_instructions = (
22
+ """
23
+ You will return a list of names for Bill of Materials (BOM) entries from this schematic file.
24
+ """
25
+ )
26
+
27
+ generate_bom_entry_instructions = (
28
+ """
29
+ Analyze the schematic and extract BOM entry data for component: {}:
30
+ - Reference designators (R1, C1, U1, etc.)
31
+ - Component values (10kΩ, 100nF, etc.)
32
+ - Part numbers and manufacturers (if visible)
33
+ - Footprints/packages (if visible)
34
+ - Any other available information
35
+ """
36
+ )
37
+
38
+
39
+ class BOMEntryNames(BaseModel):
40
+ names: List[str] = Field(default_factory=list, description="BOM entry names")
41
+
42
+
43
+ class BOMGenerator:
44
+ def __init__(self, gemini: Gemini):
45
+ self._gemini = gemini
46
+ self._sem = Semaphore(5)
47
+
48
+ async def _generate(self, file: File, model: Type[BaseModel], instructions: str) -> BaseModel:
49
+ async with self._sem:
50
+ return await self._gemini.generate(
51
+ prompt=instructions,
52
+ instructions=system_instructions,
53
+ response_model=model,
54
+ files=[file]
55
+ )
56
+
57
+ async def _get_bom_entry_names(self, file: File) -> List[str]:
58
+ response = await self._generate(file, BOMEntryNames, identify_bom_entries_instructions)
59
+ return response.names
60
+
61
+ async def generate(self, file: File) -> List[Dict]:
62
+ bom_entry_names = await self._get_bom_entry_names(file)
63
+ tasks = []
64
+ for name in bom_entry_names:
65
+ bom_entry_instructions = generate_bom_entry_instructions.format(name)
66
+ tasks.append(self._generate(file, BOMSchema, bom_entry_instructions))
67
+ results: List[BOMSchema | Exception] = await asyncio.gather(*tasks, return_exceptions=True)
68
+ response_data = []
69
+ for result in results:
70
+ if isinstance(result, Exception):
71
+ logger.warn(f"There was an error generating BOM entry data: {result}")
72
+ continue
73
+ response_data.append(result.model_dump())
74
+ return response_data
@@ -0,0 +1,71 @@
1
+ import asyncio
2
+ from asyncio import Semaphore
3
+ from typing import Dict, Type, List
4
+
5
+ from google.genai.types import File as GeminiFile
6
+ from pydantic import BaseModel
7
+
8
+ from mfcli.models.debug_setup import (
9
+ DSTargetMCU,
10
+ DebugInterface,
11
+ DebugTool,
12
+ DriverInfo,
13
+ VSCodeLaunchConfig,
14
+ DSAdditionalTools,
15
+ DSTroubleshootingGuide,
16
+ DSQuickStartGuide
17
+ )
18
+ from mfcli.models.file import File
19
+ from mfcli.pipeline.analysis.generators.debug_setup.instructions import (
20
+ ds_mcu_instructions,
21
+ ds_debug_int_instructions,
22
+ ds_recommended_tool_instructions,
23
+ ds_drivers_instructions,
24
+ ds_vscode_launch_instructions,
25
+ ds_add_tools_instructions,
26
+ ds_troubleshooting_instructions,
27
+ ds_quick_start_guide_instructions,
28
+ ds_system_instructions
29
+ )
30
+ from mfcli.pipeline.analysis.generators.generator_base import GeneratorBase
31
+ from mfcli.pipeline.run_context import PipelineRunContext
32
+ from mfcli.utils.logger import get_logger
33
+
34
+ logger = get_logger(__name__)
35
+
36
+ DSModelInstructionsMap = {
37
+ DSTargetMCU: ds_mcu_instructions,
38
+ DebugInterface: ds_debug_int_instructions,
39
+ DebugTool: ds_recommended_tool_instructions,
40
+ DriverInfo: ds_drivers_instructions,
41
+ VSCodeLaunchConfig: ds_vscode_launch_instructions,
42
+ DSAdditionalTools: ds_add_tools_instructions,
43
+ DSTroubleshootingGuide: ds_troubleshooting_instructions,
44
+ DSQuickStartGuide: ds_quick_start_guide_instructions
45
+ }
46
+
47
+
48
+ class DSCheatSheetGenerator(GeneratorBase):
49
+ def __init__(self, context: PipelineRunContext, db_file: File, uploads: List[GeminiFile]):
50
+ super().__init__(context, db_file, uploads)
51
+ self._sem = Semaphore(5)
52
+
53
+ async def _extract_ds_data(self, model: Type[BaseModel], instructions: str) -> BaseModel:
54
+ return await self._context.gemini.generate(
55
+ prompt=instructions,
56
+ instructions=ds_system_instructions,
57
+ response_model=model,
58
+ files=self._uploads
59
+ )
60
+
61
+ async def generate(self) -> Dict:
62
+ tasks = []
63
+ for model, instructions in DSModelInstructionsMap.items():
64
+ tasks.append(self._extract_ds_data(model, instructions))
65
+ results: list[BaseModel] = await asyncio.gather(*tasks)
66
+ response_dict = {}
67
+ for result in results:
68
+ response_dict.update(result.model_dump())
69
+ return {
70
+ "debug_setup": response_dict
71
+ }
@@ -0,0 +1,150 @@
1
+ ds_system_instructions = (
2
+ """
3
+ You are the Debug Setup Cheat Sheet Generator agent. Your role is to analyze schematics
4
+ to extract debug interface information and provide comprehensive setup guidance including:
5
+ - Recommended JTAG/SWD tools based on the MCU and interface
6
+ - Driver installation for Windows and Linux
7
+ - VS Code launch.json configuration
8
+ - GDB server setup
9
+ - Quick start guide
10
+
11
+
12
+ Analyze the schematic to identify debug interface:
13
+ Look for:
14
+ - Debug connector (J-TAG, SWD, ICSP headers)
15
+ - Pin labels (SWDIO, SWCLK, TMS, TCK, TDI, TDO, etc.)
16
+ - Target MCU identification
17
+ - Voltage levels
18
+ - Connector type and pinout
19
+
20
+ Determine recommended debug tool based on MCU:
21
+
22
+ Common mappings:
23
+ - STM32 → ST-Link V3 (official), J-Link (professional)
24
+ - NXP LPC/Kinetis → LPC-Link2, J-Link
25
+ - Nordic nRF → J-Link, nRF DK
26
+ - TI MSP430/MSPM0 → MSP-FET, XDS110
27
+ - Microchip PIC/AVR → PICkit, ICD, MPLAB Snap
28
+ - ARM Cortex-M (generic) → CMSIS-DAP, J-Link
29
+ - ESP32 → ESP-Prog, J-Link
30
+ - RISC-V → J-Link, OpenOCD compatible probes
31
+ """
32
+ )
33
+
34
+ ds_mcu_instructions = (
35
+ """
36
+ Extract information systematically:
37
+
38
+ TARGET MCU:
39
+ - Identify the MCU from schematic
40
+ - Note the architecture (ARM Cortex-M4, AVR, etc.)
41
+ """
42
+ )
43
+
44
+ ds_debug_int_instructions = (
45
+ """
46
+ Extract information systematically:
47
+
48
+ DEBUG INTERFACE:
49
+ - interface_type: JTAG, SWD, ICSP, PDI, UPDI, etc.
50
+ - pin_count: Number of pins on debug connector
51
+ - pin_mapping: Map pin numbers to signals
52
+ Example: {"1": "VCC", "2": "SWDIO", "3": "GND", "4": "SWCLK"}
53
+ - connector_type: Physical connector (10-pin Cortex, 20-pin JTAG, etc.)
54
+ - voltage_level: Target voltage (typically 3.3V or 5V)
55
+ """
56
+ )
57
+
58
+ ds_recommended_tool_instructions = (
59
+ """
60
+ Extract information systematically:
61
+
62
+ RECOMMENDED TOOL:
63
+ - name: Primary tool recommendation (e.g., "J-Link", "ST-Link V3")
64
+ - model: Specific model if applicable
65
+ - purchase_links: Where to buy (Digikey, Mouser, official sites)
66
+ - compatibility_notes: Why this tool is recommended
67
+ - alternative_tools: Other compatible options
68
+ """
69
+ )
70
+
71
+ ds_drivers_instructions = (
72
+ """
73
+ Extract information systematically:
74
+
75
+ 1. DRIVERS (Windows):
76
+ - download_url: Where to download drivers
77
+ - installation_steps: Step-by-step installation for Windows
78
+ Example:
79
+ 1. Download from [URL]
80
+ 2. Run installer
81
+ 3. Connect debugger
82
+ 4. Verify in Device Manager
83
+
84
+ 2. DRIVERS (Linux):
85
+ - package_name: Package to install (if available)
86
+ - installation_steps: Commands to install
87
+ Example for J-Link:
88
+ 1. Download from segger.com
89
+ 2. sudo dpkg -i JLink_*.deb
90
+ 3. Add udev rules
91
+ 4. sudo usermod -a -G plugdev $USER
92
+
93
+ 3. GDB SERVER:
94
+ - server_name: GDB server to use (JLinkGDBServer, OpenOCD, pyOCD, etc.)
95
+ - command_line: How to start server
96
+ Example: "JLinkGDBServer -device STM32F407VG -if SWD -speed 4000"
97
+ - config_file: Configuration if needed (OpenOCD .cfg)
98
+ - port: GDB port (default 3333)
99
+ """
100
+ )
101
+
102
+ ds_vscode_launch_instructions = (
103
+ """
104
+ Extract information systematically:
105
+
106
+ VS CODE LAUNCH.JSON:
107
+ - configuration_name: Name for the config (e.g., "Debug with J-Link")
108
+ - configuration_type: cortex-debug, cppdbg, etc.
109
+ - launch_json: Complete configuration object for .vscode/launch.json
110
+ - extensions_required: VS Code extensions needed
111
+ Common: "marus25.cortex-debug", "ms-vscode.cpptools"
112
+ - setup_notes: Any additional VS Code setup
113
+ """
114
+ )
115
+
116
+ ds_add_tools_instructions = (
117
+ """
118
+ Extract information systematically:
119
+
120
+ ADDITIONAL TOOLS:
121
+ - List helpful tools: OpenOCD, pyOCD, gdb-multiarch, etc.
122
+ """
123
+ )
124
+
125
+ ds_troubleshooting_instructions = (
126
+ """
127
+ Extract information systematically:
128
+
129
+ TROUBLESHOOTING:
130
+ - Common issues and solutions
131
+ - Connection problems
132
+ - Driver issues
133
+ - Permission issues (Linux)
134
+ """
135
+ )
136
+
137
+ ds_quick_start_guide_instructions = (
138
+ """
139
+ Extract information systematically:
140
+
141
+ QUICK START GUIDE:
142
+ Step-by-step instructions to get debugging working:
143
+ 1. Install drivers
144
+ 2. Connect debugger to target
145
+ 3. Install VS Code extensions
146
+ 4. Create/copy launch.json
147
+ 5. Build project
148
+ 6. Start debugging (F5)
149
+ """
150
+ )
@@ -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
+ )