@memberjunction/db-auto-doc 5.14.0 → 5.15.0

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 (58) hide show
  1. package/README.md +169 -29
  2. package/bin/run.js +1 -1
  3. package/dist/commands/analyze.d.ts +3 -0
  4. package/dist/commands/analyze.d.ts.map +1 -1
  5. package/dist/commands/analyze.js +33 -3
  6. package/dist/commands/analyze.js.map +1 -1
  7. package/dist/commands/prune.d.ts +17 -0
  8. package/dist/commands/prune.d.ts.map +1 -0
  9. package/dist/commands/prune.js +153 -0
  10. package/dist/commands/prune.js.map +1 -0
  11. package/dist/core/AnalysisEngine.d.ts +44 -0
  12. package/dist/core/AnalysisEngine.d.ts.map +1 -1
  13. package/dist/core/AnalysisEngine.js +427 -1
  14. package/dist/core/AnalysisEngine.js.map +1 -1
  15. package/dist/core/AnalysisOrchestrator.d.ts.map +1 -1
  16. package/dist/core/AnalysisOrchestrator.js +33 -10
  17. package/dist/core/AnalysisOrchestrator.js.map +1 -1
  18. package/dist/discovery/FKDetector.d.ts +6 -0
  19. package/dist/discovery/FKDetector.d.ts.map +1 -1
  20. package/dist/discovery/FKDetector.js +101 -4
  21. package/dist/discovery/FKDetector.js.map +1 -1
  22. package/dist/discovery/PKDetector.d.ts +7 -0
  23. package/dist/discovery/PKDetector.d.ts.map +1 -1
  24. package/dist/discovery/PKDetector.js +121 -6
  25. package/dist/discovery/PKDetector.js.map +1 -1
  26. package/dist/drivers/MySQLDriver.d.ts.map +1 -1
  27. package/dist/drivers/MySQLDriver.js +2 -0
  28. package/dist/drivers/MySQLDriver.js.map +1 -1
  29. package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -1
  30. package/dist/drivers/PostgreSQLDriver.js +2 -0
  31. package/dist/drivers/PostgreSQLDriver.js.map +1 -1
  32. package/dist/drivers/SQLServerDriver.d.ts.map +1 -1
  33. package/dist/drivers/SQLServerDriver.js +2 -0
  34. package/dist/drivers/SQLServerDriver.js.map +1 -1
  35. package/dist/prompts/PromptEngine.d.ts +19 -0
  36. package/dist/prompts/PromptEngine.d.ts.map +1 -1
  37. package/dist/prompts/PromptEngine.js +91 -7
  38. package/dist/prompts/PromptEngine.js.map +1 -1
  39. package/dist/types/analysis.d.ts +10 -0
  40. package/dist/types/analysis.d.ts.map +1 -1
  41. package/dist/types/config.d.ts +47 -0
  42. package/dist/types/config.d.ts.map +1 -1
  43. package/dist/types/config.js.map +1 -1
  44. package/dist/types/prompts.d.ts +26 -0
  45. package/dist/types/prompts.d.ts.map +1 -1
  46. package/dist/utils/config-loader.js +2 -2
  47. package/dist/utils/config-loader.js.map +1 -1
  48. package/dist/utils/ensureArray.d.ts +13 -0
  49. package/dist/utils/ensureArray.d.ts.map +1 -0
  50. package/dist/utils/ensureArray.js +39 -0
  51. package/dist/utils/ensureArray.js.map +1 -0
  52. package/package.json +5 -5
  53. package/prompts/fk-evaluation.md +94 -0
  54. package/prompts/fk-pruning-holistic.md +57 -0
  55. package/prompts/fk-pruning-table.md +51 -0
  56. package/prompts/pk-pruning-holistic.md +26 -0
  57. package/prompts/pk-pruning-table.md +35 -0
  58. package/prompts/table-analysis.md +28 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/db-auto-doc",
3
3
  "type": "module",
