import re STYLES = { "ranch": "ranch", "rambler": "ranch", "colonial": "colonial", "cape": "colonial", "modern": "modern", "contemporary": "modern", "victorian": "victorian", "craftsman": "craftsman", "cabin": "cabin", "cottage": "cabin", "tudor": "tudor", "farmhouse": "farmhouse", } ROOF_STYLES = { "ranch": "hip", "rambler": "hip", "colonial": "gable", "cape": "gable", "modern": "flat", "contemporary": "flat", "victorian": "steep-gable", "craftsman": "low-gable", "cabin": "gable", "cottage": "gable", "tudor": "steep-gable", "farmhouse": "gable", } ROOM_COLORS = { "living": "#e8dcc8", "family": "#e8dcc8", "kitchen": "#d4e8d0", "dining": "#e0d0c0", "bedroom": "#d0d8e8", "master": "#c8d0e8", "bath": "#d8e8e8", "bathroom": "#d8e8e8", "office": "#e0d0d8", "den": "#e0d0d8", "garage": "#d0d0d0", "hall": "#f0ece8", "hallway": "#f0ece8", "laundry": "#e8e0d8", "closet": "#ece8e0", "loft": "#e8e0d0", "basement": "#c8c0b8", "porch": "#d8d0c8", "deck": "#c8b8a8", "patio": "#c8b8a8", } def parse_description(text): text = text.lower().strip() sqft = _extract_sqft(text) width, depth = _extract_dimensions(text, sqft) style = _extract_style(text) stories = _extract_stories(text) bedrooms = _extract_bedrooms(text) bathrooms = _extract_bathrooms(text) garage = _has_garage(text) extra_rooms = _extract_extra_rooms(text) rooms = _generate_rooms(width, depth, stories, bedrooms, bathrooms, garage, extra_rooms, style) roof_style = ROOF_STYLES.get(style, "gable") return { "structure": { "width": width, "depth": depth, "stories": stories, "style": style, "roof_style": roof_style, "sqft": sqft or (width * depth * stories), }, "rooms": rooms, "features": { "bedrooms": int(bedrooms), "bathrooms": float(bathrooms), "garage": garage, "stories": int(stories), }, "summary": _generate_summary(width, depth, stories, style, bedrooms, bathrooms), } def _extract_sqft(text): m = re.search(r'(\d+[,.]?\d*)\s*(sq\s*ft|square\s*feet|sqft)', text) if m: return int(float(m.group(1).replace(",", ""))) m = re.search(r'(\d+[,.]?\d*)\s*x\s*(\d+[,.]?\d*)\s*(?:feet|ft|foot)', text) if m: w = float(m.group(1).replace(",", "")) d = float(m.group(2).replace(",", "")) return int(w * d) return None def _extract_dimensions(text, sqft): m = re.search(r'(\d+)\s*(?:feet|ft|foot)\s*(?:x|by|and)?\s*(\d+)\s*(?:feet|ft|foot)', text) if m: return int(m.group(1)), int(m.group(2)) m = re.search(r'(\d+)\s*x\s*(\d+)', text) if m: return int(m.group(1)), int(m.group(2)) m = re.search(r'(\d+)\s*(?:feet|ft|foot)\s*(?:wide|width)?', text) if m: w = int(m.group(1)) if sqft: d = max(1, sqft // w) else: d = int(w * 0.75) return w, d m = re.search(r'(\d+)\s*(?:by|wide|width)\s*(\d+)', text) if m: return int(m.group(1)), int(m.group(2)) if sqft: w = int(sqft ** 0.5) d = max(1, sqft // w) return w, d return 40, 30 def _extract_style(text): for style, normalized in STYLES.items(): if style in text: return normalized if "2 story" in text or "two story" in text or "two-story" in text: return "colonial" return "modern" def _extract_stories(text): m = re.search(r'(\d+)\s*[-]?\s*(?:story|floor|level|storey)', text) if m: return min(int(m.group(1)), 3) if "single story" in text or "ranch" in text or "rambler" in text: return 1 return 1 def _extract_bedrooms(text): m = re.search(r'(\d+)\s*(?:bedroom|bed\b)', text) if m: return int(m.group(1)) m = re.search(r'(\d+)\s*br\b', text) if m: return int(m.group(1)) return 3 def _extract_bathrooms(text): m = re.search(r'(\d+)\s*(?:bathroom|bath\b)', text) if m: return int(m.group(1)) m = re.search(r'(\d+)\s*ba\b', text) if m: return int(m.group(1)) m = re.search(r'(\d+\.?\d*)\s*-?\s*bath', text) if m: return float(m.group(1)) return 2 def _has_garage(text): if re.search(r'\bgarage\b', text): return True if re.search(r'\bcar\s*(?:garage|port)\b', text): return True return False def _extract_extra_rooms(text): extras = [] room_keywords = ["office", "den", "library", "gym", "theater", "workshop", "laundry", "mudroom", "sunroom", "conservatory", "studio", "loft", "basement", "attic", "porch", "deck", "patio", "wine cellar", "pantry", "playroom", "nursery"] for kw in room_keywords: if kw in text: extras.append(kw) return extras def _generate_rooms(width, depth, stories, bedrooms, bathrooms, garage, extras, style): rooms = [] color = lambda name: ROOM_COLORS.get(name, "#e8e0d8") w3 = width / 3.0 d2 = depth / 2.0 rooms.append({"name": "Living Room", "x": 0, "z": 0, "w": w3 * 1.5, "d": d2, "color": color("living"), "floor": 0}) rooms.append({"name": "Kitchen", "x": w3 * 1.5, "z": 0, "w": w3 * 0.8, "d": d2, "color": color("kitchen"), "floor": 0}) rooms.append({"name": "Dining", "x": w3 * 1.5 + w3 * 0.8, "z": 0, "w": w3 * 0.7, "d": d2, "color": color("dining"), "floor": 0}) bed_colors = ["#d0d8e8", "#c8d0e8", "#d8d0e0", "#d0d0e0", "#d8d8e8"] bed_w = min(w3 * 0.7, 14) bed_d = min(d2 * 0.6, 14) for i in range(bedrooms): bx = (i % 3) * w3 bz = d2 + (i // 3) * bed_d if bx + bed_w > width: bx = width - bed_w name = "Master Bedroom" if i == 0 else f"Bedroom {i+1}" rooms.append({"name": name, "x": bx, "z": bz, "w": bed_w, "d": bed_d, "color": bed_colors[i % len(bed_colors)], "floor": 0}) bath_w = 6 bath_d = 6 for i in range(int(bathrooms)): bx = width - bath_w - (i * bath_w) % (width / 3) bz = 0 if i % 2 == 0 else d2 - bath_d if bx < 0: bx = width - bath_w rooms.append({"name": f"{'Master ' if i == 0 and bedrooms > 0 else ''}Bathroom" if i == 0 else f"Bathroom {i+1}", "x": bx, "z": bz, "w": bath_w, "d": bath_d, "color": color("bathroom"), "floor": 0}) if garage: gw = min(width * 0.35, 24) gd = min(depth * 0.4, 24) rooms.append({"name": "Garage", "x": 0, "z": depth - gd, "w": gw, "d": gd, "color": color("garage"), "floor": 0}) hall_w = 4 rooms.append({"name": "Hallway", "x": 0, "z": d2, "w": hall_w, "d": bed_d if bedrooms > 0 else d2 * 0.5, "color": color("hallway"), "floor": 0}) for extra in extras: ex = width * 0.6 ez = depth * 0.3 ew = min(w3 * 0.6, 12) ed = min(d2 * 0.5, 10) if extra in ("deck", "patio", "porch"): rooms.append({"name": extra.capitalize(), "x": width * 0.3, "z": -8, "w": width * 0.4, "d": 8, "color": color(extra), "floor": 0, "exterior": True}) else: rooms.append({"name": extra.capitalize(), "x": ex, "z": ez, "w": ew, "d": ed, "color": color(extra), "floor": 0}) if stories > 1: for room in rooms: if room["floor"] == 0 and room["name"] not in ("Garage",): upper = room.copy() upper["name"] = f"Upper {room['name']}" upper["floor"] = 1 upper["color"] = "#e0dcd8" rooms.append(upper) return rooms def _generate_summary(width, depth, stories, style, bedrooms, bathrooms): sqft = width * depth * stories return f"A {style} {stories}-story home, {width}ft x {depth}ft — approximately {sqft} sqft. {bedrooms} bedrooms, {bathrooms} bathrooms." if __name__ == "__main__": tests = [ "2000 sq ft ranch house with 3 bedrooms and 2 baths", "modern 2-story house 30x40 with 4 bedrooms 3 bathrooms and a 2 car garage", "small cabin 800 sq ft with 1 bedroom", "victorian 3500 sq ft 5 bedroom 3 bath with office and library", "colonial with 4 bedrooms and 2.5 baths", ] for t in tests: r = parse_description(t) print(f"\n{'='*60}") print(f"Input: {t}") print(f"Structure: {r['structure']}") print(f"Rooms: {len(r['rooms'])}") for room in r['rooms'][:5]: print(f" {room['name']}: {room['w']:.0f}x{room['d']:.0f} @ ({room['x']:.0f},{room['z']:.0f}) floor={room.get('floor',0)}") print(f"Summary: {r['summary']}")