Skip to content

Commit 11d1489

Browse files
Nathan ParkerNathan Parker
authored andcommitted
phase 2 complete
1 parent e2a1093 commit 11d1489

132 files changed

Lines changed: 122731 additions & 17 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/CLI/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,7 @@ You can also explicitly specify the domain using the `--domain` option.
942942
- `--max-facilities-per-solution INTEGER` - Cap facilities used in one composite solution (default: 3)
943943
- `--combination-strategy [greedy]` - Composite solver strategy
944944
- `--no-alternative-solutions` - Return only the top composite solution
945+
- `--explain` - Include structured and human-readable per-solution explanations in output
945946
- `--output, -o TEXT` - Output file path
946947
- `--use-llm` - Enable LLM integration for enhanced matching
947948
- `--llm-provider [anthropic|openai|google|azure|local]` - LLM provider
@@ -952,6 +953,9 @@ You can also explicitly specify the domain using the `--domain` option.
952953
- `--table` - Output results in table format
953954
- `--verbose, -v` - Enable verbose output
954955

956+
**Verbose behavior note:**
957+
- In `match requirements`, `--verbose` automatically enables explanation output (equivalent to `--explain`) so diagnostics are consistently shown during interactive debugging.
958+
955959
**Examples:**
956960
```bash
957961
# Match OKH requirements (manufacturing domain)
@@ -987,8 +991,10 @@ ohm match requirements my-design.okh.json --output matches.json
987991

988992
**Output:**
989993
The command displays matching facilities with:
994+
- Rank (`1` = best match)
990995
- Facility name
991996
- **Full facility ID (UUID)** - required for `ohm okw get` command
997+
- Composite detail bundle (`facility_details`) in JSON mode when a solution spans multiple facilities
992998
- Confidence score
993999
- Match type (manufacturing or cooking)
9941000
- Location (if available)

docs/api/routes.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1218,9 +1218,12 @@ The endpoint returns a wrapped response with the following structure:
12181218
- `facility`: Complete facility/kitchen information
12191219
- `facility_id`: **Unique identifier** for the facility (required for API operations)
12201220
- `facility_name`: Human-readable facility name
1221+
- `facility_ids`: List of facility IDs participating in the solution (**composite solutions only**)
1222+
- `facility_names`: List of facility names participating in the solution (**composite solutions only**)
1223+
- `facility_details`: List of `{facility_id, facility_name, facility}` objects (**composite solutions only**)
12211224
- `match_type`: Domain type ("manufacturing" or "cooking")
12221225
- `confidence`: Match confidence score (0.0-1.0)
1223-
- `rank`: Solution rank (1 = best match)
1226+
- `rank`: Solution rank (1 = best match, stable after confidence filtering and result limiting)
12241227
- `total_solutions`: Total number of matching solutions found
12251228
- `matching_metrics`: Breakdown of match types
12261229
- `validation_results`: Validation results for each solution

docs/matching/match-explanation.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ When matching requirements to facilities, you can request **per-facility explana
1111
```bash
1212
ohm match requirements my-design.okh.json --explain
1313
```
14+
In current CLI behavior, `--verbose` also enables explanation output automatically for `match requirements`.
1415
Explanations are printed under each facility in table output; in JSON output they appear as `explanation` and `explanation_human` on each solution.
1516

1617
### Manual testing with synthetic data (no server/storage)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ addopts = "-v --tb=short"
5353
asyncio_mode = "strict"
5454
markers = [
5555
"e2e: end-to-end canonical workflow tests",
56+
"integration: integration tests that may require services or seeded data",
5657
"slow: tests that take more than 10 seconds",
5758
"llm: tests requiring LLM API access",
5859
"benchmark: performance benchmark tests",

src/cli/match.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ async def requirements(
269269
cli_ctx = ctx.obj
270270
cli_ctx.start_command_tracking("match-requirements")
271271

272+
# Keep explain output aligned with verbose mode ergonomics.
273+
if verbose and not include_explanation:
274+
include_explanation = True
275+
272276
# Update CLI context with parameters from decorator
273277
cli_ctx.update_llm_config(
274278
use_llm=use_llm,
@@ -575,6 +579,8 @@ async def fallback_match():
575579
solutions, key=lambda s: s.score, reverse=True
576580
)
577581
solution_dicts = [s.to_dict() for s in solutions_list]
582+
for idx, solution_dict in enumerate(solution_dicts, start=1):
583+
solution_dict["rank"] = idx
578584
total_trees = sum(len(s.all_trees) for s in solutions_list)
579585
result = {
580586
"solutions": solution_dicts,
@@ -684,8 +690,11 @@ async def fallback_match():
684690
# Continue without failing the match
685691

686692
if results_list:
693+
solution_dicts = [r.to_dict() for r in results_list]
694+
for idx, solution_dict in enumerate(solution_dicts, start=1):
695+
solution_dict["rank"] = idx
687696
result = {
688-
"solutions": [r.to_dict() for r in results_list],
697+
"solutions": solution_dicts,
689698
"total_solutions": len(results_list),
690699
"matching_mode": "single-level",
691700
}
@@ -1421,7 +1430,8 @@ async def _display_match_results(
14211430
facility_name = solution.get("name", "Unknown Facility")
14221431
confidence = solution.get("confidence", solution.get("score", 0))
14231432

1424-
click.echo(f"\n{i}. {facility_name}")
1433+
rank = solution.get("rank", i)
1434+
click.echo(f"\n{rank}. {facility_name}")
14251435

14261436
# Show facility ID if available
14271437
facility_id = solution.get("facility_id")

src/core/api/routes/match.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,9 +1956,18 @@ async def _perform_enhanced_matching(
19561956
facility_id = str(uuid4())[:8]
19571957

19581958
# Create solution dict in expected format
1959+
if not facility_data:
1960+
facility_data = {
1961+
"id": facility_id,
1962+
"name": facility_name,
1963+
}
1964+
elif isinstance(facility_data, dict):
1965+
facility_data.setdefault("id", facility_id)
1966+
facility_data.setdefault("name", facility_name)
1967+
19591968
solution_dict = {
19601969
"tree": representative_tree.to_dict(),
1961-
"facility": facility_data if facility_data else {},
1970+
"facility": facility_data,
19621971
"facility_id": facility_id,
19631972
"facility_name": facility_name,
19641973
"match_type": "manufacturing",
@@ -1987,15 +1996,34 @@ async def _perform_enhanced_matching(
19871996
tree_facility_id = (
19881997
str(tree.okw_reference) if tree.okw_reference else None
19891998
)
1990-
if tree_facility_id:
1991-
for facility in facilities:
1992-
if str(getattr(facility, "id", "")) == tree_facility_id:
1993-
tree_facility_data = (
1994-
facility.to_dict()
1995-
if hasattr(facility, "to_dict")
1996-
else facility
1997-
)
1998-
break
1999+
for facility in facilities:
2000+
if (
2001+
tree_facility_id
2002+
and str(getattr(facility, "id", "")) == tree_facility_id
2003+
):
2004+
tree_facility_data = (
2005+
facility.to_dict()
2006+
if hasattr(facility, "to_dict")
2007+
else facility
2008+
)
2009+
break
2010+
if (
2011+
hasattr(facility, "name")
2012+
and facility.name == tree.facility_name
2013+
):
2014+
tree_facility_data = (
2015+
facility.to_dict()
2016+
if hasattr(facility, "to_dict")
2017+
else facility
2018+
)
2019+
if not tree_facility_id:
2020+
tree_facility_id = str(getattr(facility, "id", ""))
2021+
break
2022+
if not tree_facility_data:
2023+
tree_facility_data = {
2024+
"id": tree_facility_id,
2025+
"name": tree.facility_name,
2026+
}
19992027
facility_details.append(
20002028
{
20012029
"facility_id": tree_facility_id,

src/core/services/matching_service.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,7 +2531,7 @@ async def _match_component_to_facilities(
25312531
)
25322532
]
25332533

2534-
# CRITICAL: Log component processes in the main message so it's always visible
2534+
# Log component process shape for diagnostics.
25352535
logger.info(
25362536
f"Matching component '{component.name}' to {len(facilities)} facilities | "
25372537
f"Component processes: {component_processes} | "
@@ -2548,9 +2548,11 @@ async def _match_component_to_facilities(
25482548
)
25492549

25502550
if non_uri_processes:
2551-
logger.error(
2552-
f"CRITICAL: Component '{component.name}' has non-URI processes! "
2553-
f"non_uri_processes={non_uri_processes}, all_processes={component_processes}",
2551+
# Non-URI processes are expected for many manifests after normalization.
2552+
# Keep this visible for debugging, but do not emit as an ERROR.
2553+
logger.warning(
2554+
f"Component '{component.name}' includes non-URI process labels "
2555+
f"(accepted): {non_uri_processes}",
25542556
extra={
25552557
"component_id": component.id,
25562558
"component_name": component.name,

0 commit comments

Comments
 (0)