4
- "version": "5.14.0",
4
+ "version": "5.15.0",
5
5
  "description": "AI-powered database documentation generator for SQL Server, MySQL, and PostgreSQL. Analyzes your database structure, uses AI to generate comprehensive descriptions, and saves them as metadata. Works standalone - no MemberJunction runtime required.",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -72,10 +72,10 @@
72
72
  },
73
73
  "dependencies": {
74
74
  "@inquirer/prompts": "^8.2.0",
75
- "@memberjunction/ai": "5.14.0",
76
- "@memberjunction/server-bootstrap": "5.14.0",
77
- "@memberjunction/core": "5.14.0",
78
- "@memberjunction/global": "5.14.0",
75
+ "@memberjunction/ai": "5.15.0",
76
+ "@memberjunction/server-bootstrap": "5.15.0",
77
+ "@memberjunction/core": "5.15.0",
78
+ "@memberjunction/global": "5.15.0",
79
79
  "@oclif/core": "^3.27.0",
80
80
  "@oclif/plugin-help": "^6.2.37",
81
81
  "chalk": "^5.6.2",
@@ -0,0 +1,94 @@
1
+ You are a database expert evaluating foreign key candidates that were identified by statistical analysis. Your job is to confirm or reject each candidate based on semantic plausibility, directionality, and database design principles.
2
+
3
+ ## Database Context
4
+ {% if seedContext %}
5
+ {% if seedContext.overallPurpose %}- **Purpose**: {{ seedContext.overallPurpose }}{% endif %}
6
+ {% if seedContext.businessDomains %}- **Business Domains**: {{ seedContext.businessDomains | join(', ') }}{% endif %}
7
+ {% if seedContext.industryContext %}- **Industry**: {{ seedContext.industryContext }}{% endif %}
8
+ {% endif %}
9
+
10
+ ## All Tables
11
+ {% for tbl in allTables %}
12
+ - **{{ tbl.schema }}.{{ tbl.name }}**{% if tbl.description %}: {{ tbl.description }}{% endif %}{% if tbl.pk %} (PK: {{ tbl.pk }}){% endif %}
13
+ {% endfor %}
14
+
15
+ ## FK Candidates to Evaluate
16
+ The following candidates were found by statistical analysis (value overlap, naming patterns, cardinality). Each has statistical evidence but needs semantic validation.
17
+
18
+ {% for fk in candidates %}
19
+ {{ loop.index }}. **{{ fk.sourceSchema }}.{{ fk.sourceTable }}.{{ fk.sourceColumn }}** → **{{ fk.targetSchema }}.{{ fk.targetTable }}.{{ fk.targetColumn }}**
20
+ - Statistical confidence: {{ fk.confidence }}%
21
+ - Value overlap: {{ (fk.valueOverlap * 100) | round(1) }}%
22
+ - Source nulls: {{ (fk.nullPercentage * 100) | round(1) }}%
23
+ - Cardinality ratio (source/target distinct): {{ fk.cardinalityRatio | round(2) }}
24
+ {% endfor %}
25
+
26
+ ## Evaluation Rules
27
+
28
+ ### Rule 1: Value overlap is the strongest signal — respect it
29
+ High value overlap (>85%) means the source column's values are almost entirely contained within the target column's values. This is near-proof of a FK relationship. **Only reject high-overlap candidates if you have a concrete, specific reason** (e.g., clearly wrong direction, or an obviously better target exists for the same source column).
30
+
31
+ Do NOT reject candidates just because column names don't match. Real databases frequently use alias names for FK columns:
32
+ - `PersonID` referencing `BusinessEntityID` (alias for the same concept)
33
+ - `ComponentID` referencing `ProductID` (a component IS a product)
34
+ - `Owner` referencing `BusinessEntityID` (semantic alias)
35
+ - `SizeUnitMeasureCode` referencing `UnitMeasureCode` (prefixed FK)
36
+ - `FromCurrencyCode` / `ToCurrencyCode` referencing `CurrencyCode` (role-based aliases)
37
+
38
+ These are all valid FKs despite name mismatches. The statistical overlap proves the relationship.
39
+
40
+ ### Rule 2: Directionality — child references parent
41
+ FKs point FROM the child table TO the parent table. The child table contains the FK column referencing the parent's PK/unique key.
42
+ - `OrderDetail.ProductID → Product.ProductID` is CORRECT (child → parent, many-to-one)
43
+ - `Product.ProductID → OrderDetail.ProductID` is WRONG (parent → child, one-to-many)
44
+
45
+ **How to determine direction**: The target table should generally have FEWER or EQUAL distinct values in the referenced column compared to the source. A cardinality ratio > 1.0 suggests correct child→parent direction.
46
+
47
+ ### Rule 3: Inheritance / specialization — prefer the most specific target
48
+ When a source column has candidates pointing to multiple target tables (e.g., `BusinessEntityID` exists in both `BusinessEntity` and `Person`), the correct FK is usually to the **most specialized table**, not the root/base table. This is the Table-Per-Type inheritance pattern common in databases:
49
+ - `Employee.BusinessEntityID → Person.BusinessEntityID` is CORRECT (Employee IS-A Person)
50
+ - `Employee.BusinessEntityID → BusinessEntity.BusinessEntityID` is WRONG (too generic — Employee relates to Person specifically)
51
+
52
+ Look at the table names and relationships to identify inheritance chains. The FK should point to the table that the source table has the most specific relationship with.
53
+
54
+ ### Rule 4: Transitive hops — reject indirect relationships
55
+ If `A.col` and `B.col` both reference the same parent table but A and B have no direct relationship, reject `A.col → B.col`. Example:
56
+ - `EmployeePayHistory.BusinessEntityID → PersonPhone.BusinessEntityID` — REJECT (both reference Person independently; PayHistory doesn't depend on PersonPhone)
57
+
58
+ **Key distinction**: A transitive hop has LOW cardinality ratio (close to 1.0) and makes no business sense. A real FK has a meaningful dependency.
59
+
60
+ ### Rule 5: Semantic plausibility
61
+ Does the relationship make business sense? Consider the table purposes. But remember: statistical evidence (high value overlap) outweighs naming concerns. If the data proves the relationship, confirm it even if the naming seems unusual.
62
+
63
+ ### Rule 6: Multiple candidates for same source column
64
+ When a source column has multiple FK candidates, you may confirm MORE THAN ONE if they are genuinely valid (e.g., a column that references different tables in different contexts). But typically, prefer the single best target and reject the others.
65
+
66
+ ## Response Format
67
+
68
+ Return a JSON array where each object represents your evaluation of ONE candidate. Use the same index as the input list. Only include candidates you are confirming — omit rejected ones entirely.
69
+
70
+ ```json
71
+ [
72
+ {
73
+ "index": 1,
74
+ "verdict": "confirm",
75
+ "confidence": 0.95,
76
+ "reasoning": "Brief explanation"
77
+ },
78
+ {
79
+ "index": 3,
80
+ "verdict": "confirm",
81
+ "confidence": 0.80,
82
+ "reasoning": "Brief explanation"
83
+ }
84
+ ]
85
+ ```
86
+
87
+ - **index**: The 1-based index from the candidate list above
88
+ - **verdict**: Always "confirm" (omit rejected candidates entirely)
89
+ - **confidence**: Your adjusted confidence (0-1 scale)
90
+ - **reasoning**: Brief explanation of why this is a valid FK
91
+
92
+ **IMPORTANT**: Err on the side of confirming when statistical evidence is strong. It is better to include a borderline FK than to miss a real one. Only reject when you are confident the relationship is wrong.
93
+
94
+ Return ONLY valid JSON. Do not include markdown code fences or explanatory text.
@@ -0,0 +1,57 @@
1
+ You are a database expert making final decisions on proposed FK removals. A per-table analysis has proposed removing certain foreign keys. Your job is to review ALL proposed removals holistically and make final keep/remove decisions.
2
+
3
+ ## Database Context
4
+ {% if seedContext %}
5
+ {% if seedContext.overallPurpose %}- **Purpose**: {{ seedContext.overallPurpose }}{% endif %}
6
+ {% if seedContext.businessDomains %}- **Business Domains**: {{ seedContext.businessDomains | join(', ') }}{% endif %}
7
+ {% if seedContext.industryContext %}- **Industry**: {{ seedContext.industryContext }}{% endif %}
8
+ {% endif %}
9
+
10
+ ## All Database Tables
11
+ {% for tbl in allTables %}
12
+ - **{{ tbl.schema }}.{{ tbl.name }}**{% if tbl.pk %} (PK: {{ tbl.pk }}){% endif %}{% if tbl.description %}: {{ tbl.description }}{% endif %}
13
+ {% endfor %}
14
+
15
+ ## Proposed FK Removals
16
+ The per-table analysis proposed removing these FKs. Review each one and decide whether to confirm the removal or keep the FK.
17
+
18
+ {% for proposal in proposals %}
19
+ {{ loop.index }}. **{{ proposal.sourceSchema }}.{{ proposal.sourceTable }}.{{ proposal.sourceColumn }}** → **{{ proposal.targetSchema }}.{{ proposal.targetTable }}.{{ proposal.targetColumn }}** (confidence: {{ proposal.confidence }}%)
20
+ - **Removal reason**: {{ proposal.reasoning }}
21
+ {% endfor %}
22
+
23
+ ## Review Guidelines
24
+
25
+ Consider the FULL relationship graph when making decisions:
26
+ - If removing an FK would leave a table with NO outgoing relationships, reconsider — most tables have at least one FK
27
+ - If the per-table pass proposed removing an FK because a "better" target exists, verify that the better target FK actually exists in the confirmed set
28
+ - If multiple tables have the same column pointing to the same target and the per-table pass wants to remove only some, consider consistency
29
+ - Reverse-direction FKs (parent→child) should almost always be removed
30
+ - Transitive hops (A→B when both reference C independently) should almost always be removed
31
+
32
+ ## Response Format
33
+
34
+ Return a JSON array with your final decision for EACH proposed removal:
35
+
36
+ ```json
37
+ [
38
+ {
39
+ "index": 1,
40
+ "action": "remove",
41
+ "reasoning": "Confirmed — reverse direction FK, Department is the parent"
42
+ },
43
+ {
44
+ "index": 3,
45
+ "action": "keep",
46
+ "reasoning": "On reflection, this is a valid FK — the per-table analysis missed that these tables are directly related"
47
+ }
48
+ ]
49
+ ```
50
+
51
+ - **index**: The 1-based index from the proposed removals list above
52
+ - **action**: `"remove"` to confirm removal, `"keep"` to override and keep the FK
53
+ - **reasoning**: Brief explanation
54
+
55
+ **Every proposed removal must have a decision.** Do not omit any.
56
+
57
+ Return ONLY valid JSON. Do not include markdown code fences or explanatory text.
@@ -0,0 +1,51 @@
1
+ You are a database expert reviewing foreign key relationships for a single table. Your job is to identify ONLY the clearly incorrect FKs that should be removed.
2
+
3
+ ## Table: {{ sourceSchema }}.{{ sourceTable }}
4
+ {% if tableDescription %}**Description**: {{ tableDescription }}{% endif %}
5
+
6
+ ## All Database Tables (for reference)
7
+ {% for tbl in allTables %}
8
+ - **{{ tbl.schema }}.{{ tbl.name }}**{% if tbl.pk %} (PK: {{ tbl.pk }}){% endif %}{% if tbl.description %}: {{ tbl.description }}{% endif %}
9
+ {% endfor %}
10
+
11
+ ## Foreign Keys to Review
12
+ These FKs were identified by statistical analysis and/or LLM analysis. Some are correct, some may be false positives.
13
+
14
+ **NOTE**: FKs marked as **[LOCKED]** have very high confidence and CANNOT be removed. Only evaluate the unlocked ones.
15
+
16
+ {% for fk in candidates %}
17
+ {{ loop.index }}. {% if fk.locked %}**[LOCKED]** {% endif %}**{{ fk.sourceColumn }}** → {{ fk.targetSchema }}.{{ fk.targetTable }}.{{ fk.targetColumn }} (confidence: {{ fk.confidence }}%)
18
+ {% endfor %}
19
+
20
+ ## What to look for when proposing removals:
21
+
22
+ 1. **Reverse direction**: FK goes from parent→child instead of child→parent. Example: `Department.DepartmentID → EmployeeDepartmentHistory.DepartmentID` is backwards — the history table should reference Department, not the other way around.
23
+
24
+ 2. **Transitive/indirect relationships**: Two tables both reference a common parent but aren't directly related. Example: `EmployeePayHistory.BusinessEntityID → PersonPhone.BusinessEntityID` — both reference Person, but PayHistory doesn't depend on PersonPhone.
25
+
26
+ 3. **Wrong target when better target exists**: If a column points to a generic table but a more specific table is the correct target. Example: `Employee.BusinessEntityID → BusinessEntity.BusinessEntityID` when `Employee.BusinessEntityID → Person.BusinessEntityID` is the correct FK (Person is more specific).
27
+
28
+ 4. **Column name mismatch creating false match**: Same data type and overlapping values but no real relationship. Example: `OrderQty → OnOrderQty` — both are integers with overlapping ranges but aren't referential.
29
+
30
+ 5. **Sibling fan-out**: When a source column has multiple FK targets with the same column name, usually only ONE is the correct FK (to the parent/lookup table). The others are sibling tables that independently reference the same parent. Look for the pattern: `A.TerritoryID → SalesTerritory.TerritoryID` (correct — SalesTerritory is the lookup) vs `A.TerritoryID → SalesTerritoryHistory.TerritoryID` (wrong — History is a sibling, not a parent). The correct target is typically:
31
+ - The table whose PK matches the FK column
32
+ - The shorter/simpler table name (lookup/master vs history/detail)
33
+ - The table with fewer columns (lookup tables are small)
34
+
35
+ ## Response Format
36
+
37
+ Return a JSON array of FKs you propose to REMOVE. Only include FKs you are confident are wrong. Do NOT include locked FKs. If all unlocked FKs look correct, return an empty array `[]`.
38
+
39
+ ```json
40
+ [
41
+ {
42
+ "index": 2,
43
+ "action": "remove",
44
+ "reasoning": "Reverse direction — Department is the parent table, not the child"
45
+ }
46
+ ]
47
+ ```
48
+
49
+ **Be moderately aggressive** — remove FKs that follow the sibling/reverse/transitive patterns described above. The locked FKs protect the high-confidence correct relationships, so your job is to clean up the lower-confidence noise.
50
+
51
+ Return ONLY valid JSON. Do not include markdown code fences or explanatory text.
@@ -0,0 +1,26 @@
1
+ You are performing a final review of proposed primary key removals across a database.
2
+
3
+ ## Proposed PK Removals:
4
+ {% for p in proposals %}
5
+ {{ loop.index }}. {{ p.sourceSchema }}.{{ p.sourceTable }}: columns [{{ p.columns | join(", ") }}] (confidence: {{ p.confidence }}%)
6
+ Reasoning: {{ p.reasoning }}
7
+ {% endfor %}
8
+
9
+ ## All Database Tables:
10
+ {% for tbl in allTables %}
11
+ - {{ tbl.schema }}.{{ tbl.name }}{% if tbl.pk %} (PK: {{ tbl.pk }}){% endif %}{% if tbl.description %} — {{ tbl.description }}{% endif %}
12
+ {% endfor %}
13
+
14
+ ## Your Task:
15
+ Review each proposed removal. Consider:
16
+ 1. Would removing this PK leave the table without any primary key?
17
+ 2. Is this PK actually correct and should not be removed?
18
+ 3. Are there any cross-table consistency issues?
19
+
20
+ For each proposal, confirm or reject the removal:
21
+ [
22
+ { "index": 1, "action": "remove", "reasoning": "Confirmed: not the real PK" },
23
+ { "index": 2, "action": "keep", "reasoning": "Actually correct, do not remove" }
24
+ ]
25
+
26
+ Return ONLY valid JSON. Do not include markdown code fences or explanatory text.
@@ -0,0 +1,35 @@
1
+ You are evaluating primary key candidates for a database table.
2
+
3
+ ## Table: {{ sourceSchema }}.{{ sourceTable }}
4
+ {{ tableDescription }}
5
+
6
+ ## PK Candidates (evaluate these):
7
+ {% for pk in candidates %}
8
+ {{ loop.index }}. Columns: {{ pk.columns | join(", ") }} (confidence: {{ pk.confidence }}%{% if pk.locked %}, LOCKED - do not modify{% endif %})
9
+ {% endfor %}
10
+
11
+ ## All Database Tables (for context):
12
+ {% for tbl in allTables %}
13
+ - {{ tbl.schema }}.{{ tbl.name }}{% if tbl.pk %} (PK: {{ tbl.pk }}){% endif %}
14
+ {% endfor %}
15
+
16
+ ## Your Task:
17
+ Evaluate each UNLOCKED PK candidate. A valid primary key must:
18
+ 1. Uniquely identify every row in the table
19
+ 2. Be the most natural identifier for the entity (prefer table-specific IDs over generic ones)
20
+ 3. For junction/bridge tables, be the combination of the foreign key columns
21
+ 4. Only ONE primary key should exist per table
22
+
23
+ For each candidate, respond with:
24
+ - `"action": "keep"` or `"action": "remove"`
25
+ - `"reasoning": "why this is/isnt the correct PK"`
26
+
27
+ If multiple candidates exist for a table, only one should be kept.
28
+
29
+ Return a JSON array:
30
+ [
31
+ { "index": 1, "action": "keep", "reasoning": "..." },
32
+ { "index": 2, "action": "remove", "reasoning": "..." }
33
+ ]
34
+
35
+ Return ONLY valid JSON. Do not include markdown code fences or explanatory text.
@@ -83,6 +83,17 @@ The database owner has provided the following authoritative documentation. Your
83
83
  {% if seedContext.customInstructions %}- **Special Instructions**: {{ seedContext.customInstructions }}{% endif %}
84
84
  {% endif %}
85
85
 
86
+ {% if fkCandidateStats and fkCandidateStats.length > 0 %}
87
+ ## FK Evidence from Statistical Analysis
88
+ The following columns in this table were identified as potential foreign keys by statistical analysis. Use this evidence to inform (but not limit) your FK assessment — you may identify additional FKs not listed here.
89
+
90
+ {% for fk in fkCandidateStats %}
91
+ - **{{ fk.sourceColumn }}** → {{ fk.targetSchema }}.{{ fk.targetTable }}.{{ fk.targetColumn }} (value overlap: {{ (fk.valueOverlap * 100) | round(1) }}%, cardinality ratio: {{ fk.cardinalityRatio | round(2) }}, confidence: {{ fk.confidence }}%)
92
+ {% endfor %}
93
+
94
+ *Value overlap = % of source values that exist in the target column. 100% = strong FK evidence. Cardinality ratio = source distinct / target distinct — values > 1 suggest child→parent direction.*
95
+ {% endif %}
96
+
86
97
  {% if allTables %}
87
98
  ## All Database Tables
88
99
  **IMPORTANT**: When referring to foreign key relationships, you MUST use one of these exact table names:
@@ -111,6 +122,11 @@ Based on the evidence above, generate a JSON response with this exact structure:
111
122
  "reasoning": "Brief explanation of the evidence"
112
123
  }
113
124
  ],
125
+ "primaryKey": {
126
+ "columns": ["CustomerID"],
127
+ "confidence": 0.95,
128
+ "reasoning": "Single auto-increment column with 100% uniqueness, named after the table"
129
+ },
114
130
  "foreignKeys": [
115
131
  {
116
132
  "columnName": "prd_id",
@@ -136,14 +152,23 @@ Based on the evidence above, generate a JSON response with this exact structure:
136
152
  2. **Reasoning**: Reference specific evidence (column names, FK relationships, sample values, cardinality patterns)
137
153
  3. **Confidence**: 0-1 scale. Be conservative. Use < 0.7 if ambiguous.
138
154
  4. **Column Descriptions**: Every column should be described. Explain its role and meaning.
139
- 5. **Foreign Keys**: **CRITICAL** - Use structured format for ALL foreign key relationships:
155
+ 5. **Primary Key**: Identify the column(s) that most likely form this table's primary key.
156
+ - Look for columns with 100% uniqueness, zero nulls, and names like `ID`, `TableNameID`, or `Code`
157
+ - For junction/bridge tables (e.g., `ProductModelIllustration`), the PK is likely a composite of the FK columns
158
+ - If the table inherits an ID from a parent (e.g., `Employee` using `BusinessEntityID` from `BusinessEntity`), that inherited column IS the PK
159
+ - Use `"columns": ["Col1", "Col2"]` for composite keys
160
+ - Confidence should reflect how certain you are (0-1 scale)
161
+ - If a PK is already marked in the column list above, you may confirm it or propose a different one
162
+ 6. **Foreign Keys**: **CRITICAL** - Use structured format for ALL foreign key relationships:
140
163
  - Include EVERY column that references another table
141
164
  - Use EXACT schema and table names from the "All Database Tables" list above
142
165
  - Specify confidence (0-1 scale) based on evidence strength
143
166
  - Example: If `prd_id` exists, add: `{"columnName": "prd_id", "referencesSchema": "inv", "referencesTable": "prd", "referencesColumn": "prd_id", "confidence": 0.95}`
144
167
  - **Leave empty array if no foreign keys detected**
145
- 6. **Business Domain**: Infer from table name and purpose (e.g., "Sales", "HR", "Inventory", "Billing", "Security")
146
- 7. **Parent Table Insights**: If analyzing this child table reveals new information about parent tables, include it. Examples:
168
+ - **Inheritance/specialization**: When a column could reference either a generic base table (e.g., BusinessEntity) or a more specialized table (e.g., Person, Employee, Vendor) that inherits from it, **always prefer the most specialized table**. The specialized table is the one that adds domain-specific columns beyond the base table. For example, `Employee.BusinessEntityID` should reference `Person.BusinessEntityID` (not `BusinessEntity.BusinessEntityID`) because Person is the specialized entity that Employee relates to.
169
+ - **Polymorphic FKs**: Some columns may reference different tables depending on the row (e.g., a `ReferenceOrderID` that could point to a SalesOrder, PurchaseOrder, or WorkOrder). If you detect this pattern, pick the **single most common/likely target** and note the polymorphic nature in your reasoning. Do not create multiple FK entries for the same column pointing to different tables unless you are highly confident each is valid.
170
+ 7. **Business Domain**: Infer from table name and purpose (e.g., "Sales", "HR", "Inventory", "Billing", "Security")
171
+ 8. **Parent Table Insights**: If analyzing this child table reveals new information about parent tables, include it. Examples:
147
172
  - Discovering enum values in the parent (e.g., "Member table has a 'Type' column with values: Individual, Corporate, Student")
148
173
  - Revealing parent table classification/purpose (e.g., "BoardMember reveals that Member table includes leadership roles, not just general members")
149
174
  - Identifying parent table patterns (e.g., "Multiple child tables suggest Organization serves as a multi-tenant partition key")